PdfTrueTypeFont.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.commons.utils.StringNormalizer;
import com.itextpdf.io.font.FontEncoding;
import com.itextpdf.io.font.FontNames;
import com.itextpdf.io.font.FontProgramFactory;
import com.itextpdf.io.font.TrueTypeFont;
import com.itextpdf.io.font.Type1Font;
import com.itextpdf.io.font.constants.StandardFonts;
import com.itextpdf.io.font.otf.Glyph;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfStream;
import java.io.IOException;
import java.util.SortedSet;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Note. For TrueType FontNames.getStyle() is the same to Subfamily(). So, we shouldn't add style to /BaseFont.
*/
public class PdfTrueTypeFont extends PdfSimpleFont<TrueTypeFont> {
PdfTrueTypeFont(TrueTypeFont ttf, String encoding, boolean embedded) {
super();
setFontProgram(ttf);
this.embedded = embedded;
FontNames fontNames = ttf.getFontNames();
if (embedded && !fontNames.allowEmbedding()) {
throw new PdfException("{0} cannot be embedded due to licensing restrictions.")
.setMessageParams(fontNames.getFontName());
}
if ((encoding == null || encoding.length() == 0) && ttf.isFontSpecific()) {
encoding = FontEncoding.FONT_SPECIFIC;
}
if (encoding != null &&
StringNormalizer.toLowerCase(FontEncoding.FONT_SPECIFIC).equals(StringNormalizer.toLowerCase(encoding))) {
fontEncoding = FontEncoding.createFontSpecificEncoding();
} else {
fontEncoding = FontEncoding.createFontEncoding(encoding);
}
}
PdfTrueTypeFont(PdfDictionary fontDictionary) {
super(fontDictionary);
newFont = false;
subset = false;
fontEncoding = DocFontEncoding.createDocFontEncoding(fontDictionary.get(PdfName.Encoding), toUnicode);
PdfName baseFontName = fontDictionary.getAsName(PdfName.BaseFont);
// Section 9.6.3 (ISO-32000-1): A TrueType font dictionary may contain the same entries as a Type 1 font
// dictionary (see Table 111), with these differences...
// Section 9.6.2.2. (ISO-32000-1) associate standard fonts with Type1 fonts but there does not
// seem to be a strict requirement on the subtype
// Cases when a font with /TrueType subtype has base font which is one of the Standard 14 fonts
// does not seem to be forbidden and it's handled by many PDF tools, so we handle it here as well
if (baseFontName != null && StandardFonts.isStandardFont(baseFontName.getValue())
&& !fontDictionary.containsKey(PdfName.FontDescriptor) && !fontDictionary.containsKey(PdfName.Widths)) {
try {
fontProgram = FontProgramFactory.createFont(baseFontName.getValue(), true);
} catch (IOException e) {
throw new PdfException(KernelExceptionMessageConstant.IO_EXCEPTION_WHILE_CREATING_FONT, e);
}
} else {
fontProgram = DocTrueTypeFont.createFontProgram(fontDictionary, fontEncoding, toUnicode);
}
embedded = fontProgram instanceof IDocFontProgram && ((IDocFontProgram) fontProgram).getFontFile() != null;
}
@Override
public Glyph getGlyph(int unicode) {
if (fontEncoding.canEncode(unicode)) {
Glyph glyph = getFontProgram().getGlyph(fontEncoding.getUnicodeDifference(unicode));
if (glyph == null && (glyph = notdefGlyphs.get(unicode)) == null) {
final Glyph notdef = getFontProgram().getGlyphByCode(0);
if (notdef != null) {
glyph = new Glyph(notdef, unicode);
notdefGlyphs.put(unicode, glyph);
}
}
return glyph;
}
return null;
}
@Override
public boolean containsGlyph(int unicode) {
if (fontEncoding.isFontSpecific()) {
return fontProgram.getGlyphByCode(unicode) != null;
} else {
return fontEncoding.canEncode(unicode)
&& getFontProgram().getGlyph(fontEncoding.getUnicodeDifference(unicode)) != null;
}
}
@Override
public void flush() {
if (isFlushed()) {
return;
}
ensureUnderlyingObjectHasIndirectReference();
if (newFont) {
PdfName subtype;
String fontName;
if (((TrueTypeFont) getFontProgram()).isCff()) {
subtype = PdfName.Type1;
fontName = fontProgram.getFontNames().getFontName();
} else {
subtype = PdfName.TrueType;
fontName = updateSubsetPrefix(fontProgram.getFontNames().getFontName(), subset, embedded);
}
flushFontData(fontName, subtype);
}
super.flush();
}
@Override
public boolean isBuiltWith(String fontProgram, String encoding) {
// Now Identity-H is default for true type fonts. However, in case of Identity-H the method from
// PdfType0Font would be triggered, hence we need to return false there.
return null != encoding && !"".equals(encoding) && super.isBuiltWith(fontProgram, encoding);
}
@Override
protected void addFontStream(PdfDictionary fontDescriptor) {
if (embedded) {
PdfName fontFileName;
PdfStream fontStream;
if (fontProgram instanceof IDocFontProgram) {
fontFileName = ((IDocFontProgram) fontProgram).getFontFileName();
fontStream = ((IDocFontProgram) fontProgram).getFontFile();
} else if (((TrueTypeFont) getFontProgram()).isCff()) {
fontFileName = PdfName.FontFile3;
try {
byte[] fontStreamBytes = ((TrueTypeFont) getFontProgram()).getFontStreamBytes();
fontStream = getPdfFontStream(fontStreamBytes, new int[]{fontStreamBytes.length});
fontStream.put(PdfName.Subtype, new PdfName("Type1C"));
} catch (PdfException e) {
Logger logger = LoggerFactory.getLogger(PdfTrueTypeFont.class);
logger.error(e.getMessage());
fontStream = null;
}
} else {
fontFileName = PdfName.FontFile2;
SortedSet<Integer> glyphs = new TreeSet<>();
for (int k = 0; k < usedGlyphs.length; k++) {
if (usedGlyphs[k] != 0) {
int uni = fontEncoding.getUnicode(k);
Glyph glyph = uni > -1 ? fontProgram.getGlyph(uni) : fontProgram.getGlyphByCode(k);
if (glyph != null) {
glyphs.add(glyph.getCode());
}
}
}
((TrueTypeFont) getFontProgram()).updateUsedGlyphs(glyphs, subset, subsetRanges);
try {
byte[] fontStreamBytes;
//getDirectoryOffset() > 0 means ttc, which shall be subset anyway.
if (subset || ((TrueTypeFont) getFontProgram()).getDirectoryOffset() > 0) {
fontStreamBytes = ((TrueTypeFont) getFontProgram()).getSubset(glyphs, subset);
} else {
fontStreamBytes = ((TrueTypeFont) getFontProgram()).getFontStreamBytes();
}
fontStream = getPdfFontStream(fontStreamBytes, new int[]{fontStreamBytes.length});
} catch (PdfException e) {
Logger logger = LoggerFactory.getLogger(PdfTrueTypeFont.class);
logger.error(e.getMessage());
fontStream = null;
}
}
if (fontStream != null) {
fontDescriptor.put(fontFileName, fontStream);
if (fontStream.getIndirectReference() != null) {
fontStream.flush();
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected boolean isBuiltInFont() {
return fontProgram instanceof Type1Font && ((Type1Font) fontProgram).isBuiltInFont();
}
}