UNPKG

red-agate-barcode

Version:

red-agate barcode tag library.

339 lines (280 loc) 11.1 kB
// 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); } }