mirror of
https://github.com/MultiMote/niimblue
synced 2026-01-19 19:37:11 +03:00
Add Barcode feature (#10)
This commit is contained in:
134
niimblue/src/fabric-object/barcode.class.ts
Normal file
134
niimblue/src/fabric-object/barcode.class.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { fabric } from 'fabric'
|
||||
import { code128b, ean13 } from '../utils/barcode'
|
||||
import { equalSpacingFillText } from '../utils/canvas'
|
||||
|
||||
const PRESERVE_PROPERTIES = ['text', 'encoding', 'printText']
|
||||
const EAN13_LONG_IDX: number[] = [0, 1, 2, 45, 46, 47, 48, 49, 92, 93, 94]
|
||||
export type BarcodeCoding = 'EAN13' | 'CODE128B'
|
||||
export interface IBarcodeOptions extends fabric.IObjectOptions {
|
||||
text?: string
|
||||
encoding?: BarcodeCoding
|
||||
printText?: boolean
|
||||
}
|
||||
|
||||
export interface Barcode extends fabric.Object {
|
||||
text: string
|
||||
encoding: BarcodeCoding
|
||||
printText: boolean
|
||||
constructor(options?: IBarcodeOptions): void
|
||||
initialize(options?: IBarcodeOptions): Barcode
|
||||
}
|
||||
|
||||
export const Barcode = fabric.util.createClass(fabric.Object, {
|
||||
type: 'Barcode',
|
||||
stateProperties: PRESERVE_PROPERTIES.concat(...(fabric.Object.prototype.stateProperties ?? [])),
|
||||
cacheProperties: PRESERVE_PROPERTIES.concat(...(fabric.Object.prototype.cacheProperties ?? [])),
|
||||
bandcode: '',
|
||||
|
||||
/**
|
||||
* QRCode text
|
||||
* @var {string}
|
||||
*/
|
||||
text: '',
|
||||
/**
|
||||
* Encoding
|
||||
* @type {BarcodeCoding}
|
||||
*/
|
||||
encoding: 'EAN13',
|
||||
/**
|
||||
* Print text
|
||||
* @type {boolean}
|
||||
*/
|
||||
printText: true,
|
||||
|
||||
initialize(options = {}) {
|
||||
this.callSuper('initialize', options);
|
||||
this._createBandCode();
|
||||
},
|
||||
|
||||
_set(key: any, value: any) {
|
||||
this.callSuper('_set', key, value);
|
||||
switch (key) {
|
||||
case 'text':
|
||||
case 'encoding':
|
||||
this._createBandCode();
|
||||
break;
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
_createBandCode() {
|
||||
if (this.encoding === 'EAN13') {
|
||||
const { text, bandcode } = ean13(this.text)
|
||||
this.text = text
|
||||
this.bandcode = bandcode
|
||||
} else {
|
||||
this.bandcode = code128b(this.text)
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
_render(ctx: CanvasRenderingContext2D) {
|
||||
if (this.bandcode === '') return
|
||||
|
||||
ctx.save();
|
||||
|
||||
ctx.fillStyle = this.fill;
|
||||
const fontHeight = Math.round(Math.max(Math.min(this.height / 10, this.width / this.text.length * 1.5), 12))
|
||||
ctx.font = `bold ${fontHeight}px Courier New`
|
||||
ctx.textBaseline = 'top'
|
||||
const fontWidth = ctx.measureText('0').width
|
||||
const w2 = this.width / 2, h2 = this.height / 2
|
||||
const realX = (x: number) => x - w2
|
||||
const realY = (y: number) => y - h2
|
||||
|
||||
// render bandcode
|
||||
let dh = this.height, dp = 0, dw = this.width / this.bandcode.length
|
||||
if (this.printText) {
|
||||
if (this.encoding === 'EAN13') {
|
||||
dp = 2 * fontWidth
|
||||
}
|
||||
dh = this.height - fontHeight * 1.2
|
||||
dw = (this.width - dp * 2) / this.bandcode.length
|
||||
}
|
||||
for (let i = 0; i < this.bandcode.length; i++) {
|
||||
const d = this.bandcode[i]
|
||||
if (d === '1') {
|
||||
if (this.encoding === 'EAN13' && EAN13_LONG_IDX.includes(i))
|
||||
ctx.fillRect(realX(dp + i * dw), realY(0), dw, dh + fontHeight * 0.7)
|
||||
else
|
||||
ctx.fillRect(realX(dp + i * dw), realY(0), dw, dh)
|
||||
}
|
||||
}
|
||||
|
||||
// render text
|
||||
if (this.printText) {
|
||||
const fastY = realY(this.height - fontHeight)
|
||||
if (this.encoding === 'EAN13') {
|
||||
let partW = 41 * dw
|
||||
ctx.fillText(this.text[0], realX(fontWidth * 0.5), fastY) // first digit
|
||||
equalSpacingFillText(ctx, this.text.slice(1, 7), realX(fontWidth * 2 + dw * 4), fastY, partW) // part 1
|
||||
equalSpacingFillText(ctx, this.text.slice(7, 13), realX(fontWidth * 2 + dw * 9 + partW), fastY, partW) // part 2
|
||||
ctx.fillText('>', realX(this.width - fontWidth * 1.5), fastY) // last digit
|
||||
} else {
|
||||
equalSpacingFillText(ctx, this.text, realX(0), fastY, this.width)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
toObject(propertiesToInclude = []) {
|
||||
return this.callSuper('toObject', PRESERVE_PROPERTIES.concat(...propertiesToInclude));
|
||||
},
|
||||
})
|
||||
Barcode.ATTRIBUTE_NAMES = [];
|
||||
Barcode.fromElement = () => { };
|
||||
Barcode.fromObject = (object: any, callback: any) => {
|
||||
return fabric.Object._fromObject('Barcode', object, callback);
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fabric.Barcode = Barcode
|
||||
|
||||
export default Barcode
|
||||
@@ -1,5 +1,6 @@
|
||||
import { fabric } from "fabric";
|
||||
import { QRCode } from "./fabric-object/qrcode.class";
|
||||
import Barcode from "./fabric-object/barcode.class";
|
||||
|
||||
export class ImageEditorUtils {
|
||||
static readonly SIZE_DEFAULT: number = 64;
|
||||
@@ -134,4 +135,14 @@ export class ImageEditorUtils {
|
||||
});
|
||||
canvas.add(qr);
|
||||
}
|
||||
}
|
||||
|
||||
public static addBarcode(canvas: fabric.Canvas): void {
|
||||
const barcode = new Barcode({
|
||||
text: "1234567890128",
|
||||
width: ImageEditorUtils.SIZE_DEFAULT * 2,
|
||||
height: ImageEditorUtils.SIZE_DEFAULT,
|
||||
encoding: "EAN13",
|
||||
})
|
||||
canvas.add(barcode);
|
||||
}
|
||||
}
|
||||
75
niimblue/src/lib/BarcodeParamsControls.svelte
Normal file
75
niimblue/src/lib/BarcodeParamsControls.svelte
Normal file
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import type { ChangeEventHandler } from "svelte/elements";
|
||||
import { Barcode, type BarcodeCoding } from "../fabric-object/barcode.class";
|
||||
import FaIcon from "./FaIcon.svelte";
|
||||
|
||||
export let selectedObject: fabric.Object | undefined;
|
||||
export let valueUpdated: () => void;
|
||||
let selectedBarcode: Barcode | undefined;
|
||||
let encoding: BarcodeCoding | undefined;
|
||||
let text: string | undefined;
|
||||
let printText: boolean | undefined;
|
||||
|
||||
$: {
|
||||
selectedBarcode =
|
||||
selectedObject instanceof Barcode
|
||||
? (selectedObject as Barcode)
|
||||
: undefined;
|
||||
text = selectedBarcode?.text;
|
||||
encoding = selectedBarcode?.encoding;
|
||||
printText = selectedBarcode?.printText;
|
||||
};
|
||||
|
||||
const togglePrintText = () => {
|
||||
if (selectedBarcode) {
|
||||
printText = !printText;
|
||||
selectedBarcode.set('printText', printText);
|
||||
valueUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
const textChange: ChangeEventHandler<HTMLTextAreaElement> = (e) => {
|
||||
if (selectedBarcode) {
|
||||
text = e.currentTarget.value;
|
||||
selectedBarcode.set('text', text);
|
||||
valueUpdated();
|
||||
}
|
||||
};
|
||||
|
||||
const encodingChange: ChangeEventHandler<HTMLSelectElement> = (e) => {
|
||||
if (selectedBarcode) {
|
||||
encoding = e.currentTarget.value as BarcodeCoding;
|
||||
selectedBarcode.set('encoding', encoding);
|
||||
valueUpdated();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if selectedBarcode}
|
||||
<div class="input-group input-group-sm flex-nowrap">
|
||||
<span class="input-group-text" title="Encoding"><FaIcon icon="code" /></span
|
||||
>
|
||||
<select class="form-select" value={encoding} on:change={encodingChange}>
|
||||
<option value="EAN13">EAN13</option>
|
||||
<option value="CODE128B">Code128 B</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-sm {printText ? 'btn-secondary' : ''}" on:click={togglePrintText}>
|
||||
<FaIcon icon="font" />
|
||||
</button>
|
||||
<textarea
|
||||
class="qrcode-content form-control"
|
||||
value={text}
|
||||
on:change={textChange}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.input-group {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.qrcode-content {
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
@@ -13,6 +13,8 @@
|
||||
import { ImageEditorUtils } from "../image_editor_utils";
|
||||
import { QRCode } from "../fabric-object/qrcode.class";
|
||||
import QrCodeParamsPanel from "./QRCodeParamsControls.svelte";
|
||||
import { Barcode } from "../fabric-object/barcode.class";
|
||||
import BarcodeParamsPanel from './BarcodeParamsControls.svelte';
|
||||
|
||||
let htmlCanvas: HTMLCanvasElement;
|
||||
let fabricCanvas: fabric.Canvas;
|
||||
@@ -153,6 +155,8 @@
|
||||
ImageEditorUtils.addImageWithFilePicker(fabricCanvas);
|
||||
} else if (name === "qrcode") {
|
||||
ImageEditorUtils.addQrCode(fabricCanvas);
|
||||
} else if (name === 'barcode') {
|
||||
ImageEditorUtils.addBarcode(fabricCanvas);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -241,6 +245,18 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
fabricCanvas.on('object:scaling', (e) => {
|
||||
const grid = 5;
|
||||
if (e.target && e.target instanceof Barcode && e.target.width !== undefined && e.target.height !== undefined) {
|
||||
e.target.set({
|
||||
width: Math.round(e.target.width * (e.target.scaleX ?? 1)),
|
||||
height: Math.round(e.target.height * (e.target.scaleY ?? 1)),
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
@@ -307,6 +323,9 @@
|
||||
{#if selectedObject instanceof QRCode}
|
||||
<QrCodeParamsPanel {selectedObject} valueUpdated={() => fabricCanvas.requestRenderAll()} />
|
||||
{/if}
|
||||
{#if selectedObject instanceof Barcode}
|
||||
<BarcodeParamsPanel {selectedObject} valueUpdated={() => fabricCanvas.requestRenderAll()} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,9 +30,12 @@
|
||||
<button class="btn me-1" on:click={() => onSubmit("image")}>
|
||||
<FaIcon icon="image" /> Image
|
||||
</button>
|
||||
<button class="btn me-1" on:click={() => onSubmit("qrcode")}>
|
||||
<button class="btn me-1" on:click={() => onSubmit('qrcode')}>
|
||||
<FaIcon icon="qrcode" /> QR Code
|
||||
</button>
|
||||
<button class="btn me-1" on:click={() => onSubmit('barcode')}>
|
||||
<FaIcon icon="barcode" /> Barcode
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,4 +15,4 @@ export type LabelPreset = {
|
||||
printDirection: PrintDirection;
|
||||
};
|
||||
|
||||
export type OjectType = "text" | "rectangle" | "line" | "circle" | "image" | "qrcode"
|
||||
export type OjectType = "text" | "rectangle" | "line" | "circle" | "image" | "qrcode" | "barcode"
|
||||
|
||||
221
niimblue/src/utils/barcode.ts
Normal file
221
niimblue/src/utils/barcode.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
type EAN13BitPattern = {
|
||||
A: string;
|
||||
B: string;
|
||||
C: string;
|
||||
}
|
||||
const ean13_bp: Record<'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9', EAN13BitPattern> = {
|
||||
'0': { A: '0001101', B: '0100111', C: '1110010' },
|
||||
'1': { A: '0011001', B: '0110011', C: '1100110' },
|
||||
'2': { A: '0010011', B: '0011011', C: '1101100' },
|
||||
'3': { A: '0111101', B: '0100001', C: '1000010' },
|
||||
'4': { A: '0100011', B: '0011101', C: '1011100' },
|
||||
'5': { A: '0110001', B: '0111001', C: '1001110' },
|
||||
'6': { A: '0101111', B: '0000101', C: '1010000' },
|
||||
'7': { A: '0111011', B: '0010001', C: '1000100' },
|
||||
'8': { A: '0110111', B: '0001001', C: '1001000' },
|
||||
'9': { A: '0001011', B: '0010111', C: '1110100' },
|
||||
}
|
||||
const ean13_table_switch_mask = {
|
||||
'0': 'AAAAAA',
|
||||
'1': 'AABABB',
|
||||
'2': 'AABBAB',
|
||||
'3': 'AABBBA',
|
||||
'4': 'ABAABB',
|
||||
'5': 'ABBAAB',
|
||||
'6': 'ABBBAA',
|
||||
'7': 'ABABAB',
|
||||
'8': 'ABABBA',
|
||||
'9': 'ABBABA',
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert 12 or 13 digit numbers to EAN13 barcode
|
||||
* @param data string of 12 or 13 digits
|
||||
* @returns string of EAN13 barcode, it is an array of 95 characters, each character is either 0 or 1, representing a white or black stripe, respectively.
|
||||
*/
|
||||
export function ean13(data: string) {
|
||||
if (data.length > 13) throw new Error('Data too long for EAN13');
|
||||
if (data.length < 12) data = data.padEnd(12, '0');
|
||||
if (/^\d+$/.test(data) === false) throw new Error('Invalid character in EAN13');
|
||||
|
||||
// checksum
|
||||
let checksum = 0;
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const digit = parseInt(data[i], 10);
|
||||
checksum += (i % 2 === 0 ? 1 : 3) * digit;
|
||||
}
|
||||
checksum = (10 - (checksum % 10)) % 10;
|
||||
if (data.length === 12) data += checksum.toString();
|
||||
else if (data.length === 13 && data[12] !== checksum.toString()) throw new Error('Invalid checksum in EAN13');
|
||||
|
||||
const result: string[] = [];
|
||||
|
||||
result.push('101'); // Start
|
||||
// Left Side
|
||||
const table_switch = ean13_table_switch_mask[data[0] as keyof typeof ean13_table_switch_mask];
|
||||
for (let i = 1; i < 7; i++) {
|
||||
const digit = data[i];
|
||||
const tab = table_switch[i - 1] as keyof EAN13BitPattern;
|
||||
const coding = ean13_bp[digit as keyof typeof ean13_bp][tab]
|
||||
result.push(coding);
|
||||
}
|
||||
result.push('01010'); // Center Guard
|
||||
// Right Side
|
||||
for (let i = 7; i < 13; i++) {
|
||||
const digit = data[i];
|
||||
const coding = ean13_bp[digit as keyof typeof ean13_bp].C
|
||||
result.push(coding);
|
||||
}
|
||||
result.push('101'); // Stop
|
||||
|
||||
return {
|
||||
text: data,
|
||||
bandcode: result.join('')
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
|
||||
type Code128BitPattern = {
|
||||
ascii: number,
|
||||
code: string,
|
||||
}
|
||||
const code128_bp: Code128BitPattern[] = [
|
||||
{ ascii: 32, code: '11011001100' }, // A: SP, B: SP, C: 00, BandCode: 212222
|
||||
{ ascii: 33, code: '11001101100' }, // A: !, B: !, C: 01, BandCode: 222122
|
||||
{ ascii: 34, code: '11001100110' }, // A: “, B: “, C: 02, BandCode: 222221
|
||||
{ ascii: 35, code: '10010011000' }, // A: #, B: #, C: 03, BandCode: 121223
|
||||
{ ascii: 36, code: '100h0001100' }, // A: $, B: $, C: 04, BandCode: 121322
|
||||
{ ascii: 37, code: '10001001100' }, // A: %, B: %, C: 05, BandCode: 131222
|
||||
{ ascii: 38, code: '10011001000' }, // A: &, B: &, C: 06, BandCode: 122213
|
||||
{ ascii: 39, code: '10011000100' }, // A: ‘, B: ‘, C: 07, BandCode: 122312
|
||||
{ ascii: 40, code: '10001100100' }, // A: (, B: (, C: 08, BandCode: 132212
|
||||
{ ascii: 41, code: '1100h00h000' }, // A: ), B: ), C: 09, BandCode: 221213
|
||||
{ ascii: 42, code: '11001000100' }, // A: *, B: *, C: 10, BandCode: 221312
|
||||
{ ascii: 43, code: '11000100100' }, // A: +, B: +, C: 11, BandCode: 231212
|
||||
{ ascii: 44, code: '10110011100' }, // A: ,, B: ,, C: 12, BandCode: 112232
|
||||
{ ascii: 45, code: '10011011100' }, // A: -, B: -, C: 13, BandCode: 122132
|
||||
{ ascii: 46, code: '10011001110' }, // A: ., B: ., C: 14, BandCode: 122231
|
||||
{ ascii: 47, code: '10111001100' }, // A: /, B: /, C: 15, BandCode: 113222
|
||||
{ ascii: 48, code: '10011101100' }, // A: 0, B: 0, C: 16, BandCode: 123122
|
||||
{ ascii: 49, code: '10011100110' }, // A: 1, B: 1, C: 17, BandCode: 123221
|
||||
{ ascii: 50, code: '11001110010' }, // A: 2, B: 2, C: 18, BandCode: 223211
|
||||
{ ascii: 51, code: '11001011100' }, // A: 3, B: 3, C: 19, BandCode: 221132
|
||||
{ ascii: 52, code: '11001001110' }, // A: 4, B: 4, C: 20, BandCode: 221231
|
||||
{ ascii: 53, code: '11011100100' }, // A: 5, B: 5, C: 21, BandCode: 213212
|
||||
{ ascii: 54, code: '11001110100' }, // A: 6, B: 6, C: 22, BandCode: 223112
|
||||
{ ascii: 55, code: '11101101110' }, // A: 7, B: 7, C: 23, BandCode: 312131
|
||||
{ ascii: 56, code: '11101001100' }, // A: 8, B: 8, C: 24, BandCode: 311222
|
||||
{ ascii: 57, code: '11100101100' }, // A: 9, B: 9, C: 25, BandCode: 321122
|
||||
{ ascii: 58, code: '11100100110' }, // A: :, B: :, C: 26, BandCode: 321221
|
||||
{ ascii: 59, code: '11101100100' }, // A: ;, B: ;, C: 27, BandCode: 312212
|
||||
{ ascii: 60, code: '11100110100' }, // A: <, B: <, C: 28, BandCode: 322112
|
||||
{ ascii: 61, code: '11100110010' }, // A: =, B: =, C: 29, BandCode: 322211
|
||||
{ ascii: 62, code: '11011011000' }, // A: >, B: >, C: 30, BandCode: 212123
|
||||
{ ascii: 63, code: '11011000110' }, // A: ?, B: ?, C: 31, BandCode: 212321
|
||||
{ ascii: 64, code: '11000110110' }, // A: @, B: @, C: 32, BandCode: 232121
|
||||
{ ascii: 65, code: '10100011000' }, // A: A, B: A, C: 33, BandCode: 111323
|
||||
{ ascii: 66, code: '10001011000' }, // A: B, B: B, C: 34, BandCode: 131123
|
||||
{ ascii: 67, code: '10001000110' }, // A: C, B: C, C: 35, BandCode: 131321
|
||||
{ ascii: 68, code: '10110001000' }, // A: D, B: D, C: 36, BandCode: 112313
|
||||
{ ascii: 69, code: '10001101000' }, // A: E, B: E, C: 37, BandCode: 132113
|
||||
{ ascii: 70, code: '10001100010' }, // A: F, B: F, C: 38, BandCode: 132311
|
||||
{ ascii: 71, code: '11010001000' }, // A: G, B: G, C: 39, BandCode: 211313
|
||||
{ ascii: 72, code: '11000101000' }, // A: H, B: H, C: 40, BandCode: 231113
|
||||
{ ascii: 73, code: '11000100010' }, // A: I, B: I, C: 41, BandCode: 231311
|
||||
{ ascii: 74, code: '10110111000' }, // A: J, B: J, C: 42, BandCode: 112133
|
||||
{ ascii: 75, code: '10110001110' }, // A: K, B: K, C: 43, BandCode: 112331
|
||||
{ ascii: 76, code: '10001101110' }, // A: L, B: L, C: 44, BandCode: 132131
|
||||
{ ascii: 77, code: '10111011000' }, // A: M, B: M, C: 45, BandCode: 113123
|
||||
{ ascii: 78, code: '10111000110' }, // A: N, B: N, C: 46, BandCode: 113321
|
||||
{ ascii: 79, code: '10001110110' }, // A: O, B: O, C: 47, BandCode: 133121
|
||||
{ ascii: 80, code: '11101110110' }, // A: P, B: P, C: 48, BandCode: 313121
|
||||
{ ascii: 81, code: '11010001110' }, // A: Q, B: Q, C: 49, BandCode: 211331
|
||||
{ ascii: 82, code: '11000101110' }, // A: R, B: R, C: 50, BandCode: 231131
|
||||
{ ascii: 83, code: '11011101000' }, // A: S, B: S, C: 51, BandCode: 213113
|
||||
{ ascii: 84, code: '11011100010' }, // A: T, B: T, C: 52, BandCode: 213311
|
||||
{ ascii: 85, code: '11011101110' }, // A: U, B: U, C: 53, BandCode: 213131
|
||||
{ ascii: 86, code: '11101011000' }, // A: V, B: V, C: 54, BandCode: 311123
|
||||
{ ascii: 87, code: '11101000110' }, // A: W, B: W, C: 55, BandCode: 311321
|
||||
{ ascii: 88, code: '11100010110' }, // A: X, B: X, C: 56, BandCode: 331121
|
||||
{ ascii: 89, code: '11101101000' }, // A: Y, B: Y, C: 57, BandCode: 312113
|
||||
{ ascii: 90, code: '11101100010' }, // A: Z, B: Z, C: 58, BandCode: 312311
|
||||
{ ascii: 91, code: '11100011010' }, // A: [, B: [, C: 59, BandCode: 332111
|
||||
{ ascii: 92, code: '11101111010' }, // A: \, B: \, C: 60, BandCode: 314111
|
||||
{ ascii: 93, code: '11001000010' }, // A: ], B: ], C: 61, BandCode: 221411
|
||||
{ ascii: 94, code: '11110001010' }, // A: ^, B: ^, C: 62, BandCode: 431111
|
||||
{ ascii: 95, code: '10100110000' }, // A: _, B: _, C: 63, BandCode: 111224
|
||||
{ ascii: 96, code: '10100001100' }, // A: NUL, B: `, C: 64, BandCode: 111422
|
||||
{ ascii: 97, code: '10010110000' }, // A: SOH, B: a, C: 65, BandCode: 121124
|
||||
{ ascii: 98, code: '10010000110' }, // A: STX, B: b, C: 66, BandCode: 121421
|
||||
{ ascii: 99, code: '10000101100' }, // A: ETX, B: c, C: 67, BandCode: 141122
|
||||
{ ascii: 100, code: '10000100110' }, // A: EOT, B: d, C: 68, BandCode: 141221
|
||||
{ ascii: 101, code: '10110010000' }, // A: ENQ, B: e, C: 69, BandCode: 112214
|
||||
{ ascii: 102, code: '10110000100' }, // A: ACK, B: f, C: 70, BandCode: 112412
|
||||
{ ascii: 103, code: '10011010000' }, // A: BEL, B: g, C: 71, BandCode: 122114
|
||||
{ ascii: 104, code: '10011000010' }, // A: BS, B: h, C: 72, BandCode: 122411
|
||||
{ ascii: 105, code: '10000110100' }, // A: HT, B: i, C: 73, BandCode: 142112
|
||||
{ ascii: 106, code: '10000110010' }, // A: LF, B: j, C: 74, BandCode: 142211
|
||||
{ ascii: 107, code: '11000010010' }, // A: VT, B: k, C: 75, BandCode: 241211
|
||||
{ ascii: 108, code: '11001010000' }, // A: FF, B: l, C: 76, BandCode: 221114
|
||||
{ ascii: 109, code: '11110111010' }, // A: CR, B: m, C: 77, BandCode: 413111
|
||||
{ ascii: 110, code: '11000010100' }, // A: SO, B: n, C: 78, BandCode: 241112
|
||||
{ ascii: 111, code: '10001111010' }, // A: SI, B: o, C: 79, BandCode: 134111
|
||||
{ ascii: 112, code: '10100111100' }, // A: DLE, B: p, C: 80, BandCode: 111242
|
||||
{ ascii: 113, code: '10010111100' }, // A: DC1, B: q, C: 81, BandCode: 121142
|
||||
{ ascii: 114, code: '10010011110' }, // A: DC2, B: r, C: 82, BandCode: 121241
|
||||
{ ascii: 115, code: '10111100100' }, // A: DC3, B: s, C: 83, BandCode: 114212
|
||||
{ ascii: 116, code: '10011110100' }, // A: DC4, B: t, C: 84, BandCode: 124112
|
||||
{ ascii: 117, code: '10011110010' }, // A: NAK, B: u, C: 85, BandCode: 124211
|
||||
{ ascii: 118, code: '11110100100' }, // A: SYN, B: v, C: 86, BandCode: 411212
|
||||
{ ascii: 119, code: '11110010100' }, // A: ETB, B: w, C: 87, BandCode: 421112
|
||||
{ ascii: 120, code: '11110010010' }, // A: CAN, B: x, C: 88, BandCode: 421211
|
||||
{ ascii: 121, code: '11011011110' }, // A: EM, B: y, C: 89, BandCode: 212141
|
||||
{ ascii: 122, code: '11011110110' }, // A: SUB, B: z, C: 90, BandCode: 214121
|
||||
{ ascii: 123, code: '11110110110' }, // A: ESC, B: {, C: 91, BandCode: 412121
|
||||
{ ascii: 124, code: '10101111000' }, // A: FS, B: |, C: 92, BandCode: 111143
|
||||
{ ascii: 125, code: '10100011110' }, // A: GS, B: }, C: 93, BandCode: 111341
|
||||
{ ascii: 126, code: '10001011110' }, // A: RS, B: ~, C: 94, BandCode: 131141
|
||||
{ ascii: 200, code: '10111101000' }, // A: US, B: DEL, C: 95, BandCode: 114113
|
||||
{ ascii: 201, code: '10111100010' }, // A: FNC3, B: FNC3, C: 96, BandCode: 114311
|
||||
{ ascii: 202, code: '11110101000' }, // A: FNC2, B: FNC2, C: 97, BandCode: 411113
|
||||
{ ascii: 203, code: '11110100010' }, // A: SHIFT, B: SHIFT, C: 98, BandCode: 411311
|
||||
{ ascii: 204, code: '10111011110' }, // A: CODEC, B: CODEC, C: 99, BandCode: 113141
|
||||
{ ascii: 205, code: '10111101110' }, // A: CODEB, B: FNC4, C: CODEB, BandCode: 114131
|
||||
{ ascii: 206, code: '11101011110' }, // A: FNC4, B: CODEA, C: CODEA, BandCode: 311141
|
||||
{ ascii: 207, code: '11110101110' }, // A: FNCl, B: FNCl, C: FNCl, BandCode: 411131
|
||||
{ ascii: 208, code: '11010000100' }, // A: StartA, B: StartA, C: StartA, BandCode: 211412
|
||||
{ ascii: 209, code: '11010010000' }, // A: StartB, B: StartB, C: StartB, BandCode: 211214
|
||||
{ ascii: 210, code: '11010011100' }, // A: StartC, B: StartC, C: StartC, BandCode: 211232
|
||||
{ ascii: 211, code: '1100011101011' }, // A: Stop, B: Stop, C: Stop, BandCode: 2331112
|
||||
];
|
||||
const code128_ascii_to_id = code128_bp.reduce((acc, { ascii }, idx) => {
|
||||
acc[ascii] = idx;
|
||||
return acc;
|
||||
}, {} as Record<number, number>);
|
||||
|
||||
/**
|
||||
* Converts a string to Code128B barcode
|
||||
* @param data string to convert
|
||||
* @returns string of Code128B barcode, it is a sequence of 0 and 1, representing a white or black stripe, respectively.
|
||||
*/
|
||||
export function code128b(data: string) {
|
||||
// Code128 allows only 232 characters, but we need to add start, stop, and checksum, so there are 229 characters left.
|
||||
if (data.length > 229) throw new Error('Data too long for Code128B');
|
||||
|
||||
const result: string[] = []
|
||||
|
||||
result.push(code128_bp[104].code); // Start Code B
|
||||
// Convert each character to Code128B
|
||||
let checksum = 104
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const id = code128_ascii_to_id[data.charCodeAt(i)];
|
||||
if (id === undefined) throw new Error('Invalid character in Code128B');
|
||||
result.push(code128_bp[id].code);
|
||||
checksum += (i + 1) * id;
|
||||
}
|
||||
result.push(code128_bp[checksum % 103].code) // Checksum
|
||||
result.push(code128_bp[106].code); // Stop
|
||||
|
||||
return result.join('');
|
||||
}
|
||||
19
niimblue/src/utils/canvas.ts
Normal file
19
niimblue/src/utils/canvas.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export function equalSpacingFillText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, printWidth: number) {
|
||||
// calculate every character width, and spacing
|
||||
const widths = []
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text.charAt(i)
|
||||
const metrics = ctx.measureText(char)
|
||||
widths.push(metrics.width)
|
||||
}
|
||||
const totalWidth = widths.reduce((a, b) => a + b, 0)
|
||||
const spacing = (printWidth - totalWidth) / (text.length - 1)
|
||||
|
||||
// print every character with calculated spacing
|
||||
let offset = 0
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text.charAt(i)
|
||||
ctx.fillText(char, x + offset, y)
|
||||
offset += widths[i] + spacing
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user