Skip to content

Commit

Permalink
Encode mixed numeric/alphanumeric data segments as one alphanumeric d…
Browse files Browse the repository at this point in the history
…ata segment if that's more efficient. Also add error check to NewWithForcedVersion(): Check if the content is too large for the requested QR code version.
  • Loading branch information
skip2 committed Jun 17, 2020
1 parent 7ac0b40 commit da1b656
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 4 deletions.
20 changes: 16 additions & 4 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func (d *dataEncoder) encode(data []byte) (*bitset.Bitset, error) {
}

// Classify data into unoptimised segments.
d.classifyDataModes()
highestRequiredMode := d.classifyDataModes()

// Optimise segments.
err := d.optimiseDataModes()
Expand All @@ -190,13 +190,13 @@ func (d *dataEncoder) encode(data []byte) (*bitset.Bitset, error) {
optimizedLength += length
}

singleByteSegmentLength, err := d.encodedLength(dataModeByte, len(d.data))
singleByteSegmentLength, err := d.encodedLength(highestRequiredMode, len(d.data))
if err != nil {
return nil, err
}

if singleByteSegmentLength <= optimizedLength {
d.optimised = []segment{segment{dataMode: dataModeByte, data: d.data}}
d.optimised = []segment{segment{dataMode: highestRequiredMode, data: d.data}}
}

// Encode data.
Expand All @@ -211,9 +211,15 @@ func (d *dataEncoder) encode(data []byte) (*bitset.Bitset, error) {
// classifyDataModes classifies the raw data into unoptimised segments.
// e.g. "123ZZ#!#!" =>
// [numeric, 3, "123"] [alphanumeric, 2, "ZZ"] [byte, 4, "#!#!"].
func (d *dataEncoder) classifyDataModes() {
//
// Returns the highest data mode needed to encode the data. e.g. for a mixed
// numeric/alphanumeric input, the highest is alphanumeric.
//
// dataModeNone < dataModeNumeric < dataModeAlphanumeric < dataModeByte
func (d *dataEncoder) classifyDataModes() dataMode {
var start int
mode := dataModeNone
highestRequiredMode := mode

for i, v := range d.data {
newMode := dataModeNone
Expand All @@ -236,9 +242,15 @@ func (d *dataEncoder) classifyDataModes() {

mode = newMode
}

if newMode > highestRequiredMode {
highestRequiredMode = newMode
}
}

d.actual = append(d.actual, segment{dataMode: mode, data: d.data[start:len(d.data)]})

return highestRequiredMode
}

// optimiseDataModes optimises the list of segments to reduce the overall output
Expand Down
51 changes: 51 additions & 0 deletions encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,57 @@ func TestOptimiseEncoding(t *testing.T) {
{dataModeByte, 8},
},
},
// HTTPS://ABC.DE/Q/393AABB6998877XYZ0518AUQCRVJN25
// AAAAAAAAAAAAAAAAANNNAAAANNNNNNNAAANNNNAAAAAAAANN
// different to below---------^--------------------
{
dataEncoderType1To9,
[]testModeSegment{
{dataModeAlphanumeric, 17},
{dataModeNumeric, 3},
{dataModeAlphanumeric, 4},
{dataModeNumeric, 7},
{dataModeAlphanumeric, 3},
{dataModeNumeric, 4},
{dataModeAlphanumeric, 8},
{dataModeNumeric, 2},
},
[]testModeSegment{
{dataModeAlphanumeric, 48},
},
},
// HTTPS://ABC.DE/Q/393AABB699E877XYZ0518AUQCRVJN25
// AAAAAAAAAAAAAAAAANNNAAAANNNANNNAAANNNNAAAAAAAANN
// different to above---------^--------------------
{
dataEncoderType1To9,
[]testModeSegment{
{dataModeAlphanumeric, 17},
{dataModeNumeric, 3},
{dataModeAlphanumeric, 4},
{dataModeNumeric, 3},
{dataModeAlphanumeric, 1},
{dataModeNumeric, 3},
{dataModeAlphanumeric, 3},
{dataModeNumeric, 4},
{dataModeAlphanumeric, 8},
{dataModeNumeric, 2},
},
[]testModeSegment{
{dataModeAlphanumeric, 48},
},
},
// 0123456789
// NNNNNNNNNN
{
dataEncoderType1To9,
[]testModeSegment{
{dataModeNumeric, 10},
},
[]testModeSegment{
{dataModeNumeric, 10},
},
},
}

for _, test := range tests {
Expand Down
7 changes: 7 additions & 0 deletions qrcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,13 @@ func NewWithForcedVersion(content string, version int, level RecoveryLevel) (*QR
return nil, errors.New("cannot find QR Code version")
}

if encoded.Len() > chosenVersion.numDataBits() {
return nil, fmt.Errorf("Cannot encode QR code: content too large for fixed size QR Code version %d (encoded length is %d bits, maximum length is %d bits)",
version,
encoded.Len(),
chosenVersion.numDataBits())
}

q := &QRCode{
Content: content,

Expand Down

0 comments on commit da1b656

Please sign in to comment.