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

15

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?

Como posso resolver esse problema irritante?

Também vi isso acontecer no passado, quando imprimo um arquivo do Microsoft Office Word na minha impressora.

Tamara Wijsman
fonte

Respostas:

13

Isso parece um problema de fonte. O PDF provavelmente está usando a fi ligadura OpenType na palavra define, e a fonte atual do aplicativo de destino está sem esse glifo.

Não sei se existe uma maneira fácil de o Acrobat decompor a ligadura na cópia.

Seus problemas com a impressão provavelmente também estão relacionados à fonte. Provavelmente, algo 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 está faltando esse glifo específico. Você precisaria informar ao Windows para sempre baixar fontes para a impressora para solucionar esse problema.

Outra possibilidade ao imprimir: o UniScribe pode não estar ativado. O MS KB 2642020 fala sobre isso e algumas soluções possíveis (a saber, 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.

afrazier
fonte
1
Interessante sobre as ligaduras, gostaria de saber se, de alguma forma, pode ser configurado para se comportar corretamente. Talvez eu possa ver como outros leitores de PDF se comportam. Onde exatamente eu o configuro para que as fontes sejam enviadas para a impressora?
Tamara Wijsman
1
Na caixa de diálogo de impressão de um aplicativo: Clique em Properties(ou Preferences, dependendo da versão da caixa de diálogo) da impressora, verifique se está nas guias Layoutou Quality, clique no Advancedbotão. No Graphicgrupo, altere a TrueType Fontopção para Download as Softfont. Isso abrange a maioria das impressoras PostScript e impressoras que usam caixas de diálogo internas do Windows (eu acho), mas outros drivers podem ter algumas coisas deslocadas ou ausentes.
afrazier
Você pode encontrar o MS KB 2642020 de alguma utilidade. Eu editei minha resposta com essa informação.
afrazier
Obrigado por descrever o problema. Ainda não tentei resolver isso, mas certamente tentarei quando encontrar um problema de impressão novamente. Eu acho que uma das duas soluções seria certeza de resolver este problema muito específico ... :)
Tamara Wijsman
@afrazier, a solução que você escreveu no seu comentário, começando em "Na caixa de diálogo de impressão de um aplicativo:" funcionou para mim. Sugiro colocar esse texto em sua resposta. (Eu poderia editá-lo, mas acho que a decisão deve ser até você.)
Alan
9

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

  • gosto deneou rey, não é uma palavra real
  • como defineou firefly, há uma maneira de sequeneces re-add ligadura ( ff, fi, fl, ffi, ou ffl) e fazer uma palavra real

A maioria dos problemas de ligadura se encaixa 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...
  • cusporque poderia se tornar um cuffsouficus
    • também stiffed/ stifled, rifle/ riffle, flung/ fluffing...

No presente 496 mil palavras dicionário Inglês , 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 dessas palavras que faltam colidiram como cuffse ficus, e o último 3 são porque esse dicionário contém as palavras ff, fie fl.

790 dessas palavras "removidas por ligadura" são palavras reais us, mas 15089 são palavras quebradas. 14960 das palavras quebradas podem ser substituídas com segurança pela palavra original, o que significa que 99,1% das palavras quebradas são corrigíveis e 93,2% das palavras originais que contêm uma ligadura podem ser recuperadas depois de copiar e colar um PDF. 6,8% das palavras que contêm sequências de ligaduras são perdidas para as colisões ( cus) e sub-palavras ( us), a menos que você escolha alguma forma (contexto palavra / documento?) Para escolher o melhor substituto para cada uma das palavras que não têm garantia garantida substituição.

Abaixo está o meu script Python que gerou as estatísticas acima. Ele 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 as palavras originais.

Aqui está um link para baixar o CSV: http://www.filedropper.com/brokenligaturewordfixes Combine esse mapeamento com algo como 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])
Jan Van Bruggen
fonte
O link para o .csvestá quebrado. Seria ótimo se você pudesse enviá-lo novamente! De qualquer forma, obrigado pelo código.
precisa saber é
@Enora Voltei a carregar o CSV no mesmo link - espero que ajude! Também notei alguns problemas no código / resultados (usando pontos como espaços reservados enquanto o novo dicionário tem pontos em suas palavras, e não em minúsculas antes de compará-los). Acredito que todas as substituições estão corretas, mas leve-as com um pouco de sal e saiba que são possíveis mais substituições boas. Eu recomendo automatizar as substituições com regex, mas depois confirmar que cada substituição é boa com seus próprios olhos.
Jan Van Bruggen
7

A questão aqui é, como a outra resposta observa, 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 pouco 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 séries de glifos de uma fonte em determinadas posições. Então você obtém algo como »Coloque o número 72, o número 101, o número 108, ...«. Nesse nível não é fundamentalmente nenhuma noção de texto em tudo . É apenas uma descrição da aparência . Há dois problemas para extrair significado de vários glifos:

  1. O layout espacial. Como o PDF já contém informações específicas 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, mas não no PDF. Por que emitir um glifo em branco quando você não pode emitir nenhum? O resultado é o mesmo, afinal. Portanto, os leitores de PDF precisam reunir cuidadosamente o texto novamente, inserindo um espaço sempre que encontrarem um espaço maior entre os glifos.

  2. O 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 é possível recuperar o texto ASCII ou Latin 1 com bastante frequência, dependendo de quem criou o PDF em primeiro lugar (alguns garble tudo no processo). Porém, muitas vezes até os PDFs que permitem que você publique textos ASCII com precisão irão alterar tudo que não é ASCII. Especialmente horrível com scripts complexos, como o árabe, que contêm apenas ligaduras e glifos alternativos 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 delas restrita a 256 glifos) para obter sua saída. Fontes diferentes para texto normal, matemática (usa mais de uma) 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. Os trema também são renderizados por uma diérese sobreposta a uma carta, por exemplo, 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 optar por incluir o texto real como metadados. Caso contrário, você ficará à mercê de como as fontes incorporadas são tratadas e se o leitor de PDF pode reunir o texto original novamente. Mas "fi" ser copiado em branco ou não é geralmente um sinal de um PDF do LaTeX. Você deve pintar caracteres Unicode em pedras e jogá-los no produtor, esperando que eles mudem para o XeLaTeX e, finalmente, chegando na década de 90 de codificação de caracteres e padrões de fonte.

Joey
fonte