Skip to content

Commit 26b310f

Browse files
committed
Rename spec modes; errors on mutex options; honour hdim in {strict,loose}spec
1 parent d5511da commit 26b310f

165 files changed

Lines changed: 1408 additions & 1027 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CONTRIBUTING.md

Lines changed: 133 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ containing optional keys that affect the behaviour of BWIPP:
123123
- `enabledontdraw` - Allow the user to set dontdraw, in case they are providing custom renderers
124124
- `hooks` - Dictionary of hook procedures for debugging purposes, including profiling
125125
- `default_*` - Defaults for certain renderer options set by the user, e.g. `default_barcolor`
126+
- `loosespec` - Enable best-fit specification mode globally (implies `strictspec`, lenient)
127+
- `strictspec` - Enable strict physical specification mode globally
128+
- `propspec` - Enable proportional specification mode globally (linear only)
126129

127130
For example:
128131

@@ -360,7 +363,7 @@ Example for an encoder:
360363
% 5. Apply AST spec overrides and resolve physical specification
361364
%
362365
/encoder ast /apply_ast //render exec not { //raiseerror exec } if
363-
/resolve_physspec //render exec
366+
/resolve_strictspec //render exec
364367
365368
%
366369
% 6. Main encoding logic using //encoder.staticdata and loaded data from latevars
@@ -388,7 +391,7 @@ Example for an encoder:
388391
/ren /renlinear % or /renmatrix, /renmaximatrix
389392
% /sbs [...] % 1D: space-bar-space widths
390393
% /pixs [...] % 2D: row-major pixel array
391-
/physspec physspec
394+
/strictspec strictspec
392395
/xdim xdim
393396
/xmin xmin
394397
/xmax xmax
@@ -587,7 +590,7 @@ pixel-locking at 72 DPI:
587590
on a square grid.
588591

589592
External scaling or the `width`/`height` options resize from these defaults.
590-
The `physspec` system (below) provides an alternative path that renders at
593+
The `strictspec` system (below) provides an alternative path that renders at
591594
specification-accurate physical dimensions.
592595

593596

@@ -605,7 +608,7 @@ cause gridfit to silently skip via the `hwxres` guard.
605608
**Rounding modes:**
606609
- Default: round to nearest whole pixel per module
607610
- `width` forced (renlinear only): floor, to avoid exceeding requested width
608-
- `physspec` with spec bounds: smart rounding — try round first; if the
611+
- `strictspec` with spec bounds: smart rounding — try round first; if the
609612
effective X-dimension falls outside `xmin`/`xmax`, try the alternate
610613
direction; if neither fits, return `/errorname (info) false` to the caller
611614

@@ -616,70 +619,163 @@ error via `raiseerror`.
616619

617620
**EPS safety:** When `gridfit` is not enabled (and `griddpi` is not set), no
618621
device-dependent operators (`defaultmatrix`, `dtransform`) are executed.
619-
`physspec` without gridfit is fully EPS-safe.
622+
`strictspec` without gridfit is fully EPS-safe.
620623

621624

622625
### Physical Specification System (`render.ps.src`, encoders)
623626

624627
Shared helpers in the `render` resource enable encoders to generate output at
625628
physical specification dimensions.
626629

