Fonts and merging documents
This example was written in answer to the question Click How to create a PDF with font information and embed the actual font while merging the files into a single PDF?
mergeandaddfont
JAVA
JAVA
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2023 Apryse Group NV
Authors: Apryse Software.
For more information, please contact iText Software at this address:
sales@itextpdf.com
*/
package com.itextpdf.samples.sandbox.fonts;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.font.PdfFontFactory.EmbeddingStrategy;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfStream;
import com.itextpdf.kernel.pdf.CompressionConstants;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfNumber;
import com.itextpdf.kernel.pdf.PdfObject;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;
public class MergeAndAddFont {
public static final String FONT = "./src/main/resources/font/GravitasOne.ttf";
public static final String[] FILE_A = {
"./target/sandbox/fonts/testA1.pdf",
"./target/sandbox/fonts/testA2.pdf",
"./target/sandbox/fonts/testA3.pdf"
};
public static final String[] FILE_B = {
"./target/sandbox/fonts/testB1.pdf",
"./target/sandbox/fonts/testB2.pdf",
"./target/sandbox/fonts/testB3.pdf"
};
public static final String[] FILE_C = {
"./target/sandbox/fonts/testC1.pdf",
"./target/sandbox/fonts/testC2.pdf",
"./target/sandbox/fonts/testC3.pdf"
};
public static final String[] CONTENT = {
"abcdefgh", "ijklmnopq", "rstuvwxyz"
};
public static final Map<String, String> DEST_NAMES = new HashMap<>();
static {
DEST_NAMES.put("A1", "testA_merged1.pdf");
DEST_NAMES.put("A2", "testA_merged2.pdf");
DEST_NAMES.put("B1", "testB_merged1.pdf");
DEST_NAMES.put("B2", "testB_merged2.pdf");
DEST_NAMES.put("C1", "testC_merged1.pdf");
DEST_NAMES.put("C2", "testC_merged2.pdf");
}
public static final String DEST = "./target/sandbox/fonts/";
public static void main(String[] args) throws Exception {
File file = new File(DEST);
file.mkdirs();
new MergeAndAddFont().manipulatePdf(DEST);
}
public void createPdf(String filename, String text, EmbeddingStrategy embeddingStrategy, boolean subset) throws IOException {
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(filename));
Document doc = new Document(pdfDoc);
// The 3rd parameter indicates whether the font is to be embedded into the target document.
PdfFont font = PdfFontFactory.createFont(FONT, PdfEncodings.WINANSI, embeddingStrategy);
// When set to true, only the used glyphs will be included in the font.
// When set to false, the full font will be included and all subset ranges will be removed.
font.setSubset(subset);
doc.add(new Paragraph(text).setFont(font));
doc.close();
}
public void mergeFiles(String[] files, String result, boolean isSmartModeOn) throws IOException {
PdfWriter writer = new PdfWriter(result);
// In smart mode when resources (such as fonts, images,...) are encountered,
// a reference to these resources is saved in a cache and can be reused.
// This mode reduces the file size of the resulting PDF document.
writer.setSmartMode(isSmartModeOn);
PdfDocument pdfDoc = new PdfDocument(writer);
// This method initializes an outline tree of the document and sets outline mode to true.
pdfDoc.initializeOutlines();
for (int i = 0; i < files.length; i++) {
PdfDocument addedDoc = new PdfDocument(new PdfReader(files[i]));
addedDoc.copyPagesTo(1, addedDoc.getNumberOfPages(), pdfDoc);
addedDoc.close();
}
pdfDoc.close();
}
protected void manipulatePdf(String dest) throws Exception {
for (int i = 0; i < FILE_A.length; i++) {
// Create pdf files with font, which will be embedded into the target document,
// and only the used glyphs will be included in the font.
createPdf(FILE_A[i], CONTENT[i], EmbeddingStrategy.FORCE_EMBEDDED, true);
}
mergeFiles(FILE_A, dest + DEST_NAMES.get("A1"), false);
mergeFiles(FILE_A, dest + DEST_NAMES.get("A2"), true);
for (int i = 0; i < FILE_B.length; i++) {
// Create pdf files with font, which will embedded into the target document.
// Full font will be included and all subset ranges will be removed
createPdf(FILE_B[i], CONTENT[i], EmbeddingStrategy.FORCE_EMBEDDED, false);
}
mergeFiles(FILE_B, dest + DEST_NAMES.get("B1"), false);
mergeFiles(FILE_B, dest + DEST_NAMES.get("B2"), true);
for (int i = 0; i < FILE_C.length; i++) {
// Create pdf files with font, which won't be embedded into the target document.
// Full font will be included and all subset ranges will be removed
createPdf(FILE_C[i], CONTENT[i], EmbeddingStrategy.FORCE_NOT_EMBEDDED, false);
}
mergeFiles(FILE_C, dest + DEST_NAMES.get("C1"), true);
// Embed the font manually
embedFont(dest + DEST_NAMES.get("C1"), FONT, dest + DEST_NAMES.get("C2"));
}
protected void embedFont(String merged, String fontfile, String result) throws IOException {
// The font file
RandomAccessFile raf = new RandomAccessFile(fontfile, "r");
byte fontbytes[] = new byte[(int) raf.length()];
raf.readFully(fontbytes);
raf.close();
// Create a new stream for the font file
PdfStream stream = new PdfStream(fontbytes);
stream.setCompressionLevel(CompressionConstants.DEFAULT_COMPRESSION);
stream.put(PdfName.Length1, new PdfNumber(fontbytes.length));
PdfDocument pdfDoc = new PdfDocument(new PdfReader(merged), new PdfWriter(result));
int numberOfPdfObjects = pdfDoc.getNumberOfPdfObjects();
// Search for the font dictionary
for (int i = 0; i < numberOfPdfObjects; i++) {
PdfObject object = pdfDoc.getPdfObject(i);
if (object == null || !object.isDictionary()) {
continue;
}
PdfDictionary fontDictionary = (PdfDictionary) object;
PdfFont font = PdfFontFactory.createFont(fontfile, PdfEncodings.WINANSI);
PdfName fontname = new PdfName(font.getFontProgram().getFontNames().getFontName());
if (PdfName.FontDescriptor.equals(fontDictionary.get(PdfName.Type))
&& fontname.equals(fontDictionary.get(PdfName.FontName))) {
// Embed the passed font to the pdf document
fontDictionary.put(PdfName.FontFile2, stream.makeIndirect(pdfDoc).getIndirectReference());
}
}
pdfDoc.close();
}
}
C#
C#
using System;
using System.Collections.Generic;
using System.IO;
using iText.IO.Font;
using iText.Kernel.Font;
using iText.Kernel.Pdf;
using iText.Layout;
using iText.Layout.Element;
namespace iText.Samples.Sandbox.Fonts
{
public class MergeAndAddFont
{
public static readonly String FONT = "../../../resources/font/GravitasOne.ttf";
public static readonly String[] FILE_A =
{
"results/sandbox/fonts/testA1.pdf",
"results/sandbox/fonts/testA2.pdf",
"results/sandbox/fonts/testA3.pdf"
};
public static readonly String[] FILE_B =
{
"results/sandbox/fonts/testB1.pdf",
"results/sandbox/fonts/testB2.pdf",
"results/sandbox/fonts/testB3.pdf"
};
public static readonly String[] FILE_C =
{
"results/sandbox/fonts/testC1.pdf",
"results/sandbox/fonts/testC2.pdf",
"results/sandbox/fonts/testC3.pdf"
};
public static readonly String[] CONTENT =
{
"abcdefgh", "ijklmnopq", "rstuvwxyz"
};
public static readonly Dictionary<String, String> DEST_NAMES = new Dictionary<string, string>();
static MergeAndAddFont()
{
DEST_NAMES.Add("A1", "testA_merged1.pdf");
DEST_NAMES.Add("A2", "testA_merged2.pdf");
DEST_NAMES.Add("B1", "testB_merged1.pdf");
DEST_NAMES.Add("B2", "testB_merged2.pdf");
DEST_NAMES.Add("C1", "testC_merged1.pdf");
DEST_NAMES.Add("C2", "testC_merged2.pdf");
}
public static readonly String DEST = "results/sandbox/fonts/";
public static void Main(String[] args)
{
FileInfo file = new FileInfo(DEST);
file.Directory.Create();
new MergeAndAddFont().ManipulatePdf(DEST);
}
public void CreatePdf(String filename, String text, PdfFontFactory.EmbeddingStrategy embeddingStrategy,
bool subset)
{
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(filename));
Document doc = new Document(pdfDoc);
// The 3rd parameter indicates whether the font is to be embedded into the target document.
PdfFont font = PdfFontFactory.CreateFont(FONT, PdfEncodings.WINANSI, embeddingStrategy);
// When set to true, only the used glyphs will be included in the font.
// When set to false, the full font will be included and all subset ranges will be removed.
font.SetSubset(subset);
doc.Add(new Paragraph(text).SetFont(font));
doc.Close();
}
public void MergeFiles(String[] files, String result, bool isSmartModeOn)
{
PdfWriter writer = new PdfWriter(result);
// In smart mode when resources (such as fonts, images,...) are encountered,
// a reference to these resources is saved in a cache and can be reused.
// This mode reduces the file size of the resulting PDF document.
writer.SetSmartMode(isSmartModeOn);
PdfDocument pdfDoc = new PdfDocument(writer);
// This method initializes an outline tree of the document and sets outline mode to true.
pdfDoc.InitializeOutlines();
for (int i = 0; i < files.Length; i++)
{
PdfDocument addedDoc = new PdfDocument(new PdfReader(files[i]));
addedDoc.CopyPagesTo(1, addedDoc.GetNumberOfPages(), pdfDoc);
addedDoc.Close();
}
pdfDoc.Close();
}
protected void ManipulatePdf(String dest)
{
for (int i = 0; i < FILE_A.Length; i++)
{
// Create pdf files with font, which will be embedded into the target document,
// and only the used glyphs will be included in the font.
CreatePdf(FILE_A[i], CONTENT[i],
PdfFontFactory.EmbeddingStrategy.FORCE_EMBEDDED, true);
}
MergeFiles(FILE_A, dest + DEST_NAMES["A1"], false);
MergeFiles(FILE_A, dest + DEST_NAMES["A2"], true);
for (int i = 0; i < FILE_B.Length; i++)
{
// Create pdf files with font, which will embedded into the target document.
// Full font will be included and all subset ranges will be removed
CreatePdf(FILE_B[i], CONTENT[i],
PdfFontFactory.EmbeddingStrategy.FORCE_EMBEDDED, false);
}
MergeFiles(FILE_B, dest + DEST_NAMES["B1"], false);
MergeFiles(FILE_B, dest + DEST_NAMES["B2"], true);
for (int i = 0; i < FILE_C.Length; i++)
{
// Create pdf files with font, which won't be embedded into the target document.
// Full font will be included and all subset ranges will be removed
CreatePdf(FILE_C[i], CONTENT[i],
PdfFontFactory.EmbeddingStrategy.FORCE_NOT_EMBEDDED, false);
}
MergeFiles(FILE_C, dest + DEST_NAMES["C1"], true);
// Embed the font manually
EmbedFont(dest + DEST_NAMES["C1"], FONT, dest + DEST_NAMES["C2"]);
}
protected void EmbedFont(String merged, String fontfile, String result)
{
// The font file
FileStream raf = new FileStream(fontfile, FileMode.Open, FileAccess.Read);
byte[] fontbytes = new byte[(int) raf.Length];
raf.Read(fontbytes, 0, fontbytes.Length);
raf.Close();
// Create a new stream for the font file
PdfStream stream = new PdfStream(fontbytes);
stream.SetCompressionLevel(CompressionConstants.DEFAULT_COMPRESSION);
stream.Put(PdfName.Length1, new PdfNumber(fontbytes.Length));
PdfDocument pdfDoc = new PdfDocument(new PdfReader(merged), new PdfWriter(result));
int numberOfPdfObjects = pdfDoc.GetNumberOfPdfObjects();
// Search for the font dictionary
for (int i = 0; i < numberOfPdfObjects; i++)
{
PdfObject pdfObject = pdfDoc.GetPdfObject(i);
if (pdfObject == null || !pdfObject.IsDictionary())
{
continue;
}
PdfDictionary fontDictionary = (PdfDictionary) pdfObject;
PdfFont font = PdfFontFactory.CreateFont(fontfile, PdfEncodings.WINANSI);
PdfName fontname = new PdfName(font.GetFontProgram().GetFontNames().GetFontName());
if (PdfName.FontDescriptor.Equals(fontDictionary.Get(PdfName.Type))
&& fontname.Equals(fontDictionary.Get(PdfName.FontName)))
{
// Embed the passed font to the pdf document
fontDictionary.Put(PdfName.FontFile2, stream.MakeIndirect(pdfDoc).GetIndirectReference());
}
}
pdfDoc.Close();
}
}
}