为什么当我从 PDF 复制或打印文档时文本“fi”会被剪切?

为什么当我从 PDF 复制或打印文档时文本“fi”会被剪切?

当我从包含以下内容的 Adob​​e Reader PDF 文件复制时

Define an operation

我更愿意看到

Dene an operation

当我粘贴文本时,为什么会出现这种情况?

我怎样才能解决这个恼人的问题呢?

当我用打印机打印 Microsoft Office Word 文件时,我也遇到过这种情况。

答案1

这听起来像是字体问题。PDF 可能使用 OpenTypefi 结扎在单词中define,并且目标应用程序的当前字体缺少该字形。

我不知道是否有一种简单的方法可以让 Acrobat 分解复制的连字符。

您的打印问题可能也与字体有关。某些原因可能允许打印机用其内置字体替换文档的字体,而打印机版本的字体也缺少该特定字形。您必须告诉 Windows 始终将字体下载到打印机才能解决此问题。

打印时的另一种可能性:UniScribe 可能未启用。 微软知识库 2642020讨论了这个问题以及一些可能的解决方法(即使用 RAW 类型打印而不是 EMF 类型打印)。虽然上下文与您的具体问题略有不同,但原因可能相同,并且可能适用相同的解决方法。

答案2

这里的问题是,其他答案注释,带有连字。然而,这与 OpenType 毫无关系。根本问题是 PDF 是一种预印格式,它很少关注内容和语义,而是致力于忠实地呈现打印出来的页面。

文本不是以文本形式布局,而是以字形字体的某些位置。因此,你会得到类似这样的信息:“将字形编号 72 放在那里,将字形编号 101 放在那里,将字形编号 108 放在那里,...”。在这个层面上,从根本上来说,没有文本的概念根本. 这只是描述如何看起来从一堆字形中提取含义有两个问题:

  1. 空间布局。由于 PDF 已经包含了每个字形的具体位置信息,因此它下面没有实际的文本,这很正常。另一个副作用是没有空格。当然,如果你看文本的话,你会发现有空格,但 PDF 中没有。为什么要发出一个空白的字形,而你可以根本不发出任何字形呢?毕竟结果是一样的。因此,PDF 阅读器必须小心地将文本重新拼凑起来,每当遇到字形之间的较大间隙时就插入一个空格。

  2. PDF 渲染的是字形,而不是文本。大多数情况下,字形 ID 与嵌入字体中的 Unicode 代码点或至少 ASCII 代码相对应,这意味着您通常可以很好地恢复 ASCII 或 Latin 1 文本,具体取决于最初创建 PDF 的人(一些乱码一切过程中)。但通常,即使是允许你顺利输出 ASCII 文本的 PDF 也会损坏所有内容。不是ASCII。对于包含以下内容的复杂文字(例如阿拉伯文)尤其糟糕仅有的布局阶段之后的连字和替代字形,这意味着阿拉伯语 PDF 几乎从不包含实际文本

第二个问题和您遇到的问题类似。这里常见的罪魁祸首是 LaTeX,它使用估计有 238982375 种不同的字体(每种字体限制为 256 个字形)来实现其输出。普通文本、数学(使用多种字体)等的不同字体使事情变得非常困难,尤其是 Metafont 比 Unicode 早了近二十年,因此从未有过 Unicode 映射。变音符号也由字母上叠加的分音符呈现,例如,从 PDF 复制时,您会得到 »¨a« 而不是 »ä«(当然也无法搜索它)。

生成 PDF 的应用程序可以选择将实际文本作为元数据包含在内。如果不这样做,您就只能听天由命,看看嵌入字体的处理方式以及 PDF 阅读器是否可以将原始文本重新拼凑起来。但是,如果 »fi« 被复制为空白或根本没有复制,通常是 LaTeX PDF 的标志。您应该将 Unicode 字符画在石头上,然后扔给制作者,希望他们会改用 XeLaTeX,从而最终迎来字符编码和字体标准的 90 年代。

