iTextSharp重复使用Acrofield中嵌入的字体

时间:2014-03-05 00:19:50

标签: c# .net pdf fonts itextsharp

“iText In Action”中有一些提示,包括设置字体,以及“FontFactory.RegisterDirectories”方法(正如书中所说的那样......一个昂贵的电话)。但是,就我而言,我想用于新字段的字体已经嵌入到文档中(在现有的Acrofield中)。不能保证在用户的机器上(或在Web服务器上)存在相同的字体....有没有办法可以注册已经嵌入的字体,以便我可以将它重新用于其他对象?在下面的代码中,Acrofield“TheFieldIWantTheFontFrom”具有我想要为名为“my_new_field”的字段重用的字体。任何帮助将不胜感激!

        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:\MadScience\MSE_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:\MadScience\MSE_Results.pdf"))
            {
                //byte[] b = outList[i];
                fs.Write(content, 0, (int)content.Length);
                fs.Flush();
            }

        }

1 个答案:

答案 0 :(得分:3)

是的,您可以重复使用字体,PDF规范实际上鼓励它。但是,您应该记住,某些字体可能仅作为子集嵌入。

以下代码改编自this post(小心,该网站有时会出现令人讨厌的弹出窗口)。有关更多信息,请参阅代码中的注释。此代码已针对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;
}

这是运行此代码的测试代码。它首先创建一个带有嵌入字体的示例文档,然后根据该文档创建第二个文档并重新使用该字体。在您的代码中,您需要事先知道您正在搜索的字体名称。如果你没有安装ROCK.TTF(罗克韦尔),你需要选择一个不同的字体文件来运行它。

//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();
            }
        }
    }
}

修改

我对上面的findFontInFontDict方法进行了一些小修改。第二个参数现在是可选的。如果为null,则返回它在提供的字典中找到的第一个字体对象。此更改允许我引入以下方法,该方法按名称查找特定字段并获取字体。

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