iTextSharp Re-use Font Embedded In Acrofield

与世无争的帅哥 提交于 2019-11-28 11:36:16
Chris Haas

Yes you can re-use fonts and the PDF specification actually encourages it. You should, however, keep in mind that some fonts may be embedded as subsets only.

The below code is adapted from this post (be careful, that site has nasty popups sometimes). See the comments in the code for more information. This code was tested against iTextSharp 5.4.4.

/// <summary>
/// Look for the given font name (not file name) in the supplied PdfReader's AcroForm dictionary.
/// </summary>
/// <param name="reader">An open PdfReader to search for fonts in.</param>
/// <param name="fontName">The font's name as listed in the PDF.</param>
/// <returns>A BaseFont object if the font is found or null.</returns>
static BaseFont findFontInForm(PdfReader reader, String fontName) {
    //Get the document's acroform dictionary
    PdfDictionary acroForm = (PdfDictionary)PdfReader.GetPdfObject(reader.Catalog.Get(PdfName.ACROFORM));

    //Bail if there isn't one
    if (acroForm == null) {
        return null;
    }

    //Get the resource dictionary
    var DR = acroForm.GetAsDict(PdfName.DR);

    //Get the font dictionary (required per spec)
    var FONT = DR.GetAsDict(PdfName.FONT);

    //Look for the actual font and return it
    return findFontInFontDict(FONT, fontName);
}

/// <summary>
/// Helper method to look at a specific font dictionary for a given font string.
/// </summary>
/// <remarks>
/// This method is a helper method and should not be called directly without knowledge of
/// the internals of the PDF spec.
/// </remarks>
/// <param name="fontDict">A /FONT dictionary.</param>
/// <param name="fontName">Optional. The font's name as listed in the PDF. If not supplied then the first font found is returned.</param>
/// <returns>A BaseFont object if the font is found or null.</returns>
static BaseFont findFontInFontDict(PdfDictionary fontDict, string fontName) {
    //This code is adapted from http://osdir.com/ml/java.lib.itext.general/2004-09/msg00018.html
    foreach (var internalFontName in fontDict.Keys) {
        var internalFontDict = (PdfDictionary)PdfReader.GetPdfObject(fontDict.Get(internalFontName));
        var baseFontName = (PdfName)PdfReader.GetPdfObject(internalFontDict.Get(PdfName.BASEFONT));
        //// compare names, ignoring the initial '/' in the baseFontName
        if (fontName == null || baseFontName.ToString().IndexOf(fontName) == 1) {
            var iRef = (PRIndirectReference)fontDict.GetAsIndirectObject(internalFontName);
            if (iRef != null) {
                return BaseFont.CreateFont(iRef);
            }
        }
    }

    return null;
}

And here's the test code that runs this. It first creates a sample document with an embedded font and then it creates a second document based upon that and re-uses that font. In your code you'll need to actually know beforehand what the font name is that you're searching for. If you don't have ROCK.TTF (Rockwell) installed you'll need to pick a different font file to run this.

//Test file that we'll create with an embedded font
var file1 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.pdf");

//Secondary file that we'll try to re-use the font above from
var file2 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test2.pdf");

//Path to font file that we'd like to use
var fontFilePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "ROCK.TTF");

//Create a basefont object
var font = BaseFont.CreateFont(fontFilePath, BaseFont.WINANSI, true);

//Get the name that we're going to be searching for later on.
var searchForFontName = font.PostscriptFontName;

//Step #1 - Create sample document
//The below block creates a sample PDF file with an embedded font in an AcroForm, nothing too special
using (var fs = new FileStream(file1, FileMode.Create, FileAccess.Write, FileShare.None)) {
    using (var doc = new Document()) {
        using (var writer = PdfWriter.GetInstance(doc, fs)) {
            doc.Open();

            //Create our field, set the font and add it to the document
            var tf = new TextField(writer, new iTextSharp.text.Rectangle(50, 50, 400, 150), "first-name");
            tf.Font = font;
            writer.AddAnnotation(tf.GetTextField());

            doc.Close();
        }
    }
}

//Step #2 - Look for font
//This uses a stamper to draw on top of the existing PDF using a font already embedded
using (var fs = new FileStream(file2, FileMode.Create, FileAccess.Write, FileShare.None)) {
    using (var reader = new PdfReader(file1)) {
        using (var stamper = new PdfStamper(reader, fs)) {

            //Try to get the font file
            var f = findFontInForm(reader, searchForFontName);

            //Make sure we found something
            if (f != null) {

                //Draw some text
                var cb = stamper.GetOverContent(1);
                cb.BeginText();
                cb.MoveText(200, 400);
                cb.SetFontAndSize(f, 72);
                cb.ShowText("Hello!");
                cb.EndText();
            }
        }
    }
}

EDIT

I made a small modification to the findFontInFontDict method above. The second parameter is now optional. If null it returns the first font object that it finds in the supplied dictionary. This change allows me to introduce the below method which looks for a specific field by name and gets the font.

static BaseFont findFontByFieldName(PdfReader reader, String fieldName) {
    //Get the document's acroform dictionary
    PdfDictionary acroForm = (PdfDictionary)PdfReader.GetPdfObject(reader.Catalog.Get(PdfName.ACROFORM));

    //Bail if there isn't one
    if (acroForm == null) {
        return null;
    }

    //Get the fields array
    var FIELDS = acroForm.GetAsArray(PdfName.FIELDS);
    if (FIELDS == null || FIELDS.Length == 0) {
        return null;
    }

    //Loop through each field reference
    foreach (var fieldIR in FIELDS) {
        var field = (PdfDictionary)PdfReader.GetPdfObject(fieldIR);

        //Check the field name against the supplied field name
        if (field.GetAsString(PdfName.T).ToString() == fieldName) {

            //Get the resource dictionary
            var DR = acroForm.GetAsDict(PdfName.DR);

            //Get the font dictionary (required per spec)
            var FONT = DR.GetAsDict(PdfName.FONT);

            return findFontInFontDict(FONT);
        }
    }

    return null;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!