Skip to main content
Skip table of contents

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();
        }
    }
}
JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.