【问题标题】:iTextSharp generates corrupted PDF fileiTextSharp 生成损坏的 PDF 文件
【发布时间】:2016-01-25 18:44:35
【问题描述】:

我正在尝试从 HTML 字符串和外部 css 文件生成 PDF 文件并将 PDF 保存到磁盘。从这个例子可以看出,我使用的是非常简单的 html。我知道 css 文件通过查看智能感知被读入 ccsResolver。

这是我正在使用的代码:

internal string Create(PdfDocumentDefinition documentDefinition)
{
    MemoryStream output = new MemoryStream();
    MemoryStream input = new MemoryStream(Encoding.UTF8.GetBytes("<html><head></head><body>Hello, World!</body></html>"));

    string pathName = @WebConfigurationManager.AppSettings["StagingPath"] + documentDefinition.DocumentName + ".pdf";
    Document document = new Document(PageSize.A4, 30, 30, 30, 30);
    PdfWriter writer = PdfWriter.GetInstance(document, output);

    using (output)
    {
        using (document)
        {
            document.Open();

            CssResolverPipeline pipeline = SetCssResolver(documentDefinition.CssFiles, document, writer);

            XMLWorker worker = new XMLWorker(pipeline, true);

            XMLParser parser = new XMLParser(worker);
            parser.Parse(input);

            output.Position = 0;
        }

        Byte[] data = output.ToArray();
        File.WriteAllBytes(pathName, data);
    }

    return pathName;
}

private CssResolverPipeline SetCssResolver(List<String> cssFiles, Document     document, PdfWriter writer)
{            
    var htmlContext = new HtmlPipelineContext(null);
htmlContext.SetTagFactory(iTextSharp.tool.xml.html.Tags.GetHtmlTagProcessorFactory());
    ICSSResolver cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(false);
    if (cssFiles != null)
    {
        foreach (String cssFile in cssFiles)
        {
             //cssResolver.AddCssFile(cssFile, true);
        }
    }

    return new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer)));            
}

这是在 NotePad++ 中查看的输出:

2 0 obj
<</Length 117/Filter/FlateDecode>>stream
xœ+ä*ä2гP€á¢t.c 256U0·0R(JåJã
ĪÊÜÒXÏÔHÁÌBÏÌBÁÐPÏ¢Ø@!¨¤Å)¤ÌÂÐH!$(¬khbè»*€„Ò¸4<RsròuÂó‹rR5C²€Š@J\C€ú¼i!*
endstream
endobj
4 0 obj
<</Type/Page/MediaBox[0 0 595 842]/Resources<</Font<</F1 1 0 R>>>>/Contents 2 0 R/Parent 3 0 R>>
endobj
1 0 obj
<</Type/Font/Subtype/Type1/BaseFont/Helvetica/Encoding/WinAnsiEncoding>>
endobj
3 0 obj
<</Type/Pages/Count 1/Kids[4 0 R]>>
endobj
5 0 obj
<</Type/Catalog/Pages 3 0 R>>
endobj
6 0 obj
<</Producer(iTextSharp’ 5.5.7 ©2000-2015 iText Group NV \(AGPL-version\))/CreationDate(D:20151026102026-05'00')/ModDate(D:20151026102026-05'00')>>
endobj
xref
0 7
0000000000 65535 f 
0000000311 00000 n 
0000000015 00000 n 
0000000399 00000 n 
0000000199 00000 n 
0000000450 00000 n 
0000000495 00000 n 
trailer
<</Size 7/Root 5 0 R/Info 6 0 R/ID [<055082e8139638e35ce08dedae069690><055082e8139638e35ce08dedae069690>]>>
%iText-5.5.7
startxref
657
%%EOF

我已经为此工作了大约 4 个小时。谁能看到为什么它没有生成有效的 PDF?

【问题讨论】:

  • 您想使用FileStream 而不是MemoryStream 谷歌也可以非常有用地帮助您找到示例这里是一个帮助您入门的链接.. mikesdotnetting.com/article/80/…
  • 这对我不起作用。我需要将 HTML 作为字符串发送到方法中。无法从文件中读取。 PDFWriter 类是否不适用于 MemoryStream?
  • 而且,顺便说一句,我在过去 4 个小时的大部分时间里都在搜索 Google。
  • 如果你摆脱output.Position = 0;会发生什么
  • 删除输出。位置根本没有任何区别。

标签: c# pdf-generation itextsharp


【解决方案1】:

感谢 mkl 的提示,我能够解决这个问题,但是,必须以这种方式完成似乎并不正确。一定会有更好的办法。但解决方案是将输出刷新到一个数组以获取前 15 个字节,然后关闭文档并刷新到另一个数组以获取前 15 个字节之后的所有内容(据我所知,输出流永远不会包含所有字节),然后创建第三个数组并将前 2 个复制到其中。完整代码如下:

internal string Create(PdfDocumentDefinition documentDefinition)
{
    string pathName = @WebConfigurationManager.AppSettings["StagingPath"] + documentDefinition.DocumentName + ".pdf";

    MemoryStream input = new MemoryStream(Encoding.UTF8.GetBytes(documentDefinition.Source));

    Document document = new Document(PageSize.A4, 30, 30, 30, 30);
    MemoryStream output = new MemoryStream();
    using (output)
    { 
        PdfWriter writer = PdfWriter.GetInstance(document, output);
        document.Open();

        CssResolverPipeline pipeline = SetCssResolver(documentDefinition.CssFiles, document, writer);

        XMLWorker worker = new XMLWorker(pipeline, true);

        XMLParser parser = new XMLParser(worker);
        parser.Parse(input);

        output.Position = 0;

        Byte[] firstBytes = output.ToArray();

        document.Close();

        Byte[] lastBytes = output.ToArray();
        Byte[] allBytes = new Byte[firstBytes.Length + lastBytes.Length];

        firstBytes.CopyTo(allBytes, 0);
        lastBytes.CopyTo(allBytes, firstBytes.Length);
        File.WriteAllBytes(pathName, allBytes);
    }

    return pathName;
}

private CssResolverPipeline SetCssResolver(List<String> cssFiles, Document document, PdfWriter writer)
{            
    var htmlContext = new HtmlPipelineContext(null);
       htmlContext.SetTagFactory(iTextSharp.tool.xml.html.Tags.GetHtmlTagProcessorFactory());
    ICSSResolver cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(false);
    if (cssFiles != null)
    {
        foreach (String cssFile in cssFiles)
        {
            cssResolver.AddCssFile(cssFile, true);
        }
    }
    return new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer)));            
}

