red-agate-barcode
Version:
red-agate barcode tag library.
818 lines (722 loc) • 31.7 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';
import { Gf2e8Field } from 'red-agate-math/modules/math/Gf2Ext';
import { BCH } from 'red-agate-math/modules/error-correction/BCH';
import { ReedSolomon } from 'red-agate-math/modules/error-correction/ReedSolomon';
import { BitStreamWriter } from 'red-agate-util/modules/io/BitStream';
import { TextEncoding } from 'red-agate-util/modules/convert/TextEncoding';
import { Bitmap } from 'red-agate-util/modules/imaging/Bitmap';
import { QrSourceDataTypes,
QrDataChunkType,
ecLevelMap,
numberModeCharMap,
alnumModeCharMap } from "./Qr.defs";
import * as qr from './data/Qr.m2.data';
export interface QrProps extends ShapeProps, ImagingShapeBasePropsMixin {
data?: Array<Uint8Array | string | number> | Uint8Array | string;
version?: number | "auto";
ecLevel?: "L" | "M" | "Q" | "H";
encoding?: "number" | "alnum" | "8bit" | "auto";
cellSize?: number;
unit?: string;
asDataUrl?: boolean;
asImgTag?: boolean;
}
export interface QrPropsNoUndefined extends ShapeProps, ImagingShapeBasePropsMixin {
data?: Array<Uint8Array | string | number> | Uint8Array | string;
version: number | "auto";
ecLevel: "L" | "M" | "Q" | "H";
encoding: "number" | "alnum" | "8bit" | "auto";
cellSize: number;
unit?: string;
asDataUrl?: boolean;
asImgTag?: boolean;
}
export const qrPropsDefault: QrPropsNoUndefined = Object.assign({}, shapePropsDefault, {
version: "auto",
ecLevel: "M",
encoding: "auto",
cellSize: 0.33,
unit: "mm",
} as any);
const field = new Gf2e8Field();
let gxList: number[][] = [];
const masks = [
{ index: 0x00, fn: (x: number, y: number) => ((x + y) % 2) === 0 },
{ index: 0x01, fn: (x: number, y: number) => ( y % 2) === 0 },
{ index: 0x02, fn: (x: number, y: number) => ( x % 3) === 0 },
{ index: 0x03, fn: (x: number, y: number) => ((x + y) % 3) === 0 },
{ index: 0x04, fn: (x: number, y: number) => ((Math.floor(x / 3) + Math.floor(y / 2)) % 2) === 0},
{ index: 0x05, fn: (x: number, y: number) => ( ((x * y) % 2) + (x * y) % 3) === 0},
{ index: 0x06, fn: (x: number, y: number) => ((((x * y) % 2) + (x * y) % 3) % 2) === 0},
{ index: 0x07, fn: (x: number, y: number) => ((((x * y) % 3) + (x + y) % 2) % 2) === 0}
];
export class Qr extends Shape<QrProps> {
public constructor(props: QrProps) {
super(Object.assign({}, qrPropsDefault, props));
}
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);
}
const data = this.props.data || "";
const encoded = this.encodeData(data);
const bitmap = this.buildBitmap(encoded.data, encoded.version);
this.drawBitmap(canvas, bitmap);
if (contextHasCanvas) {
return ``;
} else {
super.afterRender(contexts);
this.unsetContext(contexts, CONTEXT_SVG_CANVAS);
const total = bitmap.width * (this.props.cellSize as number) + (this.props.margin as number) * 2;
const imageWidth = total + (this.props.x || 0);
const imageHeight = total + (this.props.y || 0);
return renderSvgCanvas(this.props, canvas, imageWidth, imageHeight);
}
}
protected evaluteMask(bitmap: Bitmap): number {
const nx = bitmap.width,
ny = bitmap.height;
let z = 0;
for (let i = 0; i < ny; i++) {
let p: number | null = null;
let c = 1;
for (let j = 0; j < nx; j++) {
const q = bitmap.get(j, i);
if (p !== q) c = 1;
else c += 1;
if (5 === c) z += 3;
else if (5 < c) z += 1;
p = q;
}
}
for (let i = 0; i < nx; i++) {
let p: number | null = null;
let c = 1;
for (let j = 0; j < ny; j++) {
const q = bitmap.get(i, j);
if (p !== q) c = 1;
else c += 1;
if (5 === c) z += 3;
else if (5 < c) z += 1;
p = q;
}
}
for (let i = 0; i < (ny - 1); i++) {
for (let j = 0; j < (nx - 1); j++) {
const v = bitmap.get(j, i);
if (v === bitmap.get(j + 1, i ) &&
v === bitmap.get(j , i + 1) &&
v === bitmap.get(j + 1, i + 1)) {
z += 3;
}
}
}
for (let i = 0; i < ny; i++) {
for (let j = 0; j < (nx - 10); j++) {
if (bitmap.get(j , i) !== 0 &&
bitmap.get(j + 1, i) === 0 &&
bitmap.get(j + 2, i) !== 0 &&
bitmap.get(j + 3, i) !== 0 &&
bitmap.get(j + 4, i) !== 0 &&
bitmap.get(j + 5, i) === 0 &&
bitmap.get(j + 6, i) !== 0 &&
bitmap.get(j + 7, i) === 0 &&
bitmap.get(j + 8, i) === 0 &&
bitmap.get(j + 9, i) === 0 &&
bitmap.get(j + 10, i) === 0) {
z += 40;
} else if (
bitmap.get(j , i) === 0 &&
bitmap.get(j + 1, i) === 0 &&
bitmap.get(j + 2, i) === 0 &&
bitmap.get(j + 3, i) === 0 &&
bitmap.get(j + 4, i) !== 0 &&
bitmap.get(j + 5, i) === 0 &&
bitmap.get(j + 6, i) !== 0 &&
bitmap.get(j + 7, i) !== 0 &&
bitmap.get(j + 8, i) !== 0 &&
bitmap.get(j + 9, i) === 0 &&
bitmap.get(j + 10, i) !== 0) {
z += 40;
}
}
}
for (let i = 0; i < nx; i++) {
for (let j = 0; j < (ny - 10); j++) {
if (bitmap.get(i, j ) !== 0 &&
bitmap.get(i, j + 1) === 0 &&
bitmap.get(i, j + 2) !== 0 &&
bitmap.get(i, j + 3) !== 0 &&
bitmap.get(i, j + 4) !== 0 &&
bitmap.get(i, j + 5) === 0 &&
bitmap.get(i, j + 6) !== 0 &&
bitmap.get(i, j + 7) === 0 &&
bitmap.get(i, j + 8) === 0 &&
bitmap.get(i, j + 9) === 0 &&
bitmap.get(i, j + 10) === 0) {
z += 40;
} else if (
bitmap.get(i, j ) === 0 &&
bitmap.get(i, j + 1) === 0 &&
bitmap.get(i, j + 2) === 0 &&
bitmap.get(i, j + 3) === 0 &&
bitmap.get(i, j + 4) !== 0 &&
bitmap.get(i, j + 5) === 0 &&
bitmap.get(i, j + 6) !== 0 &&
bitmap.get(i, j + 7) !== 0 &&
bitmap.get(i, j + 8) !== 0 &&
bitmap.get(i, j + 9) === 0 &&
bitmap.get(i, j + 10) !== 0) {
z += 40;
}
}
}
let white = 0, black = 0;
for (let i = 0; i < ny; i++) {
for (let j = 0; j < nx; j++) {
if (bitmap.get(j, i) === 0) { white++; }
else { black++; }
}
}
z += Math.floor(Math.abs((black / (white + black)) * 100 - 50) / 5) * 10;
return z;
}
protected encodeNumberData(data: string): {data: BitStreamWriter, charLength: number} | null {
const length = Math.ceil(data.length * 10 / 24);
const buf = new BitStreamWriter(length);
let i = 0, v = 0;
for (; i < data.length; i++) {
const c = numberModeCharMap.get(data[i]);
if (c === void 0) return null;
v = v * 10 + c;
if (2 === (i % 3)) {
// 10 bits
buf.writeBits(v, 10);
v = 0;
}
}
i = i % 3;
if (i) {
// i is 1: 4bits, 2: 7bits
buf.writeBits8(v, 1 + 3 * i);
}
return {data: buf, charLength: data.length};
}
protected encodeAlnumData(data: string): {data: BitStreamWriter, charLength: number} | null {
const length = Math.ceil(data.length * 11 / 16);
const buf = new BitStreamWriter(length);
let i = 0, v = 0;
for (; i < data.length; i++) {
const c = alnumModeCharMap.get(data[i]);
if (c === void 0) return null;
v = v * 45 + c;
if (1 === (i % 2)) {
// 11 bits
buf.writeBits(v, 11);
v = 0;
}
}
if (i % 2) {
buf.writeBits8(v, 6);
}
return {data: buf, charLength: data.length};
}
protected encode8bitData(data: string): {data: BitStreamWriter, charLength: number} {
const buf = new BitStreamWriter(0, TextEncoding.encodeToUtf8(data));
return {data: buf, charLength: buf.byteLength};
}
protected encodeChunks(data: QrSourceDataTypes[] | QrSourceDataTypes): Array<{
type: QrDataChunkType, data: BitStreamWriter, charLength: number }> {
if (!Array.isArray(data)) {
data = [data];
}
const chunks: Array<{type: QrDataChunkType, data: BitStreamWriter, charLength: number}> = [];
for (const d of data) {
if (d instanceof BitStreamWriter) chunks.push({type: QrDataChunkType.Binary, data: d, charLength: d.byteLength});
else if (d instanceof Uint8Array) chunks.push({type: QrDataChunkType.Binary, data: new BitStreamWriter(0, d), charLength: d.length});
else if (typeof d === "string") {
let chunk: {data: BitStreamWriter, charLength: number} | null = null;
let type: QrDataChunkType;
let isManual = true;
switch (this.props.encoding) {
case "auto":
isManual = false;
case "number":
chunk = this.encodeNumberData(d);
type = QrDataChunkType.Number;
if (isManual || chunk) break;
case "alnum":
chunk = this.encodeAlnumData(d);
type = QrDataChunkType.Alnum;
if (isManual || chunk) break;
case "8bit": default:
chunk = this.encode8bitData(d);
type = QrDataChunkType.Binary;
break;
}
if (chunk === null) {
throw new Error("QrModel2: character is out of range.");
}
chunks.push({type, data: chunk.data, charLength: chunk.charLength});
}
else if (typeof d === "number") {
// TODO: not imple
}
}
return chunks;
}
protected determineSymbolVersion(chunks: Array<{type: QrDataChunkType, data: BitStreamWriter}>):
{ version: number, segments: number[][], dataLength: number, maxDataLength: number } {
let version = this.props.version === "auto" ? 1 : (this.props.version || 1);
let segments: number[][];
let maxDataLength: number = 0;
let dataLength: number = 0;
while (true) {
dataLength = 0;
for (const c of chunks) {
switch (c.type) {
case QrDataChunkType.Number:
if (version < 10) dataLength += 14;
else if (version < 27) dataLength += 16;
else dataLength += 18;
break;
case QrDataChunkType.Alnum:
if (version < 10) dataLength += 13;
else if (version < 27) dataLength += 15;
else dataLength += 17;
break;
case QrDataChunkType.Binary:
if (version < 10) dataLength += 12;
else dataLength += 20;
break;
}
dataLength += c.data.bitLength;
}
switch (this.props.ecLevel) {
case "L":
segments = qr.segments[version].L;
maxDataLength = 8 * qr.dataCodewords.L[version];
break;
case "Q":
segments = qr.segments[version].Q;
maxDataLength = 8 * qr.dataCodewords.Q[version];
break;
case "H":
segments = qr.segments[version].H;
maxDataLength = 8 * qr.dataCodewords.H[version];
break;
case "M": default:
segments = qr.segments[version].M;
maxDataLength = 8 * qr.dataCodewords.M[version];
break;
}
// check total length
if (dataLength <= maxDataLength) {
break;
}
if (this.props.version !== "auto" || !qr.segments[++version]) {
throw new Error("QrModel2: data is too large.");
}
}
return { version, segments, dataLength, maxDataLength };
}
protected encodeData(data: QrSourceDataTypes[] | QrSourceDataTypes): {
version: number, data: Uint8Array, ecLevel: "L" | "M" | "Q" | "H" } {
// determine chunks' encoding, and encoding data.
const chunks = this.encodeChunks(data);
// determine symbol's version.
const { version, segments, dataLength, maxDataLength } = this.determineSymbolVersion(chunks);
// convert chunks to QR data structure bit stream
let bytes: Uint8Array;
{
const bits: BitStreamWriter[] = [];
// make global headers.
// build data from chunks.
for (const c of chunks) {
let n = 0;
const header = new BitStreamWriter(3);
switch (c.type) {
case QrDataChunkType.Number:
header.writeBits8(1, 4);
if (version < 10) n = 10;
else if (version < 27) n = 12;
else n = 14;
header.writeBits(c.charLength, n);
break;
case QrDataChunkType.Alnum:
header.writeBits8(2, 4);
if (version < 10) n = 9;
else if (version < 27) n = 11;
else n = 13;
header.writeBits(c.charLength, n);
break;
case QrDataChunkType.Binary:
header.writeBits8(4, 4);
if (version < 10) n = 8;
else n = 16;
header.writeBits(c.charLength, n);
break;
}
if (0 < header.bitLength) bits.push(header);
bits.push(c.data);
}
// make global footers.
// finalize data.
// if data bits and codewords are remained, add terminator, padding bits, padding codewords.
if (dataLength < maxDataLength) {
let rem = maxDataLength - dataLength;
const fin = new BitStreamWriter(Math.ceil(rem / 8));
// terminator
let len = Math.min(4, rem);
fin.writeBits8(0, len);
rem -= len;
// padding bits
if ((0 < rem) && (0 !== (rem % 8))) {
len = rem % 8;
fin.writeBits8(0, len);
rem -= len;
}
// padding codewords
for (let i = 0; 0 < rem; i++) {
len = Math.min(8, rem);
fin.writeBits8(0 === (i % 2) ? 0x00ec : 0x0011, len);
rem -= len;
}
bits.push(fin);
}
bytes = BitStreamWriter.concat(...bits).toBytes();
}
// make data codewords, generate error correction codewords.
let totalLength = 0;
const dataCwStack: Uint8Array[] = [];
const ecCwStack: Uint8Array[] = [];
for (let i = 0, p = 0; i < segments.length; i++) {
const repeats = segments[i][0];
const dataCwSize = segments[i][2];
const ecCwSize = segments[i][1] - dataCwSize;
let gx: number[];
if (gxList.length <= (ecCwSize - 1)) {
gxList = ReedSolomon.listGx(field, gxList, ecCwSize, 0);
}
gx = gxList[ecCwSize - 1];
const rs = new ReedSolomon(field, gx, 0);
for (let j = 0; j < repeats; j++) {
const dataCodewords = bytes.slice(p, p + dataCwSize).reverse();
const ecCodewords = Uint8Array.from(rs.encode(dataCodewords));
dataCwStack.push(dataCodewords.reverse());
ecCwStack .push(ecCodewords.reverse());
totalLength += dataCwSize + ecCwSize;
p += dataCwSize;
}
}
// interleaving codewords.
const codewords = new Uint8Array(totalLength);
{
const stacks = [dataCwStack, ecCwStack];
let p = 0;
for (const stack of stacks) {
for (let i = 0, z = true; z; i++) {
z = false;
for (let j = 0; j < stack.length; j++) {
if (i < stack[j].length) {
codewords[p++] = stack[j][i];
z = true;
}
}
}
}
}
const props = this.props as QrPropsNoUndefined;
return { data: codewords, version, ecLevel: props.ecLevel };
}
protected applyMask(bitmap: Bitmap, funcPatternsMap: Bitmap, fn: (x: number, y: number) => boolean) {
const nx = bitmap.width,
ny = bitmap.width;
for (let x = 0; x < nx; x++) {
for (let y = 0; y < ny; y++) {
if (0 === funcPatternsMap.get(nx - 1 - x, ny - 1 - y)) {
bitmap.set(nx - 1 - x, ny - 1 - y, bitmap.get(nx - 1 - x, ny - 1 - y) ^ (fn(x, y) ? 1 : 0));
}
}
}
}
protected buildBitmap(data: Uint8Array, version: number): Bitmap {
const width = qr.matrixSize[version];
const nx = width,
ny = width;
const bitmap = new Bitmap(width, width);
const funcPatternsMap = new Bitmap(width, width);
{
{
// finder patterns + format info
funcPatternsMap.fill( 0, ny - 9, 8, 9, 1);
funcPatternsMap.fill(nx - 9, 0, 9, 8, 1);
funcPatternsMap.fill(nx - 9, ny - 9, 9, 9, 1);
// version info
if (7 <= version) {
funcPatternsMap.fill( 8, ny - 6, 3, 6, 1);
funcPatternsMap.fill(nx - 6, 8, 6, 3, 1);
}
}
// finder patterns
for (const {px, py} of [{px: nx - 7, py: ny - 7}, {px: 0, py: ny - 7}, {px: nx - 7, py: 0}]){
bitmap.set(px + 0, py + 0, 1);
bitmap.set(px + 0, py + 1, 1);
bitmap.set(px + 0, py + 2, 1);
bitmap.set(px + 0, py + 3, 1);
bitmap.set(px + 0, py + 4, 1);
bitmap.set(px + 0, py + 5, 1);
bitmap.set(px + 0, py + 6, 1);
bitmap.set(px + 1, py + 0, 1);
bitmap.set(px + 2, py + 0, 1);
bitmap.set(px + 3, py + 0, 1);
bitmap.set(px + 4, py + 0, 1);
bitmap.set(px + 5, py + 0, 1);
bitmap.set(px + 6, py + 0, 1);
bitmap.set(px + 6, py + 1, 1);
bitmap.set(px + 6, py + 2, 1);
bitmap.set(px + 6, py + 3, 1);
bitmap.set(px + 6, py + 4, 1);
bitmap.set(px + 6, py + 5, 1);
bitmap.set(px + 1, py + 6, 1);
bitmap.set(px + 2, py + 6, 1);
bitmap.set(px + 3, py + 6, 1);
bitmap.set(px + 4, py + 6, 1);
bitmap.set(px + 5, py + 6, 1);
bitmap.set(px + 6, py + 6, 1);
bitmap.set(px + 2, py + 2, 1);
bitmap.set(px + 2, py + 3, 1);
bitmap.set(px + 2, py + 4, 1);
bitmap.set(px + 3, py + 2, 1);
bitmap.set(px + 4, py + 2, 1);
bitmap.set(px + 4, py + 3, 1);
bitmap.set(px + 4, py + 4, 1);
bitmap.set(px + 3, py + 4, 1);
bitmap.set(px + 3, py + 3, 1);
}
// alignment patterns
const aps = qr.alignmentPatterns[version];
for (let i = 0; i < aps.length; i++) {
const a = nx - aps[i] - 1;
for (let j = 0; j < aps.length; j++) {
const b = ny - aps[j] - 1;
let q = true;
for (let x = (a - 2); x <= (a + 2); x++) {
for (let y = (b - 2); y <= (b + 2); y++) {
if (funcPatternsMap.get(x, y)) {
q = false;
break;
}
}
if (!q) break;
}
if (q) {
funcPatternsMap.fill(a - 2, b - 2, 5, 5, 1);
bitmap.set(a - 2, b + 2, 1);
bitmap.set(a - 2, b + 1, 1);
bitmap.set(a - 2, b , 1);
bitmap.set(a - 2, b - 1, 1);
bitmap.set(a - 2, b - 2, 1);
bitmap.set(a + 2, b + 2, 1);
bitmap.set(a + 2, b + 1, 1);
bitmap.set(a + 2, b , 1);
bitmap.set(a + 2, b - 1, 1);
bitmap.set(a + 2, b - 2, 1);
bitmap.set(a + 1, b - 2, 1);
bitmap.set(a + 1, b + 2, 1);
bitmap.set(a , b + 2, 1);
bitmap.set(a , b , 1);
bitmap.set(a , b - 2, 1);
bitmap.set(a - 1, b - 2, 1);
bitmap.set(a - 1, b + 2, 1);
}
}
}
// timing pattern
funcPatternsMap.fill( 8, ny - 7, nx - 17, 1, 1);
funcPatternsMap.fill(nx - 7, 8, 1, ny - 17, 1);
// timing pattern
for (let i = 8; i <= ny - 9; i += 2) {
bitmap.set(nx - 7, i, 1);
bitmap.set(i, ny - 7, 1);
}
}
// place codewords
{
let cx = 1;
let cy = -1;
let b2t = true; // bottom to top
let odd = false;
for (let i = 0; i < data.length; i++) {
for (let j = 7; j >= 0; j--) {
for (; ; ) {
if (odd) {
cx += 1;
} else if ((((ny - 1) === cy) && b2t) || ((0 === cy) && (!b2t))) {
if (cx === (nx - 8)) cx += 2;
else cx += 1;
b2t = !b2t;
} else {
cx -= 1;
if (b2t) cy += 1;
else cy -= 1;
}
odd = !odd;
// determine
if (0 === funcPatternsMap.get(cx, cy)) {
bitmap.set(cx, cy, (data[i] >>> j) & 0x01);
break;
} else {
continue;
}
}
}
}
}
// place version info
if (7 <= version) {
// gx=0x1f25 BCH Code is built on GF(2^4). but BCH#encode() is not use arithmetic on GF.
const vi = ((version << 12) | new BCH(field, 0x1f25).encode(version));
bitmap.set(nx - 1, 10, (vi >>> 0) & 1);
bitmap.set(nx - 1, 9, (vi >>> 1) & 1);
bitmap.set(nx - 1, 8, (vi >>> 2) & 1);
bitmap.set(nx - 2, 10, (vi >>> 3) & 1);
bitmap.set(nx - 2, 9, (vi >>> 4) & 1);
bitmap.set(nx - 2, 8, (vi >>> 5) & 1);
bitmap.set(nx - 3, 10, (vi >>> 6) & 1);
bitmap.set(nx - 3, 9, (vi >>> 7) & 1);
bitmap.set(nx - 3, 8, (vi >>> 8) & 1);
bitmap.set(nx - 4, 10, (vi >>> 9) & 1);
bitmap.set(nx - 4, 9, (vi >>> 10) & 1);
bitmap.set(nx - 4, 8, (vi >>> 11) & 1);
bitmap.set(nx - 5, 10, (vi >>> 12) & 1);
bitmap.set(nx - 5, 9, (vi >>> 13) & 1);
bitmap.set(nx - 5, 8, (vi >>> 14) & 1);
bitmap.set(nx - 6, 10, (vi >>> 15) & 1);
bitmap.set(nx - 6, 9, (vi >>> 16) & 1);
bitmap.set(nx - 6, 8, (vi >>> 17) & 1);
bitmap.set(10, ny - 1, (vi >>> 0) & 1);
bitmap.set( 9, ny - 1, (vi >>> 1) & 1);
bitmap.set( 8, ny - 1, (vi >>> 2) & 1);
bitmap.set(10, ny - 2, (vi >>> 3) & 1);
bitmap.set( 9, ny - 2, (vi >>> 4) & 1);
bitmap.set( 8, ny - 2, (vi >>> 5) & 1);
bitmap.set(10, ny - 3, (vi >>> 6) & 1);
bitmap.set( 9, ny - 3, (vi >>> 7) & 1);
bitmap.set( 8, ny - 3, (vi >>> 8) & 1);
bitmap.set(10, ny - 4, (vi >>> 9) & 1);
bitmap.set( 9, ny - 4, (vi >>> 10) & 1);
bitmap.set( 8, ny - 4, (vi >>> 11) & 1);
bitmap.set(10, ny - 5, (vi >>> 12) & 1);
bitmap.set( 9, ny - 5, (vi >>> 13) & 1);
bitmap.set( 8, ny - 5, (vi >>> 14) & 1);
bitmap.set(10, ny - 6, (vi >>> 15) & 1);
bitmap.set( 9, ny - 6, (vi >>> 16) & 1);
bitmap.set( 8, ny - 6, (vi >>> 17) & 1);
}
// masking
let maskNo = 0;
{
let maskScore = Number.MAX_SAFE_INTEGER;
// for each mask procs
for (const mask of masks) {
// mask
this.applyMask(bitmap, funcPatternsMap, mask.fn);
// calculate score
const score = this.evaluteMask(bitmap);
if (score < maskScore) {
maskNo = mask.index;
maskScore = score;
}
// unmask
this.applyMask(bitmap, funcPatternsMap, mask.fn);
}
// mask
this.applyMask(bitmap, funcPatternsMap, masks[maskNo].fn);
}
// place format info
{
const props = this.props as QrPropsNoUndefined;
const ecLevel = ecLevelMap.get(props.ecLevel);
if (ecLevel === void 0) {
throw new Error(`Qr#buildBitmap: bad ecLevel '${props.ecLevel}' is specified.`);
}
let fi = (ecLevel << 3) | masks[maskNo].index;
// gx=0x0537 BCH Code is built on GF(2^4). but BCH#encode() is not use arithmetic on GF.
fi = ((fi << 10) | new BCH(field, 0x0537).encode(fi)) ^ 0x5412;
for (let i = 0; i <= 7; i++) {
bitmap.set(i , ny - 9, (fi >>> i) & 1);
}
{
bitmap.set( nx - 8, ny - 9, (fi >>> 8) & 1);
}
for (let i = 9; i <= 14; i++) {
bitmap.set(i + nx - 15, ny - 9, (fi >>> i) & 1);
}
for (let i = 0; i <= 5; i++) {
bitmap.set(nx - 9, ny - 1 - i, (fi >>> i) & 1);
}
for (let i = 6; i <= 7; i++) {
bitmap.set(nx - 9, ny - 2 - i, (fi >>> i) & 1);
}
{
bitmap.set(nx - 9, 7 , 1);
}
for (let i = 8; i <= 14; i++) {
bitmap.set(nx - 9, 14 - i, (fi >>> i) & 1);
}
}
return bitmap;
}
protected drawBitmap(canvas: SvgCanvas, bitmap: Bitmap) {
// bitmap's coodinate origin is right-bottom.
const c = this.props.cellSize as number;
const nx = bitmap.width,
ny = bitmap.height;
for (let i = 0; i < nx; i++) {
for (let j = 0; j < ny; j++) {
if (bitmap.get(i, j)) canvas.rect((nx - 1 - i) * c, (ny - 1 - j) * c, c, c);
}
}
canvas.fill();
canvas.beginPath();
}
}