¿Por qué se corta el texto `fi` cuando copio de un PDF o imprimo un documento?

15

Cuando copio de un archivo PDF de Adobe Reader que contiene

Define an operation

Prefiero ver

Dene an operation

cuando pego el texto, ¿por qué es esto?

¿Cómo puedo remediar este molesto problema?

También he visto que esto ocurre en el pasado cuando imprimo un archivo de Microsoft Office Word en mi impresora.

Tamara Wijsman
fuente

Respuestas:

13

Esto suena como un problema de fuente. El PDF probablemente esté usando la fi ligadura OpenType en la palabra define, y la fuente actual de la aplicación de destino no tiene ese glifo.

No sé si hay una manera fácil de hacer que Acrobat descomponga la ligadura en la copia.

Sus problemas con la impresión probablemente también estén relacionados con la fuente. Probablemente, algo esté permitiendo que la impresora sustituya la fuente del documento con sus propias fuentes incorporadas y la versión de la impresora de la fuente también le falta ese glifo en particular. Tendría que decirle a Windows que siempre descargue las fuentes a la impresora para solucionar este problema.

Otra posibilidad al imprimir: UniScribe puede no estar habilitado. MS KB 2642020 habla sobre esto y algunas posibles soluciones (es decir, utilizar la impresión de tipo RAW en lugar de la impresión de tipo EMF). Aunque el contexto es ligeramente diferente de su problema específico, la causa puede ser la misma y pueden aplicarse las mismas soluciones.

afrazier
fuente
1
Interesante sobre las ligaduras, me pregunto si de alguna manera se puede configurar para que se comporte correctamente. Quizás podría ver cómo se comportan otros lectores de PDF. ¿Dónde lo configuro exactamente para que las fuentes se envíen a la impresora?
Tamara Wijsman
1
Desde el cuadro de diálogo de impresión de una aplicación: haga clic Properties(o Preferences, según la versión del cuadro de diálogo) para la impresora, asegúrese de estar en las pestañas Layouto Quality, haga clic en el Advancedbotón. En el Graphicgrupo, cambie la TrueType Fontopción a Download as Softfont. Esto cubre la mayoría de las impresoras PostScript e impresoras que usan cuadros de diálogo integrados de Windows (creo), pero otros controladores pueden tener cosas movidas o faltan por completo.
Afrazier
Puede encontrar MS KB 2642020 de alguna utilidad. He editado mi respuesta con esa información.
Afrazier
Gracias por describir el problema. Todavía no he intentado resolver esto, pero seguro lo intentaré cuando vuelva a encontrar un problema de impresión. Supongo que una de las dos soluciones seguramente resolvería este problema muy específico ... :)
Tamara Wijsman
@afrazier, la solución que escribió en su comentario comenzando "Desde el diálogo de impresión de una aplicación:" funcionó para mí. Sugiero poner ese texto en tu respuesta. (Podría editarlo, pero creo que la decisión debería depender de usted.)
Alan
9

Puede reemplazar la mayoría de estas palabras "rotas" con los originales. Puede reemplazar con seguridad una palabra si:

  • como deneo rey, no es una palabra real
  • al igual que defineo fireflyhay una manera de volver a agregar sequeneces de ligadura ( ff, fi, fl, ffi, o ffl) y hacer una palabra real

La mayoría de los problemas de ligadura se ajustan a estos criterios. Sin embargo, no puede reemplazar:

  • us porque es una palabra real, aunque originalmente podría haber sido fluffs
    • también affirm, butterfly` fielders` fortifies` flimflam` misfits...
  • cusporque podría convertirse ya sea en cuffsoficus
    • también stiffed/ stifled, rifle/ riffle, flung/ fluffing...

En este 496 mil palabras diccionario Inglés , hay 16055 palabras que contienen al menos un ff, fi, fl, ffi, o ffl, que se convierten en 15879 palabras, cuando se quitan sus ligaduras. 173 de esas palabras que faltan chocaron como cuffsy ficus, y el último 3 se deben a que el diccionario contiene las palabras ff, fiy fl.

790 de estas palabras "eliminadas por ligadura" son palabras reales, como us, pero 15089 son palabras rotas. 14960 de las palabras rotas se pueden reemplazar de forma segura con la palabra original, lo que significa que el 99.1% de las palabras rotas son reparables y el 93.2% de las palabras originales que contienen una ligadura se pueden recuperar después de copiar y pegar un PDF. El 6,8% de las palabras que contienen secuencias de ligadura se pierden en las colisiones ( cus) y sub-palabras ( us), a menos que elija alguna forma (¿contexto de palabra / documento?) Para elegir el mejor reemplazo para cada una de las palabras que no tienen una garantía reemplazo.

