Skip to content

Commit

Permalink
PDF417: Automated structured append
Browse files Browse the repository at this point in the history
Resolves #92
  • Loading branch information
uwolfer committed Mar 15, 2024
1 parent 1a8bfa2 commit 5e139e3
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 0 deletions.
126 changes: 126 additions & 0 deletions src/main/java/uk/org/okapibarcode/backend/Pdf417.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package uk.org.okapibarcode.backend;

import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static uk.org.okapibarcode.util.Arrays.positionOf;

import java.math.BigInteger;
Expand Down Expand Up @@ -1847,4 +1848,129 @@ public String toString() {
return mode + "x" + length;
}
}

/**
* Splits up the data into a series of structured append PDF417 symbols which include
* segment macro data <code>total</code> and <code>position</code>.
* Input data will be assumed to be of the type set by {@link Pdf417#setDataType(Symbol.DataType)}.
*
* @param data the data to encode
* @param template the PDF417 symbol template which will be used for all created
* symbols. The following properties will be ignored from the template:
* <ul>
* <li>{@link Pdf417#setContent(String)}</li>
* <li>{@link Pdf417#setContent(byte[])}</li>
* <li>{@link Pdf417#setStructuredAppendTotal(int)}</li>
* <li>{@link Pdf417#setStructuredAppendPosition(int)}</li>
* </ul>
* @throws OkapiException if no data or data is invalid
*/
public static List<Pdf417> createStructuredAppendSymbols(String data, Pdf417 template) {
List<String> dataList = splitData(data, template);
return createStructuredAppendSymbols(dataList, template);
}

/**
* Overloaded method of {@link #createStructuredAppendSymbols(String, Pdf417)} which
* accepts binary data.
*
* <p><b>NOTE:</b>See linked method above for details.
*/
public static List<Pdf417> createStructuredAppendSymbols(byte[] data, Pdf417 template) {
return createStructuredAppendSymbols(new String(data, ISO_8859_1), template);
}

private static List<String> splitData(String data, Pdf417 template) {
Pdf417 testSymbol = new Pdf417() {
@Override
protected void plotSymbol() {
} // expensive plotting is not required
};
clone(template, testSymbol);
testSymbol.setStructuredAppendTotal(2);

List<String> dataList = new ArrayList<>();
String remainingData = data;
while (!remainingData.isEmpty()) {
int low = 0;
int high = remainingData.length();

while (low <= high) {
int mid = low + high >>> 1;
int currentLength = Math.min(mid, remainingData.length());
String currentData = remainingData.substring(0, currentLength);
boolean fit = fits(currentData, testSymbol, false);
if (fit && mid < remainingData.length() &&
fits(remainingData.substring(0, currentLength + 1), testSymbol, false)) {
low = mid + 1;
} else {
if (fit && currentLength == mid) {
dataList.add(currentData);
remainingData = remainingData.substring(mid);
}
high = mid - 1;
}
}
}
if (!dataList.isEmpty()) {
String lastData = dataList.get(dataList.size() - 1);
if (!fits(lastData, testSymbol, true)) {
int endIndex = lastData.length() - 1;
dataList.set(dataList.size() - 1, lastData.substring(0, endIndex));
dataList.add(lastData.substring(endIndex));
}
}
return dataList;
}

private static boolean fits(String data, Pdf417 testSymbol, boolean last) {
testSymbol.setStructuredAppendPosition(last ? 2 : 1);
try {
if (!data.isEmpty()) {
testSymbol.setContent(data);
}
} catch (OkapiInputException e) {
return false;
}
return true;
}

private static List<Pdf417> createStructuredAppendSymbols(List<String> dataList, Pdf417 template) {
int structuredAppendTotal = dataList.size();
List<Pdf417> symbols = new ArrayList<>(structuredAppendTotal);
for (int i = 0; i < structuredAppendTotal; i++) {
String data = dataList.get(i);
Pdf417 symbol = new Pdf417();
symbols.add(symbol);
clone(template, symbol);

symbol.setStructuredAppendPosition(i + 1);
symbol.setStructuredAppendTotal(structuredAppendTotal);

symbol.setContent(data);
}
return symbols;
}

