red-agate-barcode
Version:
red-agate barcode tag library.
339 lines (280 loc) • 11.1 kB
text/typescript
// Copyright (c) 2017, Shellyl_N and Authors
// license: ISC
// https://github.com/shellyln
import * as RedAgate from 'red-agate/modules/red-agate';
import { SvgCanvas,
SvgTextAttributes,
TextAlignValue,
TextBaselineValue } from 'red-agate-svg-canvas/modules/drawing/canvas/SvgCanvas';
import { Rect2D } from 'red-agate-svg-canvas/modules/drawing/canvas/TransferMatrix2D';
import { WebColor } from 'red-agate-svg-canvas/modules/drawing/canvas/WebColor';
import { ShapeProps,
shapePropsDefault,
Shape,
ImagingShapeBasePropsMixin,
renderSvgCanvas,
toImgTag,
toElementStyle,
toDataUrl,
toSvg,
CONTEXT_SVG_CANVAS } from 'red-agate/modules/red-agate/tags/Shape';
export interface BarcodeBaseProps extends ShapeProps, ImagingShapeBasePropsMixin {
fillColor?: string | WebColor;
font?: string;
rotation?: number;
height?: number;
quietWidth?: number;
quietHeight?: number;
unit?: string;
drawText?: boolean;
useRawDataAsText?: boolean;
textHeight?: number;
data?: string;
text?: string;
asDataUrl?: boolean;
asImgTag?: boolean;
}
export interface BarcodeBasePropsNoUndefined extends ShapeProps, ImagingShapeBasePropsMixin {
fillColor: string | WebColor;
font: string;
rotation: number;
height: number;
quietWidth: number;
quietHeight: number;
unit?: string;
drawText: boolean;
useRawDataAsText: boolean;
textHeight: number;
data?: string;
text?: string;
asDataUrl?: boolean;
asImgTag?: boolean;
}
export const barcodeBasePropsDefault: BarcodeBasePropsNoUndefined = Object.assign({}, shapePropsDefault, {
fillColor: "black",
font: "normal 3.5px 'OCRB'", // "initial user coordinate" 1px == "viewport coordinate" 1mm
rotation: 0,
height: 6.35,
quietWidth: 2.54,
quietHeight: 0.66,
unit: "mm",
drawText: true,
useRawDataAsText: false,
textHeight: 3.55
});
export class BarcodeBase<T extends BarcodeBaseProps> extends Shape<T> {
public constructor(props: T, protected charactersMap: Map<string, {index: number, pattern: string}>) {
super(Object.assign({}, barcodeBasePropsDefault, props as any));
}
public toImgTag(): string {
return toImgTag(this);
}
public toElementStyle(): string {
return toElementStyle(this);
}
public toDataUrl(): string {
return toDataUrl(this);
}
public toSvg(): string {
return toSvg(this);
}
public toRendered(): string {
return RedAgate.renderAsHtml_noDefer(this);
}
public render(contexts: Map<string, any>, children: string) {
let canvas: SvgCanvas = this.getContext(contexts, CONTEXT_SVG_CANVAS);
const contextHasCanvas = Boolean(canvas);
if (!contextHasCanvas) {
canvas = new SvgCanvas();
this.setContext(contexts, CONTEXT_SVG_CANVAS, canvas);
super.beforeRender(contexts);
}
let data = this.props.data || "";
let text = this.props.text;
const originalData = data;
let heightData: string | undefined,
labelText: string | undefined,
startChar: string,
stopChar: string;
({data, heightData, labelText, startChar, stopChar} = this.encodeData(data));
const cdChar = this.calcCheckDigit(data);
// tw: total width (quiet + data + start + stop + cd)
// th: total height (quiet + bar + text)
const {tw, th} = this.calcSymbolSize(data, startChar, stopChar, cdChar);
data = `${startChar}${data}${cdChar}${stopChar}`;
if (labelText !== void 0) {
text = labelText;
} else {
if (text === void 0 || text === null) text = this.props.text;
if (text === void 0 || text === null) text = originalData;
}
let rotation = (this.props.rotation === void 0 || this.props.rotation === null) ?
0 : Math.floor(this.props.rotation / 90) % 4;
if (rotation < 0) rotation += 4;
switch (rotation) {
case 1:
canvas.rotate(Math.PI * 1.5);
canvas.translate(-tw, 0);
break;
case 2:
canvas.rotate(Math.PI);
canvas.translate(-tw, -th);
break;
case 3:
canvas.rotate(Math.PI * 0.5);
canvas.translate(0, -th);
break;
}
if (this.props.drawText) {
canvas.beginGroup();
}
if (this.isHeightModulated) {
this.renderHeightModulatedBarData(canvas, tw, th, data, heightData, text);
} else {
this.renderBarData(canvas, tw, th, data, heightData, text);
}
this.renderAdditional(canvas, tw, th, data, text);
if (this.props.drawText) {
canvas.endGroup();
canvas.beginGroup();
}
if (this.props.drawText) {
if (this.props.font) canvas.font = this.props.font;
this.renderText(canvas, tw, th, data, text);
}
if (this.props.drawText) {
canvas.endGroup();
}
if (contextHasCanvas) {
return ``;
} else {
super.afterRender(contexts);
this.unsetContext(contexts, CONTEXT_SVG_CANVAS);
const imageWidth = tw + (this.props.x || 0);
const imageHeight = th + (this.props.y || 0);
return renderSvgCanvas(this.props, canvas, imageWidth, imageHeight);
}
}
// total width (quiet + data + start + stop + cd)
// total height (quiet + bar + text)
protected calcSymbolSize(
data: string, startChar: string, stopChar: string, cdChar: string
): {tw: number, th: number} {
return {
// total width (quiet + data + start + stop + cd)
tw: 0,
// total height (quiet + bar + text)
th: 0
};
}
protected calcCheckDigit(data: string): string {
return "";
}
protected encodeData(data: string):
{data: string, heightData?: string, labelText?: string, startChar: string, stopChar: string} {
return {data, startChar: "", stopChar: ""};
}
protected getBarSpaceWidth(): number[] {
return [];
}
protected getBarSpaceHeight(): Array<Array<{offset: number, height: number}>> {
const props = this.props as BarcodeBasePropsNoUndefined;
return [[{offset: 0, height: props.height}]];
}
protected getRenderStartCoodinate(data: string, text: string): {rx: number, ry: number} {
const props = this.props as BarcodeBasePropsNoUndefined;
return {rx: props.quietWidth, ry: props.quietHeight};
}
protected get isHeightModulated(): boolean {
return false;
}
protected renderBarData(
canvas: SvgCanvas, tw: number, th: number,
data: string, heightData: string | undefined, text: string) {
const bw = this.getBarSpaceWidth();
const vseg = this.getBarSpaceHeight();
// tslint:disable-next-line:prefer-const
let {rx, ry} = this.getRenderStartCoodinate(data, text);
for (let i = 0; i < data.length; i++) {
const cmap = this.charactersMap.get(data[i]);
if (! cmap) {
throw new Error("BarcodeBase#renderBarData: character is out of range.");
}
const pattern = cmap.pattern;
let bar = true;
let dx = 0;
for (let j = 0; j < pattern.length; j++) {
const c = pattern[j];
switch (c) {
case "0": case "1": case "2": case "3": case "4":
case "5": case "6": case "7": case "8": case "9":
// "0" is character gap
{
const w = bw[Number.parseInt(c, 10)];
if (bar) {
const ss = (heightData === void 0) ? vseg[0] : vseg[Number.parseInt(heightData[i], 10)];
for (const seg of ss) {
canvas.rect(rx + dx, ry + seg.offset, w, seg.height);
}
}
dx += w;
bar = !bar;
}
break;
case "+":
bar = true;
break;
case "-":
bar = false;
break;
}
}
rx += dx;
}
canvas.fill();
canvas.beginPath();
}
protected renderHeightModulatedBarData(
canvas: SvgCanvas, tw: number, th: number,
data: string, heightData: string | undefined, text: string) {
const bw = this.getBarSpaceWidth();
const w = bw[1];
const vseg = this.getBarSpaceHeight();
// tslint:disable-next-line:prefer-const
let {rx, ry} = this.getRenderStartCoodinate(data, text);
for (let i = 0; i < data.length; i++) {
const pattern = (this as any).charactersMap.get(data[i]).pattern;
const bar = true;
let dx = 0;
for (let j = 0; j < pattern.length; j++) {
const c = pattern[j];
switch (c) {
case "1": case "2": case "3": case "4":
case "5": case "6": case "7": case "8": case "9":
{
const ss = vseg[Number.parseInt(c, 10)];
for (const seg of ss) {
canvas.rect(rx + dx, ry + seg.offset, w, seg.height);
}
}
// FALL THRU
case "0":
// "0" is space
dx += w * 2;
break;
}
}
rx += dx;
}
canvas.fill();
canvas.beginPath();
}
protected renderAdditional(canvas: SvgCanvas, tw: number, th: number, data: string, text: string) {
}
protected renderText(canvas: SvgCanvas, tw: number, th: number, data: string, text: string) {
const props = this.props as BarcodeBasePropsNoUndefined;
canvas.textAlign = "center";
canvas.textBaseline = "alphabetic";
canvas.fillText(props.useRawDataAsText ? data : text, tw / 2, th - props.quietHeight);
}
}