Skip to content

Developing a Frontend to BWIPP

Terry Burton edited this page Jul 27, 2023 · 29 revisions

Developing a Frontend to BWIPP

There are a number of frontends to BWIPP that vary in terms of the functionality that they expose and the way that they express this through their API or GUI, etc.

It would be nice to unify some of these projects but in the meantime this document attempts to provide some guidelines to apply when developing something that places BWIPP in the hands of developers and users.

The author would ideally like any language binding, library or graphical frontend to be representative of the complete functionality of the BWIPP resource and to be maintainable with minimal effort and these guideline help to achieve this goal.

Make Early Contact with the BWIPP Author

Contact the author of BWIPP whilst you're still experimenting. I will try not to insist on my own way as it's you that will end up supporting your creation so I want you to be happy with it, but it will help everyone if there is some consistency between your code and the next person's.

Author's commitment: If I know about your project then I will make a best efforts commitment to assist with end user support and developer support for any library or application that makes a genuine attempt to adopt the principles given here.

Use the BWIPP C helper library and bindings...

Be aware that we have produced a C library and language-specific bindings with a common API to help with manipulating the BWIPP resources: https://github.com/bwipp/postscriptbarcode/tree/master/libs

You should attempt to use these where possible as it takes most of the pain out of working with the PostScript. If the API doesn't support something that you need then we can extend the interface as necessary.

... or at least parse the BWIPP metadata

If you choose to work directly with the PostScript then it is better to parse the inline metadata rather than embedding a load of static data in your code.

You should support new barcode formats automatically by scanning the barcode.ps metadata for BEGIN/END ENCODER blocks. From these extract descriptions, example data, options, etc. by using the DESC, EXAM, EXOP, ... stanzas within the BEGIN/ENCODER ENCODER blocks.

Example BWIPP metadata for an encoder:

% --BEGIN ENCODER ean8--
% --REQUIRES preamble raiseerror renlinear ean5 ean2--
% --DESC: EAN-8
% --EXAM: 02345673
% --EXOP: includetext guardwhitespace
% --RNDR: renlinear
... PostScript resource definition here ...
% --END ENCODER ean8--

The best strategy is for libraries and graphical frontends to be light on compiled-in data and can therefore be enhanced by simply replacing the barcode.ps file.

To fully meet this objective may require extending the barcode.ps metadata to describe the individual options that are available for each encoder. The BWIPP author is certainly interested in having such a discussion so please make contact regarding your requirements.

Let Users Drive BWIPP Directly

Whether part of your design or as a fall back, allow advanced users to specify the data, options and encoder directly. This will allow them to access BWIPP functionality that you haven't anticipated or chosen to expose via your API or GUI.

Use BWIPP's Error Reporting

Use the BWIPP error reporting mechanism to provide specific error messages to users so that they can understand why a given input is invalid.

The preferred way to do this is to wrap the BWIPP invocation in a "stopped context" which allows you to handle BWIPP-specific exceptions. For example, the following will invoke BWIPP and on error will emit formatted, descriptive text of the error (e.g. BWIPP ERROR: EAN-13 must be 12 or 13 digits) to STDERR which the calling program can recognise as an error and prompt the user:

{  % "try" BWIPP invocation
  0 0 moveto (ABC) () /code39 /uk.co.terryburton.bwipp findresource exec
  showpage
} stopped {  % "catch" all exceptions
  $error /errorname get dup length string cvs 0 6 getinterval (bwipp.) ne {
    stop  % Rethrow non-BWIPP exceptions
  } if
  % Handle BWIPP exceptions, e.g. emit formatted error to stderr
  (%stderr) (w) file
  dup (\nBWIPP ERROR: ) writestring
  dup $error /errorname get dup length string cvs writestring
  dup ( ) writestring
  dup $error /errorinfo get dup length string cvs writestring
  dup (\n) writestring
  dup flushfile
} if

Less advised, but which may be useful in some circumstances, it is possible to override the PostScript VM's default handleerror procedure to recognise and take some special action when handling BWIPP-specific exceptions. For example, the following will invoke barcode.ps and on error will emit formatted, descriptive text of the error (e.g. BWIPP ERROR: EAN-13 must be 12 or 13 digits) to STDERR which the calling program can recognise as an error and prompt the user:

%!PS
errordict begin
/handleerror {
  $error begin
  errorname dup length string cvs 0 6 getinterval (bwipp.) eq {
    (%stderr) (w) file
    dup (\nBWIPP ERROR: ) writestring
    dup errorname dup length string cvs writestring
    dup ( ) writestring
    dup errorinfo dup length string cvs writestring
    dup (\n) writestring
    dup flushfile end quit
  } if
  end //handleerror exec
} bind def
end

% If necessary, set up anything else specific to the environment just here.

% Include the BWIPP resource, either directly or from PS
(barcode.ps) run

