Developing a Frontend to BWIPP

Terry Burton edited this page Aug 22, 2016 · 18 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. Such projects should also feel free to adopt the BWIPP mailing list if they are so inclined and to request access to extend this wiki.

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. For example, the following PS procedure can be used to invoke barcode.ps and on error will halt with the descriptive text of the error (e.g. BWIPP ERROR: EAN-13 must be 12 or 13 digits) written to STDERR which can be intercepted and thrown or displayed in whatever manner is most appropriate.

%!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 fairly environment agnostic but if you have any idea or want to improve them 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).'>';
}