Skip to content

Commit

Permalink
add CIDSet for font subset to ensure PDF/A compliance
Browse files Browse the repository at this point in the history
(cherry picked from commit 4873173b0edcf83d871e6fb86b96b81cec28653e)
  • Loading branch information
bsanchezb authored and asturio committed Jan 16, 2023
1 parent f27caab commit 3002cba
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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});
Expand Down
103 changes: 103 additions & 0 deletions openpdf/src/test/java/com/lowagie/text/pdf/FontSubsetTest.java
Original file line number Diff line number Diff line change
@@ -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);
}

}

This file was deleted.

0 comments on commit 3002cba

Please sign in to comment.