-
Notifications
You must be signed in to change notification settings - Fork 309
Annotated Example Drawing Object
Mark Warren edited this page Aug 4, 2023
·
11 revisions
The file shown here is available in examples/
under the root bwip-js directory.
// drawing-example.js
//
// Example use of the drawing interface. This code is for expository purposes only.
// Using the HTML5 canvas API creates "fuzzy" barcodes which are anathema
// to reliable scanning.
//
// For the methods that take a color `rgb` parameter, the value is always a
// string with format RRGGBB. Internally, BWIPP accepts both RGB and CMYK values
// but bwip-js always converts CMYK to RGB before the drawing code sees it.
// The signature of this factory constructor is up to you. It is your code that
// calls it and passes the returned drawing instance to `bwipjs.render()`.
// See example.html.
function DrawingExample(canvas, opts) {
let ctx = canvas.getContext('2d');
// PostScript transparently creates compound path regions.
// We must do it explicitly with canvas.
let compound = null;
return {
// setopts() is called after the options are fixed-up/normalized,
// but before calling into BWIPP.
//
// This method allows omitting the options object in the constructor call,
// which simplifies the pattern:
//
// bwipjs.render({ bcid:'code128', ... }, myDrawing());
//
// In the above, it is awkward to pass the options object to the drawing
// constructor.
//
// The method is optional. Implemented in v4.0.
setopts(options) {
opts = options;
},
// Adjust scale. The return value is a two-element array with the
// scale-x and scale-y values adjusted as desired.
//
// For this example, we want fractions of pixels, so do nothing.
// The builtin drawing returns [ floor(sx), floor(sy) ] to ensure all
// bars and spaces are sized uniformly.
//
// Composite symbols cause this method to be called multiple times; be
// consistent if you adjust the values.
scale(sx, sy) {
return null;
},
// Measure text. measure() and scale() are the only drawing primitives
// called before init().
//
// `font` is the font name, typically OCR-B or OCR-A.
//
// Only the text above the ISBN, ISMN, ISSN barcodes default to OCR-A.
// All other text defaults to OCR-B.
//
// The user can explicitly change the font name via the BWIPP text options.
//
// `width` and `height` are the requested font cell size. They will
// usually be the same, except when the x/y scaling is not symmetric.
measure(str, font, width, height) {
// The canvas measure api is basically useless, especially when dealing
// with asymmetric scaling. The best we can hope for is a rough
// approximation...
ctx.font = height + 'px monospace';
let bbox = ctx.measureText(str);
// The return is an object with properties { width, ascent, descent }.
// All values in pixels.
let descent = /[Qgjpqy]/.test(str) ? 0.25 * height : 0;
return { width:bbox.width * width / height, ascent:0.65 * height, descent:descent };
},
// Initialize the drawing surface.
// `width` and `height` represent the maximum bounding box the graphics will occupy.
// The dimensions are for an unrotated rendering. Adjust as necessary.
init(width, height) {
// Add in the effects of padding
let padl = opts.paddingleft;
let padr = opts.paddingright;
let padt = opts.paddingtop;
let padb = opts.paddingbottom;
width += padl + padr;
height += padt + padb;
// Set up the transform. The values in the arrays are:
// [0] : swap width/height dimensions flag
// [1],[2] : dx,dy translate needed with padding-left and padding-top
// [3] : rotation (multiple of PI)
let tx = { R:[ 1, height-padt, padl, 0.5 ],
L:[ 1, padt, width-padl, 1.5 ],
I:[ 0, width-padl, height-padt, 1 ] }[opts.rotate] ||
[ 0, padl, padt, 0 ];
canvas.width = tx[0] ? height : width;
canvas.height = tx[0] ? width : height;
ctx.setTransform(1, 0, 0, 1, 0, 0); // Reset to unity transform
// Set background before the transform makes this tricky.
if (/^[0-9a-fA-F]{6}$/.test(''+opts.backgroundcolor)) {
ctx.fillStyle = '#' + opts.backgroundcolor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
// Apply the transform
ctx.translate(tx[1], tx[2]);
ctx.rotate(tx[3] * Math.PI);
},
// Unconnected stroked lines are used to draw the bars in linear barcodes.
// No line cap should be applied. These lines are always orthogonal.
line(x0, y0, x1, y1, linew, rgb) {
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.lineWidth = linew;
ctx.lineCap = 'butt';
ctx.strokeStyle = '#' + rgb;
ctx.stroke();
},
// Polygons are used to draw the connected regions in a 2d barcode.
// These will always be unstroked, filled, non-intersecting,
// orthogonal shapes.
//
// You will see a series of polygon() calls, followed by a fill().
polygon(pts) {
if (!compound) {
compound = new Path2D;
}
let path = new Path2D();
path.moveTo(pts[0][0], pts[0][1]);
for (let i = 1; i < pts.length; i++) {
path.lineTo(pts[i][0], pts[i][1]);
}
path.closePath();
compound.addPath(path);
},
// An unstroked, filled hexagon used only by maxicode. You can choose
// to fill each individually, or wait for the final fill().
//
// The hexagon is drawn from the top, counter-clockwise.
hexagon(pts, rgb) {
// A hexagon is just a polygon... bwip-js differentiates to allow the
// built-in drawing to optimize.
this.polygon(pts);
},
// An unstroked filled ellipse. Used by dotcode and maxicode at present.
// Maxicode plots pairs of ellipse() calls (one cw, one ccw) followed by a
// fill() to create the bullseye rings. Dotcode plots all of its ellipses
// followed by a single a fill().
ellipse(x, y, rx, ry, ccw) {
if (!compound) {
compound = new Path2D;
}
let path = new Path2D();
path.ellipse(x, y, rx, ry, 0, 0, 2*Math.PI, ccw);
compound.addPath(path);
},
// PostScript's default fill rule is even-odd.
fill(rgb) {
if (!compound) {
return;
}
ctx.fillStyle = '#' + rgb;
ctx.fill(compound, 'evenodd');
compound = undefined;
},
// Draw text.
// `y` is the baseline.
// `font` is an object with properties { name, width, height, dx }
//
// `font.name` will be the same as the font name in `measure()`.
// `font.width` and `font.height` are the font cell size.
// `font.dx` is extra space requested between characters (usually zero).
//
// This code ignores the inter-character spacing to keep it simple.
text(x, y, str, rgb, font) {
let sx = font.width / font.height;
ctx.save();
ctx.scale(sx, 1);
ctx.font = font.height + 'px monospace';
ctx.fillStyle = '#' + rgb;
ctx.textBaseline = 'alphabetic';
ctx.textAlign = 'left';
ctx.fillText(str, x / sx, y);
ctx.restore();
},
// Called after all drawing is complete. The return value from this function
// is the return value for `bwipjs.render()`.
end() {
},
};
}