Skip to content

Commit 3cf7da3

Browse files
committed
feat(grid-matrix): add structured append controls
1 parent 1524d30 commit 3cf7da3

2 files changed

Lines changed: 77 additions & 6 deletions

File tree

libs/GridMatrixCore.js

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,14 @@ export class GridMatrixCore {
7474

7575
generate() {
7676
const { bytes, eci } = normalizeInput(this.data, this.options.eci)
77+
const controls = normalizeControls(this.options)
7778
const requestedMode = this.options.mode == null ? 'auto' : String(this.options.mode).toLowerCase()
7879
if (!['auto', 'byte'].includes(requestedMode)) {
7980
throw new RangeError("Grid Matrix mode must be 'auto' or 'byte'.")
8081
}
8182
const { bits, modes } = requestedMode === 'byte'
82-
? { bits: encodeByteSegments(bytes, eci), modes: Array(bytes.length).fill(BYTE) }
83-
: encodeOptimized(bytes, eci)
83+
? { bits: encodeByteSegments(bytes, eci, controls.prefix), modes: Array(bytes.length).fill(BYTE) }
84+
: encodeOptimized(bytes, eci, controls.prefix)
8485
const dataCodewords = bitsToCodewords(bits)
8586
const layers = chooseLayers(dataCodewords.length, this.options.layers)
8687
const eccLevel = chooseEccLevel(dataCodewords.length, layers, this.options.eccLevel)
@@ -93,6 +94,8 @@ export class GridMatrixCore {
9394
layers,
9495
eccLevel,
9596
eci,
97+
readerInitialization: controls.readerInitialization,
98+
structuredAppend: controls.structuredAppend,
9699
encoding: requestedMode,
97100
modes: modes.map(mode => MODE_NAMES[mode - 1]),
98101
width: modules.length,
@@ -105,6 +108,44 @@ export class GridMatrixCore {
105108
}
106109
}
107110

111+
function normalizeControls(options) {
112+
const requestedReaderInitialization = options.readerInitialization === true
113+
if (options.readerInitialization != null && typeof options.readerInitialization !== 'boolean') {
114+
throw new TypeError('Grid Matrix readerInitialization must be boolean.')
115+
}
116+
117+
let structuredAppend = null
118+
if (options.structuredAppend != null) {
119+
const value = options.structuredAppend
120+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
121+
throw new TypeError('Grid Matrix structuredAppend must be an object.')
122+
}
123+
const count = Number(value.count), index = Number(value.index)
124+
const id = value.id == null ? 0 : Number(value.id)
125+
if (!Number.isInteger(count) || count < 2 || count > 16) {
126+
throw new RangeError('Grid Matrix structuredAppend count must be an integer from 2 to 16.')
127+
}
128+
if (!Number.isInteger(index) || index < 1 || index > count) {
129+
throw new RangeError('Grid Matrix structuredAppend index must be an integer from 1 to count.')
130+
}
131+
if (!Number.isInteger(id) || id < 0 || id > 255) {
132+
throw new RangeError('Grid Matrix structuredAppend id must be an integer from 0 to 255.')
133+
}
134+
structuredAppend = { index, count, id }
135+
}
136+
137+
const readerInitialization = requestedReaderInitialization && (!structuredAppend || structuredAppend.index === 1)
138+
const prefix = []
139+
if (readerInitialization) appendBits(prefix, 10, 4)
140+
if (structuredAppend) {
141+
appendBits(prefix, 9, 4)
142+
appendBits(prefix, structuredAppend.id, 8)
143+
appendBits(prefix, structuredAppend.count - 1, 4)
144+
appendBits(prefix, structuredAppend.index - 1, 4)
145+
}
146+
return { prefix, readerInitialization, structuredAppend }
147+
}
148+
108149
function normalizeInput(value, requestedEci) {
109150
let bytes, automaticEci = 0
110151
if (typeof value === 'string') {
@@ -177,8 +218,8 @@ function buildGb2312EncodeMap() {
177218
return map
178219
}
179220

180-
function encodeByteSegments(bytes, eci) {
181-
const bits = []
221+
function encodeByteSegments(bytes, eci, prefix = []) {
222+
const bits = [...prefix]
182223
appendEci(bits, eci)
183224
const flatBytes = Array.from(bytes).flatMap(value => value > 0xFF ? [value >> 8, value & 0xFF] : [value])
184225
for (let offset = 0; offset < flatBytes.length; offset += 512) {
@@ -193,8 +234,8 @@ function encodeByteSegments(bytes, eci) {
193234
return bits
194235
}
195236

196-
function encodeOptimized(data, eci) {
197-
const bits = []
237+
function encodeOptimized(data, eci, prefix = []) {
238+
const bits = [...prefix]
198239
appendEci(bits, eci)
199240
const modes = defineModes(data)
200241
let position = 0, currentMode = 0, lastMode = 0

tests/grid-matrix-core.test.mjs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,36 @@ test('can force byte mode when compact high-level modes are not wanted', () => {
6666
assert.deepEqual(bytes.modes, ['byte', 'byte', 'byte'])
6767
})
6868

69+
test('matches Reader Initialization and Structured Append reference vectors', () => {
70+
const vectors = [
71+
[{ readerInitialization: true }, [0x51, 0x11, 0x71, 0x7E, 0x40]],
72+
[{ structuredAppend: { index: 1, count: 16 } }, [0x48, 0x03, 0x60, 0x24, 0x3C, 0x3F, 0x50]],
73+
[{ readerInitialization: true, structuredAppend: { index: 1, count: 16 } }, [0x54, 0x40, 0x1E, 0x02, 0x23, 0x63, 0x7D, 0x00]],
74+
[{ structuredAppend: { index: 2, count: 16 } }, [0x48, 0x03, 0x62, 0x24, 0x3C, 0x3F, 0x50]],
75+
[{ structuredAppend: { index: 3, count: 3, id: 255 } }, [0x4F, 0x7C, 0x44, 0x24, 0x3C, 0x3F, 0x50]],
76+
]
77+
for (const [options, expected] of vectors) {
78+
assert.deepEqual(new GridMatrixCore('12', options).generate().dataCodewords, expected, JSON.stringify(options))
79+
}
80+
})
81+
82+
test('omits Reader Initialization after the first Structured Append symbol', () => {
83+
const symbol = new GridMatrixCore('12', {
84+
readerInitialization: true,
85+
structuredAppend: { index: 2, count: 16 },
86+
}).generate()
87+
assert.equal(symbol.readerInitialization, false)
88+
assert.deepEqual(symbol.dataCodewords, [0x48, 0x03, 0x62, 0x24, 0x3C, 0x3F, 0x50])
89+
})
90+
91+
test('validates Reader Initialization and Structured Append metadata', () => {
92+
assert.throws(() => new GridMatrixCore('A', { readerInitialization: 1 }).generate(), /boolean/)
93+
assert.throws(() => new GridMatrixCore('A', { structuredAppend: true }).generate(), /object/)
94+
assert.throws(() => new GridMatrixCore('A', { structuredAppend: { index: 1, count: 1 } }).generate(), /count/)
95+
assert.throws(() => new GridMatrixCore('A', { structuredAppend: { index: 3, count: 2 } }).generate(), /index/)
96+
assert.throws(() => new GridMatrixCore('A', { structuredAppend: { index: 1, count: 2, id: 256 } }).generate(), /id/)
97+
})
98+
6999
test('supports all thirteen explicit versions and produces square boolean matrices', () => {
70100
for (let layers = 1; layers <= 13; layers++) {
71101
const symbol = new GridMatrixCore('A', { layers }).generate()

0 commit comments

Comments
 (0)