A continuación se muestra mi script de Python que generó las estadísticas anteriores. Espera un archivo de texto de diccionario con una palabra por línea. Al final, escribe un archivo CSV que asigna palabras rotas reparables a sus palabras originales.

Aquí hay un enlace para descargar el CSV: http://www.filedropper.com/brokenligaturewordfixes Combine esta asignación con algo así como un script de reemplazo de expresiones regulares para reemplazar la mayoría de las palabras rotas.

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
fuente
El enlace al .csvestá roto. ¡Eso sería genial si pudieras subirlo de nuevo! En cualquier caso, gracias por el código.
MagTun
@Enora Recargué el CSV en el mismo enlace, ¡espero que ayude! También noté algunos problemas en el código / resultados (usando puntos como marcadores de posición mientras que el nuevo diccionario tiene puntos en sus palabras, y no minúsculas antes de compararlas). Creo que todos los reemplazos son correctos, pero tómelos con un grano de sal y sepa que son posibles más reemplazos buenos. Recomiendo automatizar los reemplazos con expresiones regulares, pero luego confirmar que cada reemplazo es bueno con sus propios ojos.
Jan Van Bruggen
7

El problema aquí es, como señala la otra respuesta , con las ligaduras. Sin embargo, no tiene nada que ver con OpenType. El problema fundamental es que los PDF son un formato de preimpresión que solo se ocupa poco de los contenidos y la semántica, sino que está orientado a representar fielmente una página tal como se imprimiría.

El texto se presenta no como texto sino como series de glifos de una fuente en ciertas posiciones. Entonces obtienes algo así como »Coloca el glifo número 72 allí, el glifo número 101 allí, el glifo número 108 allí, ...«. En ese nivel no es fundamentalmente ninguna noción de texto en absoluto . Es solo una descripción de cómo se ve . Hay dos problemas para extraer significado de un montón de glifos:

  1. El diseño espacial. Como PDF ya contiene información específica sobre dónde colocar cada glifo, no hay texto real subyacente, como sería normal. Otro efecto secundario es que no hay espacios. Claro, si miras el texto que hay, pero no en el PDF. ¿Por qué emitir un glifo en blanco cuando simplemente no puedes emitir ninguno? El resultado es el mismo, después de todo. Por lo tanto, los lectores de PDF deben reconstruir cuidadosamente el texto nuevamente, insertando un espacio cada vez que encuentren una brecha mayor entre los glifos.

  2. PDF representa glifos, no texto. La mayoría de las veces, las ID de glifos corresponden con puntos de código Unicode o al menos códigos ASCII en las fuentes incrustadas, lo que significa que a menudo puede recuperar el texto ASCII o Latin 1 lo suficientemente bien, dependiendo de quién creó el PDF en primer lugar (algunos Garble todo en el proceso). Pero a menudo, incluso los archivos PDF que le permiten obtener el texto ASCII perfectamente destruirán todo lo que no sea ASCII. Especialmente horrible con scripts complejos como el árabe que contienen solo ligaduras y glifos alternativos después de la etapa de diseño, lo que significa que los PDF en árabe casi nunca contienen texto real

El segundo problema es como el que enfrentas. Un culpable común aquí es LaTeX, que utiliza un número estimado de 238982375 fuentes diferentes (cada una de las cuales está restringida a 256 glifos) para lograr su salida. Las diferentes fuentes para texto normal, matemáticas (usa más de una), etc., hacen que las cosas sean muy difíciles, especialmente porque Metafont es anterior a Unicode en casi dos décadas y, por lo tanto, nunca hubo un mapeo Unicode. Los diéresis también se representan mediante una diéresis superpuesta a una letra, por ejemplo, se obtiene »¨a« en lugar de »ä« al copiar desde un PDF (y, por supuesto, tampoco se puede buscar).

Las aplicaciones que producen archivos PDF pueden optar por incluir el texto real como metadatos. Si no lo hacen, queda a merced de cómo se manejan las fuentes incrustadas y si el lector de PDF puede reconstruir el texto original nuevamente. Pero la copia "fi" como un espacio en blanco o nada en general es un signo de un PDF de LaTeX. Debería pintar personajes Unicode en piedras y arrojarlos al productor, con la esperanza de que cambien a XeLaTeX y, por lo tanto, finalmente lleguen a la década de 1990 de codificaciones de caracteres y estándares de fuente.

Joey
fuente