From cc7583e885848c45e8378a8a437e508e8215293f Mon Sep 17 00:00:00 2001 From: Daniel White Date: Wed, 16 Sep 2026 23:59:32 +0900 Subject: [PATCH] pdf417: cluster zero left row indicator fixed This correction is souced from the USS PDF417 specification: > The symbol character value of the left row indicator (L_i) is given > by: > > L_i = 30 * x_i + y if c_i = 0 > L_i = 30 * x_i + z if c_i = 3 > L_i = 30 * x_i + v if c_i = 6 > > Where: > > x_i = (row number -1) div 3 for i = 1 to 90 > y = (number of rows - 1) div 3 > z = (error correction level) * 3 + (number of rows - 1) mod 3 > v = number of columns in the data region - 1 > c_i = cluster number of the i th row > > div is the integer division operator, rounding down > mod is the remainder after division. A regression test has been included with hand-computed values to ensure that we get the expected results. The major impact of this change to downstream consumers is that any golden tests against the output of this package will need to be updated. --- pdf417/encoder.go | 2 +- pdf417/encoder_test.go | 43 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 pdf417/encoder_test.go diff --git a/pdf417/encoder.go b/pdf417/encoder.go index db19e52..e6a53e8 100644 --- a/pdf417/encoder.go +++ b/pdf417/encoder.go @@ -101,7 +101,7 @@ func getLeftCodeWord(rowNum int, rows int, columns int, securityLevel byte) int switch tableId { case 0: - x = (rows - 3) / 3 + x = (rows - 1) / 3 case 1: x = int(securityLevel) * 3 x += (rows - 1) % 3 diff --git a/pdf417/encoder_test.go b/pdf417/encoder_test.go new file mode 100644 index 0000000..3f6d3a1 --- /dev/null +++ b/pdf417/encoder_test.go @@ -0,0 +1,43 @@ +package pdf417 + +import "testing" + +func TestRowIndicatorCodewords(t *testing.T) { + tests := []struct { + name string + rowNum int + rows int + columns int + securityLevel byte + wantLeft int + wantRight int + }{ + {"table0/group0", 0, 17, 6, 3, 5, 5}, + {"table1/group0", 1, 17, 6, 3, 10, 5}, + {"table2/group0", 2, 17, 6, 3, 5, 10}, + {"table0/group1", 3, 17, 6, 3, 35, 35}, + {"table1/group1", 4, 17, 6, 3, 40, 35}, + {"table2/group1", 5, 17, 6, 3, 35, 40}, + {"table1/group5", 16, 17, 6, 3, 160, 155}, + {"table0/rowsMod1", 0, 16, 6, 3, 5, 5}, + {"table0/maxParams", 0, maxRows, maxCols, 8, 9, 29}, + {"table1/maxParams", 1, maxRows, maxCols, 8, 26, 9}, + {"table2/maxParams", 2, maxRows, maxCols, 8, 29, 26}, + {"table0/lastRow", maxRows - 3, maxRows, maxCols, 8, 279, 299}, + {"table1/lastRow", maxRows - 2, maxRows, maxCols, 8, 296, 279}, + {"table2/lastRow", maxRows - 1, maxRows, maxCols, 8, 299, 296}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getLeftCodeWord(tt.rowNum, tt.rows, tt.columns, tt.securityLevel); got != tt.wantLeft { + t.Errorf("getLeftCodeWord(rowNum=%d, rows=%d, columns=%d, securityLevel=%d) = %d, want %d", + tt.rowNum, tt.rows, tt.columns, tt.securityLevel, got, tt.wantLeft) + } + if got := getRightCodeWord(tt.rowNum, tt.rows, tt.columns, tt.securityLevel); got != tt.wantRight { + t.Errorf("getRightCodeWord(rowNum=%d, rows=%d, columns=%d, securityLevel=%d) = %d, want %d", + tt.rowNum, tt.rows, tt.columns, tt.securityLevel, got, tt.wantRight) + } + }) + } +}