【问题标题】:Unable to merge 2 PDFs using MemoryStream无法使用 MemoryStream 合并 2 个 PDF
【发布时间】:2019-12-28 16:37:21
【问题描述】:

我有一个 c# 类,它接受 HTML 并使用 wkhtmltopdf 将其转换为 PDF。
正如您将在下面看到的,我正在生成 3 个 PDF - 横向、纵向和两者的组合。

properties 对象包含作为字符串的 html,以及横向/纵向的参数。

System.IO.MemoryStream PDF = new WkHtmlToPdfConverter().GetPdfStream(properties);
System.IO.FileStream file = new System.IO.FileStream("abc_landscape.pdf", System.IO.FileMode.Create);
PDF.Position = 0;

properties.IsHorizontalOrientation = false;
System.IO.MemoryStream PDF_portrait = new WkHtmlToPdfConverter().GetPdfStream(properties);
System.IO.FileStream file_portrait = new System.IO.FileStream("abc_portrait.pdf", System.IO.FileMode.Create);
PDF_portrait.Position = 0;

System.IO.MemoryStream finalStream = new System.IO.MemoryStream();
PDF.CopyTo(finalStream);
PDF_portrait.CopyTo(finalStream);
System.IO.FileStream file_combined = new System.IO.FileStream("abc_combined.pdf", System.IO.FileMode.Create);

try
{
    PDF.WriteTo(file);
    PDF.Flush();

    PDF_portrait.WriteTo(file_portrait);
    PDF_portrait.Flush();

    finalStream.WriteTo(file_combined);
    finalStream.Flush();
}
catch (Exception)
{
    throw;
}
finally
{
    PDF.Close();
    file.Close();

    PDF_portrait.Close();
    file_portrait.Close();

    finalStream.Close();
    file_combined.Close();
}

PDF“abc_landscape.pdf”和“abc_portrait.pdf”按预期正确生成,但是当我尝试将两者合并为第三个 pdf (abc_combined.pdf) 时操作失败。

我正在使用MemoryStream 进行合并,在调试时,我可以看到finalStream.length 等于前两个PDF 的总和。但是当我尝试打开 PDF 时,我只看到两个 PDF 中的一个的内容。
同样可以在下面看到:

此外,当我尝试关闭“abc_combined.pdf”时,系统会提示我保存它,而其他 2 个 PDF 不会发生这种情况。

以下是我已经尝试过的一些事情,但无济于事:

  1. 将 CopyTo() 更改为 WriteTo()
  2. 将同一 PDF(横向或纵向)与其自身合并

    如果需要,下面是GetPdfStream()方法的详细说明。
var htmlStream = new MemoryStream();
var writer = new StreamWriter(htmlStream);
writer.Write(htmlString);
writer.Flush();
htmlStream.Position = 0;
return htmlStream;

Process process = Process.Start(psi);
process.EnableRaisingEvents = true;
try
{
    process.Start();
    process.BeginErrorReadLine();

    var inputTask = Task.Run(() =>
    {
        htmlStream.CopyTo(process.StandardInput.BaseStream);
        process.StandardInput.Close();
    });

    // Copy the output to a memorystream
    MemoryStream pdf = new MemoryStream();
    var outputTask = Task.Run(() =>
    {
        process.StandardOutput.BaseStream.CopyTo(pdf);
    });

    Task.WaitAll(inputTask, outputTask);

    process.WaitForExit();

    // Reset memorystream read position
    pdf.Position = 0;

    return pdf;
}
catch (Exception ex)
{
    throw ex;
}
finally
{
    process.Dispose();
}

【问题讨论】:

  • Pdf 是一种结构化文件格式,这意味着它由许多微小的部分组成以构建完整的文档。请参阅adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/… 的第 7.5 节,这也是 pdf 阅读器阅读的格式。他们希望在一个文件中找到“标题、正文、交叉引用表、预告片”,但他们却找到了“标题、正文、交叉引用表、预告片、标题、正文、交叉引用表、预告片”。您需要一个理解这种格式的库(最简单),或者自己编写一个(规范在我之前提到的文档中)。
  • @Caramiriel 这很有意义。能否请您将此作为答案。我想将此标记为已解决
  • 重复:stackoverflow.com/q/808670/2441442(赏金时不能关闭)
  • @ChristianGollhardt 虽然上述问题已经通过问题的实现得到了回答,但它并没有告诉我为什么我应该使用库。我正在寻找的答案要么是 Matthew 和 Caramiriel 提供的解释,要么是没有库的代码解决方案(我现在意识到这是一个不合理的期望)。请您重新考虑。谢谢。

标签: c# wkhtmltopdf


【解决方案1】:

如果不使用 3rd 方库,在 C# 或任何其他语言中合并 pdf 并不简单。

我假设您不使用库的要求是大多数免费库、nuget 包都有限制或/并且需要花钱用于商业用途。

我进行了研究,发现一个名为PdfClown 的开源库带有nuget 包,它也可用于Java。它是免费的,没有限制(如果你愿意,可以捐赠)。图书馆有很多features。一种可以将 2 个或更多文档合并到一个文档中。