答案3

您可以用原始单词替换大部分“损坏”的单词。如果符合以下条件,则可以安全地替换单词:

  • denerey,这不是一个真正的词
  • 类似definefirefly,有重新添加连字符序列(fffiflffiffl)并形成真实单词的方法

大多数连字符问题都符合这些标准。但是,您不能替换:

  • us因为它是一个真实的词,尽管它最初可能是fluffs
    • affirmbutterflyfieldersfortifiesflimflammisfits...
  • cus因为它可能会cuffs变成ficus
    • stiffed/ stifledrifle/ riffleflung/ fluffing...

这本收录 49.6 万词的英语词典, 有16055包含至少一个fffiflffi或 的单词ffl,可转换为15879单词的连字符被删除后。173那些缺失的单词像cuffs和一样碰撞ficus,最后3是因为该词典包含单词fffifl

790这些“去掉连字符”的单词都是真实单词,例如us,但是15089都是破碎的话语。14960被破坏的单词可以安全地用原来的单词替换,这意味着99.1%破碎的词语是可以修复的,93.2%复制粘贴 PDF 后,可以恢复包含连字符的原始单词。6.8%包含连字序列的单词会因碰撞(cus)和子词(us)而丢失,除非您选择某种方式(单词/文档上下文?)为每个没有保证替换的单词选择最佳替换。

下面是生成上述统计数据的 Python 脚本。它需要一个每行一个单词的字典文本文件。最后,它会写入一个 CSV 文件,将可修复的破损单词映射到其原始单词。

以下是下载 CSV 的链接: http://www.filedropper.com/brokenligaturewordfixes 将此映射与正则表达式替换脚本之类的东西结合起来,以便替换大多数损坏的单词。

import csv
import itertools
import operator
import re


dictionary_file_path = 'dictionary.txt'
broken_word_fixes_file_path = 'broken_word_fixes.csv'
ligatures = 'ffi', 'ffl', 'ff', 'fi', 'fl'


with open(dictionary_file_path, 'r') as dictionary_file:
    dictionary_words = list(set(line.strip()
                                for line in dictionary_file.readlines()))


broken_word_fixes = {}
ligature_words = set()
ligature_removed_words = set()
broken_words = set()
multi_ligature_words = set()


# Find broken word fixes for words with one ligature sequence
# Example: "dene" --> "define"
words_and_ligatures = list(itertools.product(dictionary_words, ligatures))
for i, (word, ligature) in enumerate(words_and_ligatures):
    if i % 50000 == 0:
        print('1-ligature words {percent:.3g}% complete'
              .format(percent=100 * i / len(words_and_ligatures)))
    for ligature_match in re.finditer(ligature, word):
        if word in ligature_words:
            multi_ligature_words.add(word)
        ligature_words.add(word)
        if word == ligature:
            break
        # Skip words that contain a larger ligature
        if (('ffi' in word and ligature != 'ffi') or
                ('ffl' in word and ligature != 'ffl')):
            break
        # Replace ligatures with dots to avoid creating new ligatures
        # Example: "offline" --> "of.ine" to avoid creating "fi"
        ligature_removed_word = (word[:ligature_match.start()] +
                                 '.' +
                                 word[ligature_match.end():])
        # Skip words that contain another ligature
        if any(ligature in ligature_removed_word for ligature in ligatures):
            continue
        ligature_removed_word = ligature_removed_word.replace('.', '')
        ligature_removed_words.add(ligature_removed_word)
        if ligature_removed_word not in dictionary_words:
            broken_word = ligature_removed_word
            broken_words.add(broken_word)
            if broken_word not in broken_word_fixes:
                broken_word_fixes[broken_word] = word
            else:
                # Ignore broken words with multiple possible fixes
                # Example: "cus" --> "cuffs" or "ficus"
                broken_word_fixes[broken_word] = None


