Dlaczego tekst „fi” jest odcinany, gdy kopiuję z pliku PDF lub drukuję dokument?

15

Kiedy kopiuję z pliku Adobe Reader PDF, który zawiera

Define an operation

Raczej widzę

Dene an operation

kiedy wklejam tekst, dlaczego?

Jak mogę rozwiązać ten irytujący problem?

Widziałem to również w przeszłości, kiedy drukowałem plik Microsoft Office Word na mojej drukarce.

Tamara Wijsman
źródło

Odpowiedzi:

13

To brzmi jak problem z czcionką. Plik PDF prawdopodobnie używa fi ligatury OpenType w tym słowie define, a w bieżącej czcionce aplikacji docelowej brakuje tego glifu.

Nie wiem, czy istnieje prosty sposób, aby Acrobat rozłożył ligaturę na kopii.

Problemy z drukowaniem prawdopodobnie dotyczą również czcionek. Coś prawdopodobnie pozwala drukarce zastąpić czcionkę dokumentu własnymi wbudowanymi czcionkami, a wersja czcionki drukarki również nie ma tego konkretnego glifu. Będziesz musiał powiedzieć systemowi Windows, aby zawsze pobierał czcionki do drukarki, aby obejść ten problem.

Inna możliwość podczas drukowania: UniScribe może nie być włączony. MS KB 2642020 mówi o tym i niektórych możliwych obejściach (mianowicie, aby używać drukowania typu RAW zamiast drukowania typu EMF). Chociaż kontekst jest nieco inny niż określony problem, przyczyna może być taka sama i mogą obowiązywać te same obejścia.

afrazier
źródło
1
Ciekawe o ligaturach, zastanawiam się, czy można go w jakiś sposób skonfigurować, aby zachowywał się poprawnie. Być może mógłbym sprawdzić, jak zachowują się inne czytniki plików PDF. Gdzie dokładnie go skonfigurować, aby czcionki były wysyłane do drukarki?
Tamara Wijsman,
1
Z okna drukowania aplikacji: Kliknij Properties(lub Preferences, w zależności od wersji okna dialogowego) drukarki, upewnij się, że jesteś na kartach Layoutlub Quality, kliknij Advancedprzycisk. W Graphicgrupie zmień TrueType Fontopcję na Download as Softfont. Dotyczy to większości drukarek PostScript i drukarek korzystających z wbudowanych okien dialogowych systemu Windows (tak myślę), ale inne sterowniki mogą mieć problemy z przemieszczaniem się lub brakować.
afrazier
MS KB 2642020 może się przydać. Zredagowałem swoją odpowiedź z tymi informacjami.
afrazier
Dziękujemy za opisanie problemu. Nie próbowałem jeszcze tego rozwiązać, ale na pewno spróbuję, gdy ponownie napotkam problem z drukowaniem. Chyba jeden z obu rozwiązań byłoby na pewno rozwiązać ten bardzo specyficzny problem ... :)
Tamara Wijsman
@afrazier, rozwiązanie napisane w komentarzu rozpoczynającym się od „Z okna drukowania aplikacji:” zadziałało dla mnie. Proponuję umieścić ten tekst w swojej odpowiedzi. (Mógłbym to edytować, ale myślę, że decyzja powinna zależeć od ciebie.)
Alan
9

Większość tych „połamanych” słów możesz zastąpić oryginałami. Możesz bezpiecznie zamienić słowo, jeśli:

  • jak denelub rey, to nie jest prawdziwe słowo
  • jak defineczy fireflyistnieje jeden sposób, aby ponownie dodać sequeneces ligaturą ( ff, fi, fl, ffi, lub ffl) i zrobić prawdziwe słowo

Większość problemów z ligaturą spełnia te kryteria. Nie można jednak zastąpić:

  • us ponieważ to jest prawdziwe słowo, nawet jeśli pierwotnie tak było fluffs
    • również affirm, butterfly, fielders, fortifies, flimflam, misfits...
  • cusponieważ mogłoby to stać albo cuffsalboficus
    • także stiffed/ stifled, rifle/ riffle, flung/ fluffing...

W tym 496 tysięcy słowo słownik angielsko istnieje 16055 słów, które zawierają co najmniej jeden ff, fi, fl, ffi, lub ffl, które zamieniają się w 15879 słów, gdy ich ligatury są usuwane. 173 z tych brakujących słów kolidowały jak cuffsi ficus, a ostatnie 3 są dlatego, że Słownik zawiera słowa ff, fii fl.

790 z tych słów „usuniętych przez ligaturę” to prawdziwe słowa us, ale 15089 to wyłamane słowa. 14960 złamanych słów można bezpiecznie zastąpić oryginalnym słowem, co oznacza, że 99,1% złamanych słów można naprawić, a 93,2% oryginalnych słów zawierających ligaturę można odzyskać po wklejeniu pliku PDF. 6,8% słów zawierających sekwencje ligatur ginie w kolizjach ( cus) i pod słowach ( us), chyba że wybierzesz sposób (kontekst słowa / dokumentu?), Aby wybrać najlepsze zamienniki dla każdego ze słów, które nie mają gwarancji zastąpienie.