627-
**Options:** `physspec` (bool), `propspec` (bool, linear only), `mag` (real,
628-
default 1.0), `xdim` (mm), `hdim` (mm), `ast` (string, default `(default)`),
629-
`xnom`/`hnom`/`xmin`/`xmax` (mm, sentinel -1.0), `modunit` (int).
630+
**Specification modes** control how symbology dimensions relate to spec:
631+
632+
- **No spec** (default): 1pt per module. Encoder's default bar height.
633+
Modules land on pixel boundaries at 72 DPI. The user scales externally
634+
to reach their target X-dimension.
635+
636+
- **`propspec`** (linear only): 1pt per module, but bar height is derived
637+
from the spec ratio `hnom/xnom`, rounded to a whole number of points
638+
(pixel-locked). The coordinate system stays at 1pt-per-module so modules
639+
remain pixel-locked at 72 DPI — the user applies a single scale factor
640+
to hit both their target X-dimension and resolution. Silently falls back
641+
to default height when `hnom` is not available (harmless no-op).
642+
User-supplied `height` overrides the derived value. EPS-safe.
643+
644+
- **`strictspec`**: The renderer scales the symbol to physical spec
645+
dimensions derived from `xnom` (or explicit `xdim`). Bar height is
646+
derived from `hnom/xnom` (not pixel-locked). The output is at the
647+
correct absolute size for direct placement in a page layout. Use with
648+
`gridfit`/`griddpi` to additionally snap to device pixels for bitmap
649+
output. EPS-safe without gridfit. `gridfit` without explicit `griddpi`
650+
probes the device via `defaultmatrix`/`dtransform` (not EPS-safe).
651+
Strict: errors if `xnom`/`xdim` missing or effective X outside bounds.
652+
653+
- **`loosespec`**: Implies `strictspec`. Lenient variant that silently falls
654+
back to default dimensions when `xnom`/`xdim` is missing, suppresses
655+
bounds violations, and picks the closest-to-spec gridfit snap when
656+
neither rounding direction is in-spec. The recommended mode for
657+
general-purpose output.
658+
659+
`propspec` exists because `strictspec` changes the coordinate system away
660+
from 1pt-per-module, which makes external scaling for bitmap generation
661+
less predictable. `propspec` retains the 1pt base while applying the spec
662+
height ratio — the only spec-aware mode that is fully EPS-safe and
663+
naturally pixel-locked.
664+
665+
All three modes (`strictspec`, `propspec`, `loosespec`) can be set per-symbol
666+
via options or globally via `global_ctx`. Each encoder reads global
667+
defaults via the `global_encoder_defaults` dispatch helper, guarded by
668+
`_dontdraw` so that sub-encoders called by wrappers do not re-read
669+
global defaults. Typical global configurations:
630670

631-
**Encoder integration pattern** (see `ean13.ps.src` for linear,
632-
`qrcode.ps.src` for matrix):
633-
634-
1. Declare spec options with -1.0 sentinels after encoder-specific options
635-
2. After input validation, call AST and physspec resolution:
636-
```postscript
637-
/ENCODER ast /apply_ast //render exec not { //raiseerror exec } if
638-
/resolve_physspec //render exec
639-
```
640-
3. For linear encoders, call `/resolve_height //render exec` before bhs/bbs
641-
4. Pass `physspec`, `xdim`, `xmin`, `xmax`, `modunit` in the intermediate dict
671+
```postscript
672+
/uk.co.terryburton.bwipp.global_ctx << /loosespec true >> def % General purpose (vector, EPS)
673+
/uk.co.terryburton.bwipp.global_ctx << /loosespec true /gridfit true >> def % Bitmap output
674+
/uk.co.terryburton.bwipp.global_ctx << /strictspec true >> def % Strict validation
675+
```
642676

643-
**Linear encoders** add `propspec`, `hdim`, `hnom` and call `resolve_height`.
644-
**Matrix encoders** omit these; `modunit` is typically 2 (1 for stacked-linear
645-
types such as pdf417, micropdf417, codablockf, code16k, code49).
677+
**Options:** `strictspec` (bool), `propspec` (bool, linear only), `loosespec`
678+
(bool), `mag` (real, default 1.0), `xdim` (mm), `hdim` (mm), `ast`
679+
(string, default `(default)`), `xnom`/`hnom`/`xmin`/`xmax` (mm, sentinel
680+
-1.0), `modunit` (int). `height` defaults to -1.0 (sentinel); each
681+
encoder falls back to its own default when the sentinel survives.
646682