% Now make the calls to BWIPP
0 0 moveto (ABC) () /code39 /uk.co.terryburton.bwipp findresource exec

Locating the Resource

Allow the location of the barcode.ps file to be configured by the user so that non-admins users can provide a local version and distributions that deprecate bundled libraries can provide a separately packaged version.

In any case, use the following search order to locate the barcode.ps resource:

  1. [%USER_SPECIFIED_LOCATION%]
  2. ~/.[%APP_RC_DIRECTORY%] (a user's own replacement)
  3. [%APP_INSTALL_DIR%] (a version you have bundled)
  4. /usr/share/postscriptbarcode (Fedora's postscriptbarcode package)
  5. /usr/share/libpostscriptbarcode (Debian's libpostscriptbarcode package)

Displaying the List of Supported Symbologies

To make the presentation of the list of barcode formats manageable any such list of barcodes should be rendered in the same/similar way as the web-based generator.

Refer Users to the BWIPP Documentation

Point your users at the online BWIPP symbologies and options references.

The reference is written these in a way that is intended to be fairly environment agnostic but if you have any ideas or want to improve them in some way then please contribute.

Safe Argument Passing

Pass arguments to BWIPP in an injection-proof way that does not allow users to invoke arbitrary PostScript commands by means of un-escaped ) or otherwise.

The best way is to "hexify" the data, options and encoder string data in your output, for example:

0 0 moveto
<3032333435363733>                          <-- Instead of (02345673)
<696e636c75646574657874>                    <-- Instead of (includetext)
<65616e38> cvn                              <-- Instead of /ean8
/uk.co.terryburton.bwipp findresource exec

Example Python:

import binascii, textwrap
def hexify(input):
  return textwrap.TextWrapper(subsequent_indent=' ', width=72). \
    fill('<' + binascii.hexlify(string) + '>')

Example Perl:

sub hexify {
  return '<'.(join "\n ", unpack '(A72)*', unpack 'H*', shift).'>';
}

Generating cropped images via EPS

Frontends often have a requirement to render an cropped image file (vector or bitmap) based on the PostScript description of the symbol.

One approach is to create an EPS file that includes a BoundingBox description. It is usual to derive the BoundingBox using a dedicated pass over the PostScript with GhostScript's "bbox" driver.

Start by populating a basic PostScript template from the user input:

%!PS
[% BWIPP_resources %]
0 0 moveto
[% hexify($DAT) %] [% hexify($OPT) %] [% hexify($ENC) %] cvn /uk.co.terryburton.bwipp findresource exec
showpage

Process the populated template (in.ps) with the bbox device to extract the bounding box with something like:

PAGE_OFFSET=3000
gs -dSAFER -dQUIET -dNOPAUSE -dBATCH -sDEVICE=bbox \
    -c "<</PageOffset [$PAGE_OFFSET $PAGE_OFFSET]>> setpagedevice" -f in.ps 

Note: PAGE_OFFSET is an arbitrarily large value sufficient to force the entire symbol into the first quadrant due to deficiencies in GhostScript's bbox driver.

The output will be of the form:

%%BoundingBox: <BB_X1> <BB_Y1> <BB_X2> <BB_Y2>

From this output, you can now derive the symbol dimensions and the translation that is required to draw from the coordinate system's origin:

  • WID = $BB_X2 - $BB_X1
  • HEI = $BB_Y2 - $BB_Y1
  • T_X = $PAGE_OFFSET - $BB_X1
  • T_Y = $PAGE_OFFSET - $BB_Y1

Finally, create an EPS file by populating a template such as the one below from the user input and the values derived above:

%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 0 0 [% $WID %] [% $HEI %]
%%Pages: 1
%%LanguageLevel: 2
%%EndComments
%%BeginProlog
[% BWIPP_resources_with_DSC_comments %]
%%EndProlog
%%Page: 1 1
[% $T_X %] [% $T_Y %] moveto
[% hexify($DAT) %] [% hexify($OPT) %] [% hexify($ENC) %] cvn /uk.co.terryburton.bwipp findresource exec
showpage
%%Trailer
%%EOF

This EPS could now be rendered to a vector format such as PDF with something like:

gs -dSAFER -dQUIET -dNOPAUSE -dBATCH -sDEVICE=pdfwrite "-dDEVICEWIDTHPOINTS=$WID" "-dDEVICEHEIGHTPOINTS=$HEI" \
    -sOutputFile=out.pdf in.eps

Or to a bitmap format such as PNG with something like:

gs -dSAFER -dQUIET -dNOPAUSE -dBATCH -sDEVICE=png16m "-dDEVICEWIDTHPOINTS=$WID" "-dDEVICEHEIGHTPOINTS=$HEI" -r288 \
    -dTextAlphaBits=4 -dGraphicsAlphaBits=1 -sOutputFile=out.png in.eps
Clone this wiki locally