# Find broken word fixes for word with multiple ligature sequences
# Example: "rey" --> "firefly"
multi_ligature_words = sorted(multi_ligature_words)
numbers_of_ligatures_in_word = 2, 3
for number_of_ligatures_in_word in numbers_of_ligatures_in_word:
    ligature_lists = itertools.combinations_with_replacement(
        ligatures, r=number_of_ligatures_in_word
    )
    words_and_ligature_lists = list(itertools.product(
        multi_ligature_words, ligature_lists
    ))
    for i, (word, ligature_list) in enumerate(words_and_ligature_lists):
        if i % 1000 == 0:
            print('{n}-ligature words {percent:.3g}% complete'
                  .format(n=number_of_ligatures_in_word,
                          percent=100 * i / len(words_and_ligature_lists)))
        # Skip words that contain a larger ligature
        if (('ffi' in word and 'ffi' not in ligature_list) or
                ('ffl' in word and 'ffl' not in ligature_list)):
            continue
        ligature_removed_word = word
        for ligature in ligature_list:
            ligature_matches = list(re.finditer(ligature, ligature_removed_word))
            if not ligature_matches:
                break
            ligature_match = ligature_matches[0]
            # Replace ligatures with dots to avoid creating new ligatures
            # Example: "offline" --> "of.ine" to avoid creating "fi"
            ligature_removed_word = (
                ligature_removed_word[:ligature_match.start()] +
                '.' +
                ligature_removed_word[ligature_match.end():]
            )
        else:
            # Skip words that contain another ligature
            if any(ligature in ligature_removed_word for ligature in ligatures):
                continue
            ligature_removed_word = ligature_removed_word.replace('.', '')
            ligature_removed_words.add(ligature_removed_word)
            if ligature_removed_word not in dictionary_words:
                broken_word = ligature_removed_word
                broken_words.add(broken_word)
                if broken_word not in broken_word_fixes:
                    broken_word_fixes[broken_word] = word
                else:
                    # Ignore broken words with multiple possible fixes
                    # Example: "ung" --> "flung" or "fluffing"
                    broken_word_fixes[broken_word] = None


# Remove broken words with multiple possible fixes
for broken_word, fixed_word in broken_word_fixes.copy().items():
    if not fixed_word:
        broken_word_fixes.pop(broken_word)


number_of_ligature_words = len(ligature_words)
number_of_ligature_removed_words = len(ligature_removed_words)
number_of_broken_words = len(broken_words)
number_of_fixable_broken_words = len(
    [word for word in set(broken_word_fixes.keys())
     if word and broken_word_fixes[word]]
)
number_of_recoverable_ligature_words = len(
    [word for word in set(broken_word_fixes.values())
     if word]
)
print(number_of_ligature_words, 'ligature words')
print(number_of_ligature_removed_words, 'ligature-removed words')
print(number_of_broken_words, 'broken words')
print(number_of_fixable_broken_words,
      'fixable broken words ({percent:.3g}% fixable)'
      .format(percent=(
      100 * number_of_fixable_broken_words / number_of_broken_words
  )))
print(number_of_recoverable_ligature_words,
      'recoverable ligature words ({percent:.3g}% recoverable)'
      '(for at least one broken word)'
      .format(percent=(
          100 * number_of_recoverable_ligature_words / number_of_ligature_words
      )))


with open(broken_word_fixes_file_path, 'w+', newline='') as broken_word_fixes_file:
    csv_writer = csv.writer(broken_word_fixes_file)
    sorted_broken_word_fixes = sorted(broken_word_fixes.items(),
                                      key=operator.itemgetter(0))
    for broken_word, fixed_word in sorted_broken_word_fixes:
        csv_writer.writerow([broken_word, fixed_word])

答案4

如果您正在生成此 pdf,您可以首先阻止输入连字符。

我从 markdown 和 css 生成 pdf 并包含以下内容,以便我可以将我的文档转换为其他格式 - 包括 word - 而不会丢失 ff、fi、fl 等:

* {
font-variant-ligatures: none;
}

相关内容