【讨论】:

  • 必须以这种方式完成似乎不对 - 正确。但我从未见过你之前观察到的行为......
  • 有什么可能导致它的想法吗?
  • 我刚刚测试了您原始代码的简化版本。没有那个虚假的output.Position = 0;,输出 PDF 是完整的,而该行的输出被破坏了。这实际上是有道理的,在程序仍在写入流时更改当前流位置可能会破坏流内容。
【解决方案2】:

尝试一下

我将 OP 的原始代码简化为

[Test]
public void ResetStreamPositionAtEndOfUsing()
{
    string outputFilePath = @"test-results\misc\resetStreamPosition.pdf";
    Directory.CreateDirectory(@"test-results\misc\");

    MemoryStream output = new MemoryStream();

    Document document = new Document(PageSize.A4, 30, 30, 30, 30);
    PdfWriter writer = PdfWriter.GetInstance(document, output);

    using (output)
    {
        using (document)
        {
            document.Open();
            document.Add(new Paragraph("Test"));
            output.Position = 0;
        }

        Byte[] data = output.ToArray();
        File.WriteAllBytes(outputFilePath, data);
    }
}

运行它会产生一个无效的 PDF 文件,该文件与 OP 粘贴到问题中的文件几乎相同。特别是缺少 PDF 标题。

按照Chris Haas 的建议,我随后删除了伪线

            output.Position = 0;

事实上,现在输出的 PDF 是有效的,特别是它有它的标题。

分析

MemoryStream output 会发生什么?

    MemoryStream output = new MemoryStream();

output 被创建为空。

    Document document = new Document(PageSize.A4, 30, 30, 30, 30);
    PdfWriter writer = PdfWriter.GetInstance(document, output);

新的PdfWriter只是被实例化了,什么也没写,output还是空的。

    using (output)
    {
        using (document)
        {
            document.Open();

document 通知writer 文档构建开始,所以writer 从编写PDF 序言开始,即标题行和“二进制”注释; output 现在包含 %PDF-1.4\n%âãÏÓ\n,即当前流的末尾位置。

            document.Add(new Paragraph("Test"));

一个新的段落被添加到当前(第一页)页面,但仅在内存中,构成当前页面内容的对象只会在新页面开始或文档完成时写入。 output 仍包含 %PDF-1.4\n%âãÏÓ\n,当前流位置仍在末尾。

            output.Position = 0;

流位置已重置。 output 仍包含 %PDF-1.4\n%âãÏÓ\n,但 当前流位置现在位于开头

        }

这是using (document)代码块的结尾。因此,调用了文档的Dispose 方法。其中document 告诉writer 文档创建已完成。因此,writer 现在将所有文档对象写入内存中,然后添加 PDF 文件尾声(交叉引用、预告片......)。

由于流位置现在位于流的开头,现有内容将被覆盖output 现在包含 2 0 obj...%%EOF,即完整的 PDF 仅缺少 PDF 序言。

【讨论】:

  • 哇!很好的答案,mkl,它工作得很好。不知道为什么在我第一次删除 output.position 语句时它没有任何区别,但那时有很多活动部件。感谢您和 Chris Haas 的所有帮助。
猜你喜欢
  • 2011-09-16
  • 1970-01-01
  • 2011-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-30
  • 1970-01-01
  • 2016-10-02
相关资源
最近更新 更多