我提供了我的示例,该示例采用一个包含多个 pdf 文件的文件夹,将其合并并将其保存到相同或另一个文件夹中。也可以使用 MemoryStream,但我觉得在这种情况下没有必要。

代码是自解释的,这里的重点是使用SerializationModeEnum.Incremental

public static void MergePdf(string srcPath, string destFile)
{
    var list = Directory.GetFiles(Path.GetFullPath(srcPath));
    if (string.IsNullOrWhiteSpace(srcPath) || string.IsNullOrWhiteSpace(destFile) || list.Length <= 1)
        return;
    var files = list.Select(File.ReadAllBytes).ToList();
    using (var dest = new org.pdfclown.files.File(new org.pdfclown.bytes.Buffer(files[0])))
    {
        var document = dest.Document;
        var builder = new org.pdfclown.tools.PageManager(document);
        foreach (var file in files.Skip(1))
        {
            using (var src = new org.pdfclown.files.File(new org.pdfclown.bytes.Buffer(file)))
            { builder.Add(src.Document); }
        }

        dest.Save(destFile, SerializationModeEnum.Incremental);
    }
}

测试一下

var srcPath = @"C:\temp\pdf\input";
var destFile = @"c:\temp\pdf\output\merged.pdf";
MergePdf(srcPath, destFile);

输入示例
PDF 文档 A 和 PDF 文档 B

输出示例

我的研究链接:

免责声明:这个答案的一部分来自我的个人网站https://itbackyard.com/merge-multiple-pdf-files-to-one-pdf-file-in-c/,源代码在 github。

【讨论】:

  • 感谢您抽出宝贵时间写下来。选择不使用图书馆是我的前辈给我的决定。您的回答帮助我提供了使用图书馆的部分解释,但正是 Matthew 的回答和 Caramiriel 的评论(关于该问题)帮助我提出了支持图书馆的有力理由,因此我标记了答案跨度>
  • @Sanketh.K.Jain 确定没问题,cmets 链接和 Matthew 的回答确实很有用。但并不知道您正在寻找它的理论部分。
【解决方案2】:

Andrew Burns 来自 Stack Overflow (Combine two (or more) PDF's) 的这个答案对我有用:

        using (PdfDocument one = PdfReader.Open("pdf 1.pdf", PdfDocumentOpenMode.Import))
        using (PdfDocument two = PdfReader.Open("pdf 2.pdf", PdfDocumentOpenMode.Import))
        using (PdfDocument outPdf = new PdfDocument())
        {
            CopyPages(one, outPdf);
            CopyPages(two, outPdf);

            outPdf.Save("file1and2.pdf");
        }

        void CopyPages(PdfDocument from, PdfDocument to)
        {
            for (int i = 0; i < from.PageCount; i++)
            {
                to.AddPage(from.Pages[i]);
            }
        }

【讨论】:

  • 我正在寻找没有 PdfSharp 的东西
  • @Sanketh.K.Jain MemoryStream 独占还是允许使用其他技术? (stackoverflow.com/a/32225966/6925434)
  • 仅 C#。没有其他技术。到目前为止,我将我的 PDF 保存在一个流中,这些 PDF 是作为 wkhtmltopdf 的输出生成的。
  • 当你已经在使用 wkhtmltopdf 时,我不明白为什么你不能使用另一个 nuget 包,但这只是我的看法。
  • 我明白了。但这是我收到的要求:P
【解决方案3】:

这不是 PDF 的工作方式。 PDF 是特定格式的结构化文件。 您不能只是将一个字节附加到另一个字节并期望结果是一个有效的文档。

您将拥有了解格式并可以为您执行操作或开发您自己的解决方案的to use a library

【讨论】:

    【解决方案4】:

    PDF 文件不仅仅是文本和图像。在幕后,有一个严格的文件格式来描述诸如 PDF 版本、文件中包含的对象以及 在哪里可以找到它们

    为了合并 2 个 PDF,您需要操作流。

    首先,您需要仅保留其中一个文件的标头。这很简单,因为它只是第一行。

    然后你可以写第一页的正文,然后是第二页。

    现在最困难的部分,也可能是说服您使用库的部分,是您必须重新构建外部参照表。外部参照表是一个交叉引用表,它描述了文档的内容,更重要的是在哪里可以找到每个元素。您必须计算第二页的字节偏移量,将其外部参照表中的所有元素移动这么多,然后将其外部参照表添加到第一页。您还需要确保在外部参照表中为分页符创建对象。

    完成后,您需要重新构建文档预告片,该预告片会告诉应用程序文档的各个部分在哪里。

    https://resources.infosecinstitute.com/pdf-file-format-basic-structure/

    这不是小事,你最终会重写大量已经存在的代码。

    【讨论】:

    • 真的很欣赏这种洞察力。这帮助我向我的前辈提出了使用 pdf 库的理由。
    • @Sanketh.K.Jain 很高兴!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-16
    • 2013-12-09
    • 1970-01-01
    • 2021-11-16
    • 1970-01-01
    • 2021-03-20
    相关资源
    最近更新 更多