private static void clone(Pdf417 template, Pdf417 target) {
target.setFontName(template.getFontName());
target.setFontSize(template.getFontSize());
target.setDataType(template.getDataType());
target.setEmptyContentAllowed(template.getEmptyContentAllowed());
target.setHumanReadableAlignment(template.getHumanReadableAlignment());
target.setHumanReadableLocation(template.getHumanReadableLocation());
target.setModuleWidth(template.getModuleWidth());
target.setQuietZoneHorizontal(template.getQuietZoneHorizontal());
target.setQuietZoneVertical(template.getQuietZoneVertical());
target.setReaderInit(template.getReaderInit());
target.setDataColumns(template.getDataColumns());
target.setRows(template.getRows());
target.setPreferredEccLevel(template.getPreferredEccLevel());
target.setStructuredAppendFileId(template.getStructuredAppendFileId());
target.setStructuredAppendFileName(template.getStructuredAppendFileName());
target.setBarHeight(template.getBarHeight());
target.setMode(template.getMode());
target.setStructuredAppendIncludeSegmentCount(template.getStructuredAppendIncludeSegmentCount());
target.setForceByteCompaction(template.getForceByteCompaction());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright 2024 Urs Wolfer
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package uk.org.okapibarcode.backend;

import com.google.zxing.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.FormatException;
import com.google.zxing.LuminanceSource;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.pdf417.PDF417Reader;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import uk.org.okapibarcode.output.Java2DRenderer;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;

import static java.awt.image.BufferedImage.TYPE_BYTE_BINARY;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static uk.org.okapibarcode.graphics.Color.BLACK;
import static uk.org.okapibarcode.graphics.Color.WHITE;

/**
* @author Urs Wolfer
*/
class Pdf417AutoStructuredAppendTest {

@ParameterizedTest
@CsvSource({
"0,0,0",
"1,1,1",
"2,1,1",
"3,1,1",
"270,2,2",
"271,2,2",
"282,2,3",
"283,2,3",
"288,2,3",
"289,2,3",
"290,2,3",
"291,3,3",
"292,3,3",
"293,3,3",
"294,3,3"
})
public void testCreateStructuredAppendSymbols(int dataLength, int expectedSymbolsCount, int expectedSymbolsCountByteCompaction) throws Exception {
byte[] bytes = new byte[dataLength];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) i;
}

Pdf417 symbolTemplate = new Pdf417();
symbolTemplate.setPreferredEccLevel(4);
symbolTemplate.setBarHeight(1);
symbolTemplate.setRows(12);
symbolTemplate.setDataColumns(13);
symbolTemplate.setStructuredAppendIncludeSegmentCount(true);

List<Pdf417> symbols = Pdf417.createStructuredAppendSymbols(new String(bytes, ISO_8859_1), symbolTemplate);
assertions(symbols, bytes, expectedSymbolsCount);

symbolTemplate.setForceByteCompaction(true);
List<Pdf417> symbolsByteEncoded = Pdf417.createStructuredAppendSymbols(bytes, symbolTemplate);
assertions(symbolsByteEncoded, bytes, expectedSymbolsCountByteCompaction);
}

private static void assertions(List<Pdf417> symbols, byte[] bytes, int expectedSymbolsCount) throws IOException, NotFoundException, FormatException, ChecksumException {
assertEquals(expectedSymbolsCount, symbols.size());

try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
for (Pdf417 barcode : symbols) {
assertEquals(4, barcode.getPreferredEccLevel());
assertEquals(1, barcode.getBarHeight());
assertEquals(12, barcode.getRows());
assertEquals(13, barcode.getDataColumns());

BufferedImage img = new BufferedImage(barcode.getWidth(), barcode.getHeight(), TYPE_BYTE_BINARY);
Graphics2D g2d = img.createGraphics();
Java2DRenderer renderer = new Java2DRenderer(g2d, 1, WHITE, BLACK);
renderer.render(barcode);
g2d.dispose();

LuminanceSource source = new BufferedImageLuminanceSource(img);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
PDF417Reader reader = new PDF417Reader();
Result result = reader.decode(bitmap);

byte[] output = result.getText().getBytes(ISO_8859_1);
outputStream.write(output);

}
assertArrayEquals(bytes, outputStream.toByteArray());
}
}
}

0 comments on commit 5e139e3

Please sign in to comment.