Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
394 views
in Technique[技术] by (71.8m points)

c# - iTextSharp Re-use Font Embedded In Acrofield

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). However, in my case, the font that I want to use for new fields is already embedded in the document (in an existing Acrofield). With no guarantee that the same font will exist on the user's machine (or on a web server)....is there a way that I can register that already-embedded font, so that I can re-use it for other objects? In the code below, Acrofield "TheFieldIWantTheFontFrom" has the font that I want to re-use for a field named "my_new_field". Any help would be greatly appreciated!

        using (MemoryStream output = new MemoryStream())
        {

            // Use iTextSharp PDF Reader, to get the fields and send to the 
            //Stamper to set the fields in the document
            PdfReader pdfReader = new PdfReader(@"C:MadScienceMSE_030414.pdf");

            // Initialize Stamper (ms is a MemoryStream object)
            PdfStamper pdfStamper = new PdfStamper(pdfReader, output);

            // Get Reference to PDF Document Fields
            AcroFields pdfFormFields = pdfStamper.AcroFields;

            //*** CODE THAT HAVE NOT YET BEEN ABLE TO MAKE USE OF TO ASSIST WITH MY FONT ISSUE
            //*** MIGHT BE HELP?
            //List<object[]> fonts = BaseFont.GetDocumentFonts(pdfReader);
            //BaseFont[] baseFonts = new BaseFont[fonts.Count];
            //string[] fn = new string[fonts.Count];
            //for (int i = 0; i < fonts.Count; i++)
            //{
            //    Object[] obj = (Object[])fonts[i];
            //    baseFonts[i] = BaseFont.CreateFont((PRIndirectReference)(obj[1]));
            //    fn[i] = baseFonts[i].PostscriptFontName.ToString();
            //    //Console.WriteLine(baseFonts[i].FamilyFontName[0][1].ToString());
            //    //FontFactory.RegisteredFonts.Add(fn[i]);
            //    //FontFactory.Register(
            //    Console.WriteLine(fn[i]);
            //}

            //ICollection<string> registeredFonts = iTextSharp.text.FontFactory.RegisteredFonts;

            //foreach (string s in registeredFonts)
            //{
            //    Console.WriteLine("pre-registered:  " + s);
            //}

            if (!FontFactory.Contains("georgia-bold"))
            {
                FontFactory.RegisterDirectories();
                Console.WriteLine("had to register everything");               }

            //registeredFonts = iTextSharp.text.FontFactory.RegisteredFonts;

            //foreach (string s in registeredFonts)
            //{
            //    Console.WriteLine("post-registered:  " + s);
            //}

            Font myfont = FontFactory.GetFont("georgia-bold");

            string nameOfField = "my_field";

            AcroFields.Item fld = pdfFormFields.GetFieldItem(nameOfField);

            //set the text of the form field
            pdfFormFields.SetField(nameOfField, "test stuff");

            pdfFormFields.SetField("TheFieldIWantTheFontFrom", "test more stuff");

            bool madeit = pdfFormFields.SetFieldProperty(nameOfField, "textfont", myfont.BaseFont, null);

            bool madeit2 = pdfFormFields.SetFieldProperty(nameOfField, "textsize", 8f, null);

            pdfFormFields.RegenerateField(nameOfField);

            // Set the flattening flag to false, so the document can continue to be edited
            pdfStamper.FormFlattening = true;

            // close the pdf stamper
            pdfStamper.Close();

            //get the bytes from the MemoryStream
            byte[] content = output.ToArray();

            using (FileStream fs = File.Create(@"C:MadScienceMSE_Results.pdf"))
            {
                //byte[] b = outList[i];
                fs.Write(content, 0, (int)content.Length);
                fs.Flush();
            }

        }
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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;
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...