iTextSharp Re-use Font Embedded In Acrofield

后端 未结 1 459
醉梦人生
醉梦人生 2020-12-10 20:11

There are tips in \"iText In Action\" that cover setting fonts, as well as the \"FontFactory.RegisterDirectories\" method (which is, as the book says...an expensive call).

相关标签:
1条回答
  • 2020-12-10 20:57

    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;
    }
    
    0 讨论(0)
提交回复
热议问题