Poniżej znajduje się mój skrypt w języku Python, który wygenerował powyższe statystyki. Oczekuje pliku tekstowego słownika z jednym słowem w wierszu. Na koniec zapisuje plik CSV, który mapuje naprawiane łamane słowa na ich oryginalne słowa.

Oto link do pobrania pliku CSV: http://www.filedropper.com/brokenligaturewordfixes Połącz to mapowanie z czymś w rodzaju skryptu zastępczego wyrażenia regularnego, aby zastąpić większość łamanych słów.

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
źródło
Link do strony .csvjest zepsuty. Byłoby wspaniale, gdybyś mógł przesłać go ponownie! W każdym razie dzięki za kod.
MagTun,
@Enora Ponownie przesłałem plik CSV pod tym samym linkiem - mam nadzieję, że to pomoże! Zauważyłem również kilka problemów w kodzie / wynikach (używanie kropek jako symboli zastępczych, podczas gdy nowy słownik ma kropki w swoich słowach, a nie małe litery przed porównywaniem). Uważam, że wszystkie zamienniki są prawidłowe, ale weź je z odrobiną soli i wiem, że możliwe są inne dobre zamienniki. Zalecam zautomatyzowanie zamienników za pomocą wyrażenia regularnego, ale następnie potwierdzenie, że każde zastąpienie jest dobre na własne oczy.
Jan Van Bruggen,
7

Problemem jest tutaj, podobnie jak inne odpowiedzi , ligatury. Nie ma to jednak nic wspólnego z OpenType. Podstawowym problemem jest to, że pliki PDF są formatem przed wydrukiem, który niewiele zajmuje się treścią i semantyką, ale jest ukierunkowany na wierne odwzorowanie strony w takiej formie, w jakiej byłaby drukowana.

Tekst jest ułożony nie jako tekst, ale jako ciąg glifów z czcionki w niektórych pozycjach. Otrzymujesz więc coś takiego: »Umieść glif numer 72, glif numer 101 tam, glif numer 108, ...«. Na tym poziomie znajduje się fundamentalnie nie pojęcie tekstu w ogóle . To tylko opis, jak to wygląda . Istnieją dwa problemy z wyodrębnieniem znaczenia z szeregu glifów:

  1. Układ przestrzenny. Ponieważ plik PDF zawiera już określone informacje o tym, gdzie umieścić każdy glif, nie ma pod nim faktycznego tekstu, co byłoby normalne. Innym efektem ubocznym jest brak spacji. Jasne, jeśli spojrzysz na tekst, ale nie w pliku PDF. Po co emitować pusty glif, skoro nie można go wcale emitować? W końcu wynik jest taki sam. Czytelnicy PDF muszą ponownie starannie poskładać tekst, wstawiając spację za każdym razem, gdy napotkają większą przerwę między glifami.

  2. PDF renderuje glify, a nie tekst. Przez większość czasu identyfikatory glifów odpowiadają punktom kodu Unicode lub przynajmniej kodom ASCII w osadzonych czcionkach, co oznacza, że ​​często można uzyskać tekst ASCII lub Latin 1 wystarczająco dobrze, w zależności od tego, kto utworzył plik PDF (niektóre rozwalić wszystko w trakcie). Ale często nawet pliki PDF, które pozwalają dobrze wydobyć tekst ASCII, zmieszają wszystko, co nie jest ASCII. Szczególnie straszne ze złożonymi skryptami, takimi jak arabski, które zawierają tylko ligatury i alternatywne glify po etapie układania, co oznacza, że ​​arabskie pliki PDF prawie nigdy nie zawierają rzeczywistego tekstu

Drugi problem jest podobny do tego, z którym się mierzysz. Częstym winowajcą jest tutaj LaTeX, który wykorzystuje szacunkową liczbę 238982375 różnych czcionek (z których każda jest ograniczona do 256 glifów) w celu osiągnięcia swoich wyników. Różne czcionki dla normalnego tekstu, matematyki (używa więcej niż jednej) itp. Bardzo utrudniają pracę, zwłaszcza że Metafont wyprzedza Unicode o prawie dwie dekady, a zatem nigdy nie było mapowania Unicode. Umlauty są również renderowane przez diaeresis nałożoną na literę, np. Dostajesz »¨a« zamiast »ä« podczas kopiowania z pliku PDF (i oczywiście nie można go wyszukać).

Aplikacje tworzące pliki PDF mogą włączyć faktyczny tekst jako metadane. Jeśli tak nie jest, pozostajesz na łasce, jak obsługiwane są osadzone czcionki i czy czytnik PDF może ponownie złożyć oryginalny tekst. Ale kopiowanie „fi” jako puste lub wcale nie jest zwykle oznaką pliku LaTeX PDF. Powinieneś pomalować znaki Unicode na kamienie i rzucić je producentowi, mając nadzieję, że przestawią się na XeLaTeX i ostatecznie dotrą do lat 90. kodowania znaków i standardów czcionek.

Joey
źródło