Skip to content

Commit 0f50f3f

Browse files
committed
Add bleed-through and b/w density settings for AirScan
1 parent aa6e880 commit 0f50f3f

File tree

7 files changed

+59
-21
lines changed

7 files changed

+59
-21
lines changed

README.en.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,9 @@ Access the built-in management interface at `http://<host>:8080/ui/`.
145145

146146
- **Status** &mdash; Connection state, ADF paper presence, error states (paper jam, cover open, multi-feed)
147147
- **Device Info** &mdash; Scanner name, serial, IP, firmware revision
148-
- **Scan Settings** &mdash; Color mode, resolution, duplex, output format, blank page removal, bleed-through reduction
148+
- **Button Scan Settings** &mdash; Color mode, resolution, paper size, output format, duplex, blank page removal, bleed-through reduction
149149
- **Save Destination** &mdash; Configure local folder / FTP / Paperless-ngx for button scans
150+
- **AirScan Settings** &mdash; Auto paper size detect, bleed-through reduction, B&W density overrides for AirScan clients
150151
- **eSCL Endpoint** &mdash; URL for manual eSCL client configuration
151152
- **i18n** &mdash; English / Japanese toggle
152153

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,9 @@ AIRSCAP_PASSWORD=0700 AIRSCAP_SCANNER_IP=192.168.1.100 ./airscap
145145

146146
- **ステータス** &mdash; 接続状態、ADF の用紙有無、エラー状態(紙詰まり・カバーオープン・重送検知)
147147
- **デバイス情報** &mdash; スキャナ名、シリアル番号、IP、ファームウェアリビジョン
148-
- **スキャン設定** &mdash; カラーモード、解像度、両面、出力形式、白紙スキップ、裏写り軽減
149-
- **保存先** &mdash; ローカルフォルダ / FTP / Paperless-ngx のボタンスキャン設定
148+
- **ボタンスキャン設定** &mdash; カラーモード、解像度、用紙サイズ、出力形式、両面、白紙スキップ、裏写り軽減
149+
- **保存先** &mdash; ローカルフォルダ / FTP / Paperless-ngx のボタンスキャン保存先設定
150+
- **AirScan 設定** &mdash; 用紙サイズ自動検出、裏写り軽減、白黒濃度の AirScan クライアント向けオーバーライド
150151
- **eSCL エンドポイント** &mdash; eSCL クライアント手動設定用の URL
151152
- **多言語対応** &mdash; 英語 / 日本語切り替え
152153

