This document is authoritative for both human contributors and automated tools.
All constraints are intentional and reflect hard-won experience with PostScript performance, RIP compatibility, and maintenance risk.
PostScript library generating 100+ barcode formats entirely within PostScript, executed by printer/RIP.
Performance, execution cost, and interpreter compatibility are critical.
These must be followed, otherwise you must be prepared to defend your choices:
- Create atomic, logical commits that complete one task.
- If a file is updated then its copyright date should be bumped. Ensure that the current year is used.
- Create an entry in CHANGES for each user-visible change. If the current change block has been published, create a new block dated "XXXX-XX-XX". Observe the layout and style of pre-existing contents.
- Unless the changes are tiny and consistent, there should be one commit per updated resource.
- Use sparse comments explaining "why" not "how".
- Ensure that code comments describe only the current code and do not reference changes made with respect to old code - that's what commit descriptions are for.
- New code should match existing idioms. Complex encoders such as QR Code are the best source of idiomatic code.
- Maintain existing user API (encoder interfaces and metadata).
- Prefer derived values over opaque constants so that the code is auditable, unless prohibitively expensive (even for lazy init).
- Static data should be hoisted out of the main procedure, and deferred with lazy initialisation if it must be derived
- Static and cached structures should be
readonlyby the time they are used in the main procedure - Prefer stack work over dictionary heavy code, especially in hot loops.
- Do not replace stack-based logic with dictionary-heavy abstractions.
- Do not refactor for readability at the expense of execution cost.
- Do not assume GhostScript-only execution. Assume modern implementation limit, and warn when approaching those limits:
- Integer representation may be 32- or 64-bit. Do not assume overflow or promotion at 32-bit. Encoders that require 64-bit integers should detect this and exit gracefully.
- Maximum of 65535 entries within dictionaries, arrays, and on the stack. (Assume user might already have entries on the stack.)
- Maximum string length of 65535 characters. Maximum name length of 127 characters.
- Where an allocation could exceed these limits, wrap it with a
stoppedguard (see Implementation Limit Guards below).
- Integer literals in source code must not exceed the signed 32-bit range (−2147483648 to 2147483647). The packager encodes integers as 32-bit binary tokens; larger values are silently corrupted. Compute large values at define time from small operands and reference via
//name(see Packager Integer Limitation below). - Tests should be extended to cover any error conditions raised by new code.
- Do not push commits or rewrite history.
- Always search for and follow pre-existing patterns before writing code. Warn if the existing pattern does not appear to follow best practise.
- Warn about potential issues such as potential performance regressions in hot paths.
- Warn about potentially incorrect code when introducing new idioms involving stack-based constructions.
- Don't ever
git resetto try to rewrite history when there is a risk of losing recent work. - Backup work before running irreversible commands such as
sedagainst a large number of files. - Plan complex tasks. Interview the user regarding significant design choices.
- To analyse barcode image output, generate a PNM image (that can be directly interpreted) using
contrib/development/make_image.sh pnm <encoder> <data> [options] > output.pnm. Requiresbuild/monolithic/barcode.ps(i.e. runmakefirst).
- AI is not very good at writing and reasoning about stack-based code.
- AI seems unable to accurately track the position of items on the stack, resulting in spurious inputs to
indexandroll. - AI does not consider the side effect of inserting new code into existing stack based code, e.g. later indexes needing to be recalculated.
- AI benefits from being shown a preferred pattern then applying it.
make -j $(nproc)- Build all distribution targets (resource, packaged_resource, monolithic, monolithic_package)make -j $(nproc) test- Run all tests; should be ran before declaring a code change completemake clean- Cleanmake build/standalone/<encoder>.ps- Build standalone encodermake build/standalone_package/<encoder>.ps- Build packaged standalone encodermake syncsyntaxdict- Sync gs1process.ps.src with latest upstream GS1 Syntax Dictionarymake -C wikidocs -f __pandoc/Makefile pst-barcode-docs- Generate pst-barcode-doc.tex for pst-barcode LaTeX package
Note: On MacOS it's sysctl -n hw.ncpu instead of $(nproc).
Build requires Ghostscript (gs) in PATH and Perl.
Quick iteration when developing a resource (with custom invocation):
make build/resource/Resource/uk.co.terryburton.bwipp/qrcode && \
gs -q -dNOSAFER -dNOPAUSE -dBATCH -sDEVICE=nullpage -I build/resource/Resource \
-c '10 10 moveto (Hello World) () /qrcode /uk.co.terryburton.bwipp findresource exec'Or (with a debug option):
make build/resource/Resource/uk.co.terryburton.bwipp/qrcode && \
gs -q -dNOSAFER -dNOPAUSE -dBATCH -sDEVICE=nullpage -I build/resource/Resource \
-c '/uk.co.terryburton.bwipp.global_ctx << /enabledebug true >> def' \
-c '10 10 moveto (Hello World) (debugcws) /qrcode /uk.co.terryburton.bwipp findresource exec'Or (with standard tests):
make build/resource/Resource/uk.co.terryburton.bwipp/qrcode && \
gs -q -dNOSAFER -dNOPAUSE -dBATCH -sDEVICE=nullpage -I build/resource/Resource \
-f tests/ps_tests/test_utils.ps -f tests/ps_tests/qrcode.ps.test"Packaging" refers to PostScript resources processed (by the build system's "packager") into a form easier to distribute but is harder to debug:
- Efficient byte-based encodings for numbers and operators
- Compressed numeric/string arrays
- ASCII85-wrapped output (watermarked)
- Results in ~30-50% smaller files
Packager Integer Limitation: The packager encodes integers as signed 32-bit
binary tokens. Literals outside that range are silently corrupted. Compute
large values at define time from small operands and reference via //name.
"Global context" is an optional dictionary configured in global VM containing optional keys that affect the behaviour of BWIPP:
preload- Perform eager initialisation of normally lazy variables during resource definitionenabledebug- Allow the user to set debug options (e.g.debugcws), for development purposes including activation of hooksenabledontdraw- Allow the user to set dontdraw, in case they are providing custom renderershooks- Dictionary of hook procedures for debugging purposes, including profilingdefault_*- Defaults for certain renderer options set by the user, e.g.default_barcolor
For example:
currentglobal
true setglobal
/uk.co.terryburton.bwipp.global_ctx << /preload true >> def
setglobalCore library:
src/*.ps.src- PostScript resource source filessrc/uk.co.terryburton.bwipp.upr– PostScript resource index mapping resource names to file paths; required by Distiller and used by the build system to enforce resource order in monolithic and standalone outputstests/ps_tests/*.ps.test- PostScript test files
Build scripts:
build/make_resource.pl- Resource builder (invokes GhostScript)build/make_deps.pl- Dependency rule generator (creates .d files)build/make_monolithic.pl- Monolithic resource assemblerbuild/make_standalone.pl- Standalone file assembler (supports multiple encoders)
Automatic build outputs:
build/resource/-make resource: Unpackaged resourcesbuild/packaged_resource/-make packaged_resource: Packaged resourcesbuild/monolithic/barcode.ps-make monolithic: All encoders combinedbuild/monolithic_package/barcode.ps-make monolithic_package: Packaged monolithic
On-demand build outputs:
build/standalone/<encoder>.ps- Standalone encoder containing all required resource dependenciesbuild/standalone_package/<encoder>.ps- As above, but packaged
Each resource source file has a similar structure:
-
Header comments
- Project name and URL
- VIM modeline
- Copyright notice (year should be maintained)
- License text
-
Metadata comments
- Declares dependencies between resources (for assembling monolithic resources)
- For encoders, gives example data (see Encoder Metadata below)
-
Allocation mode setup
- Object storage placed in global VM
- Executable arrays packed for efficiency
-
Dependent resource loading
- Uses
findresourceto load dependencies into working dictionary
- Uses
-
Hooks setup
- Call
setuphooksto create encoder-specific or generic hook procedures - Enables profiling and debugging via global context hooks
- Call
-
Static data structures
- Plain definitions of literal structured data (arrays and dicts)
- Runs once during resource definition, with values immediately referenced by main procedure; allocate once during initialisation, then reuse at runtime
- Names MUST be prefixed with the resource name (e.g.,
encoder.charmap) - the Makefile extracts all//namereferences to populate the packager's atload template, requiring globally unique names //nameimmediate references are resolved at parse time, embedding the value directly into procedures- Embedded procedures should have explicit
bind - Must be marked
readonly
-
Initialisation of any FIFO caches
- Definition of cache capacity parameters and loading of parameter overrides from global context
- Definition of a generator procedure and a cardinality function
- Executing the
fifocacheresource to return a named "FIFO cache object"
-
Lazy initialisation procedure
- Data that must be computed (expensive) and is deferred until first execution
- First run derives and stores values (in global VM); subsequent runs load cached values
- Embedded procedures do not require
bind(propagates from outer procedure)
-
Main procedure
- Exported by the resource and called on demand
- Uses immediate references to static data
- Calls lazy initialisation procedure
- Makes use of any named FIFO caches by executing their
fetchmethod - Bind the main procedure whilst inhibiting binding of non-standard operators defined on some RIPs, i.e.
barcode
-
Resource definition
- Define the main procedure as a resource
-
Allocation mode restore
- Return to previous defaults
Encoder source files contain metadata comments:
% --BEGIN ENCODER encodername--
% --DESC: Human-readable description
% --EXAM: example input data
% --EXOP: example options
% --RNDR: renlinear|renmatrix|renmaximatrix
% --FMLY: Family Name
% --REQUIRES preamble raiseerror [other deps...]
% --END ENCODER encodername--REQUIRES lists dependencies of the resource in order. It is used by the build
system and is API for users that want to assemble the resources into a PS file
prolog. It is not transitive: If must list the recursive dependencies of all
required resources.
DESC and FMLY apply only to encoders. Utility resources (e.g. preamble,
raiseerror, renderers) intentionally omit both. Internal encoders (e.g.
gs1-cc, raw, daft) intentionally omit FMLY, which groups encoders into
families for the C library API.
Built resource files have a similar structure to their source files, with their source (packaged or otherwise) wrapped by comments that follow the PostScript language Document Structuring Conventions.
BeginResource:DSC comment contains VM memory usage that is measured by the build system per-resource by pre-loading all dependencies before measurement, so each resource's VMusage reflects only its own consumption.
Monolithic outputs contain comments that feature a --BEGIN/END TEMPLATE-- marker pair that users can use to splice the relevant contents into the prolog of a PS document.
/resource.latevars dup 16 dict def load /init {
currentglobal
true setglobal % Lazy vars must be in global VM, like resource definitions
//resource.latevars begin
%
% Derived data computed here (e.g., Reed-Solomon tables, lookup dicts)
%
/charvals 256 dict def
% ... populate charvals ...
/charvals charvals readonly def
%
% Redefine /init to just load cached values on subsequent calls
%
/init { //resource.latevars {def} forall } def
end
//resource.latevars /init get exec
setglobal
} bind put
/uk.co.terryburton.bwipp.global_ctx dup where { % Do eager initialisation, if desired
exch get /preload known {//resource.latevars /init get exec} if
} {pop} ifelse
%
% latevars procedure is called in main procedure, **after** options processing
%
% User must not be able to override variables defined in latevars via options.
%
/resource {
% Options processing code
...
//resource.latevars /init get exec
...
}Resources are registered to the /uk.co.terryburton.bwipp category:
/encodername dup load /uk.co.terryburton.bwipp defineresource popDependencies are loaded at the start of the resource definition:
N dict
dup /raiseerror dup /uk.co.terryburton.bwipp findresource put
dup /parseinput dup /uk.co.terryburton.bwipp findresource put
begin
% resource definitions using //raiseerror, //parseinput
endExample for an encoder:
/encoder {
20 dict begin
{ % stopped context for error cleanup
%
% 1. Option defaults
%
/dontdraw false def
/parse false def
/option1 defaultvalue def
%
% 2. Process input
%
//processoptions exec /options exch def
/barcode exch def
%
% 3. Initialize lazy computation
%
//encoder.latevars /init get exec
%
% 4. Validation (with early return on error)
%
barcode () eq {
/bwipp.encoderEmptyData (Data must not be empty) //raiseerror exec
} if
%
% 5. Main encoding logic using //encoder.staticdata and loaded data from latevars
%
% The barcode generation process is typically some variation of these steps:
% - High-level encoding to codewords
% - Termination and padding
% - Symbol size selection
% - Blocking data for Reed-Solomon ECC generation
% - Matrix construction with fixtures
% - Data layout in matrix
% - Mask evaluation and selection
% - Function module placement
%
%
% 6. Build output dictionary
%
<<
/ren /renlinear % or /renmatrix, /renmaximatrix
% /sbs [...] % 1D: space-bar-space widths
% /pixs [...] % 2D: row-major pixel array
/opt options
>>
%
% 7. Conditional rendering
%
dontdraw not //renlinear if
} stopped {end stop} if % Prevent dict stack leak on error
end
}
[/barcode] {null def} forall % Inhibit binding of non-standard operators defined on some RIPs
bind def
/encoder dup load /uk.co.terryburton.bwipp defineresource popUpon encountering an error, a resource shall clean up only its own stack
entries and then call the raiseerror procedure. This halts execution and
results in either a custom error handler or a user-provided stopped context
being run.
To avoid orphaning stack entries when a resource does not run to completion — such
as when another resource invokes raiseerror — resources must execute other
resources only with a clean stack.
To avoid leaving entries on the dict stack when an error is raised, each
resource's stopped context catches the stop from raiseerror, and then
runs {end stop} to close the dictionary and re-propagate.
An example is "wrapper encoders" that delegate to another encoder with modified options:
% Good: /args is only pushed after inner encoder succeeds
barcode options //innerencoder exec /args exch defWhen an encoder's input can cause an internal allocation (string, array or
dictionary) to exceed the guaranteed PostScript implementation limits, the
allocation must be wrapped with stopped to catch the error gracefully.
GhostScript has higher limits so these guards may only trigger on other
interpreters.
The guard must pop the failed size operand left on the stack by stopped and
raise a descriptive error:
barcode length 8 mul { string } stopped {
pop /bwipp.encoderInputTooLarge (Input data exceeds implementation limits) //raiseerror exec
} if /bits exch def<barcode data string> <options string or dict> /<encoder> /uk.co.terryburton.bwipp findresource execExample:
10 10 moveto
(\(01\)09521234543213(3103)000123) % Data: Note quoting of parentheses
(segments=4 includetext alttext=TEST) % Options
/databarexpandedstacked % Encoder
/uk.co.terryburton.bwipp findresource exec
showpage- Accepts space-separated
key=valuepairs or justkey(to set boolean to true) - Options update corresponding PostScript variable in the current dictionary, only if it exists
- Values are type checked against the option's default value type, otherwise error is raised
Options parse and parsefnc preprocess data to allow denoting ASCII control characters and non-data characters (FNC1-4, ECI) as printable text
- Uses
^as escape character:^NUL,^000-^255(withparseoption)^FNC1,^ECInnnnnn(withparsefncoption)
GS1 AI syntax is first processed by gs1process.ps.src before regular parsing
Processes GS1 data and validates against the GS1 Barcode Syntax Dictionary, which describes each Application Identifier's format specification (component data types and lengths), and component validation rules (using "linters").
The contrib/development directory contains:
gs1-syntax-dictionary.txt- Local copy of the GS1 Syntax Dictionarybuild-gs1-syntax-dict.pl- Maintainer script to transform it into the/gs1syntaxcode structure
Calling convention:
For bracketed AI syntax:
(\(01\)09521234543213\(10\)ABC123) /ai //gs1process exec % => ais vals fncsFor a GS1 Digital Link URI:
(https://id.gs1.org/01/09521234543213/10/ABC123) /dl //gs1process exec % => ais vals fncsais- Application Identifier strings (e.g.,[(01) (10)])vals- Corresponding value strings (e.g.,[(09521234543213) (ABC123)])fncs- Boolean array indicating which AIs require FNC1 separator (based oncontrib/development/ai_not_needing_fnc1.txt)
parseinput is applied to each extracted AI value (from vals) of bracketed AI syntax inputs, and to the overall URI for GS1 Digital Link inputs.
GS1-enabled encoders can disable validation checks using the dontlint option.
- Errors are raised by popping stack items added by current resource, pushing a user-friendly info string and error name, then calling
raiseerror - Uses standard PostScript
stopmechanism to invoke custom error handlers or astoppedcontext - Error names typically follow pattern:
/bwipp.<resource><ErrorType>, e.g.,/bwipp.code39badCharacter
Example custom error handlers in contrib/development/:
error_handler_to_stderr.ps- Writes BWIPP errors to stderr (for scripted/batch use)error_handler_as_image.ps- Renders error message as page output (for viewers)
Encoders create a common dictionary structure expected by their renderer:
1D Barcodes (renlinear):
<<
/ren /renlinear
/sbs [1 2 1 1 ...] % space-bar-space widths (digits 0-9)
/bhs [0.5 0.5 ...] % bar heights
/bbs [0 0 ...] % bar baseline positions
/txt [[...] [...]] % text elements: [string x y font size]
/opt options
>>2D Barcodes (renmatrix):
<<
/ren /renmatrix
/pixs [1 0 1 0 ...] % row-major array: 1=black, 0=white
/pixx width % symbol width in modules
/pixy height % symbol height in modules
/opt options
>>MaxiCode (renmaximatrix):
<<
/ren /renmaximatrix
/pixs [...] % 30x29 hexagon grid values
/opt options
>>Callers can access the intermediate dictionary by setting the dontdraw options with enabledontdraw set in global context.
- Dict reads are ~1.3x faster than stack
index; writes (def) are ~1.25x slower than reads - Stack manipulation wins by avoiding
def, not by faster access forallavoids per-iterationgetoverhead compared tofor+getN indexcost is independent of stack depthgetintervalcost is fixed regardless of size (returns a view, not a copy)
- Use
//nameimmediate lookup for static data (avoids runtime allocation and dictionary lookups); use directly rather than creating local aliases (e.g.,//encoder.fnc1not/fnc1 //encoder.fnc1 def) - Defer expensive computation to lazy init (first-run cost, cached thereafter)
- When
latevarsneeds a helper function, define it in static scope (bind it; seeauspost.rsprod) and reference via//encoder.helper exec
Conditional Assignment Pattern
Use an inline condition when performing simple conditional assignments:
% Bad: Verbose
condition {
/a 2 def
} {
/a 5 def
} ifelse
% Good: Concise
/a condition { 2 } { 5 } ifelse def"Switch" blocks (common exit)
Long ifelse chains can be replaced with a "common exit" pattern for
maintainability. Nested ifelse avoids the repeat/exit overhead, so prefer it in hot paths.
% Nested ifelse (faster, harder to modify)
condition1 {
/c1
} {
condition2 {
/c2
} ... {
/default
} ifelse ... } ifelse
% Common exit (slower, easier to maintain)
1 { % Common exit
condition1 { /c1 exit } if
condition2 { /c2 exit } if
...
/default
} repeat
/result exch defLoops with conditional exit
Loops should be commented as such in the first line:
{ % loop
condition { exit } if
...
} loopReading Global Context
Read configuration from (optional) global context with a default:
/configvalue 42 def % Default
/uk.co.terryburton.bwipp.global_ctx dup where {
exch get /configkey 2 copy known {get /configvalue exch def} {pop pop} ifelse
} {pop} ifelseModule-Level Caching with FIFO caches
For expensive computations that benefit from caching across invocations (e.g.,
generation of Reed-Solomon polynomial coefficients), use the fifocache
resource:
/encoder.coeffscachemax N def % Override with global_ctx.encoder.coeffscachemax
/encoder.coeffscachelimit M def % Override with global_ctx.encoder.coeffscachelimit
/uk.co.terryburton.bwipp.global_ctx dup where {
exch get dup
/encoder.coeffscachemax 2 copy known {get /encoder.coeffscachemax exch def} {pop pop} ifelse
/encoder.coeffscachelimit 2 copy known {get /encoder.coeffscachelimit exch def} {pop pop} ifelse
} {pop} ifelse
/encoder.coeffscache encoder.coeffscachemax encoder.coeffscachelimit //fifocache exec def
/encoder.coeffscachefetch {
/key exch def
key
{ //encoder.gencoeffs exec } % Generator procedure
{ length } % Cardinality function; applied to output of generator procedure
//encoder.coeffscache /fetch get exec
} bind defCache parameter guidelines:
maxlimits cache entry count (number of keys);limitlimits total elements (as measured by cardinality function)- Set
limit=0to disable caching - Set
maxandlimithigh enough to avoid evictions when memory allows
- Creating variables (dictionary entries) in hot loops
- Defining static data in the main procedure (hoist to define time, then use
//name) - Computing derived data on every invocation (use
latevars)
Array Extension in Loops
Avoid /arr [ arr aload pop newelem ] def within loops. This idiom creates a
new array each iteration by unpacking the old array onto the stack, adding new
elements, then repacking.
% Bad: O(n^2) array rebuilding
/result [] def
0 1 n 1 sub {
/i exch def
/result [ result aload pop i computeValue ] def
} for
% Good: Pre-allocate and fill
/result n array def
0 1 n 1 sub {
/i exch def
result i i computeValue put
} forWhen array size isn't known in advance, mark ... counttomark array astore is
fastest (~2x faster than pre-allocate, ~5x faster than aload rebuild):
mark
0 1 n 1 sub { computeValue } for % Leave values on the stack
counttomark array astore
/result exch defIf use of stack is difficult, e.g. due to complex backtracking, then
pre-allocate and, if size is bounded, over-size then trim with getinterval.
Extension of small arrays with /arr [ arr aload pop ... ] def is
acceptable for one-time operations outside loops.
Hot Loop Stack Pattern
In hot loops, avoid /idx exch def which incurs def overhead each iteration.
Keep loop index on stack instead:
% Bad: Creates dictionary entry each iteration
0 1 k { % E.g. k from an outer loop
/idx exch def
% Stuff using "idx" variable...
arr k idx sub 1 sub get
} for
% Good: Iterator referenced by "index" and finally consumed by "roll"
0 1 k 1 sub { % idx on stack
% Stuff using "N index" to access idx on the stack...
% Finally, roll moves idx to top where we consume it:
arr k 3 -1 roll sub 1 sub get
} forThe RSEC loops in qrcode, datamatrix, pdf417 demonstrate advanced uses of this
pattern, including stack-based access to variables outside of the inner loop.
Updating a dictionary item
When applying a function to a dictionary key:
% Bad: More lookups; verbose
dic /item dic /item get 1 add put
% Good: Clean; efficient
dic /item 2 copy get 1 add putBeware built-in operators
When a procedure is binded, names that match operators are resolved at bind
time. Using operator names as variables causes unexpected behavior:
% count is a built-in operator that returns stack depth
(a) (b) (c) count = % prints 3
% Problem: "count" as a variable in a bound procedure
/badproc {
/count 5 def
/count count 1 add def % BUG: count resolves to operator
count
} bind def
badproc = % prints 0 (stack depth), not 6!Common operators to avoid as variable names: count, length, index.
Simple profiling of the overall runtime for some encoder:
time gs -q -dNOSAFER -dNOPAUSE -dBATCH -sDEVICE=nullpage -c \
'(build/monolithic/barcode.ps) run
100 { 0 0 moveto (DATA) (options) /encoder /uk.co.terryburton.bwipp findresource exec } repeat'Detailed per-phase timing using the resource timer utility:
gs -q -dNOSAFER -dNOPAUSE -dBATCH -sDEVICE=nullpage \
-I build/resource/Resource -f contrib/development/resource_timer.ps -c '
0 0 moveto (DATA) (options) /encoder /uk.co.terryburton.bwipp findresource exec
resource_timer_report
'The resource timer uses the hooks framework to measure time spent in each phase of resource execution, with hierarchical grouping of related phases.
Resources may be configured to permit named "hooks" at any point in their code:
The following placed at the beginning of the resource definition will create
two hooks (before and after):
/qrcode [/before /after] //setuphooks execHooks accept a single parameter and may appear anywhere in the code (as many times as required):
(matrix.layout) //qrcode.before exec
...
(matrix.layout) //qrcode.after execThe above as examples of existing hooks placed at execution phase boundaries.
Configuring hooks procedures:
By default hooks will do nothing other than pop the parameter.
The above pair of hooks is intended to allow measurement of time spent in each phase via the definition of suitable procedures within global context.
The global context must be configured with enabledebug set and a hooks
dictionary containing custom procedures related to the hooks, for example:
currentglobal true setglobal
/uk.co.terryburton.bwipp.global_ctx <<
/enabledebug true
/hooks <<
/before {20 string cvs print ( before ) print print ( ) print realtime =}
/after {20 string cvs print ( after ) print print ( ) print realtime =}
>>
>> def
setglobalExample output:
datamatrix before setup.define 20
datamatrix after setup.define 21
datamatrix before setup.latevars 22
datamatrix after setup.latevars 40
...
When triggered, a hook procedure receives the resource name that triggered it, as well as the parameter from the code.
To configure a hook procedure for the named hooks in just a single encoder,
create a hooks entries named like qrcode.before.
Performance enhancements are welcome, but significant gains are hard won. Large 2D symbols have different bottlenecks: QR Code is mask evaluation bound; Data Matrix, Aztec, and PDF417 are RSEC bound (mitigated by FIFO caches). See encoder source for attempted optimizations.
tests/run_tests- Top-level test orchestrator to run tests for all build variantstests/<variant>/run- Script to run the tests for a single build varianttests/ps_tests/test_utils.ps- PostScript test utility functionstests/ps_tests/*.ps.test- Individual resource tests (run from eitherbuild/resource/Resourceorbuild/packaged_resource/Resource); require that test_utils.ps utility is loadedtests/ps_tests/test.ps- PostScript test entrypoint: Loads utility functions then runs all resource tests
The test_utils.ps file contains the following utility functions required for
all resource tests:
debugIsEqual- Compare codeword arrays (used withdebugcwsoption)isEqual- Compare output arrays (pixs,sbs)isError- Verify specific error is raised
To access the intermediate dictionary without rendering, each of the encoders
support the following option, which requires enabledontdraw to be set in
global context:
dontdraw- Return structured dict without rendering
Encoders may have one or more of the following debug options, each of which
requires enabledebug to be set in global context:
debugcws- Return codeword arraydebugbits- Return bit arraydebugecc- Return error correction datadebughexagons- Return hexagon positions (MaxiCode)
Run tests for just one of the encoders:
gs -q -dNOSAFER -dNOPAUSE -dBATCH -sDEVICE=nullpage -I build/resource/Resource \
-f tests/ps_tests/test_utils.ps -f tests/ps_tests/qrcode.ps.testDebug an encoder's high-level encoding codeword generation:
gs -q -dNOSAFER -dNOPAUSE -dBATCH -sDEVICE=nullpage -I build/resource/Resource \
-c '10 10 moveto (TEST) (debugcws) /qrcode /uk.co.terryburton.bwipp findresource exec =='Debug an encoder's ECC codeword generation:
gs -q -dNOSAFER -dNOPAUSE -dBATCH -sDEVICE=nullpage -I build/resource/Resource \
-c '10 10 moveto (TEST) (debugecc) /datamatrix /uk.co.terryburton.bwipp findresource exec =='Success test - validate 1D barcode graphical structure via sbs array:
(INPUT) (dontdraw) encoder /sbs get
[1 2 1 1 ...] debugIsEqualSuccess test - validate 2D barcode graphical structure via pixs array:
(INPUT) (dontdraw) encoder /pixs get
[
1 0 1 0 ... % Aligned to shape of 2D bitmap
0 1 0 1 ...
] debugIsEqualSuccess test - validate encoder codeword output:
{
(TESTING) (debugcws) encoder
} [32 61 39 ...] debugIsEqualError test:
{ (INPUT) (dontdraw) encoder } /bwipp.encoderErrorName isErrorFor repetitive tests, use template procedures, for example:
/eq_tmpl {
exch { 0 (dontdraw) encoder /sbs get } dup 3 -1 roll 0 exch put
exch isEqual
} def
(12345678) [1 1 1 1 2 ...] eq_tmpl
(87654321) [1 2 1 1 1 ...] eq_tmpl/eq_tmpl {
3 1 roll { 0 0 encoder /pixs get }
dup 3 -1 roll 1 exch put % insert options
dup 3 -1 roll 0 exch put % insert data
isEqual
} def
(DATA) (eclevel=M) [...pixs...] eq_tmpl%
% Success-only smoke test; not comparing output
%
/ok_tmpl {
{ 0 setanycolor true } dup 3 -1 roll 0 exch put
true isEqual
} def
(112233) ok_tmpl%
% Expecting an error
%
/er_tmpl {
exch { 0 (dontdraw) encoder } dup 3 -1 roll 0 exch put
exch isError
} def
(INVALID) /bwipp.encoderBadData er_tmplTest for errors that are raised by the code should be comprehensive.
However some linter errors cannot be triggered because they are masked by other checks.
GS1 format checks error that are impossible due to format validation running before linters:
GS1valueTooShortForOffsetGCP- AIs have min >= 14GS1badDateLength- AIs have fixed-length date fieldsGS1badTimeLength- linter defined but unusedGS1badPieceTotalLength- AIs have fixed even lengthGS1couponTooShortGCPVLI,GS1couponTooShortFormatCode- AIs have min >= 1GS1badLatitudeLength,GS1badLongitudeLength- AIs have fixed length
Unreachable due to earlier validation:
GS1UnknownCSET82Character-lintcset82catches firstGS1alphaTooLong- max length fits primes arrayGS1requiresNonDigit- checksum requires non-digits
Documentation is hosted in the wiki at https://github.com/bwipp/postscriptbarcode/wiki, and these pages are the source for the PDF and HTML documentation that is hosted at in GitHub Releases.
The wikidocs/ directory is a Git submodule that tracks the project's GitHub wiki repository.
The GitHub Actions workflow (.github/workflows/ci.yml) builds the
documentation. Upon release (i.e. pushing a tag), the job uploads the build
documentation to GitHub releases as postscriptbarcode-manual.pdf and
postscriptbarcode-manual.html.
To regenerate the documentation locally:
git -C wikidocs pull origin master
git add wikidocs
got commit -m "Bumped wikidocs"From the wikidocs directory:
make -f __pandoc/Makefile allOutputs:
__pandoc/barcodewriter.pdf- Complete PDF manual__pandoc/barcodewriter.html- Self-contained HTML documentation
The build requires Pandoc, the Haskell runtime and LaTeX.
Example images in the wiki are generated from formatted code blocks in the markdown files:
Data: <barcode data>
Options: <encoder options>
Encoder: <encoder name>
Followed by an image reference: 
Generating individual images:
contrib/development/make_image.sh svg qrcode 'Hello World' '' > qrcode.svg
contrib/development/make_image.sh pdf qrcode 'Hello World' '' > qrcode.pdfThe script supports png, pnm, eps, pdf, and svg formats. Wiki images use SVG
for the GitHub wiki and PDF for LaTeX documentation.
Regenerating all wiki images:
wikidocs/__pandoc/regenerate_images.plBoth scripts require build/monolithic/barcode.ps (run make first).
Source code:
src/<symbology>.ps.src- Create the resource file (use existing encoder as template)src/uk.co.terryburton.bwipp.upr- Add entry to the resource indextests/ps_tests/<symbology>.ps.test- Create test file- Verify build:
make build/standalone/<symbology>.ps
Wiki content for symbologies (in wikidocs/ submodule):
symbologies/<Symbology-Name>.md- Create the documentation pagesymbologies/_Sidebar.md- Add link in appropriate category sectionsymbologies/Symbologies-Reference.md- Add entry with thumbnail image(s)images/<name>.svgandimages/<name>.pdf- Example images (usescale=1)- Related symbology pages - Add cross-references if applicable
Wiki content for options (in wikidocs/ submodule):
options/<Option-Name>.md- Create the documentation pageoptions/_Sidebar.md- Add link in appropriate sectionoptions/Options-Reference.md- Add entry if applicable
Build system:
wikidocs/__pandoc/Makefile- Add toREF_FILESin appropriate category
Release tasks (maintainer only):
- Update homepage in
postscriptbarcodegh-pagesbranch - Update GitHub project tags
Distribution packages are built on OBS for multiple targets across DEB, RPM, and Arch-based distributions, using two projects:
| Project | Purpose | Source | Version format |
|---|---|---|---|
home:terryburton:postscriptbarcode |
Release | Pinned tag | @PARENT_TAG@ |
home:terryburton:postscriptbarcode:dev |
Dev/nightly | HEAD | @PARENT_TAG@.@TAG_OFFSET@~nightly |
Packaging files:
debian/- DEB packagingpackaging-examples/rpm-based/postscriptbarcode.spec- RPM spec filepackaging-examples/arch-linux/PKGBUILD- Arch packaging
Triggering builds:
The release project's services run on commit and can be re-triggered via
osc service remoterun. Trigger promptly after tagging, before further
commits land. The dev project builds from HEAD on each trigger.
# Release
osc service remoterun home:terryburton:postscriptbarcode libpostscriptbarcode
osc results home:terryburton:postscriptbarcode libpostscriptbarcode
# Dev/nightly
osc service remoterun home:terryburton:postscriptbarcode:dev libpostscriptbarcode
osc results home:terryburton:postscriptbarcode:dev libpostscriptbarcodePost-install smoke tests verify that packages work after installation. Shared
test scripts in packaging-examples/install_tests/ run across all distros.
Package test scripts:
test_postscript.sh- Loadsbarcode.psin GhostScript and generates a barcodetest_clib.sh- Compiles and runs C example against installed librarytest_<binding>.sh- Tests binding via installed example
- Update CHANGES date:
sed -i '1s/XXXX-XX-XX/YYYY-MM-DD/' CHANGESand commit - Review CHANGES; verify wikidocs has corresponding symbology/option pages
- Update wikidocs submodule if needed:
git -C wikidocs pull origin master - Push commits, wait for CI:
gh run watch - After CI succeeds:
make tag && git push origin YYYY-MM-DD - Verify release:
gh release view YYYY-MM-DD - Trigger OBS release build (promptly, before further commits land):
osc service remoterun home:terryburton:postscriptbarcode libpostscriptbarcode - Add next placeholder:
sed -i '1i XXXX-XX-XX\n\n*\n\n' CHANGESand commit/push
Stack operations:
(a) (b) (c) 3 1 roll => (c) (a) (b)
(a) (b) (c) 3 -1 roll => (b) (c) (a)
(a) (b) (c) 1 index => (a) (b) (c) (b)
(a) (b) (c) /x 1 index def => x = (c), not (b) due to /x on stack!readonly returns a new reference:
/a [ 1 2 3 ] def % a is writable
a readonly pop % achieves nothing; a still writable
/c a readonly def % c is readonly; a remains writableString literals are shared across invocations (arrays/dicts are not):
/proc {
/a (0000) def % Safer: /a (0000) 4 string copy def
a 0 (1234) putinterval
} def
proc % First run: a = "1234"
proc % Subsequent: a starts as "1234", not "0000"!