-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Resolves #92
- Loading branch information
Showing
3 changed files
with
280 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
179 changes: 179 additions & 0 deletions
179
src/main/java/uk/org/okapibarcode/backend/Pdf417AutoStructuredAppend.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
/* | ||
* Copyright 2024 Daniel Gredler | ||
* | ||
* 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 java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import static java.nio.charset.StandardCharsets.ISO_8859_1; | ||
import static uk.org.okapibarcode.backend.Pdf417.MAX_STRUCTURED_APPEND_TOTAL; | ||
|
||
/** | ||
* @author Urs Wolfer | ||
*/ | ||
public class Pdf417AutoStructuredAppend { | ||
|
||
/** | ||
* 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) { | ||
return createStructuredAppendSymbols(data, template, false); | ||
} | ||
|
||
/** | ||
* Overloaded method of {@link #createStructuredAppendSymbols(String, Pdf417)} which | ||
* accepts binary data and passes it to {@link Pdf417#setContent(byte[])}. | ||
* | ||
* <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, true); | ||
} | ||
|
||
private static List<Pdf417> createStructuredAppendSymbols(String data, Pdf417 template, boolean forceByteCompaction) { | ||
List<String> contentList = calculateStructuredAppendContent(data, template, forceByteCompaction); | ||
return plotStructuredAppendSymbols(contentList, template, forceByteCompaction); | ||
} | ||
|
||
private static List<String> calculateStructuredAppendContent(String content, Pdf417 template, boolean forceByteCompaction) { | ||
List<String> contentList = new ArrayList<>(); | ||
String remainingContent = content; | ||
while (!remainingContent.isEmpty()) { | ||
Pdf417 symbol = new Pdf417() { | ||
@Override | ||
protected void plotSymbol() { | ||
// expensive plotting is not required in this case | ||
} | ||
}; | ||
clone(template, symbol); | ||
|
||
symbol.setStructuredAppendPosition(contentList.size() + 1); | ||
symbol.setStructuredAppendTotal(MAX_STRUCTURED_APPEND_TOTAL); | ||
|
||
String currentContent = remainingContent; | ||
remainingContent = setContentAndGetRemainingWithBinarySearch(currentContent, symbol, forceByteCompaction); | ||
contentList.add(currentContent.replace(remainingContent, "")); | ||
} | ||
return contentList; | ||
} | ||
|
||
private static List<Pdf417> plotStructuredAppendSymbols(List<String> contentList, Pdf417 template, boolean forceByteCompaction) { | ||
int structuredAppendTotal = contentList.size(); | ||
List<Pdf417> symbols = new ArrayList<>(structuredAppendTotal); | ||
for (int i = 0; i < structuredAppendTotal; i++) { | ||
String content = contentList.get(i); | ||
Pdf417 symbol = new Pdf417(); | ||
symbols.add(symbol); | ||
clone(template, symbol); | ||
|
||
symbol.setStructuredAppendPosition(i + 1); | ||
symbol.setStructuredAppendTotal(structuredAppendTotal); | ||
|
||
setContent(symbol, content, forceByteCompaction); | ||
} | ||
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()); | ||
} | ||
|
||
private static String setContentAndGetRemainingWithBinarySearch(String remainingContent, Pdf417 symbol, boolean forceByteCompaction) { | ||
int low = 0; | ||
int high = remainingContent.length(); | ||
|
||
while (low <= high) { | ||
int mid = low + high >>> 1; | ||
SetContentStatus result = trySetContent(remainingContent, symbol, mid, forceByteCompaction); | ||
if (result == SetContentStatus.TRY_MORE) { | ||
low = mid + 1; | ||
} else { | ||
if (result == SetContentStatus.MAX_REACHED) { | ||
return remainingContent.substring(mid); | ||
} | ||
high = mid - 1; | ||
} | ||
} | ||
|
||
throw new RuntimeException("Failed to set content, this is a bug."); | ||
} | ||
|
||
private static SetContentStatus trySetContent(String remainingContent, Pdf417 symbol, int lengthToTry, boolean forceByteCompaction) { | ||
int remainingContentLength = remainingContent.length(); | ||
int currentLength = Math.min(lengthToTry, remainingContentLength); | ||
String currentContent = remainingContent.substring(0, currentLength); | ||
try { | ||
setContent(symbol, currentContent, forceByteCompaction); | ||
if (currentLength == remainingContentLength) { | ||
return SetContentStatus.MAX_REACHED; | ||
} | ||
currentContent = remainingContent.substring(0, currentLength + 1); | ||
try { | ||
setContent(symbol, currentContent, forceByteCompaction); | ||
return SetContentStatus.TRY_MORE; | ||
} catch (OkapiInputException e) { | ||
return SetContentStatus.MAX_REACHED; | ||
} | ||
} catch (OkapiInputException e) { | ||
return SetContentStatus.TOO_BIG; | ||
} | ||
} | ||
|
||
private static void setContent(Pdf417 symbol, String currentContent, boolean forceByteCompaction) { | ||
if (forceByteCompaction) { | ||
symbol.setContent(currentContent.getBytes(ISO_8859_1)); | ||
} else { | ||
symbol.setContent(currentContent); | ||
} | ||
} | ||
|
||
private enum SetContentStatus { | ||
TOO_BIG, | ||
TRY_MORE, | ||
MAX_REACHED | ||
} | ||
} |
98 changes: 98 additions & 0 deletions
98
src/test/java/uk/org/okapibarcode/backend/Pdf417AutoStructuredAppendTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* Copyright 2024 Daniel Gredler | ||
* | ||
* 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.api.Test; | ||
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 { | ||
|
||
@Test | ||
public void testCreateStructuredAppendSymbols() throws Exception { | ||
byte[] bytes = new byte[256]; | ||
for (int i = 0; i < bytes.length; i++) { | ||
bytes[i] = (byte) i; // all possible byte values | ||
} | ||
|
||
Pdf417 symbolTemplate = new Pdf417(); | ||
symbolTemplate.setPreferredEccLevel(4); | ||
symbolTemplate.setBarHeight(1); | ||
symbolTemplate.setRows(12); | ||
symbolTemplate.setDataColumns(13); | ||
symbolTemplate.setStructuredAppendIncludeSegmentCount(true); | ||
|
||
List<Pdf417> symbols = Pdf417AutoStructuredAppend.createStructuredAppendSymbols(new String(bytes, ISO_8859_1), symbolTemplate); | ||
assertions(symbols, bytes); | ||
|
||
List<Pdf417> symbolsByteEncoded = Pdf417AutoStructuredAppend.createStructuredAppendSymbols(bytes, symbolTemplate); | ||
assertions(symbolsByteEncoded, bytes); | ||
} | ||
|
||
private static void assertions(List<Pdf417> symbols, byte[] bytes) throws IOException, NotFoundException, FormatException, ChecksumException { | ||
assertEquals(2, 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()); | ||
} | ||
} | ||
} |