internal/config/store.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
type Settings struct {
1313
ColorMode string `json:"colorMode"`
1414
Resolution int `json:"resolution"`
15+
PaperSize string `json:"paperSize"` // "auto", "a4", "a5", "business_card", "postcard"
1516
Duplex bool `json:"duplex"`
1617
Format string `json:"format"`
1718
BlankPageRemoval *bool `json:"blankPageRemoval"` // nil = default (true)
@@ -24,8 +25,9 @@ type Settings struct {
2425
FTPPassword string `json:"ftpPassword"`
2526
PaperlessURL string `json:"paperlessUrl"`
2627
PaperlessToken string `json:"paperlessToken"`
27-
ForcePaperAuto bool `json:"forcePaperAuto"` // AirScan: force paper auto-detect for eSCL clients
28-
PaperSize string `json:"paperSize"` // button scan paper size: "auto", "a4", "a5", "business_card", "postcard"
28+
AirscanForcePaperAuto bool `json:"airscanForcePaperAuto"` // AirScan: force paper auto-detect for eSCL clients
29+
AirscanBleedThrough bool `json:"airscanBleedThrough"` // AirScan: apply bleed-through reduction
30+
AirscanBWDensity int `json:"airscanBwDensity"` // AirScan: B&W density override (-5 to +5)
2931
}
3032

3133
// DefaultSettings returns the default scan settings.

internal/scanner/escl.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,18 +149,28 @@ func (a *ESCLAdapter) Scan(ctx context.Context, req abstract.ScannerRequest) (ab
149149
return nil, err
150150
}
151151

152-
forcePaperAuto := a.settings != nil && a.settings.Get().ForcePaperAuto
152+
forcePaperAuto := a.settings != nil && a.settings.Get().AirscanForcePaperAuto
153153
cfg := mapScanConfig(req, forcePaperAuto)
154154
a.mu.Lock()
155155
cfg.BlankPageRemoval = a.blankPageRemoval
156156
a.mu.Unlock()
157157

158+
// Apply server-side AirScan overrides from settings
159+
if a.settings != nil {
160+
s := a.settings.Get()
161+
cfg.BleedThrough = s.AirscanBleedThrough
162+
if req.Threshold == nil {
163+
cfg.BWDensity = s.AirscanBWDensity
164+
}
165+
}
166+
158167
slog.Info("scan requested",
159168
"colorMode", req.ColorMode,
160169
"resolution", req.Resolution,
161170
"adfMode", req.ADFMode,
162171
"duplex", cfg.Duplex,
163172
"blankPageRemoval", cfg.BlankPageRemoval,
173+
"bleedThrough", cfg.BleedThrough,
164174
"bwDensity", cfg.BWDensity,
165175
"paperWidth", cfg.PaperWidth,
166176
"paperHeight", cfg.PaperHeight,

internal/scanner/escl_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,8 @@ func TestMapScanConfig_ZeroRegionIsAuto(t *testing.T) {
240240
}
241241
}
242242

243-
func TestMapScanConfig_ForcePaperAuto(t *testing.T) {
244-
// Even with a specific region, forcePaperAuto skips paper override
243+
func TestMapScanConfig_AirscanForcePaperAuto(t *testing.T) {
244+
// Even with a specific region, airscanForcePaperAuto skips paper override
245245
req := abstract.ScannerRequest{
246246
Region: abstract.Region{
247247
Width: 210 * abstract.Millimeter,
@@ -335,7 +335,7 @@ func TestBuildCapabilities_WithScanParams(t *testing.T) {
335335
t.Errorf("MaxHeight = %d, want %d", caps.ADFSimplex.MaxHeight, expectedMaxHeight)
336336
}
337337

338-
// MinWidth/MinHeight default (non-ForcePaperAuto)
338+
// MinWidth/MinHeight default (non-AirscanForcePaperAuto)
339339
if caps.ADFSimplex.MinWidth != 50*abstract.Millimeter {
340340
t.Errorf("MinWidth = %d, want %d", caps.ADFSimplex.MinWidth, 50*abstract.Millimeter)
341341
}

internal/webui/static/index.html

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@
196196
<div class="field">
197197
<label class="label is-small" x-text="t('paperSize')"></label>
198198
<div class="control">
199-
<div class="select is-fullwidth">
199+
<div class="select is-ful lwidth">
200200
<select x-model="scanConfig.paperSize" @change="debounceSaveSettings()">
201201
<template x-for="ps in ['auto', 'a4', 'a5', 'business_card', 'postcard']" :key="ps">
202202
<option :value="ps" x-text="t('paper_' + ps)"></option>
@@ -400,12 +400,28 @@
400400
</header>
401401
<div class="card-content">
402402
<div class="field">
403-
<label class="label is-small" x-text="t('forcePaperAuto')"></label>
403+
<label class="label is-small" x-text="t('airscanForcePaperAuto')"></label>
404+
<div class="buttons has-addons">
405+
<button type="button" class="button" :class="scanConfig.airscanForcePaperAuto ? 'is-primary is-selected' : ''" @click="scanConfig.airscanForcePaperAuto = true; debounceSaveSettings()">ON</button>
406+
<button type="button" class="button" :class="!scanConfig.airscanForcePaperAuto ? 'is-primary is-selected' : ''" @click="scanConfig.airscanForcePaperAuto = false; debounceSaveSettings()">OFF</button>
407+
</div>
408+
<p class="help" x-text="t('airscanForcePaperAutoHelp')"></p>
409+
</div>
410+
411+
<div class="field">
412+
<label class="label is-small" x-text="t('airscanBleedThrough')"></label>
404413
<div class="buttons has-addons">
405-
<button type="button" class="button" :class="scanConfig.forcePaperAuto ? 'is-primary is-selected' : ''" @click="scanConfig.forcePaperAuto = true; debounceSaveSettings()">ON</button>
406-
<button type="button" class="button" :class="!scanConfig.forcePaperAuto ? 'is-primary is-selected' : ''" @click="scanConfig.forcePaperAuto = false; debounceSaveSettings()">OFF</button>
414+
<button type="button" class="button" :class="scanConfig.airscanBleedThrough ? 'is-primary is-selected' : ''" @click="scanConfig.airscanBleedThrough = true; debounceSaveSettings()">ON</button>
415+
<button type="button" class="button" :class="!scanConfig.airscanBleedThrough ? 'is-primary is-selected' : ''" @click="scanConfig.airscanBleedThrough = false; debounceSaveSettings()">OFF</button>
407416
</div>
408-
<p class="help" x-text="t('forcePaperAutoHelp')"></p>
417+
<p class="help" x-text="t('airscanBleedThroughHelp')"></p>
418+
</div>
419+
420+
<div class="field">
421+
<label class="label is-small"><span x-text="t('airscanBwDensity')"></span> <span class="has-text-weight-normal has-text-grey" x-text="(scanConfig.airscanBwDensity > 0 ? '+' : '') + scanConfig.airscanBwDensity"></span></label>
422+
<input type="range" min="-5" max="5" step="1" x-model.number="scanConfig.airscanBwDensity" @change="debounceSaveSettings()">
423+
<div class="range-labels"><span>-5</span><span>0</span><span>+5</span></div>
424+
<p class="help" x-text="t('airscanBwDensityHelp')"></p>
409425
</div>
410426
</div>
411427
</div>
@@ -481,7 +497,7 @@
481497
status: null,
482498
tick: 0,
483499
lang: localStorage.getItem('lang') || (navigator.language.startsWith('ja') ? 'ja' : 'en'),
484-
scanConfig: { colorMode: 'auto', resolution: '0', duplex: false, format: 'application/pdf', blankPageRemoval: true, bleedThrough: false, bwDensity: 0, paperSize: 'auto', saveType: 'none', savePath: '', ftpHost: '', ftpUser: '', ftpPassword: '', paperlessUrl: '', paperlessToken: '', forcePaperAuto: false },
500+
scanConfig: { colorMode: 'auto', resolution: '0', duplex: false, format: 'application/pdf', blankPageRemoval: true, bleedThrough: false, bwDensity: 0, paperSize: 'auto', saveType: 'none', savePath: '', ftpHost: '', ftpUser: '', ftpPassword: '', paperlessUrl: '', paperlessToken: '', airscanForcePaperAuto: false, airscanBleedThrough: false, airscanBwDensity: 0 },
485501
scanJob: { scanning: false, lastError: '', lastScan: '', pages: 0, filePath: '' },
486502
scanPreview: { scanning: false, error: '', pages: [], showModal: false, currentPage: 0 },
487503
settingsReady: false,
@@ -536,7 +552,9 @@
536552
paperlessUrl: s.paperlessUrl || '',
537553
paperlessToken: s.paperlessToken || '',
538554
paperSize: s.paperSize || 'auto',
539-
forcePaperAuto: s.forcePaperAuto || false,
555+
airscanForcePaperAuto: s.airscanForcePaperAuto || false,
556+
airscanBleedThrough: s.airscanBleedThrough || false,
557+
airscanBwDensity: s.airscanBwDensity ?? 0,
540558
};
541559
this.clampToCapabilities();
542560
this.settingsReady = true;
@@ -588,7 +606,9 @@
588606
paperlessUrl: this.scanConfig.paperlessUrl,
589607
paperlessToken: this.scanConfig.paperlessToken,
590608
paperSize: this.scanConfig.paperSize,
591-
forcePaperAuto: this.scanConfig.forcePaperAuto,
609+
airscanForcePaperAuto: this.scanConfig.airscanForcePaperAuto,
610+
airscanBleedThrough: this.scanConfig.airscanBleedThrough,
611+
airscanBwDensity: Number(this.scanConfig.airscanBwDensity),
592612
};
593613
const resp = await fetch('api/settings', {
594614
method: 'PUT',

internal/webui/static/messages.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const MESSAGES = {
33
status: { en: 'Status', ja: 'ステータス' },
44
deviceInfo: { en: 'Device Info', ja: 'デバイス情報' },
55
capabilities: { en: 'Capabilities', ja: 'スキャン機能' },
6-
scanSettings: { en: 'Scan Settings', ja: 'スキャン設定' },
6+
scanSettings: { en: 'Button Scan Settings', ja: 'ボタンスキャン設定' },
77
saveDestination: { en: 'Save Destination', ja: '保存先' },
88
scan: { en: 'Scan', ja: 'スキャン' },
99
esclEndpoint: { en: 'eSCL Endpoint', ja: 'eSCL エンドポイント' },
@@ -78,9 +78,13 @@ const MESSAGES = {
7878
paper_postcard: { en: 'Postcard', ja: 'はがき' },
7979

8080
// AirScan settings
81-
airscanSettings: { en: 'AirScan Settings', ja: 'AirScan 設定' },
82-
forcePaperAuto: { en: 'Auto paper size detect', ja: '用紙サイズ自動検出' },
83-
forcePaperAutoHelp: { en: 'Force auto paper size detection for AirScan clients (macOS Image Capture, etc.). Takes effect on the next scan.', ja: 'AirScan クライアント (macOS Image Capture 等) で用紙サイズを自動検出に強制する。次回のスキャンから反映されます。' },
81+
airscanSettings: { en: 'AirScan Settings', ja: 'AirScan 設定' },
82+
airscanForcePaperAuto: { en: 'Auto paper size detect', ja: '用紙サイズ自動検出' },
83+
airscanForcePaperAutoHelp: { en: 'Ignore paper size specified by AirScan clients and always use auto-detect. Takes effect on the next scan.', ja: 'AirScan クライアントが指定した用紙サイズを無視し、常に自動検出を使用する。次回のスキャンから反映されます。' },
84+
airscanBleedThrough: { en: 'Bleed-through reduction', ja: '裏写り軽減' },
85+
airscanBleedThroughHelp: { en: 'Apply bleed-through reduction to AirScan scans.', ja: 'AirScan スキャンに裏写り軽減を適用する。' },
86+
airscanBwDensity: { en: 'B&W Density', ja: '白黒濃度' },
87+
airscanBwDensityHelp: { en: 'Default B&W density for AirScan clients that don\'t specify threshold.', ja: 'Threshold を指定しない AirScan クライアント用の白黒濃度デフォルト値。' },
8488

8589
// Browser scan
8690
scanNow: { en: 'Scan & Preview', ja: 'スキャンしてプレビュー' },

0 commit comments

Comments
 (0)