Por que o texto 'fi' é cortado quando copio de um PDF ou imprimo um documento?

13

Quando copio de um arquivo PDF do Adobe Reader que contém

Define an operation

Eu prefiro ver

Dene an operation

quando colo o texto, por que isso acontece?

Como posso resolver este problema irritante?

Eu também vi isso ocorrer no passado quando imprimi um arquivo do Microsoft Office Word na minha impressora.

    
por Tom Wijsman 06.01.2012 / 16:45

3 respostas

13

Isso soa como um problema de fonte. O PDF provavelmente está usando o OpenType fi ligadura na palavra define e a fonte atual do aplicativo de destino está faltando esse glifo.

Não sei se há uma maneira fácil de fazer com que o Acrobat decomponha a ligadura na cópia.

Seus problemas com a impressão provavelmente também estão relacionados à fonte. Algo provavelmente está permitindo que a impressora substitua a fonte do documento por suas próprias fontes internas e a versão da fonte da impressora também não tenha esse glifo específico. Você teria que dizer ao Windows para sempre baixar fontes para a impressora para contornar este problema.

Outra possibilidade ao imprimir: O UniScribe pode não estar ativado. O MS KB 2642020 fala sobre isso e algumas possíveis soluções alternativas (ou seja, para usar a impressão do tipo RAW em vez da impressão do tipo EMF). Embora o contexto seja um pouco diferente do seu problema específico, a causa pode ser a mesma e as mesmas soluções alternativas podem ser aplicadas.

    
por 06.01.2012 / 17:08
9

Você pode substituir a maioria dessas palavras "quebradas" pelos originais. Você pode substituir uma palavra com segurança se:

  • como dene ou rey , não é uma palavra real
  • como define ou firefly , há uma maneira de adicionar novamente sequências de ligadura ( ff , fi , fl , ffi ou ffl ) e fazer uma palavra real

A maioria dos problemas de ligadura se encaixam nesses critérios. No entanto, você não pode substituir:

  • us porque é uma palavra real, mesmo que originalmente tenha sido fluffs
    • também affirm , butterfly , fielders , fortifies , flimflam , misfits ...
  • cus porque pode tornar-se cuffs ou ficus
    • também stiffed / stifled , rifle / riffle , flung / fluffing ...

Em este dicionário de inglês com 496 mil palavras , existem 16055 palavras que contêm pelo menos um ff , fi , fl , ffi ou ffl , que se transformam em 15879 palavras quando suas ligaduras são removidas. 173 das palavras em falta colididas como cuffs e ficus e as últimas 3 porque o dicionário contém as palavras ff , fi e fl .

790 destas palavras "removidas por ligadura" são palavras reais, como us , mas 15089 são palavras quebradas. 14960 das palavras quebradas pode ser seguramente substituído pela palavra original, o que significa que 99,1% das palavras quebradas são fixáveis e 93,2% do as palavras originais que contêm uma ligadura podem ser recuperadas após copiar e colar um PDF. 6,8% de palavras contendo sequências de ligaduras são perdidas para as colisões ( cus ) e subpalavras ( us ), a menos que você escolha alguma forma (contexto de palavra / documento?) para escolher a melhor substituição para cada uma das palavras que não tenham uma substituição garantida.

Abaixo está meu script Python que gerou as estatísticas acima. Espera um arquivo de texto do dicionário com uma palavra por linha. No final, ele grava um arquivo CSV que mapeia palavras quebradas corrigíveis para suas palavras originais.

Aqui está um link para baixar o CSV: link Combine esse mapeamento com algo parecido com um script de substituição de regex para substituir a maioria das palavras quebradas.

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])
    
por 28.08.2015 / 09:41
4

A questão aqui é, como observa a outra resposta , com ligaduras. No entanto, não tem nada a ver com o OpenType. O problema fundamental é que os PDFs são um formato de pré-impressão que se preocupa apenas com conteúdo e semântica, mas é voltado para representar fielmente uma página como seria impressa.

O texto é apresentado não como texto, mas como execuções de glifos a partir de uma fonte em determinadas posições. Então você tem algo como: »Coloque o número de glifo 72 lá, o número de glifo 101 lá, o número de glifo 108 lá, ...«. Nesse nível, fundamentalmente não há noção de texto em todos os . É apenas uma descrição de como é que ele é . Existem dois problemas para extrair significado de um monte de glifos:

  1. O layout espacial. Como o PDF já contém informações específicas sobre onde colocar cada glifo, não há texto real subjacente, como seria normal. Outro efeito colateral é que não há espaços. Claro, se você olhar para o texto existem, mas não no PDF. Por que emitir um glifo em branco quando você poderia simplesmente emitir nenhum? O resultado é o mesmo, afinal. Portanto, os leitores de PDF têm que juntar cuidadosamente o texto novamente, inserindo um espaço sempre que encontrarem uma lacuna maior entre os glifos.

  2. PDF renderiza glifos, não texto. Na maioria das vezes, os IDs de glifo correspondem a pontos de código Unicode ou pelo menos códigos ASCII nas fontes incorporadas, o que significa que você pode obter texto ASCII ou Latin 1 de volta, dependendo de quem criou o PDF (alguns garble tudo no processo). Mas, muitas vezes, mesmo os PDFs que permitem que você obtenha texto ASCII muito bem irão mangle tudo o que não é ASCII. Especialmente horrível com scripts complexos, como o árabe, que contêm apenas ligaturas e glifos alternados após o estágio de layout, o que significa que os PDFs em árabe quase nunca contêm texto real

O segundo problema é como o que você enfrenta. Um culpado comum aqui é o LaTeX, que utiliza um número estimado de 238982375 fontes diferentes (cada uma das quais é restrita a 256 glifos) para alcançar sua saída. Fontes diferentes para texto normal, matemática (usa mais de um), etc. tornam as coisas muito difíceis, especialmente porque o Metafont antecede o Unicode por quase duas décadas e, portanto, nunca houve um mapeamento Unicode. Umlauts também são processados por um diaeresis sobreposto em uma carta, e. você obtém »¨a« em vez de »ä« ao copiar de um PDF (e, claro, também não pode procurá-lo).

Os aplicativos que produzem PDFs podem incluir o texto real como metadados. Caso contrário, você fica à mercê de como as fontes incorporadas são manipuladas e se o leitor de PDF pode juntar o texto original novamente. Mas "fi" sendo copiado em branco ou não é geralmente um sinal de um PDF LaTeX. Você deve pintar caracteres Unicode em pedras e jogá-los no produtor, esperando que eles mudem para o XeLaTeX e, assim, cheguem finalmente à década de 1990 das codificações de caracteres e padrões de fonte.

    
por 16.12.2012 / 14:33