Skip to content

Commit

Permalink
Check font glyphs for existence
Browse files Browse the repository at this point in the history
DEVSIX-2975
  • Loading branch information
vitali-pr committed Oct 16, 2023
1 parent 3f3c7f3 commit cf1eaa7
Show file tree
Hide file tree
Showing 18 changed files with 263 additions and 81 deletions.
6 changes: 4 additions & 2 deletions io/src/main/java/com/itextpdf/io/font/PdfEncodings.java
Original file line number Diff line number Diff line change
Expand Up @@ -211,16 +211,18 @@ public static byte[] convertToBytes(String text, String encoding) {
}

/**
* Converts a {@code String} to a {@code byte} array according
* Converts a {@code char} to a {@code byte} array according
* to the font's encoding.
*
* @param encoding the encoding
* @param ch the {@code char} to be converted
* @return an array of {@code byte} representing the conversion according to the font's encoding
*/
public static byte[] convertToBytes(char ch, String encoding) {
if (encoding == null || encoding.length() == 0)
if (encoding == null || encoding.length() == 0 || "symboltt".equals(encoding)) {
return new byte[]{(byte) ch};
}

IntHashtable hash = null;
if (encoding.equals(WINANSI))
hash = winansi;
Expand Down
41 changes: 41 additions & 0 deletions io/src/test/java/com/itextpdf/io/font/PdfEncodingsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2023 Apryse Group NV
Authors: Apryse Software.
This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itextpdf.io.font;

import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.annotations.type.UnitTest;

import org.junit.Assert;
import org.junit.Test;
import org.junit.experimental.categories.Category;

@Category(UnitTest.class)
public class PdfEncodingsTest extends ExtendedITextTest {

@Test
public void convertToBytesNoEncodingTest() {
Assert.assertArrayEquals(new byte[]{(byte) 194}, PdfEncodings.convertToBytes('Â', null));
Assert.assertArrayEquals(new byte[]{(byte) 194}, PdfEncodings.convertToBytes('Â', ""));
Assert.assertArrayEquals(new byte[]{(byte) 194}, PdfEncodings.convertToBytes('Â', "symboltt"));
}
}
3 changes: 2 additions & 1 deletion kernel/src/main/java/com/itextpdf/kernel/pdf/IsoKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ public enum IsoKey {
XREF_TABLE,
SIGNATURE,
SIGNATURE_TYPE,
CRYPTO
CRYPTO,
FONT
}
24 changes: 19 additions & 5 deletions kernel/src/main/java/com/itextpdf/kernel/pdf/PdfDocument.java
Original file line number Diff line number Diff line change
Expand Up @@ -1554,7 +1554,7 @@ public void addOutputIntent(PdfOutputIntent outputIntent) {

/**
* Checks whether PDF document conforms a specific standard.
* Shall be override.
* Shall be overridden.
*
* @param obj An object to conform.
* @param key type of object to conform.
Expand All @@ -1564,7 +1564,7 @@ public void checkIsoConformance(Object obj, IsoKey key) {

/**
* Checks whether PDF document conforms a specific standard.
* Shall be override.
* Shall be overridden.
*
* @param obj an object to conform.
* @param key type of object to conform.
Expand All @@ -1576,7 +1576,21 @@ public void checkIsoConformance(Object obj, IsoKey key, PdfResources resources,

/**
* Checks whether PDF document conforms a specific standard.
* Shall be override.
* Shall be overridden.
*
* @param obj an object to conform.
* @param key type of object to conform.
* @param resources {@link PdfResources} associated with an object to check.
* @param contentStream current content stream.
* @param extra extra data required for the check.
*/
public void checkIsoConformance(Object obj, IsoKey key, PdfResources resources, PdfStream contentStream,
Object extra) {
}

/**
* Checks whether PDF document conforms a specific standard.
* Shall be overridden.
*
* @param gState a {@link CanvasGraphicsState} object to conform.
* @param resources {@link PdfResources} associated with an object to check.
Expand Down Expand Up @@ -1950,7 +1964,7 @@ protected void storeDestinationToReaddress(PdfDestination destination,

/**
* Checks whether PDF document conforms a specific standard.
* Shall be override.
* Shall be overridden.
*/
protected void checkIsoConformance() {
}
Expand Down Expand Up @@ -2202,7 +2216,7 @@ protected void flushInfoDictionary(boolean appendMode) {

/**
* Updates XMP metadata.
* Shall be override.
* Shall be overridden.
*/
protected void updateXmpMetadata() {
try {
Expand Down
22 changes: 20 additions & 2 deletions kernel/src/main/java/com/itextpdf/kernel/pdf/canvas/PdfCanvas.java
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,9 @@ public PdfCanvas showText(GlyphLine text, Iterator<GlyphLine.GlyphLinePart> iter
throw new PdfException(
KernelExceptionMessageConstant.FONT_AND_SIZE_MUST_BE_SET_BEFORE_WRITING_ANY_TEXT, currentGs);
}

document.checkIsoConformance(text.toString(), IsoKey.FONT, null, null, currentGs.getFont());

final float fontSize = FontProgram.convertTextSpaceToGlyphSpace(currentGs.getFontSize());
float charSpacing = currentGs.getCharSpacing();
float scaling = currentGs.getHorizontalScaling() / 100f;
Expand Down Expand Up @@ -894,9 +897,20 @@ public PdfCanvas showText(PdfArray textArray) {
checkDefaultDeviceGrayBlackColor(getColorKeyForText());
document.checkIsoConformance(currentGs, IsoKey.FONT_GLYPHS, null, contentStream);

if (currentGs.getFont() == null)
if (currentGs.getFont() == null) {
throw new PdfException(
KernelExceptionMessageConstant.FONT_AND_SIZE_MUST_BE_SET_BEFORE_WRITING_ANY_TEXT, currentGs);
}

// Take text part to process
StringBuilder text = new StringBuilder();
for (PdfObject obj : textArray) {
if (obj instanceof PdfString) {
text.append(obj);
}
}
document.checkIsoConformance(text.toString(), IsoKey.FONT, null, null, currentGs.getFont());

contentStream.getOutputStream().writeBytes(ByteUtils.getIsoBytes("["));
for (PdfObject obj : textArray) {
if (obj.isString()) {
Expand Down Expand Up @@ -2377,9 +2391,13 @@ private PdfStream ensureStreamDataIsReadyToBeProcessed(PdfStream stream) {
*/
private void showTextInt(String text) {
document.checkIsoConformance(currentGs, IsoKey.FONT_GLYPHS, null, contentStream);
if (currentGs.getFont() == null)
if (currentGs.getFont() == null) {
throw new PdfException(
KernelExceptionMessageConstant.FONT_AND_SIZE_MUST_BE_SET_BEFORE_WRITING_ANY_TEXT, currentGs);
}

document.checkIsoConformance(text, IsoKey.FONT, null, null, currentGs.getFont());

currentGs.getFont().writeText(text, contentStream.getOutputStream());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,5 +396,4 @@ public void differentCodeSpaceRangeLengthsExtractionTest() throws IOException {
String extractedText = PdfTextExtractor.getTextFromPage(pdfDocument.getPage(1));
Assert.assertEquals("Hello\u7121\u540dworld\u6b98\u528d", extractedText);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,7 @@ This file is part of the iText (R) project.
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfArray;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfNumber;
import com.itextpdf.kernel.pdf.PdfObject;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.WriterProperties;
import com.itextpdf.kernel.pdf.*;
import com.itextpdf.kernel.pdf.canvas.wmf.WmfImageData;
import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
import com.itextpdf.kernel.utils.CompareTool;
Expand Down Expand Up @@ -1649,6 +1640,36 @@ public void setPositiveHorizontalScalingValueTest() throws IOException, Interrup
Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER));
}

@Test
public void createSimpleCanvasWithPdfArrayText() throws IOException, InterruptedException {
final String outPdf = DESTINATION_FOLDER + "createSimpleCanvasWithPdfArrayText.pdf";
String cmpPdf = SOURCE_FOLDER + "cmp_createSimpleCanvasWithPdfArrayText.pdf";

PdfDocument pdfDoc = new PdfDocument(new PdfWriter(outPdf));
PdfPage page1 = pdfDoc.addNewPage();
PdfCanvas canvas = new PdfCanvas(page1);

PdfArray pdfArray = new PdfArray();
pdfArray.add(new PdfString("ABC"));
pdfArray.add(new PdfNumber(-250));
pdfArray.add(new PdfString("DFG"));

//Initialize canvas and write text to it
canvas
.saveState()
.beginText()
.moveText(36, 750)
.setFontAndSize(PdfFontFactory.createFont(StandardFonts.HELVETICA), 16)
.showText(pdfArray)
.endText()
.restoreState();

canvas.release();
pdfDoc.close();

Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER, "diff_"));
}

private void createStandardDocument(PdfWriter writer, int pageCount, ContentProvider contentProvider) throws IOException {
PdfDocument pdfDoc = new PdfDocument(writer);
pdfDoc.getDocumentInfo().setAuthor(AUTHOR).
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ private static Paragraph createParagraph(String textParagraph, String font) {

@Test
public void utfToGlyphToUtfRountripTest() throws IOException {
// See DEVSIX-4945
// this should not throw a null pointer exception
try(PdfDocument pdfDoc = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()));
Document doc = new Document(pdfDoc)) {
Expand Down
18 changes: 18 additions & 0 deletions pdfa/src/main/java/com/itextpdf/pdfa/PdfADocument.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,28 @@ public PdfADocument(PdfReader reader, PdfWriter writer, StampingProperties prope
setChecker(conformanceLevel);
}

/**
* {@inheritDoc}
*/
@Override
public void checkIsoConformance(Object obj, IsoKey key) {
checkIsoConformance(obj, key, null, null);
}

/**
* {@inheritDoc}
*/
@Override
public void checkIsoConformance(Object obj, IsoKey key, PdfResources resources, PdfStream contentStream) {
checkIsoConformance(obj, key, resources, contentStream, null);
}

/**
* {@inheritDoc}
*/
@Override
public void checkIsoConformance(Object obj, IsoKey key, PdfResources resources, PdfStream contentStream,
Object extra) {
if (!isPdfADocument) {
super.checkIsoConformance(obj, key, resources, contentStream);
return;
Expand Down Expand Up @@ -217,6 +232,9 @@ public void checkIsoConformance(Object obj, IsoKey key, PdfResources resources,
case CRYPTO:
checker.checkCrypto((PdfObject) obj);
break;
case FONT:
checker.checkText((String) obj, (PdfFont) extra);
break;
}
}

Expand Down
17 changes: 17 additions & 0 deletions pdfa/src/main/java/com/itextpdf/pdfa/checker/PdfA1Checker.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.io.font.otf.Glyph;
import com.itextpdf.io.source.PdfTokenizer;
import com.itextpdf.io.source.RandomAccessFileOrArray;
import com.itextpdf.io.source.RandomAccessSourceFactory;
Expand Down Expand Up @@ -344,6 +345,22 @@ public void checkSignatureType(boolean isCAdES) {
//nothing to do
}

/**
* {@inheritDoc}
*
* @param text {@inheritDoc}
* @param font {@inheritDoc}
*/
@Override
public void checkText(String text, PdfFont font) {
for (int i = 0; i < text.length(); ++i) {
if (!font.containsGlyph(text.charAt(i))) {
throw new PdfAConformanceException(
PdfaExceptionMessageConstant.EMBEDDED_FONTS_SHALL_DEFINE_ALL_REFERENCED_GLYPHS);
}
}
}

@Override
protected void checkPageTransparency(PdfDictionary pageDict, PdfDictionary pageResources) {
// This check is irrelevant for the PdfA1 checker, so the body of the method is empty
Expand Down
14 changes: 13 additions & 1 deletion pdfa/src/main/java/com/itextpdf/pdfa/checker/PdfAChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ public void checkSignature(PdfDictionary signatureDict) {
*
* @param isCAdES true is CAdES sig type is used, false otherwise.
*
* @deprecated Will become an abstract in the next major release.
* @deprecated Will become abstract in the next major release.
*/
@Deprecated
public void checkSignatureType(boolean isCAdES) {
Expand Down Expand Up @@ -429,6 +429,18 @@ public void setPdfAOutputIntentColorSpace(PdfDictionary catalog) {
public void checkCrypto(PdfObject crypto) {
}

/**
* Verify the conformity of the text written by the specified font.
*
* @param text Text to verify.
* @param font Font to verify the text against.
*
* @deprecated Will become abstract in the next major release.
*/
@Deprecated
public void checkText(String text, PdfFont font) {
}

/**
* Attest content stream conformance with appropriate specification.
* Throws PdfAConformanceException if any discrepancy was found
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ public final class PdfaExceptionMessageConstant {
"DeviceGray shall only be used if a device independent DefaultGray colour space has been set when the "
+ "DeviceGray colour space is used, or if a PDF/A OutputIntent is in effect.";

public static final String EMBEDDED_FONTS_SHALL_DEFINE_ALL_REFERENCED_GLYPHS = "Embedded fonts shall define all " +
"glyphs referenced for rendering within the conforming file.";

public static final String ICCBASED_COLOUR_SPACE_SHALL_NOT_BE_USED_IF_IT_IS_CMYK_AND_IS_IDENTICAL_TO_CURRENT_PROFILE =
"An ICCBased colour space shall not be used where the profile is a CMYK destination profile and is "
+ "identical to that in the current PDF/A OutputIntent or the current transparency blending colorspace.";
Expand Down
Loading

0 comments on commit cf1eaa7

Please sign in to comment.