PdfType3Font.java
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2025 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.kernel.font;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.io.font.AdobeGlyphList;
import com.itextpdf.io.font.FontEncoding;
import com.itextpdf.io.font.FontMetrics;
import com.itextpdf.io.font.FontNames;
import com.itextpdf.io.font.FontProgram;
import com.itextpdf.io.font.constants.FontDescriptorFlags;
import com.itextpdf.io.font.constants.FontStretches;
import com.itextpdf.io.font.constants.FontWeights;
import com.itextpdf.io.font.otf.Glyph;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
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.PdfObjectWrapper;
import com.itextpdf.kernel.pdf.PdfString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
/**
* Low-level API class for Type 3 fonts.
* <p>
* In Type 3 fonts, glyphs are defined by streams of PDF graphics operators.
* These streams are associated with character names. A separate encoding entry
* maps character codes to the appropriate character names for the glyphs.
*
* <p>
* Note, that this class operates in a special way with glyph space units.
* In the code when working with fonts, iText expects that 1000 units of glyph-space correspond
* to 1 unit of text space. For Type3 fonts this is not always the case and depends on FontMatrix.
* That's why in {@link PdfType3Font} the font matrix and all font metrics in glyph space units
* are "normalized" in such way, that 1 to 1000 relation is preserved. This is done on
* Type3 font initialization, and is reverted back on font flushing, because the actual content
* streams of type3 font glyphs are left with original coordinates based on original font matrix.
* See also ISO-32000-2, 9.2.4 "Glyph positioning and metrics":
*
* <p>
* "The glyph coordinate system is the space in which an individual character���s glyph is defined. All path
* coordinates and metrics shall be interpreted in glyph space. For all font types except Type 3, the units
* of glyph space are one-thousandth of a unit of text space; for a Type 3 font, the transformation from
* glyph space to text space shall be defined by a font matrix specified in an explicit FontMatrix entry in
* the font."
*
* <p>
* Note, that because of this when processing Type3 glyphs content streams either process them completely independent
* from this class or take this normalization into account.
*
* <p>
* To be able to be wrapped with this {@link PdfObjectWrapper} the {@link PdfObject}
* must be indirect.
*/
public class PdfType3Font extends PdfSimpleFont<Type3Font> {
private static final int FONT_BBOX_LLX = 0;
private static final int FONT_BBOX_LLY = 1;
private static final int FONT_BBOX_URX = 2;
private static final int FONT_BBOX_URY = 3;
private static final double[] DEFAULT_FONT_MATRIX = {0.001, 0, 0, 0.001, 0, 0};
private double[] fontMatrix = DEFAULT_FONT_MATRIX;
/**
* Used to normalize font metrics expressed in glyph space units. See {@link PdfType3Font}.
*/
private double glyphSpaceNormalizationFactor;
/**
* Gets the transformation matrix that defines relation between text and glyph spaces.
*
* @return the font matrix
*/
private double[] getFontMatrix() {
return this.fontMatrix;
}
/**
* Creates a Type 3 font.
*
* @param colorized defines whether the glyph color is specified in the glyph descriptions in the font.
*/
PdfType3Font(PdfDocument document, boolean colorized) {
super();
makeIndirect(document);
subset = true;
embedded = true;
fontProgram = new Type3Font(colorized);
fontEncoding = FontEncoding.createEmptyFontEncoding();
setGlyphSpaceNormalizationFactor(1.0f);
}
/**
* Creates a Type 3 font.
*
* @param document the target document of the new font.
* @param fontName the PostScript name of the font, shall not be null or empty.
* @param fontFamily a preferred font family name.
* @param colorized indicates whether the font will be colorized
*/
PdfType3Font(PdfDocument document, String fontName, String fontFamily, boolean colorized) {
this(document, colorized);
((Type3Font) fontProgram).setFontName(fontName);
((Type3Font) fontProgram).setFontFamily(fontFamily);
setGlyphSpaceNormalizationFactor(1.0f);
}
/**
* Creates a Type 3 font based on an existing font dictionary, which must be an indirect object.
*
* @param fontDictionary a dictionary of type <code>/Font</code>, must have an indirect reference.
*/
PdfType3Font(PdfDictionary fontDictionary) {
super(fontDictionary);
subset = true;
embedded = true;
fontProgram = new Type3Font(false);
fontEncoding = DocFontEncoding.createDocFontEncoding(fontDictionary.get(PdfName.Encoding), toUnicode);
double[] fontMatrixArray = readFontMatrix();
double[] fontBBoxRect = readFontBBox();
double[] widthsArray = readWidths(fontDictionary);
setGlyphSpaceNormalizationFactor(FontProgram.convertGlyphSpaceToTextSpace(fontMatrixArray[0]));
PdfDictionary charProcsDic = fontDictionary.getAsDictionary(PdfName.CharProcs);
PdfDictionary encoding = fontDictionary.getAsDictionary(PdfName.Encoding);
PdfArray differences = encoding != null ? encoding.getAsArray(PdfName.Differences) : null;
if (charProcsDic == null || differences == null) {
LoggerFactory.getLogger(getClass()).warn(IoLogMessageConstant.TYPE3_FONT_INITIALIZATION_ISSUE);
}
fillFontDescriptor(fontDictionary.getAsDictionary(PdfName.FontDescriptor));
normalize1000UnitsToGlyphSpaceUnits(fontMatrixArray);
normalizeGlyphSpaceUnitsTo1000Units(fontBBoxRect);
normalizeGlyphSpaceUnitsTo1000Units(widthsArray);
int firstChar = initializeUsedGlyphs(fontDictionary);
fontMatrix = fontMatrixArray;
initializeFontBBox(fontBBoxRect);
initializeTypoAscenderDescender(fontBBoxRect);
int[] widths = new int[256];
for (int i = 0; i < widthsArray.length && firstChar + i < 256; i++) {
widths[firstChar + i] = (int) (widthsArray[i]);
}
addGlyphsFromDifferences(differences, charProcsDic, widths);
addGlyphsFromCharProcs(charProcsDic, widths);
}
/**
* Sets the PostScript name of the font.
*
* @param fontName the PostScript name of the font, shall not be null or empty.
*/
public void setFontName(String fontName) {
((Type3Font) fontProgram).setFontName(fontName);
}
/**
* Sets a preferred font family name.
*
* @param fontFamily a preferred font family name.
*/
public void setFontFamily(String fontFamily) {
((Type3Font) fontProgram).setFontFamily(fontFamily);
}
/**
* Sets font weight.
*
* @param fontWeight integer form 100 to 900. See {@link FontWeights}.
*/
public void setFontWeight(int fontWeight) {
((Type3Font) fontProgram).setFontWeight(fontWeight);
}
/**
* Sets cap height.
*
* @param capHeight integer in glyph-space 1000-units
*/
public void setCapHeight(int capHeight) {
((Type3Font) fontProgram).setCapHeight(capHeight);
}
/**
* Sets the PostScript italic angle.
* <p>
* Italic angle in counter-clockwise degrees from the vertical. Zero for upright text, negative for text that leans to the right (forward).
*
* @param italicAngle in counter-clockwise degrees from the vertical
*/
public void setItalicAngle(int italicAngle) {
((Type3Font) fontProgram).setItalicAngle(italicAngle);
}
/**
* Sets font width in css notation (font-stretch property)
*
* @param fontWidth {@link FontStretches}.
*/
public void setFontStretch(String fontWidth) {
((Type3Font) fontProgram).setFontStretch(fontWidth);
}
/**
* Sets Font descriptor flags.
*
* @param flags font descriptor flags.
* @see FontDescriptorFlags
*/
public void setPdfFontFlags(int flags) {
((Type3Font) fontProgram).setPdfFontFlags(flags);
}
/**
* Returns a {@link Type3Glyph} by unicode.
*
* @param unicode glyph unicode
*
* @return {@link Type3Glyph} glyph, or {@code null} if this font does not contain glyph for the unicode
*/
public Type3Glyph getType3Glyph(int unicode) {
return ((Type3Font) getFontProgram()).getType3Glyph(unicode);
}
@Override
public boolean isSubset() {
return true;
}
@Override
public boolean isEmbedded() {
return true;
}
/**
* Gets count of glyphs in Type 3 font.
*
* @return number of glyphs.
*/
public int getNumberOfGlyphs() {
return ((Type3Font) getFontProgram()).getNumberOfGlyphs();
}
/**
* Defines a glyph. If the character was already defined it will return the same content
*
* @param c the character to match this glyph.
* @param wx the advance this character will have
* @param llx the X lower left corner of the glyph bounding box. If the <CODE>colorize</CODE> option is
* <CODE>true</CODE> the value is ignored
* @param lly the Y lower left corner of the glyph bounding box. If the <CODE>colorize</CODE> option is
* <CODE>true</CODE> the value is ignored
* @param urx the X upper right corner of the glyph bounding box. If the <CODE>colorize</CODE> option is
* <CODE>true</CODE> the value is ignored
* @param ury the Y upper right corner of the glyph bounding box. If the <CODE>colorize</CODE> option is
* <CODE>true</CODE> the value is ignored
*
* @return a content where the glyph can be defined
*/
public Type3Glyph addGlyph(char c, int wx, int llx, int lly, int urx, int ury) {
Type3Glyph glyph = getType3Glyph(c);
if (glyph != null) {
return glyph;
}
int code = getFirstEmptyCode();
glyph = new Type3Glyph(getDocument(), wx, llx, lly, urx, ury, ((Type3Font) getFontProgram()).isColorized());
((Type3Font) getFontProgram()).addGlyph(code, c, wx, new int[]{llx, lly, urx, ury}, glyph);
fontEncoding.addSymbol(code, c);
if (!((Type3Font) getFontProgram()).isColorized()) {
if (fontProgram.countOfGlyphs() == 0) {
fontProgram.getFontMetrics().setBbox(llx, lly, urx, ury);
} else {
int[] bbox = fontProgram.getFontMetrics().getBbox();
int newLlx = Math.min(bbox[0], llx);
int newLly = Math.min(bbox[1], lly);
int newUrx = Math.max(bbox[2], urx);
int newUry = Math.max(bbox[3], ury);
fontProgram.getFontMetrics().setBbox(newLlx, newLly, newUrx, newUry);
}
}
return glyph;
}
@Override
public Glyph getGlyph(int unicode) {
if (fontEncoding.canEncode(unicode) || unicode < 33) {
Glyph glyph = getFontProgram().getGlyph(fontEncoding.getUnicodeDifference(unicode));
if (glyph == null && (glyph = notdefGlyphs.get(unicode)) == null) {
// Handle special layout characters like sfthyphen (00AD).
// This glyphs will be skipped while converting to bytes
glyph = new Glyph(-1, 0, unicode);
notdefGlyphs.put(unicode, glyph);
}
return glyph;
}
return null;
}
@Override
public boolean containsGlyph(int unicode) {
return (fontEncoding.canEncode(unicode) || unicode < 33)
&& getFontProgram().getGlyph(fontEncoding.getUnicodeDifference(unicode)) != null;
}
@Override
public void flush() {
if (isFlushed()) return;
ensureUnderlyingObjectHasIndirectReference();
flushFontData();
super.flush();
}
@Override
protected PdfDictionary getFontDescriptor(String fontName) {
if (fontName != null && fontName.length() > 0) {
PdfDictionary fontDescriptor = new PdfDictionary();
makeObjectIndirect(fontDescriptor);
fontDescriptor.put(PdfName.Type, PdfName.FontDescriptor);
FontMetrics fontMetrics = fontProgram.getFontMetrics();
int capHeight = fontMetrics.getCapHeight();
fontDescriptor.put(PdfName.CapHeight, new PdfNumber(normalize1000UnitsToGlyphSpaceUnits(capHeight)));
fontDescriptor.put(PdfName.ItalicAngle, new PdfNumber(fontMetrics.getItalicAngle()));
FontNames fontNames = fontProgram.getFontNames();
fontDescriptor.put(PdfName.FontWeight, new PdfNumber(fontNames.getFontWeight()));
fontDescriptor.put(PdfName.FontName, new PdfName(fontName));
if (fontNames.getFamilyName() != null && fontNames.getFamilyName().length > 0 && fontNames.getFamilyName()[0].length >= 4) {
fontDescriptor.put(PdfName.FontFamily, new PdfString(fontNames.getFamilyName()[0][3]));
}
int flags = fontProgram.getPdfFontFlags();
// reset both flags
flags &= ~(FontDescriptorFlags.SYMBOLIC | FontDescriptorFlags.NONSYMBOLIC);
// set fontSpecific based on font encoding
flags |= fontEncoding.isFontSpecific() ?
FontDescriptorFlags.SYMBOLIC : FontDescriptorFlags.NONSYMBOLIC;
fontDescriptor.put(PdfName.Flags, new PdfNumber(flags));
return fontDescriptor;
} else if (getPdfObject().getIndirectReference() != null
&& getPdfObject().getIndirectReference().getDocument().isTagged()) {
Logger logger = LoggerFactory.getLogger(PdfType3Font.class);
logger.warn(IoLogMessageConstant.TYPE3_FONT_ISSUE_TAGGED_PDF);
}
return null;
}
@Override
protected PdfArray buildWidthsArray(int firstChar, int lastChar) {
double[] widths = new double[lastChar - firstChar + 1];
for (int k = firstChar; k <= lastChar; ++k) {
int i = k - firstChar;
if (usedGlyphs[k] == 0) {
widths[i] = 0;
} else {
int uni = getFontEncoding().getUnicode(k);
Glyph glyph = uni > -1 ? getGlyph(uni) : getFontProgram().getGlyphByCode(k);
widths[i] = glyph != null ? glyph.getWidth() : 0;
}
}
normalize1000UnitsToGlyphSpaceUnits(widths);
return new PdfArray(widths);
}
@Override
protected void addFontStream(PdfDictionary fontDescriptor) {
}
protected PdfDocument getDocument() {
return getPdfObject().getIndirectReference().getDocument();
}
final double getGlyphSpaceNormalizationFactor() {
return glyphSpaceNormalizationFactor;
}
final void setGlyphSpaceNormalizationFactor(double glyphSpaceNormalizationFactor) {
this.glyphSpaceNormalizationFactor = glyphSpaceNormalizationFactor;
}
private void addGlyphsFromDifferences(PdfArray differences, PdfDictionary charProcsDic, int[] widths) {
if (differences == null || charProcsDic == null) {
return;
}
int currentNumber = 0;
for (int k = 0; k < differences.size(); ++k) {
PdfObject obj = differences.get(k);
if (obj.isNumber()) {
currentNumber = ((PdfNumber) obj).intValue();
} else if (currentNumber > SIMPLE_FONT_MAX_CHAR_CODE_VALUE) {
// Skip glyphs with id greater than 255
} else {
String glyphName = ((PdfName) obj).getValue();
int unicode = fontEncoding.getUnicode(currentNumber);
if (getFontProgram().getGlyphByCode(currentNumber) == null
&& charProcsDic.containsKey(new PdfName(glyphName))) {
fontEncoding.setDifference(currentNumber, glyphName);
((Type3Font) getFontProgram()).addGlyph(currentNumber, unicode, widths[currentNumber], null,
new Type3Glyph(charProcsDic.getAsStream(new PdfName(glyphName)), getDocument()));
}
currentNumber++;
}
}
}
/**
* Gets the first empty code that could be passed to {@link FontEncoding#addSymbol(int, int)}
*
* @return code from 1 to 255 or -1 if all slots are busy.
*/
private int getFirstEmptyCode() {
final int startFrom = 1;
for (int i = startFrom; i <= PdfFont.SIMPLE_FONT_MAX_CHAR_CODE_VALUE; i++) {
if (!fontEncoding.canDecode(i) && fontProgram.getGlyphByCode(i) == null) {
return i;
}
}
return -1;
}
private void addGlyphsFromCharProcs(PdfDictionary charProcsDic, int[] widths) {
if (charProcsDic == null) {
return;
}
Map<Integer, Integer> unicodeToCode = null;
if (getToUnicode() != null) {
try { unicodeToCode = getToUnicode().createReverseMapping(); } catch (Exception e) {/*ignored*/}
}
for (PdfName glyphName : charProcsDic.keySet()) {
int unicode = AdobeGlyphList.nameToUnicode(glyphName.getValue());
int code = -1;
if (fontEncoding.canEncode(unicode)) {
code = fontEncoding.convertToByte(unicode);
} else if (unicodeToCode != null && unicodeToCode.containsKey(unicode)) {
code = (int) unicodeToCode.get(unicode);
}
if (code != -1 && getFontProgram().getGlyphByCode(code) == null) {
((Type3Font) getFontProgram()).addGlyph(code, unicode, widths[code],
null, new Type3Glyph(charProcsDic.getAsStream(glyphName), getDocument()));
}
}
}
private void flushFontData() {
if (((Type3Font) getFontProgram()).getNumberOfGlyphs() < 1) {
throw new PdfException(KernelExceptionMessageConstant.NO_GLYPHS_DEFINED_FOR_TYPE_3_FONT);
}
PdfDictionary charProcs = new PdfDictionary();
for (int i = 0; i <= PdfFont.SIMPLE_FONT_MAX_CHAR_CODE_VALUE; i++) {
Type3Glyph glyph = null;
if (fontEncoding.canDecode(i)) {
glyph = getType3Glyph(fontEncoding.getUnicode(i));
}
if (glyph == null) {
glyph = ((Type3Font) getFontProgram()).getType3GlyphByCode(i);
}
if (glyph != null) {
charProcs.put(new PdfName(fontEncoding.getDifference(i)), glyph.getContentStream());
glyph.getContentStream().flush();
}
}
getPdfObject().put(PdfName.CharProcs, charProcs);
double[] fontMatrixDouble = getFontMatrix();
int[] fontBBoxInt = getFontProgram().getFontMetrics().getBbox();
double[] fontBBoxDouble = new double[] {
fontBBoxInt[FONT_BBOX_LLX], fontBBoxInt[FONT_BBOX_LLY],
fontBBoxInt[FONT_BBOX_URX], fontBBoxInt[FONT_BBOX_URY]};
normalizeGlyphSpaceUnitsTo1000Units(fontMatrixDouble);
normalize1000UnitsToGlyphSpaceUnits(fontBBoxDouble);
getPdfObject().put(PdfName.FontMatrix, new PdfArray(fontMatrixDouble));
getPdfObject().put(PdfName.FontBBox, new PdfArray(fontBBoxDouble));
String fontName = fontProgram.getFontNames().getFontName();
super.flushFontData(fontName, PdfName.Type3);
makeObjectIndirect(getPdfObject().get(PdfName.Widths));
//BaseFont is not listed as key in Type 3 font specification.
getPdfObject().remove(PdfName.BaseFont);
}
private double[] readWidths(PdfDictionary fontDictionary) {
PdfArray pdfWidths = fontDictionary.getAsArray(PdfName.Widths);
if (pdfWidths == null) {
throw new PdfException(KernelExceptionMessageConstant.MISSING_REQUIRED_FIELD_IN_FONT_DICTIONARY)
.setMessageParams(PdfName.Widths);
}
double[] widths = new double[pdfWidths.size()];
for (int i = 0; i < pdfWidths.size(); i++) {
PdfNumber n = pdfWidths.getAsNumber(i);
widths[i] = n != null ? n.doubleValue() : 0;
}
return widths;
}
private int initializeUsedGlyphs(PdfDictionary fontDictionary) {
int firstChar = normalizeFirstLastChar(fontDictionary.getAsNumber(PdfName.FirstChar), 0);
int lastChar = normalizeFirstLastChar(fontDictionary.getAsNumber(PdfName.LastChar),
PdfFont.SIMPLE_FONT_MAX_CHAR_CODE_VALUE);
for (int i = firstChar; i <= lastChar; i++) {
usedGlyphs[i] = 1;
}
return firstChar;
}
private double[] readFontBBox() {
PdfArray fontBBox = getPdfObject().getAsArray(PdfName.FontBBox);
if (fontBBox != null) {
double llx = fontBBox.getAsNumber(FONT_BBOX_LLX).doubleValue();
double lly = fontBBox.getAsNumber(FONT_BBOX_LLY).doubleValue();
double urx = fontBBox.getAsNumber(FONT_BBOX_URX).doubleValue();
double ury = fontBBox.getAsNumber(FONT_BBOX_URY).doubleValue();
return new double[] {llx, lly, urx, ury};
}
return new double[] {0, 0, 0, 0};
}
private double[] readFontMatrix() {
PdfArray fontMatrixArray = getPdfObject().getAsArray(PdfName.FontMatrix);
if (fontMatrixArray == null) {
throw new PdfException(KernelExceptionMessageConstant.MISSING_REQUIRED_FIELD_IN_FONT_DICTIONARY)
.setMessageParams(PdfName.FontMatrix);
}
double[] fontMatrix = new double[6];
for (int i = 0; i < fontMatrixArray.size(); i++) {
fontMatrix[i] = ((PdfNumber) fontMatrixArray.get(i)).getValue();
}
return fontMatrix;
}
private void initializeTypoAscenderDescender(double[] fontBBoxRect) {
// iText uses typo ascender/descender for text extraction, that's why we need to set
// them here to values relative to actual glyph metrics values.
((Type3Font) fontProgram).setTypoAscender((int) fontBBoxRect[FONT_BBOX_URY]);
((Type3Font) fontProgram).setTypoDescender((int) fontBBoxRect[FONT_BBOX_LLY]);
}
private void initializeFontBBox(double[] fontBBoxRect) {
fontProgram.getFontMetrics().setBbox(
(int) fontBBoxRect[FONT_BBOX_LLX],
(int) fontBBoxRect[FONT_BBOX_LLY],
(int) fontBBoxRect[FONT_BBOX_URX],
(int) fontBBoxRect[FONT_BBOX_URY]
);
}
private void normalizeGlyphSpaceUnitsTo1000Units(double[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = normalizeGlyphSpaceUnitsTo1000Units(array[i]);;
}
}
private double normalizeGlyphSpaceUnitsTo1000Units(double value) {
return value * getGlyphSpaceNormalizationFactor();
}
private void normalize1000UnitsToGlyphSpaceUnits(double[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = normalize1000UnitsToGlyphSpaceUnits(array[i]);
}
}
private double normalize1000UnitsToGlyphSpaceUnits(double value) {
return value / getGlyphSpaceNormalizationFactor();
}
private void fillFontDescriptor(PdfDictionary fontDesc) {
if (fontDesc == null) {
return;
}
PdfNumber v = fontDesc.getAsNumber(PdfName.CapHeight);
if (v != null) {
double capHeight = v.doubleValue();
setCapHeight((int) normalizeGlyphSpaceUnitsTo1000Units(capHeight));
}
v = fontDesc.getAsNumber(PdfName.ItalicAngle);
if (v != null) {
setItalicAngle(v.intValue());
}
v = fontDesc.getAsNumber(PdfName.FontWeight);
if (v != null) {
setFontWeight(v.intValue());
}
PdfName fontStretch = fontDesc.getAsName(PdfName.FontStretch);
if (fontStretch != null) {
setFontStretch(fontStretch.getValue());
}
PdfName fontName = fontDesc.getAsName(PdfName.FontName);
if (fontName != null) {
setFontName(fontName.getValue());
}
PdfString fontFamily = fontDesc.getAsString(PdfName.FontFamily);
if (fontFamily != null) {
setFontFamily(fontFamily.getValue());
}
}
private int normalizeFirstLastChar(PdfNumber firstLast, int defaultValue) {
if (firstLast == null) return defaultValue;
int result = firstLast.intValue();
return result < 0 || result > PdfFont.SIMPLE_FONT_MAX_CHAR_CODE_VALUE ? defaultValue : result;
}
}