Skip to content

Commit 598c783

Browse files
committed
Update CONTRIBUTING.md
1 parent 1f1c4de commit 598c783

File tree

1 file changed

+129
-3
lines changed

1 file changed

+129
-3
lines changed

CONTRIBUTING.md

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,13 @@ Example for an encoder:
357357
} if
358358
359359
%
360-
% 5. Main encoding logic using //encoder.staticdata and loaded data from latevars
360+
% 5. Apply AST spec overrides and resolve physical specification
361+
%
362+
/encoder ast /apply_ast //render exec not { //raiseerror exec } if
363+
/resolve_physspec //render exec
364+
365+
%
366+
% 6. Main encoding logic using //encoder.staticdata and loaded data from latevars
361367
%
362368
% The barcode generation process is typically some variation of these steps:
363369
% - High-level encoding to codewords
@@ -371,17 +377,27 @@ Example for an encoder:
371377
%
372378
373379
%
374-
% 6. Build output dictionary
380+
% 7. Resolve bar height from spec (linear only, before bhs/bbs construction)
381+
%
382+
/resolve_height //render exec
383+
384+
%
385+
% 8. Build output dictionary
375386
%
376387
<<
377388
/ren /renlinear % or /renmatrix, /renmaximatrix
378389
% /sbs [...] % 1D: space-bar-space widths
379390
% /pixs [...] % 2D: row-major pixel array
391+
/physspec physspec
392+
/xdim xdim
393+
/xmin xmin
394+
/xmax xmax
395+
/modunit modunit
380396
/opt options
381397
>>
382398
383399
%
384-
% 7. Conditional rendering
400+
% 9. Conditional rendering
385401
%
386402
dontdraw not //renlinear if
387403
@@ -556,6 +572,116 @@ Encoders create a common dictionary structure expected by their renderer:
556572
Callers can access the intermediate dictionary by setting the `dontdraw` options with `enabledontdraw` set in global context.
557573

558574

575+
### Default Normalised Units
576+
577+
The renderers operate in harmonised normalised units chosen for integer
578+
pixel-locking at 72 DPI:
579+
580+
- **Linear (`renlinear`):** 1pt per X-dimension module (narrow bar), 72pt
581+
(1 inch) default bar height. At 72 DPI, 1 module = 1 device pixel.
582+
- **Matrix (`renmatrix`):** `modunit` points per module (1pt or 2pt depending
583+
on symbology). Output dict `width`/`height` = `cols * modunit / 72` inches.
584+
- **MaxiCode (`renmaximatrix`):** Fixed 2.4945 scale from 1pt modules to
585+
0.88mm hexagons. Hex geometry uses √3-based constants for correct aspect
586+
ratio at any scale; pixel-locking of hex vertices is inherently imperfect
587+
on a square grid.
588+
589+
External scaling or the `width`/`height` options resize from these defaults.
590+
The `physspec` system (below) provides an alternative path that renders at
591+
specification-accurate physical dimensions.
592+
593+
594+
### Grid Fitting (`render.ps.src`)
595+
596+
`render.gridfit` snaps module boundaries to whole device pixels, eliminating
597+
anti-aliasing artefacts in bitmap output.
598+
599+
**Device resolution:** When `griddpi` is provided by the caller, it is used
600+
directly as the target resolution — no hardware probing occurs and gridfit
601+
can operate on devices that would otherwise be skipped (e.g., nullpage).
602+
When auto-detected (`griddpi=0`), `defaultmatrix` is queried; bogus values
603+
cause gridfit to silently skip via the `hwxres` guard.
604+
605+
**Rounding modes:**
606+
- Default: round to nearest whole pixel per module
607+
- `width` forced (renlinear only): floor, to avoid exceeding requested width
608+
- `physspec` with spec bounds: smart rounding — try round first; if the
609+
effective X-dimension falls outside `xmin`/`xmax`, try the alternate
610+
direction; if neither fits, return `/errorname (info) false` to the caller
611+
612+
**Signature:** `magnitude xdim xmin xmax usefloor snapy /gridfit //render exec`
613+
returns `true` on success, `/errorname (info) false` on spec violation. The
614+
caller (renderer) is responsible for cleanup (`grestore`) before raising the
615+
error via `raiseerror`.
616+
617+
**EPS safety:** When `gridfit` is not enabled (and `griddpi` is not set), no
618+
device-dependent operators (`defaultmatrix`, `dtransform`) are executed.
619+
`physspec` without gridfit is fully EPS-safe.
620+
621+
622+
### Physical Specification System (`render.ps.src`, encoders)
623+
624+
Shared helpers in the `render` resource enable encoders to generate output at
625+
physical specification dimensions.
626+
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+
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
642+
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).
646+
647+
**Application Specification Tables (ASTs):** `render.ast` is a static readonly
648+
dict mapping encoder names to named spec profiles (GS1 SSTs 1-13, ISO
649+
defaults). Entries may share dicts via `index` to reduce allocation.
650+
`apply_ast` fills -1.0 sentinel values from the selected profile into the
651+
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.
655+
656+
**Helpers in render (dispatch table):**
657+
658+
- `apply_ast` — fills spec sentinels from AST; returns `true` or
659+
`/errorname (info) false`
660+
- `resolve_physspec` — validates `mag`/`xdim` exclusivity, resolves `xdim`
661+
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
666+
- `validate_xdim` — low-level `xdim xmin xmax` bounds check; returns `true`
667+
or `/errorname (info) false` with formatted error string
668+
669+
**Renderer scaling:** Each renderer applies `xdim 72 mul 25.4 div modunit div
670+
dup scale` when `physspec` is true. `renmaximatrix` divides additionally by
671+
its existing 2.4945 scale factor.
672+
673+
**Inkspread under physspec:** Renderers adjust inkspread to maintain a fixed
674+
physical amount (mm) rather than a proportional reduction. For `renlinear`,
675+
the adjustment is applied before bar width computation (since bars are
676+
pre-computed into the `bars` array). For `renmatrix` and `renmaximatrix`, it
677+
is applied after the physspec scale, before module rendering.
678+
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`).
683+
684+
559685
## Performance
560686

561687
### Ghostscript Observed Behaviour

0 commit comments

Comments
 (0)