From 46463b0557a3ce29c9cf5da8e9d8977ea1256ded Mon Sep 17 00:00:00 2001 From: Steve Shreeve Date: Wed, 16 Sep 2026 16:30:43 -0600 Subject: [PATCH 1/2] Update pdf417.rip --- packages/barcodes/pdf417.rip | 50 ++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/barcodes/pdf417.rip b/packages/barcodes/pdf417.rip index d1e977b4..05cd8d68 100644 --- a/packages/barcodes/pdf417.rip +++ b/packages/barcodes/pdf417.rip @@ -212,9 +212,9 @@ PATTERNS =! do -> START_PATTERN =! 0x1fea8 # 17 modules: 8 1 1 1 1 1 1 3 STOP_PATTERN =! 0x3fa29 # 18 modules: 7 1 1 3 1 1 1 2 1 -MAX_COLUMNS =! 30 -MIN_ROWS =! 3 -MAX_ROWS =! 90 +MAX_COLUMNS =! 30 +MIN_ROWS =! 3 +MAX_ROWS =! 90 MAX_CODEWORDS =! 928 # per symbol, indicators excluded MODULUS =! 929 @@ -226,7 +226,7 @@ LATCH_NUMERIC =! 902 SHIFT_BYTE =! 913 LATCH_BYTE6 =! 924 # byte compaction, a multiple of six bytes ECI_CHARSET =! 927 -ECI_UTF8 =! 26 +ECI_UTF8 =! 26 # Text submodes and the symbols that move between them: a latch changes the # submode, a shift applies to the next character only. @@ -234,13 +234,13 @@ ALPHA =! 0 LOWER =! 1 MIXED =! 2 PUNCT =! 3 -PL =! 25 # mixed -> punct latch -LL =! 27 # alpha or mixed -> lower latch -AS =! 27 # lower: alpha shift -ML =! 28 # alpha or lower -> mixed latch -AL =! 28 # mixed -> alpha latch -PS =! 29 # punct shift -PAL =! 29 # punct -> alpha latch +PL =! 25 # mixed -> punct latch +LL =! 27 # alpha or mixed -> lower latch +AS =! 27 # lower: alpha shift +ML =! 28 # alpha or lower -> mixed latch +AL =! 28 # mixed -> alpha latch +PS =! 29 # punct shift +PAL =! 29 # punct -> alpha latch MIXED_CHARS =! '0123456789&\r\t,:#-.$/+%*=^' PUNCT_CHARS =! ';<>@[\\]_`~!\r\t,:\n-.$/"|*()?{}\'' @@ -275,7 +275,7 @@ do -> # logs indexes it without a modulo. GF929 =! do -> exp = Int32Array.new(2 * 928) - log = Int32Array.new(929) + log = Int32Array.new( 929) x = 1 for i in [0...928] exp[i] = exp[i + 928] = x @@ -727,14 +727,14 @@ export encodePDF417 =! (text, output = 'raw', opts = {}) -> asString text, 'text' asString output, 'output' asObject opts, 'opts' - scale = if opts.scale is undefined then 1 else asInt(opts.scale, 'opts.scale', 1) - border = if opts.border is undefined then 2 else asInt(opts.border, 'opts.border', 0) - rowHeight = if opts.rowHeight is undefined then 3 else asInt(opts.rowHeight, 'opts.rowHeight', 1) - aspect = if opts.aspect is undefined then 3 else asInt(opts.aspect, 'opts.aspect', 1) - columns = if opts.columns is undefined then undefined else asInt(opts.columns, 'opts.columns', 1, MAX_COLUMNS) - rows = if opts.rows is undefined then undefined else asInt(opts.rows, 'opts.rows', MIN_ROWS, MAX_ROWS) - level = if opts.ecc is undefined then undefined else asInt(opts.ecc, 'opts.ecc', 0, 8) - encoding = if opts.encoding is undefined then 'auto' else opts.encoding + scale = if opts.scale is undefined then 1 else asInt(opts.scale, 'opts.scale', 1) + border = if opts.border is undefined then 2 else asInt(opts.border, 'opts.border', 0) + rowHeight = if opts.rowHeight is undefined then 3 else asInt(opts.rowHeight, 'opts.rowHeight', 1) + aspect = if opts.aspect is undefined then 3 else asInt(opts.aspect, 'opts.aspect', 1) + columns = if opts.columns is undefined then undefined else asInt(opts.columns, 'opts.columns', 1, MAX_COLUMNS) + rows = if opts.rows is undefined then undefined else asInt(opts.rows, 'opts.rows', MIN_ROWS, MAX_ROWS) + level = if opts.ecc is undefined then undefined else asInt(opts.ecc, 'opts.ecc', 0, 8) + encoding = if opts.encoding is undefined then 'auto' else opts.encoding fail "invalid encoding=#{encoding}" unless ENCODINGS.includes encoding codes = [] bytes = messageBytes text, codes @@ -1258,7 +1258,7 @@ class Stream v = @c[@i] if v < 900 @symbol v // 30 - @symbol v % 30 + @symbol v % 30 else if v is SHIFT_BYTE fail 'PDF417 byte shift at end of data' if @i + 1 >= @count @out.push @c[++@i] @@ -1433,8 +1433,8 @@ parse =! (c, count) -> trackEdge =! (s, x, y, dx, dy, sx, sy, unit, first, sign, edge, mode) -> {img, format, rluma, rruns} = s margin = 3 * unit + 3 - length = Math.min rluma.length, Math.ceil(mode.modules * unit + 2 * margin) - slack = Math.max 2, unit + length = Math.min rluma.length, Math.ceil(mode.modules * unit + 2 * margin) + slack = Math.max 2, unit allow = Math.ceil 3 * unit misses = 0 t = 0 @@ -1459,8 +1459,8 @@ trackEdge =! (s, x, y, dx, dy, sx, sy, unit, first, sign, edge, mode) -> edge.su += u edge.stt += t * t edge.stu += t * u - edge.min = t if t < edge.min - edge.max = t if t > edge.max + edge.min = t if t < edge.min + edge.max = t if t > edge.max unit # Row count, column count and level from the first indicator of every ray: From 1f563d6fc4345c412793c45174473cce79d95932 Mon Sep 17 00:00:00 2001 From: Steve Shreeve Date: Wed, 16 Sep 2026 16:45:43 -0600 Subject: [PATCH 2/2] barcodes: final review pass over the package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four read-only reviews of every file, acting only on errors. QR: the global-projection fallback after the tile pass rebuilds the finder map and its fine-plane form, since the tiles built their homographies in the same matrices; as written it ran on the last tile's map and failed on every fine-plane path. The unreachable hasError flag in correctBlock and the unused test parameter of drawSymbol are gone. The timing and scan comments say what the code checks. Encoder parity 1452/0 and decoder parity 192/0 against the reference are unchanged. Code 128: a reversed line that opened dark ends in an empty run, which the stop's quiet-zone test rejected, so a mirrored symbol flush against the image's left edge did not read; the test covers both flush cases. The checksum leaves _tests, which the suite never used. PDF417: put and Stream.decimal are void, since a trailing loop collects an array per codeword otherwise; the corners across the symbol come from the tracked tail edge plus its pattern width when that edge was tracked; the header describes the two-edge reader; the readFrom and row-fit comments name what first and the indicator are; a guard edgeRuns could never take is gone; tryDecode's summary sits above tryDecode. ZXing sets 1-3 stay 58/58 at four rotations. ECI 12 (ISO-8859-10) joins the table. README: a clean QR raster decodes at one pixel per module, as the suite and a direct check show, so the advice to upscale is gone; the encoder matches the reference implementation, which the vectors prove, not python-qrcode; two paragraphs rewrapped. A test comment names the symbol's true shape, nine rows of two columns. Benchmarks (Bun 1.4.0, M5) match or beat the README on every row: QR encode 2.5/14.6/45.9 µs, decode 28.4 µs/577 µs/1.32 ms, miss 5.92 ms; Code 128 720p read 5.2 µs; PDF417 1080p decode 344 µs, noise 299 µs. --- packages/barcodes/README.md | 37 +++++++++--------- packages/barcodes/code128.rip | 15 ++++---- packages/barcodes/eci.rip | 2 +- packages/barcodes/pdf417.rip | 70 ++++++++++++++++++++--------------- packages/barcodes/qr.rip | 19 ++++++---- packages/barcodes/test.rip | 6 ++- 6 files changed, 83 insertions(+), 66 deletions(-) diff --git a/packages/barcodes/README.md b/packages/barcodes/README.md index d33ad79a..bbadbffe 100644 --- a/packages/barcodes/README.md +++ b/packages/barcodes/README.md @@ -101,15 +101,14 @@ encodeQR text, output, opts | `textEncoder` | custom text-to-bytes for `byte` mode | UTF-8 | Single-segment encoding is always used, and penalty scoring runs on the -reserved test form, so output matches python-qrcode module for module. +reserved test form, so output matches the reference implementation module +for module. ## Decoding `decodeQR` takes `{ width, height, data }` and returns the decoded string. It throws when no symbol decodes; in a camera loop that is a frame miss, feed -the next frame. Clean one-pixel-per-module rasters are too small for -run-length finder detection, so upscale the encoder's `raw` output at least -twice before decoding it. +the next frame. A clean raster decodes at one pixel per module. | option | meaning | default | | --- | --- | --- | @@ -211,15 +210,15 @@ The reader wants modules at least a pixel wide; rows a pixel tall read from a clean raster, and photographs want a little more. It reads the symbol in all four orientations and with the tilt and skew of a hand-held photo, as a mirror image, inverted, with whole rows torn off the top or -bottom, with the start pattern cut off at the image edge, and with holes. One parity codeword is always held back to check the -correction and the padding after the message must read as pad, so a -damaged symbol misses rather than decoding to the wrong text. The row -direction comes from the start and stop edges tracked together, so a -photograph taken from the side, whose rows stay level while the edges -lean, reads as well as a rotated one. Against ZXing's PDF417 blackbox -photo sets 1 to 3 it decodes all 58 images at each of four rotations. Its -Macro set decodes one segment per image; images holding several symbols -return the first found. +bottom, with the start pattern cut off at the image edge, and with holes. +One parity codeword is always held back to check the correction and the +padding after the message must read as pad, so a damaged symbol misses +rather than decoding to the wrong text. The row direction comes from the +start and stop edges tracked together, so a photograph taken from the +side, whose rows stay level while the edges lean, reads as well as a +rotated one. Against ZXing's PDF417 blackbox photo sets 1 to 3 it decodes +all 58 images at each of four rotations. Its Macro set decodes one segment +per image; images holding several symbols return the first found. ## Scanner @@ -344,12 +343,12 @@ ponyfill follow his design; the port keeps his algorithms, restructures them for Rip, and verifies itself against his implementation with an oracle that compares every output format byte for byte and every decode result on synthetic frames. The performance work described above is on top -of that foundation. Code 128 and PDF417 are original to this package and share only the -image input, ECI table and GIF writer. The PDF417 encoder is checked -against [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp), which decodes -its output byte for byte across compaction modes, levels, shapes and -scales, and the reader against the photographs in ZXing's PDF417 blackbox -corpus. +of that foundation. Code 128 and PDF417 are original to this package and +share only the image input, ECI table and GIF writer. The PDF417 encoder +is checked against [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp), +which decodes its output byte for byte across compaction modes, levels, +shapes and scales, and the reader against the photographs in ZXing's +PDF417 blackbox corpus. ## Test diff --git a/packages/barcodes/code128.rip b/packages/barcodes/code128.rip index b5d43bc6..37101884 100644 --- a/packages/barcodes/code128.rip +++ b/packages/barcodes/code128.rip @@ -298,8 +298,9 @@ readFrom =! (runs, n, at, start, codes) -> # The termination bar is two modules; accept one to four. bar = runs[at + 6] * 11 return 0 if bar < width or bar > 4 * width - # A quiet zone of at least half a codeword, or the edge of the line. - return 0 unless at + 7 >= n or 2 * runs[at + 7] >= width + # A quiet zone of at least half a codeword, or the edge of the line, + # which a reversed line that opened dark carries as an empty run. + return 0 unless at + 7 >= n or runs[at + 7] is 0 or 2 * runs[at + 7] >= width break at += 6 # Start, at least one data codeword, check, stop. @@ -404,10 +405,10 @@ scanLines =! (img, format, vertical, luma, runs, rev, codes) -> export readCode128 =! (img, opts = {}) -> asObject opts, 'opts' format = validateImage img, opts.format - side = Math.max(img.width, img.height) + 2 - luma = Uint8Array.new(side) - runs = Int32Array.new(side) - rev = Int32Array.new(side) + side = Math.max(img.width, img.height) + 2 + luma = Uint8Array.new(side) + runs = Int32Array.new(side) + rev = Int32Array.new(side) codes = [] vertical = false hit = scanLines img, format, false, luma, runs, rev, codes @@ -425,4 +426,4 @@ export decodeCode128 =! (img, opts = {}) -> hit.text # Internals for the test suite. -export _tests =! { codewords, decodeText, checksum } +export _tests =! { codewords, decodeText } diff --git a/packages/barcodes/eci.rip b/packages/barcodes/eci.rip index b4b48986..b86944e6 100644 --- a/packages/barcodes/eci.rip +++ b/packages/barcodes/eci.rip @@ -8,7 +8,7 @@ ECI_ENCODINGS =! 1: 'iso-8859-1', 2: 'ibm437', 3: 'iso-8859-1', 4: 'iso-8859-2', 5: 'iso-8859-3' 6: 'iso-8859-4', 7: 'iso-8859-5', 8: 'iso-8859-6', 9: 'iso-8859-7', 10: 'iso-8859-8' - 11: 'iso-8859-9', 13: 'iso-8859-11', 15: 'iso-8859-13', 16: 'iso-8859-14' + 11: 'iso-8859-9', 12: 'iso-8859-10', 13: 'iso-8859-11', 15: 'iso-8859-13', 16: 'iso-8859-14' 17: 'iso-8859-15', 18: 'iso-8859-16', 20: 'shift-jis', 21: 'windows-1250' 22: 'windows-1251', 23: 'windows-1252', 24: 'windows-1256', 25: 'utf-16be' 26: 'utf-8', 28: 'big5', 29: 'gbk', 30: 'euc-kr' diff --git a/packages/barcodes/pdf417.rip b/packages/barcodes/pdf417.rip index 05cd8d68..184e4a68 100644 --- a/packages/barcodes/pdf417.rip +++ b/packages/barcodes/pdf417.rip @@ -12,8 +12,10 @@ # # The encoder follows the specification's recommended mode switching and # lays the codewords into module rows; renderers draw those rows at a row -# height and scale. The reader locates the start and stop patterns on scan -# lines, samples every codeword in the band between them, votes each into +# height and scale. The reader finds a start pattern on a scan line, or a +# stop pattern read backward when the start is out of the picture, tracks +# its edge along the stack and the edge at the row's other end with it, +# reads every row along rays between the two, votes each codeword into # its row and column by the row indicators and cluster, then corrects # erasures and errors together before parsing the stream. # ============================================================================== @@ -601,7 +603,7 @@ modules =! (data, columns, rows, level, compact) -> row = Uint8Array.new(width) c = r % 3 x = 0 - put = (bits, n) -> + put! = (bits, n) -> for k in [n - 1..0] by -1 row[x++] = (bits >>> k) & 1 put START_PATTERN, 17 @@ -1004,7 +1006,7 @@ edgeRuns =! (luma, count, runs) -> # Cross between samples i - 1 and i. a = luma[i - 1] b = luma[i] - pos = i - 1 + (if b is a then 0.5 else (level - a) / (b - a)) + pos = i - 1 + (level - a) / (b - a) runs[n++] = pos - last last = pos k++ @@ -1055,12 +1057,12 @@ anchorNear =! (runs, n, first, expect, slack, mode) -> best # Codewords along one ray into cells, the first expected to begin `expect` -# pixels in with modules of `unit` pixels and bars on runs of parity -# `first`: the symbol index, or -1 for a cell that would not read. Each next -# codeword is expected seventeen modules on; the nearest bar edge -# resynchronizes, so a merged or split run costs one cell rather than the -# rest of the row. Stops at the pattern that ends the row or when sync is -# lost. Returns the count. +# pixels in with modules of `unit` pixels, read from run `first`, which +# also floors the resync: the symbol index, or -1 for a cell that would +# not read. Each next codeword is expected seventeen modules on; the +# nearest bar edge resynchronizes, so a merged or split run costs one cell +# rather than the rest of the row. Stops at the pattern that ends the row +# or when sync is lost. Returns the count. readFrom =! (runs, n, first, expect, unit, cells, base, mode) -> pos = 0 pos += runs[k] for k in [0...first] @@ -1309,7 +1311,7 @@ class Stream @i++ if @i < @count and @c[@i] is LATCH_NUMERIC # Digits of the base-900 codewords in [start, end) into the sink. - decimal: (start, end) -> + decimal!: (start, end) -> limbs = [0] # base 10^7, least significant first for j in [start...end] carry = @c[j] @@ -1492,7 +1494,6 @@ metadata =! (s, rays, mode) -> return null if columns * rows < 2 + (2 << level) or columns * rows > MAX_CODEWORDS { rows, columns, level } -# One located anchor pattern through to a decoded symbol, or null. # The leading edge of an anchor pattern, tracked up and down the stack # from (x, y) and fitted as a line u = a + b t across the tracked span; # returns its two ends, the mean unit and the count of lines held, or null. @@ -1513,6 +1514,7 @@ edgeEnds =! (s, x, y, dx, dy, sx, sy, unit, first, mode) -> lines: edge.n } +# One located anchor pattern through to a decoded symbol, or null. tryDecode =! (s, x, y, dx, dy, unit, first, endAt, extent, mode) -> {img, format, rluma, rruns, cells, rayRow, rayT, votes} = s # Rows stack a quarter turn from the symbol's reading direction, which a @@ -1534,17 +1536,19 @@ tryDecode =! (s, x, y, dx, dy, unit, first, endAt, extent, mode) -> # well as this one; a picture taken from the side skews the two edges # without turning the rows. Otherwise the rows are taken square to the # edge. - rx = uy * flip - ry = -ux * flip + rx = uy * flip + ry = -ux * flip + tail = null if endAt < Infinity - tail = edgeEnds s, x + endAt * dx, y + endAt * dy, dx, dy, sx, sy, unit, first ^ (mode.anchor.length & 1), mode.tail - if tail and tail.lines * 2 >= lead.lines - vx = (tail.top.x - top.x + tail.bottom.x - bottom.x) / 2 - vy = (tail.top.y - top.y + tail.bottom.y - bottom.y) / 2 + far = edgeEnds s, x + endAt * dx, y + endAt * dy, dx, dy, sx, sy, unit, first ^ (mode.anchor.length & 1), mode.tail + if far and far.lines * 2 >= lead.lines + vx = (far.top.x - top.x + far.bottom.x - bottom.x) / 2 + vy = (far.top.y - top.y + far.bottom.y - bottom.y) / 2 vl = Math.hypot vx, vy if vl > 0 and vx * rx + vy * ry > 0.7 * vl - rx = vx / vl - ry = vy / vl + rx = vx / vl + ry = vy / vl + tail = far margin = 3 * unit + 3 length = Math.min rluma.length, Math.ceil((MAX_CELLS + 2) * 17 * unit + 2 * margin), Math.ceil(extent + 2 * margin + 3 * unit) step = Math.max 1, 0.75 * unit @@ -1570,7 +1574,7 @@ tryDecode =! (s, x, y, dx, dy, unit, first, endAt, extent, mode) -> meta = metadata s, rays, mode return null unless meta {rows, columns, level} = meta - # Row of each ray from its left indicator, fitted against position along + # Row of each ray from its first indicator, fitted against position along # the edge so a misread indicator cannot move a whole line; then rays # beyond the tracked edge while the fit still lands inside the symbol, # which reads rows whose start pattern is damaged. @@ -1744,17 +1748,23 @@ tryDecode =! (s, x, y, dx, dy, unit, first, endAt, extent, mode) -> return null unless c[cell] is PAD result = try parse(c, size) catch then null return null unless result - # Corners: the tracked edge and its projection across the symbol width, - # which in mirror mode lies on the anchor's other side. - w = (17 * (columns + 4) + 1) * unit - result.columns = columns - result.rows = rows - result.ecc = level - result.errors = errors - result.erasures = e + result.columns = columns + result.rows = rows + result.ecc = level + result.errors = errors + result.erasures = e result.codewords = Array.from c.subarray(0, size) + # Corners: the tracked edge and, across the symbol, the tail edge plus + # its pattern's width, or the nominal width when the tail was not + # tracked; in mirror mode the far side is the start of the row. + if tail + w = mode.tail.modules * unit + ends = [tail.top, tail.bottom] + else + w = (17 * (columns + 4) + 1) * unit + ends = [top, bottom] near = [{ x: top.x, y: top.y }, { x: bottom.x, y: bottom.y }] - far = [{ x: top.x + rx * w, y: top.y + ry * w }, { x: bottom.x + rx * w, y: bottom.y + ry * w }] + far = ({ x: e.x + rx * w, y: e.y + ry * w } for e in ends) result.corners = if mode.mirror then [far[0], near[0], near[1], far[1]] else [near[0], far[0], far[1], near[1]] result diff --git a/packages/barcodes/qr.rip b/packages/barcodes/qr.rip index 33258ee3..c2e35f6e 100644 --- a/packages/barcodes/qr.rip +++ b/packages/barcodes/qr.rip @@ -544,7 +544,7 @@ buildSymCache =! (ver) -> # Template copy, data-bit scatter along the cached zigzag order, then mask # selection over XOR candidates; the first lowest score wins. -drawSymbol =! (ver, ecc, data, maskIdx, test = false) -> +drawSymbol =! (ver, ecc, data, maskIdx) -> symCache = buildSymCache ver unless symCache?.ver is ver {tpl, pos, planes, planesT, work} = symCache [m, t, cand, candT] = work @@ -570,7 +570,7 @@ drawSymbol =! (ver, ecc, data, maskIdx, test = false) -> mask = pl chosen = planes[mask] m.v[i] ^= chosen[i] for i in [0...m.v.length] - drawInfo m, ver, ecc, mask unless test + drawInfo m, ver, ecc, mask m # ==[ Validation ]== @@ -2003,7 +2003,8 @@ export class QRScanner true # Reject wrong triples and dimensions from the timing tracks before full - # sampling: 75% agreement across the horizontal and vertical tracks. + # sampling: 75% agreement over the horizontal and vertical tracks + # together, or one track read perfectly. timing: (s, map, size) -> gh = 0 gv = 0 @@ -2129,7 +2130,6 @@ export class QRScanner correctBlock: (offset, length, eccWords) -> blockBytes = @blockBytes syndromes = @syndromes - hasError = false # The generator divides an intact block: a zero LFSR remainder, computed # exactly as the encoder does, settles the common case without syndromes. {products} = rsCached eccWords @@ -2147,8 +2147,6 @@ export class QRScanner value = 0 value = mul(value, EXP[i]) ^ blockBytes[offset + j] for j in [0...length] syndromes[i] = value - hasError = true if value - return true unless hasError sigma = @sigma previous = @previous next = @next @@ -2540,7 +2538,11 @@ export class QRScanner failed = @projectTiles sp, sm, size, version, fine, brPoint, t, ms, ctx return failed unless failed instanceof Error # A false local alignment can corrupt a valid global projection. - failed = @projectMap sp, sm, size + # The tiles built their homographies in the same matrices, so the + # global map and its fine-plane form are derived again. + @mapFinderQuad map, size, t, brX, brY + fine = @upgrade plane, map, ctx + failed = @projectMap (if fine then @finePlane else plane), (if fine then @to else map), size return failed unless failed instanceof Error if fine failed = @projectMap plane, map, size @@ -2771,7 +2773,8 @@ export class QRScanner continue @retries-- if not mandatory and @retries isnt Infinity attempted = true - # Large blurred symbols get template-fit refinement first; other + # Large blurred symbols turned past forty-five degrees get + # template-fit refinement first, unless the layer is blocked; other # projection failures retry once after a cross refinement pass. steep = Math.abs(triple.tr.y - triple.tl.y) > Math.abs(triple.tr.x - triple.tl.x) pitch = if not @blocked and steep then edgePitch(layer, triple.tl, triple.tr, triple.inverted) else 0 diff --git a/packages/barcodes/test.rip b/packages/barcodes/test.rip index 50da9a18..4835bfc5 100644 --- a/packages/barcodes/test.rip +++ b/packages/barcodes/test.rip @@ -516,6 +516,10 @@ bars =! (text, opts, scale, W, H, invert = false, rotate = 0) -> test "round trips: scales, rotations, inversion, GS1", -> for scale in [1, 2, 3, 5] eq decodeCode128(bars('Hello, World! 2026', {}, scale, 1400, 200)), 'Hello, World! 2026', "scale #{scale}" + # No quiet zone at all: the symbol fills the line in both directions. + edge = encodeCode128('Wikipedia', 'raw', border: 0, scale: 2).length + for rotate in [0, 180] + eq decodeCode128(bars('Wikipedia', { border: 0 }, 2, edge, 100, false, rotate)), 'Wikipedia', "flush #{rotate}" for rotate in [0, 90, 180, 270] hit = readCode128 bars('Wikipedia', {}, 2, 500, 500, false, rotate) eq hit.text, 'Wikipedia', "rotate #{rotate}" @@ -695,7 +699,7 @@ test "symbols: pinned module rows", -> test "outputs agree and options apply", -> # The row height shapes the symbol: at three modules a row the same - # message fits nine rows of four columns. + # message fits nine rows of two columns. raw = encodePDF417 'Hello PDF417', 'raw' eq [raw.length, raw[0].length], [9 * 3 + 4, 17 * 6 + 1 + 4] eq raw[2 + 3][2 + 8], false