Skip to content

Commit cf1eaa7

Browse files
committed
Check font glyphs for existence
DEVSIX-2975
1 parent 3f3c7f3 commit cf1eaa7

File tree

18 files changed

+263
-81
lines changed

18 files changed

+263
-81
lines changed

io/src/main/java/com/itextpdf/io/font/PdfEncodings.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -211,16 +211,18 @@ public static byte[] convertToBytes(String text, String encoding) {
211211
}
212212

213213
/**
214-
* Converts a {@code String} to a {@code byte} array according
214+
* Converts a {@code char} to a {@code byte} array according
215215
* to the font's encoding.
216216
*
217217
* @param encoding the encoding
218218
* @param ch the {@code char} to be converted
219219
* @return an array of {@code byte} representing the conversion according to the font's encoding
220220
*/
221221
public static byte[] convertToBytes(char ch, String encoding) {
222-
if (encoding == null || encoding.length() == 0)
222+
if (encoding == null || encoding.length() == 0 || "symboltt".equals(encoding)) {
223223
return new byte[]{(byte) ch};
224+
}
225+
224226
IntHashtable hash = null;
225227
if (encoding.equals(WINANSI))
226228
hash = winansi;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2023 Apryse Group NV
4+
Authors: Apryse Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.io.font;
24+
25+
import com.itextpdf.test.ExtendedITextTest;
26+
import com.itextpdf.test.annotations.type.UnitTest;
27+
28+
import org.junit.Assert;
29+
import org.junit.Test;
30+
import org.junit.experimental.categories.Category;
31+
32+
@Category(UnitTest.class)
33+
public class PdfEncodingsTest extends ExtendedITextTest {
34+
35+
@Test
36+
public void convertToBytesNoEncodingTest() {
37+
Assert.assertArrayEquals(new byte[]{(byte) 194}, PdfEncodings.convertToBytes('Â', null));
38+
Assert.assertArrayEquals(new byte[]{(byte) 194}, PdfEncodings.convertToBytes('Â', ""));
39+
Assert.assertArrayEquals(new byte[]{(byte) 194}, PdfEncodings.convertToBytes('Â', "symboltt"));
40+
}
41+
}

kernel/src/main/java/com/itextpdf/kernel/pdf/IsoKey.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@ public enum IsoKey {
3939
XREF_TABLE,
4040
SIGNATURE,
4141
SIGNATURE_TYPE,
42-
CRYPTO
42+
CRYPTO,
43+
FONT
4344
}

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfDocument.java

+19-5
Original file line numberDiff line numberDiff line change
@@ -1554,7 +1554,7 @@ public void addOutputIntent(PdfOutputIntent outputIntent) {
15541554

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

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

15771577
/**
15781578
* Checks whether PDF document conforms a specific standard.
1579-
* Shall be override.
1579+
* Shall be overridden.
1580+
*
1581+
* @param obj an object to conform.
1582+
* @param key type of object to conform.
1583+
* @param resources {@link PdfResources} associated with an object to check.
1584+
* @param contentStream current content stream.
1585+
* @param extra extra data required for the check.
1586+
*/
1587+
public void checkIsoConformance(Object obj, IsoKey key, PdfResources resources, PdfStream contentStream,
1588+
Object extra) {
1589+
}
1590+
1591+
/**
1592+
* Checks whether PDF document conforms a specific standard.
1593+
* Shall be overridden.
15801594
*
15811595
* @param gState a {@link CanvasGraphicsState} object to conform.
15821596
* @param resources {@link PdfResources} associated with an object to check.
@@ -1950,7 +1964,7 @@ protected void storeDestinationToReaddress(PdfDestination destination,
19501964

19511965
/**
19521966
* Checks whether PDF document conforms a specific standard.
1953-
* Shall be override.
1967+
* Shall be overridden.
19541968
*/
19551969
protected void checkIsoConformance() {
19561970
}
@@ -2202,7 +2216,7 @@ protected void flushInfoDictionary(boolean appendMode) {
22022216

22032217
/**
22042218
* Updates XMP metadata.
2205-
* Shall be override.
2219+
* Shall be overridden.
22062220
*/
22072221
protected void updateXmpMetadata() {
22082222
try {

kernel/src/main/java/com/itextpdf/kernel/pdf/canvas/PdfCanvas.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,9 @@ public PdfCanvas showText(GlyphLine text, Iterator<GlyphLine.GlyphLinePart> iter
725725
throw new PdfException(
726726
KernelExceptionMessageConstant.FONT_AND_SIZE_MUST_BE_SET_BEFORE_WRITING_ANY_TEXT, currentGs);
727727
}
728+
729+
document.checkIsoConformance(text.toString(), IsoKey.FONT, null, null, currentGs.getFont());
730+
728731
final float fontSize = FontProgram.convertTextSpaceToGlyphSpace(currentGs.getFontSize());
729732
float charSpacing = currentGs.getCharSpacing();
730733
float scaling = currentGs.getHorizontalScaling() / 100f;
@@ -894,9 +897,20 @@ public PdfCanvas showText(PdfArray textArray) {
894897
checkDefaultDeviceGrayBlackColor(getColorKeyForText());
895898
document.checkIsoConformance(currentGs, IsoKey.FONT_GLYPHS, null, contentStream);
896899

897-
if (currentGs.getFont() == null)
900+
if (currentGs.getFont() == null) {
898901
throw new PdfException(
899902
KernelExceptionMessageConstant.FONT_AND_SIZE_MUST_BE_SET_BEFORE_WRITING_ANY_TEXT, currentGs);
903+
}
904+
905+
// Take text part to process
906+
StringBuilder text = new StringBuilder();
907+
for (PdfObject obj : textArray) {
908+
if (obj instanceof PdfString) {
909+
text.append(obj);
910+
}
911+
}
912+
document.checkIsoConformance(text.toString(), IsoKey.FONT, null, null, currentGs.getFont());
913+
900914
contentStream.getOutputStream().writeBytes(ByteUtils.getIsoBytes("["));
901915
for (PdfObject obj : textArray) {
902916
if (obj.isString()) {
@@ -2377,9 +2391,13 @@ private PdfStream ensureStreamDataIsReadyToBeProcessed(PdfStream stream) {
23772391
*/
23782392
private void showTextInt(String text) {
23792393
document.checkIsoConformance(currentGs, IsoKey.FONT_GLYPHS, null, contentStream);
2380-
if (currentGs.getFont() == null)
2394+
if (currentGs.getFont() == null) {
23812395
throw new PdfException(
23822396
KernelExceptionMessageConstant.FONT_AND_SIZE_MUST_BE_SET_BEFORE_WRITING_ANY_TEXT, currentGs);
2397+
}
2398+
2399+
document.checkIsoConformance(text, IsoKey.FONT, null, null, currentGs.getFont());
2400+
23832401
currentGs.getFont().writeText(text, contentStream.getOutputStream());
23842402
}
23852403

kernel/src/test/java/com/itextpdf/kernel/pdf/EncodingTest.java

-1
Original file line numberDiff line numberDiff line change
@@ -396,5 +396,4 @@ public void differentCodeSpaceRangeLengthsExtractionTest() throws IOException {
396396
String extractedText = PdfTextExtractor.getTextFromPage(pdfDocument.getPage(1));
397397
Assert.assertEquals("Hello\u7121\u540dworld\u6b98\u528d", extractedText);
398398
}
399-
400399
}

kernel/src/test/java/com/itextpdf/kernel/pdf/canvas/PdfCanvasTest.java

+31-10
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,7 @@ This file is part of the iText (R) project.
3434
import com.itextpdf.kernel.exceptions.PdfException;
3535
import com.itextpdf.kernel.font.PdfFontFactory;
3636
import com.itextpdf.kernel.geom.Rectangle;
37-
import com.itextpdf.kernel.pdf.PdfArray;
38-
import com.itextpdf.kernel.pdf.PdfDictionary;
39-
import com.itextpdf.kernel.pdf.PdfDocument;
40-
import com.itextpdf.kernel.pdf.PdfName;
41-
import com.itextpdf.kernel.pdf.PdfNumber;
42-
import com.itextpdf.kernel.pdf.PdfObject;
43-
import com.itextpdf.kernel.pdf.PdfPage;
44-
import com.itextpdf.kernel.pdf.PdfReader;
45-
import com.itextpdf.kernel.pdf.PdfWriter;
46-
import com.itextpdf.kernel.pdf.WriterProperties;
37+
import com.itextpdf.kernel.pdf.*;
4738
import com.itextpdf.kernel.pdf.canvas.wmf.WmfImageData;
4839
import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
4940
import com.itextpdf.kernel.utils.CompareTool;
@@ -1649,6 +1640,36 @@ public void setPositiveHorizontalScalingValueTest() throws IOException, Interrup
16491640
Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER));
16501641
}
16511642

1643+
@Test
1644+
public void createSimpleCanvasWithPdfArrayText() throws IOException, InterruptedException {
1645+
final String outPdf = DESTINATION_FOLDER + "createSimpleCanvasWithPdfArrayText.pdf";
1646+
String cmpPdf = SOURCE_FOLDER + "cmp_createSimpleCanvasWithPdfArrayText.pdf";
1647+
1648+
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(outPdf));
1649+
PdfPage page1 = pdfDoc.addNewPage();
1650+
PdfCanvas canvas = new PdfCanvas(page1);
1651+
1652+
PdfArray pdfArray = new PdfArray();
1653+
pdfArray.add(new PdfString("ABC"));
1654+
pdfArray.add(new PdfNumber(-250));
1655+
pdfArray.add(new PdfString("DFG"));
1656+
1657+
//Initialize canvas and write text to it
1658+
canvas
1659+
.saveState()
1660+
.beginText()
1661+
.moveText(36, 750)
1662+
.setFontAndSize(PdfFontFactory.createFont(StandardFonts.HELVETICA), 16)
1663+
.showText(pdfArray)
1664+
.endText()
1665+
.restoreState();
1666+
1667+
canvas.release();
1668+
pdfDoc.close();
1669+
1670+
Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER, "diff_"));
1671+
}
1672+
16521673
private void createStandardDocument(PdfWriter writer, int pageCount, ContentProvider contentProvider) throws IOException {
16531674
PdfDocument pdfDoc = new PdfDocument(writer);
16541675
pdfDoc.getDocumentInfo().setAuthor(AUTHOR).

layout/src/test/java/com/itextpdf/layout/font/FontSelectorLayoutTest.java

-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ private static Paragraph createParagraph(String textParagraph, String font) {
8989

9090
@Test
9191
public void utfToGlyphToUtfRountripTest() throws IOException {
92-
// See DEVSIX-4945
9392
// this should not throw a null pointer exception
9493
try(PdfDocument pdfDoc = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()));
9594
Document doc = new Document(pdfDoc)) {

pdfa/src/main/java/com/itextpdf/pdfa/PdfADocument.java

+18
Original file line numberDiff line numberDiff line change
@@ -154,13 +154,28 @@ public PdfADocument(PdfReader reader, PdfWriter writer, StampingProperties prope
154154
setChecker(conformanceLevel);
155155
}
156156

157+
/**
158+
* {@inheritDoc}
159+
*/
157160
@Override
158161
public void checkIsoConformance(Object obj, IsoKey key) {
159162
checkIsoConformance(obj, key, null, null);
160163
}
161164

165+
/**
166+
* {@inheritDoc}
167+
*/
162168
@Override
163169
public void checkIsoConformance(Object obj, IsoKey key, PdfResources resources, PdfStream contentStream) {
170+
checkIsoConformance(obj, key, resources, contentStream, null);
171+
}
172+
173+
/**
174+
* {@inheritDoc}
175+
*/
176+
@Override
177+
public void checkIsoConformance(Object obj, IsoKey key, PdfResources resources, PdfStream contentStream,
178+
Object extra) {
164179
if (!isPdfADocument) {
165180
super.checkIsoConformance(obj, key, resources, contentStream);
166181
return;
@@ -217,6 +232,9 @@ public void checkIsoConformance(Object obj, IsoKey key, PdfResources resources,
217232
case CRYPTO:
218233
checker.checkCrypto((PdfObject) obj);
219234
break;
235+
case FONT:
236+
checker.checkText((String) obj, (PdfFont) extra);
237+
break;
220238
}
221239
}
222240

pdfa/src/main/java/com/itextpdf/pdfa/checker/PdfA1Checker.java

+17
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This file is part of the iText (R) project.
2525
import com.itextpdf.commons.utils.MessageFormatUtil;
2626
import com.itextpdf.forms.fields.PdfFormField;
2727
import com.itextpdf.io.font.PdfEncodings;
28+
import com.itextpdf.io.font.otf.Glyph;
2829
import com.itextpdf.io.source.PdfTokenizer;
2930
import com.itextpdf.io.source.RandomAccessFileOrArray;
3031
import com.itextpdf.io.source.RandomAccessSourceFactory;
@@ -344,6 +345,22 @@ public void checkSignatureType(boolean isCAdES) {
344345
//nothing to do
345346
}
346347

348+
/**
349+
* {@inheritDoc}
350+
*
351+
* @param text {@inheritDoc}
352+
* @param font {@inheritDoc}
353+
*/
354+
@Override
355+
public void checkText(String text, PdfFont font) {
356+
for (int i = 0; i < text.length(); ++i) {
357+
if (!font.containsGlyph(text.charAt(i))) {
358+
throw new PdfAConformanceException(
359+
PdfaExceptionMessageConstant.EMBEDDED_FONTS_SHALL_DEFINE_ALL_REFERENCED_GLYPHS);
360+
}
361+
}
362+
}
363+
347364
@Override
348365
protected void checkPageTransparency(PdfDictionary pageDict, PdfDictionary pageResources) {
349366
// This check is irrelevant for the PdfA1 checker, so the body of the method is empty

pdfa/src/main/java/com/itextpdf/pdfa/checker/PdfAChecker.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ public void checkSignature(PdfDictionary signatureDict) {
286286
*
287287
* @param isCAdES true is CAdES sig type is used, false otherwise.
288288
*
289-
* @deprecated Will become an abstract in the next major release.
289+
* @deprecated Will become abstract in the next major release.
290290
*/
291291
@Deprecated
292292
public void checkSignatureType(boolean isCAdES) {
@@ -429,6 +429,18 @@ public void setPdfAOutputIntentColorSpace(PdfDictionary catalog) {
429429
public void checkCrypto(PdfObject crypto) {
430430
}
431431

432+
/**
433+
* Verify the conformity of the text written by the specified font.
434+
*
435+
* @param text Text to verify.
436+
* @param font Font to verify the text against.
437+
*
438+
* @deprecated Will become abstract in the next major release.
439+
*/
440+
@Deprecated
441+
public void checkText(String text, PdfFont font) {
442+
}
443+
432444
/**
433445
* Attest content stream conformance with appropriate specification.
434446
* Throws PdfAConformanceException if any discrepancy was found

pdfa/src/main/java/com/itextpdf/pdfa/exceptions/PdfaExceptionMessageConstant.java

+3
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ public final class PdfaExceptionMessageConstant {
201201
"DeviceGray shall only be used if a device independent DefaultGray colour space has been set when the "
202202
+ "DeviceGray colour space is used, or if a PDF/A OutputIntent is in effect.";
203203

204+
public static final String EMBEDDED_FONTS_SHALL_DEFINE_ALL_REFERENCED_GLYPHS = "Embedded fonts shall define all " +
205+
"glyphs referenced for rendering within the conforming file.";
206+
204207
public static final String ICCBASED_COLOUR_SPACE_SHALL_NOT_BE_USED_IF_IT_IS_CMYK_AND_IS_IDENTICAL_TO_CURRENT_PROFILE =
205208
"An ICCBased colour space shall not be used where the profile is a CMYK destination profile and is "
206209
+ "identical to that in the current PDF/A OutputIntent or the current transparency blending colorspace.";

0 commit comments

Comments
 (0)