【问题标题】:Convert PDF text into outlines?将 PDF 文本转换为大纲?
【发布时间】:2015-01-07 10:04:00
【问题描述】:

有人知道如何将 PDF 文档中的文本矢量化吗?也就是说,我希望每个字母都是一个形状/轮廓,没有任何文字内容。我使用的是 Linux 系统,最好使用开源或非 Windows 解决方案。

上下文:我正在尝试编辑一些旧的 PDF,但我不再拥有这些字体。我想在 Inkscape 中这样做,但这会用通用字体替换所有字体,而且几乎不可读。我也一直在使用pdf2psps2pdf 来回转换,但字体信息仍然存在。所以当我将它加载到 Inkscape 中时,它看起来仍然很糟糕。

有什么想法吗?谢谢。

【问题讨论】:

    标签: pdf inkscape


    【解决方案1】:

    要实现这一点,您必须:

    1. 将您的 PDF 拆分成单独的页面;
    2. 将您的 PDF 页面转换为 SVG;
    3. 编辑您想要的页面
    4. 重新组合页面

    此答案将省略第 3 步,因为这不是可编程的。

    拆分 PDF

    如果您不希望以编程方式拆分文档,现代方式将是使用stapler。在你最喜欢的外壳中:

    stapler burst file.pdf
    

    将生成{file_1.pdf,...,file_N.pdf},其中1...N 是PDF 页面。 Stapler 本身使用PyPDF2 并且用于拆分 PDF 文件的代码并不那么复杂。以下函数拆分文件并将各个页面保存在当前目录中。 (无耻地抄袭commands.py文件)

    import math
    import os
    from PyPDF2 import PdfFileWriter, PdfFileReader
    
    def split(filename):
        with open(filename) as inputfp:
            inputpdf = PdfFileReader(inputfp)
    
            base, ext = os.path.splitext(os.path.basename(filename))
    
            # Prefix the output template with zeros so that ordering is preserved
            # (page 10 after page 09)
            output_template = ''.join([
                base,
                '_',
                '%0',
                str(math.ceil(math.log10(inputpdf.getNumPages()))),
                'd',
                ext
            ])
    
            for page in range(inputpdf.getNumPages()):
                outputpdf = PdfFileWriter()
                outputpdf.addPage(inputpdf.getPage(page))
    
                outputname = output_template % (page + 1)
    
                with open(outputname, 'wb') as fp:
                    outputpdf.write(fp)
    

    将单个页面转换为 SVG

    现在要将 PDF 转换为可编辑文件,我可能会使用 pdf2svg

    pdf2svg input.pdf output.svg
    

    如果我们看一下pdf2svg.c文件,我们可以看到,原则上代码并没有那么复杂(假设输入文件名在filename变量中,输出文件名在outputname多变的)。下面是一个 Python 中的最小工作示例。它需要 pycairopypoppler 库:

    import os
    
    import cairo
    import poppler
    
    def convert(inputname, outputname):
        # Convert the input file name to an URI to please poppler
        uri = 'file://' + os.path.abspath(inputname)
    
        pdffile = poppler.document_new_from_file(uri, None)
    
        # We only have one page, since we split prior to converting. Get the page
        page = pdffile.get_page(0)
    
        # Get the page dimensions
        width, height = page.get_size()
    
        # Open the SVG file to write on
        surface = cairo.SVGSurface(outputname, width, height)
        context = cairo.Context(surface)
    
        # Now we finally can render the PDF to SVG
        page.render_for_printing(context)
        context.show_page()
    

    此时您应该拥有一个所有文本都已转换为路径的 SVG,并且能够使用 Inkscape 进行编辑而不会出现渲染问题。

    结合步骤 1 和 2

    您可以在 for 循环中调用 pdf2svg 来执行此操作。但是您需要事先知道页数。下面的代码计算了页数,并一步完成了转换。它只需要 pycairo 和 pypoppler:

    import os, math
    
    import cairo
    import poppler
    
    def convert(inputname, base=None):
        '''Converts a multi-page PDF to multiple SVG files.
    
        :param inputname: Name of the PDF to be converted
        :param base: Base name for the SVG files (optional)
        '''
        if base is None:
            base, ext = os.path.splitext(os.path.basename(inputname))
    
        # Convert the input file name to an URI to please poppler
        uri = 'file://' + os.path.abspath(inputname)
    
        pdffile = poppler.document_new_from_file(uri, None)
    
        pages = pdffile.get_n_pages()
    
        # Prefix the output template with zeros so that ordering is preserved
        # (page 10 after page 09)
        output_template = ''.join([
            base,
            '_',
            '%0',
            str(math.ceil(math.log10(pages))),
            'd',
            '.svg'
        ])
    
        # Iterate over all pages
        for nthpage in range(pages):
            page = pdffile.get_page(nthpage)
    
            # Output file name based on template
            outputname = output_template % (nthpage + 1)
    
            # Get the page dimensions
            width, height = page.get_size()
    
            # Open the SVG file to write on
            surface = cairo.SVGSurface(outputname, width, height)
            context = cairo.Context(surface)
    
            # Now we finally can render the PDF to SVG
            page.render_for_printing(context)
            context.show_page()
    
            # Free some memory
            surface.finish()
    

    将 SVG 组装成一个 PDF

    要重新组装,您可以使用成对的 inkscape / 订书机手动转换文件。但是编写执行此操作的代码并不难。下面的代码使用 rsvg 和 cairo。从 SVG 转换并将所有内容合并为一个 PDF:

    import rsvg
    import cairo
    
    def convert_merge(inputfiles, outputname):
        # We have to create a PDF surface and inform a size. The size is
        # irrelevant, though, as we will define the sizes of each page
        # individually.
        outputsurface = cairo.PDFSurface(outputname, 1, 1)
        outputcontext = cairo.Context(outputsurface)
    
        for inputfile in inputfiles:
            # Open the SVG
            svg = rsvg.Handle(file=inputfile)
    
            # Set the size of the page itself
            outputsurface.set_size(svg.props.width, svg.props.height)
    
            # Draw on the PDF
            svg.render_cairo(outputcontext)
    
            # Finish the page and start a new one
            outputcontext.show_page()
    
        # Free some memory
        outputsurface.finish()
    

    PS:应该可以使用命令pdftocairo,但是好像没有调用render_for_printing(),这使得输出的SVG保持了字体信息。

    【讨论】:

    • 这条评论让我走上了正轨,但是对于一个小的 pdf(2 页)来说,执行起来要简单得多:在 Document viewerPrint to File、Pages:1 中打开 pdf(等等on), Output format: SVG (注意页面设置有正确的尺寸和 0 边距) [你的步骤 1. 和 2.] ;在 Inskape 和 save as pdf [步骤 3.] 中编辑;在命令提示符pdfunite page1.pdf page2.pdf doc.pdf [步骤 4.]
    【解决方案2】:

    对于追随我的人: 我发现的最佳解决方案是使用 Evince 打印为 SVG,或使用可通过 Mint 上的 Synaptic 访问的 pdf2svg 程序。但是,Inkscape 无法处理生成的 SVG——它进入了一个无限循环并显示错误消息:

    File display/nr-arena-item.cpp line 323 (?): Assertion item->state & NR_ARENA_ITEM_STATE_BBOX failed

    我现在要放弃这个任务,但也许我会在一两年内再试一次。与此同时,也许其中一种解决方案对您有用。

    【讨论】:

      【解决方案3】:

      这就是您真正想要的 - 字体替换。您希望某些代码/应用程序能够遍历文件并对嵌入的字体进行适当的更改。

      这项任务是可行的,而且从简单到不平凡。当您的字体与文件中字体的度量相匹配并且用于字体的编码是健全的时,这很容易。您可能可以使用 iText 或 DotPdf 来执行此操作(后者在评估之外不是免费的,并且是我公司的产品)。如果您修改了 pdf2ps,您可能也可以在通过的过程中更改字体。

      如果文件中使用的字体是具有创造性重新编码的字体子集,那么您将陷入困境,并且在进行更改时可能会遇到各种痛苦。原因如下:

      PostScript 是在没有 Unicode 的时候设计的。 Adobe 使用单个字节来表示字符,并且无论何时渲染任何字符串,要绘制的字形都取自称为编码向量的 256 个条目表。如果标准编码没有您想要的,我们鼓励您根据仅在编码上有所不同的标准字体即时制作字体。

      当 Adob​​e 创建 Acrobat 时,他们希望尽可能轻松地从 PostScript 转换,以便对字体机制进行建模。当添加将字体嵌入 PDF 的功能时,很明显这会使文件膨胀,因此 PDF 还包括具有字体子集的功能。字体子集是通过采用现有字体并删除所有不会使用的字形并将其重新编码到 PDF 中来制作的。编码向量和文件中的代码点之间可能没有标准关系——所有这些都可能被改变。相反,可能有一个嵌入式 PostScript 函数 /ToUnicode,它将编码字符转换为 Unicode 表示。

      是的,这很重要。

      【讨论】:

      • 谢谢——这可能是最好的答案。不过,这绝对不是微不足道的。
      • 请注意,PDF 格式被设计为最终用户格式。即使是像“复制文本”这样看似微不足道的事情,也只是不在原始规范中——通常,您只是被告知回到原始软件并记录它的创建来源。
      • 小问题 - 因为我为 Acrobat 编写了原始文本选择/搜索/复制代码,我可以告诉你它在 1.0 版的规范中
      【解决方案4】:

      恐怕要对仍需要原始字体(或大量工作)的 PDF 进行矢量化处理。

      想到的一些可能性:

      • 使用 pdftk 转储未压缩的 PDF 并找出字体名称,然后在 FontMonster 或 other 字体服务上查找它们。

      • 使用一些online font recognition service 与您的字体紧密匹配,以保持字距调整(我猜字距调整和对齐是导致您的文本不可读的原因)

      • 尝试replacing the fonts manually(再次pdftk 将PDF 转换为可使用sed 编辑的PDF。此编辑将破坏 PDF,但pdftk 将随后能够将损坏的 PDF 重新压缩为可用的)。

      【讨论】:

      • 这很不幸。你能解释一下为什么会这样吗?似乎如果你打印出一个文档,something 知道所有字符的轮廓,因此它可以正确呈现。因此,如果您最喜欢的 PDF 查看程序可以使用该信息,为什么不能将其用于将所有字母转换为轮廓?
      • 你是对的,有些事情是知道的。字体至少部分嵌入在 PDF 中。但是让它们出来并不简单:我自己会建议使用 Inkscape。这个答案 stackoverflow.com/questions/17225321/… 指的是我从未使用过的 ABCpdf。也许你可以试一试。失败的 Inkscape,在我看来寻找类似的字体是最好的选择。
      猜你喜欢
      • 2015-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多