From c42b3ccf4774d3906c6f3bca78d21b0b46f27880 Mon Sep 17 00:00:00 2001 From: Aleksandr Beliakov Date: Thu, 15 Sep 2022 09:14:50 +0200 Subject: [PATCH] add CIDSet for font subset to ensure PDF/A compliance (cherry picked from commit 4873173b0edcf83d871e6fb86b96b81cec28653e) --- .../lowagie/text/pdf/TrueTypeFontUnicode.java | 10 +- .../com/lowagie/text/pdf/FontSubsetTest.java | 103 ++++++++++++++++++ .../text/pdf/SubsetPrefixCreationTest.java | 50 --------- .../LiberationSerif-Regular.ttf | Bin 4 files changed, 108 insertions(+), 55 deletions(-) create mode 100644 openpdf/src/test/java/com/lowagie/text/pdf/FontSubsetTest.java delete mode 100644 openpdf/src/test/java/com/lowagie/text/pdf/SubsetPrefixCreationTest.java rename openpdf/src/test/resources/{ => fonts/liberation-serif}/LiberationSerif-Regular.ttf (100%) diff --git a/openpdf/src/main/java/com/lowagie/text/pdf/TrueTypeFontUnicode.java b/openpdf/src/main/java/com/lowagie/text/pdf/TrueTypeFontUnicode.java index cd86a3b79..9638db959 100755 --- a/openpdf/src/main/java/com/lowagie/text/pdf/TrueTypeFontUnicode.java +++ b/openpdf/src/main/java/com/lowagie/text/pdf/TrueTypeFontUnicode.java @@ -49,6 +49,10 @@ package com.lowagie.text.pdf; +import com.lowagie.text.DocumentException; +import com.lowagie.text.Utilities; +import com.lowagie.text.error_messages.MessageLocalization; + import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -57,10 +61,6 @@ import java.util.List; import java.util.Map; -import com.lowagie.text.DocumentException; -import com.lowagie.text.Utilities; -import com.lowagie.text.error_messages.MessageLocalization; - /** Represents a True Type font with Unicode encoding. All the character * in the font can be used directly by using the encoding Identity-H or * Identity-V. This is the only way to represent some character sets such @@ -396,7 +396,7 @@ void writeFont(PdfWriter writer, PdfIndirectReference ref, Object[] params) thro PdfObject pobj = null; PdfIndirectObject obj = null; PdfIndirectReference cidset = null; - if (writer.getPDFXConformance() == PdfWriter.PDFA1A || writer.getPDFXConformance() == PdfWriter.PDFA1B) { + if (subset || writer.getPDFXConformance() == PdfWriter.PDFA1A || writer.getPDFXConformance() == PdfWriter.PDFA1B) { PdfStream stream; if (metrics.length == 0) { stream = new PdfStream(new byte[]{(byte)0x80}); diff --git a/openpdf/src/test/java/com/lowagie/text/pdf/FontSubsetTest.java b/openpdf/src/test/java/com/lowagie/text/pdf/FontSubsetTest.java new file mode 100644 index 000000000..320c39065 --- /dev/null +++ b/openpdf/src/test/java/com/lowagie/text/pdf/FontSubsetTest.java @@ -0,0 +1,103 @@ +package com.lowagie.text.pdf; + +import com.lowagie.text.Document; +import com.lowagie.text.Font; +import com.lowagie.text.Paragraph; +import org.apache.commons.io.IOUtils; +import org.bouncycastle.crypto.prng.FixedSecureRandom; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.SecureRandom; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FontSubsetTest { + + /* + * See : https://github.com/LibrePDF/OpenPDF/issues/623 + */ + @Test + public void createSubsetPrefixTest() throws Exception { + BaseFont font = BaseFont.createFont("LiberationSerif-Regular.ttf", BaseFont.IDENTITY_H, + BaseFont.EMBEDDED,true, getFontByte("fonts/liberation-serif/LiberationSerif-Regular.ttf"), null); + assertNotEquals(font.createSubsetPrefix(), font.createSubsetPrefix()); + + byte[] baseSeed = new SecureRandom().generateSeed(512); + // init deterministic SecureRandom with a custom base seed + SecureRandom secureRandom = new FixedSecureRandom(baseSeed); + font.setSecureRandom(secureRandom); + assertNotEquals(font.createSubsetPrefix(), font.createSubsetPrefix()); // still different, as FixedSecureRandom generates a new random on each step + + SecureRandom secureRandomOne = new FixedSecureRandom(baseSeed); + font.setSecureRandom(secureRandomOne); + + String subsetPrefixOne = font.createSubsetPrefix(); + + // re-init FixedSecureRandom for deterministic generation + SecureRandom secureRandomTwo = new FixedSecureRandom(baseSeed); + font.setSecureRandom(secureRandomTwo); + + String subsetPrefixTwo = font.createSubsetPrefix(); + assertEquals(subsetPrefixOne, subsetPrefixTwo); // the desired deterministic behavior + } + + private byte[] getFontByte(String fileName) throws IOException { + try (InputStream stream = BaseFont.getResourceStream(fileName, null)) { + return IOUtils.toByteArray(stream); + } + } + + /* + * This test is to ensure creation of CIDSet dictionary when using a font subset (required for PDF/A compliance) + */ + @Test + public void subsetTest() throws Exception { + checkSubsetPresence(true); + checkSubsetPresence(false); + } + + private void checkSubsetPresence(boolean subsetIncluded) throws Exception { + byte[] documentBytes; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + Document document = new Document(); + PdfWriter.getInstance(document, baos); + document.open(); + + BaseFont font = BaseFont.createFont("LiberationSerif-Regular.ttf", BaseFont.IDENTITY_H, + BaseFont.EMBEDDED,true, getFontByte("fonts/liberation-serif/LiberationSerif-Regular.ttf"), null); + font.setSubset(subsetIncluded); + String text = "This is the test string."; + document.add(new Paragraph(text, new Font(font, 12))); + document.close(); + + documentBytes = baos.toByteArray(); + } + + boolean fontFound = false; + try (PdfReader reader = new PdfReader(documentBytes)) { + for (int k = 1; k < reader.getXrefSize(); ++k) { + PdfObject obj = reader.getPdfObjectRelease(k); + if (obj == null || !obj.isDictionary()) + continue; + PdfDictionary dic = (PdfDictionary) obj; + PdfObject type = PdfReader.getPdfObjectRelease(dic.get(PdfName.TYPE)); + if (type == null || !type.isName()) + continue; + PdfDictionary fd = dic.getAsDict(PdfName.FONTDESCRIPTOR); + if (PdfName.FONT.equals(type) && fd != null) { + PdfIndirectReference cidset = fd.getAsIndirectObject(PdfName.CIDSET); + assertEquals(subsetIncluded, cidset != null); + fontFound = true; + break; + } + } + } + assertTrue(fontFound); + } + +} diff --git a/openpdf/src/test/java/com/lowagie/text/pdf/SubsetPrefixCreationTest.java b/openpdf/src/test/java/com/lowagie/text/pdf/SubsetPrefixCreationTest.java deleted file mode 100644 index fd7da0110..000000000 --- a/openpdf/src/test/java/com/lowagie/text/pdf/SubsetPrefixCreationTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.lowagie.text.pdf; - -import org.apache.commons.io.IOUtils; -import org.bouncycastle.crypto.prng.FixedSecureRandom; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.io.InputStream; -import java.security.SecureRandom; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -/** - * See : https://github.com/LibrePDF/OpenPDF/issues/623 - */ -public class SubsetPrefixCreationTest { - - @Test - public void createSubsetPrefixTest() throws Exception { - BaseFont font = BaseFont.createFont("LiberationSerif-Regular.ttf", BaseFont.IDENTITY_H, - BaseFont.EMBEDDED,true, getFontByte("LiberationSerif-Regular.ttf"), null); - assertNotEquals(font.createSubsetPrefix(), font.createSubsetPrefix()); - - byte[] baseSeed = new SecureRandom().generateSeed(512); - // init deterministic SecureRandom with a custom base seed - SecureRandom secureRandom = new FixedSecureRandom(baseSeed); - font.setSecureRandom(secureRandom); - assertNotEquals(font.createSubsetPrefix(), font.createSubsetPrefix()); // still different, as FixedSecureRandom generates a new random on each step - - SecureRandom secureRandomOne = new FixedSecureRandom(baseSeed); - font.setSecureRandom(secureRandomOne); - - String subsetPrefixOne = font.createSubsetPrefix(); - - // re-init FixedSecureRandom for deterministic generation - SecureRandom secureRandomTwo = new FixedSecureRandom(baseSeed); - font.setSecureRandom(secureRandomTwo); - - String subsetPrefixTwo = font.createSubsetPrefix(); - assertEquals(subsetPrefixOne, subsetPrefixTwo); // the desired deterministic behavior - } - - private byte[] getFontByte(String fileName) throws IOException { - try (InputStream stream = BaseFont.getResourceStream(fileName, null)) { - return IOUtils.toByteArray(stream); - } - } - -} diff --git a/openpdf/src/test/resources/LiberationSerif-Regular.ttf b/openpdf/src/test/resources/fonts/liberation-serif/LiberationSerif-Regular.ttf similarity index 100% rename from openpdf/src/test/resources/LiberationSerif-Regular.ttf rename to openpdf/src/test/resources/fonts/liberation-serif/LiberationSerif-Regular.ttf