647683
**Application Specification Tables (ASTs):** `render.ast` is a static readonly
648684
dict mapping encoder names to named spec profiles (GS1 SSTs 1-13, ISO
649685
defaults). Entries may share dicts via `index` to reduce allocation.
650686
`apply_ast` fills -1.0 sentinel values from the selected profile into the
651687
caller's dict using `currentdict`; user-provided values (non-sentinel) are
652-
preserved. GS1 wrappers must put their `ast` value into `options` so the
653-
inner encoder's `apply_ast` uses the wrapper's AST context rather than its
654-
own default.
688+
preserved. When the encoder is in the AST table but the requested profile
689+
is not found, `default` silently passes; non-default profiles error.
655690

656691
**Helpers in render (dispatch table):**
657692

693+
- `global_encoder_defaults` — reads `loosespec`, `strictspec`, `propspec` from
694+
`global_ctx` when local values are false; guarded by `_dontdraw` to
695+
prevent sub-encoders from re-reading. `loosespec` implies `strictspec`.
696+
- `global_renderer_defaults` — reads `default_barcolor`,
697+
`default_backgroundcolor`, `default_bordercolor`, `default_inkspread`,
698+
`default_griddpi`, `default_gridfit` from `global_ctx`
658699
- `apply_ast` — fills spec sentinels from AST; returns `true` or
659700
`/errorname (info) false`
660-
- `resolve_physspec` — validates `mag`/`xdim` exclusivity, resolves `xdim`
701+
- `resolve_strictspec` — validates `mag`/`xdim` exclusivity, resolves `xdim`
661702
from `xnom * mag`, resolves `hdim` from `hnom * mag` (if defined in
662-
caller's dict), validates bounds via `validate_xdim`
663-
- `resolve_height` — sets linear `height` from `hnom/xnom * modunit / 72`
664-
ratio; only runs when `propspec` or `physspec` is active and `hnom` is
665-
defined
703+
caller's dict), validates bounds via `validate_xdim`. Under `loosespec`:
704+
silently disables `strictspec` when `xnom`/`xdim` missing, suppresses
705+
bounds errors
706+
- `resolve_height` — pure function, returns derived height on the stack
707+
(or current `height` if not applicable). Derives when
708+
`hnom != -1 AND (strictspec OR (propspec AND height == sentinel))`.
709+
Pixel-locks (rounds) under propspec; not under strictspec.
666710
- `validate_xdim` — low-level `xdim xmin xmax` bounds check; returns `true`
667711
or `/errorname (info) false` with formatted error string
668712

713+
**Encoder integration pattern** (see `ean13.ps.src` for linear,
714+
`qrcode.ps.src` for matrix):
715+
716+
1. Declare spec options with -1.0 sentinels (including `height`,
717+
`loosespec`, `propspec`, `strictspec`)
718+
2. After processoptions, apply global defaults:
719+
```postscript
720+
/apply //processoptions exec /options exch def
721+
/global_encoder_defaults //render exec
722+
```
723+
3. After input validation, call AST and strictspec resolution:
724+
```postscript
725+
/ENCODER ast /apply_ast //render exec not { //raiseerror exec } if
726+
/resolve_strictspec //render exec
727+
```
728+
4. For linear encoders, resolve height with fallback to encoder default:
729+
```postscript
730+
/height /resolve_height //render exec dup -1 eq { pop <default> } if def
731+
```
732+
5. Pass `strictspec`, `loosespec`, `xdim`, `xmin`, `xmax`, `modunit` in the
733+
intermediate dict
734+
735+
**Linear encoders** add `propspec`, `hdim`, `hnom` and call `resolve_height`.
736+
**Matrix encoders** omit these; `modunit` is typically 2 (1 for stacked-linear
737+
types such as pdf417, micropdf417, codablockf, code16k, code49).
738+
669739
**Renderer scaling:** Each renderer applies `xdim 72 mul 25.4 div modunit div
670-
dup scale` when `physspec` is true. `renmaximatrix` divides additionally by
740+
dup scale` when `strictspec` is true. `renmaximatrix` divides additionally by
671741
its existing 2.4945 scale factor.
672742

673-
**Inkspread under physspec:** Renderers adjust inkspread to maintain a fixed
743+
**Inkspread under strictspec:** Renderers adjust inkspread to maintain a fixed
674744
physical amount (mm) rather than a proportional reduction. For `renlinear`,
675745
the adjustment is applied before bar width computation (since bars are
676746
pre-computed into the `bars` array). For `renmatrix` and `renmaximatrix`, it
677-
is applied after the physspec scale, before module rendering.
747+
is applied after the strictspec scale, before module rendering.
748+
749+
**Wrapper framework:** The outermost encoder consumes the AST. All wrappers
750+
that have their own AST entry must:
751+
752+
1. Declare spec options (including `propspec`, `hnom`, `ast`)
753+
2. Call `apply_ast` under their own encoder name
754+
3. Call `resolve_strictspec`
755+
4. `options (ast) undef` and `options (mag) undef` — consumed, do not
756+
leak to inner encoder
757+
5. Put all resolved spec values into `options` for the inner encoder
758+
6. For wrappers with a non-1.0 height default: fall back only when the
759+
sentinel survives and propspec won't derive:
760+
```postscript
761+
height -1.0 eq { propspec hnom -1 ne and not { /height <default> def } if } if
762+
```
678763

679-
**Wrappers:** Simple wrappers (isbn, code32, etc.) need no spec changes —
680-
options flow through via the `options` dict to the inner encoder. GS1 wrappers
681-
that tighten bounds must declare spec options and explicitly put them into
682-
`options` before calling the inner encoder (see `gs1qrcode.ps.src`).
764+
Simple wrappers without their own AST (code32, hibccode128, etc.) need no
765+
spec handling — options flow through transparently. Their `height` should
766+
default to -1.0 (sentinel) so propspec/strictspec can derive via the inner
767+
encoder.
768+
769+
**Composite wrappers** call both a linear encoder and gs1-cc. Spec options
770+
must reach the linear encoder but NOT gs1-cc (the 2D component has
771+
independent scaling). Strip `strictspec`/`propspec` from options after the
772+
linear encoder call and before the gs1-cc call:
773+
```postscript
774+
options (strictspec) undef
775+
options (propspec) undef
776+
```
777+
For `gs1-128composite` which uses `<< options {} forall >>` clones, strip
778+
from the clone passed to gs1-cc.
683779

684780

685781
## Performance

src/auspost.ps.src

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ begin
150150
/custinfoenc (character) def
151151

152152
/propspec false def
153-
/bestfit false def
154-
/physspec false def
153+
/loosespec false def
154+
/strictspec false def
155155
/mag 1.0 def
156156
/xdim -1.0 def
157157
/hdim -1.0 def
@@ -212,7 +212,7 @@ begin
212212
% Apply AST spec overrides and resolve physical specification
213213
%
214214
/auspost ast /apply_ast //render exec not { //raiseerror exec } if
215-
/resolve_physspec //render exec
215+
/resolve_strictspec //render exec
216216

217217
%
218218
% Put start character
@@ -345,8 +345,8 @@ begin
345345
/txt [ [barcode 2 barlen 2 sub getinterval 0 0 text1font text1size] ]
346346
/text1xalign (center)
347347
} if
348-
/physspec physspec
349-
/bestfit bestfit
348+
/strictspec strictspec
349+
/loosespec loosespec
350350
/xdim xdim
351351
/xmin xmin
352352
/xmax xmax

src/azteccode.ps.src

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,9 @@ begin
323323
/raw false def
324324
/parse false def
325325
/parsefnc false def
326-
/physspec false def
326+
/strictspec false def
327327
/propspec false def
328-
/bestfit false def
328+
/loosespec false def
329329
/mag 1.0 def
330330
/xdim -1.0 def
331331
/ast (default) def
@@ -348,7 +348,7 @@ begin
348348
% Apply AST spec overrides and resolve physical specification
349349
%
350350
/azteccode ast /apply_ast //render exec not { //raiseerror exec } if
351-
/resolve_physspec //render exec
351+
/resolve_strictspec //render exec
352352

353353
(input.processoptions) //azteccode.after exec
354354

@@ -1134,8 +1134,8 @@ begin
11341134
/pixy size
11351135
/height size 2 mul 72 div
11361136
/width size 2 mul 72 div
1137-
/physspec physspec
1138-
/bestfit bestfit
1137+
/strictspec strictspec
1138+
/loosespec loosespec
11391139
/xdim xdim
11401140
/xmin xmin
11411141
/xmax xmax

src/bc412.ps.src

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ begin
107107
/width 0 def
108108

109109
/propspec false def
110-
/bestfit false def
111-
/physspec false def
110+
/loosespec false def
111+
/strictspec false def
112112
/mag 1.0 def
113113
/xdim -1.0 def
114114
/hdim -1.0 def
@@ -215,7 +215,7 @@ begin
215215
% Apply AST spec overrides and resolve physical specification
216216
%
217217
/bc412 ast /apply_ast //render exec not { //raiseerror exec } if
218-
/resolve_physspec //render exec
218+
/resolve_strictspec //render exec
219219

220220
/sbs barlen 1 add 8 mul 5 add string def
221221

@@ -269,8 +269,8 @@ begin
269269
/txt [ [text 0 0 text1font text1size] ]
270270
/text1xalign (center)
271271
} if
272-
/physspec physspec
273-
/bestfit bestfit
272+
/strictspec strictspec
273+
/loosespec loosespec
274274
/xdim xdim
275275
/xmin xmin
276276
/xmax xmax

src/channelcode.ps.src

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ begin
154154
/width 0 def
155155

156156
/propspec false def
157-
/bestfit false def
158-
/physspec false def
157+
/loosespec false def
158+
/strictspec false def
159159
/mag 1.0 def
160160
/xdim -1.0 def
161161
/hdim -1.0 def
@@ -195,7 +195,7 @@ begin
195195
% Apply AST spec overrides and resolve physical specification
196196
%
197197
/channelcode ast /apply_ast //render exec not { //raiseerror exec } if
198-
/resolve_physspec //render exec
198+
/resolve_strictspec //render exec
199199

200200
%
201201
% Tail-call optimisation FTW!
@@ -321,8 +321,8 @@ begin
321321
/text1xalign (center)
322322
/borderleft 1.0
323323
/borderright 2.0
324-
/physspec physspec
325-
/bestfit bestfit
324+
/strictspec strictspec
325+
/loosespec loosespec
326326
/xdim xdim
327327
/xmin xmin
328328
/xmax xmax

src/codablockf.ps.src

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,9 @@ begin
184184
/sepheight 1 def
185185
/parse false def
186186
/parsefnc false def
187-
/physspec false def
187+
/strictspec false def
188188
/propspec false def
189-
/bestfit false def
189+
/loosespec false def
190190
/mag 1.0 def
191191
/xdim -1.0 def
192192
/ast (default) def
@@ -209,7 +209,7 @@ begin
209209
% Apply AST spec overrides and resolve physical specification
210210
%
211211
/codablockf ast /apply_ast //render exec not { //raiseerror exec } if
212-
/resolve_physspec //render exec
212+
/resolve_strictspec //render exec
213213

214214
//codablockf.latevars /init get exec
215215

@@ -686,8 +686,8 @@ begin
686686
/rowmult rowmult
687687
/height symhgt 72 div
688688
/width symwid 72 div
689-
/physspec physspec
690-
/bestfit bestfit
689+
/strictspec strictspec
690+
/loosespec loosespec
691691
/xdim xdim
692692
/xmin xmin
693693
/xmax xmax

0 commit comments

Comments
 (0)