Teknisk artikel

Fixa duplicerad text från fake bold vid PDF-extraktion

HotPDF Delphi Component tar sedan v2.768.15 bort de dubblettecken som fake bold och övertryckta logotyper lämnar i extraherad PDF-text. När en producent ritar samma glyph två gånger en bråkdel av en punkt ifrån varandra läser ExtractLoadedPageText, ExtractLoadedPageTextLayout och ExtractLoadedPageTextColumns nu det tecknet en gång, så "Bold words" kommer inte längre ut som "Bold wordsBold words"

Det här är en av de buggar som ser ut som ett avkodningsfel och inte är det. Varje glyph avkodas korrekt; texten ligger faktiskt två gånger på sidan. Fixen måste avgöra vilken av två fullt giltiga glyfer som är kopian, och göra det utan att äta upp legitima upprepade bokstäver som det dubbla l i "fill" eller de identiska cellerna i en tabell. Resten av artikeln förklarar regeln HotPDF använder, trösklarna bakom den, var den med flit skiljer sig från andra extraheringsverktyg, och en andra, orelaterad extraheringsbugg från samma release-tåg som också hackade bort text från radslut

Varför ger fake bold dubblerad text vid PDF-extraktion?

Fake bold dubblar extraherad text eftersom producenten ritar strängen två gånger och båda kopiorna är riktiga textvisningsoperationer. När en typsnittsfamilj saknar en fet variant, eller författarverktyget helt enkelt inte bryr sig, emulerar många generatorer fetstil genom att visa samma sträng med Tj eller TJ, knuffa textpositionen åt höger några tiondels punkter och visa den igen. På skärmen smälter de två kopiorna samman till ett kraftigare streck. I content streamen finns två fullständiga sekvenser av teckenkoder, och ISO 32000-1 §9.4 ger en extraktor ingen flagga som säger "den här är dekoration"

Övertryckta logotyper träffar samma problem från en annan vinkel. En logotyp kan ritas en gång som en fyllning och igen som ett streck eller en lätt förskjuten skugga, med samma typsnitt och samma tecken. En naiv extraktor som går igenom content streamen i ritordning emitterar hela första sekvensen och därefter hela andra sekvensen, vilket är varför resultatet läser "Bold wordsBold words" snarare än "BBoolldd". Före v2.768.15 behöll HotPDF:s textsammansättning båda kopiorna, och dubbletten bröt det som de flesta anropare faktiskt bryr sig om: en sökning efter "Bold words" i sidtexten kunde misslyckas eftersom ordgränsen mellan de två sekvenserna var borta

Ett riktigt textrenderingsläge 2 (fyllning och sedan streck, ISO 32000-1 §9.3.6) har inte det här problemet, eftersom det är en enda textvisningsoperation som producerar en uppsättning glyfer. Dubbletterna dyker bara upp när producenten utfärdar texten två gånger

Hur avgör HotPDF vilken glyph som är dubbletten?

HotPDF behandlar två glyfer som ett ritat tecken när de bär samma Unicode-värde, har samma skrivriktning, ligger inom 5 % av varandra i storlek och sitter nästan ovanpå varandra: högst 0,1 av glyphstorleken isär längs baslinjen och högst 0,2 av glyphstorleken isär tvärs över den. Av varje sådant par behålls glyfen som ritades först och den senare kastas. Det är värdena i v2.768.15-implementationen, och var och en finns där av en anledning

  • Samma Unicode, samma riktning. Bara identiska tecken kan vara dubbletter, och en horisontell glyph jämförs aldrig med en vertikal (WMode 1)
  • Storlek inom 5 %. En stor rubrikbokstav och en liten bildtextbokstav som råkar överlappa är inte samma text, så en storleksskillnad utesluter ett par
  • 0,1 av storleken längs baslinjen. Förskjutningar för fake bold är några tiondels punkter, några procent av en normal textstorlek. Två riktiga angränsande bokstäver, även tätt kernade "ll", ligger isär med ungefär en hel förflyttningsbredd, en storleksordning mer än den här toleransen
  • 0,2 av storleken tvärs över baslinjen. Skuggor och konturpass skiftar ofta nedåt såväl som åt höger, så den vertikala toleransen är lösare, men fortfarande långt under avståndet till nästa rad
  • Först ritad vinner. Att behålla den tidigare glyfen bevarar läsordningen i den första sekvensen, så den överlevande texten flyter fortfarande som producenten skrev den

Avstånden mäts i glyfens eget baslinjeplan snarare än i råa sid-X och Y, så roterad text och text på en lutande baslinje använder samma toleranser som vanliga horisontella rader. Storleken som används är glyfens storlek som ritad på sidan, härledd ur dess fyrhörning, inte storleksoperanden som givits till Tf; det spelar roll för dokument som sätter Tf till 1 och skalar allt genom textmatrisen, där en rå teckenstorlek skulle göra varje tolerans värdelös

Mellanslag är aldrig kandidater. Kodpunkter upp till U+0020, hårda mellanslaget U+00A0 och ideografiskt mellanslag U+3000 hoppas över, eftersom blanka glyfer inte bär något synligt bläck att duplicera och att ta bort en skulle ihopsmälta ord

Hålla kontrollen snabb på täta sidor

Dubblettkontrollen i HotPDF körs i ungefär n log n-tid, inte n i kvadrat, eftersom den sorterar först och bara jämför glyfer inom ett litet fönster. Att jämföra varje glyph med alla andra skulle vara korrekt och oanvändbart: en tät sida av småtryck kan lätt bära tiotusentals glyfer, och kontrollen körs vid varje textextraheringsanrop

Den interna rutinen sorterar de icke-mellanslagsglyferna efter Unicode-värde, sedan efter ursprungs-X, sedan efter ritordning, med en stabil merge sort. Efter sorteringen sitter varje potentiell dubblett av en glyph omedelbart efter den i ordningen, så skanningen går bara framåt medan Unicode-värdet matchar och X-skillnaden håller sig inom 0,25 av glyphstorleken. Fönstret 0,25 är med flit bredare än toleransen 0,1 längs baslinjen, så ett par på en lätt lutande baslinje landar fortfarande innanför innan det exakta baslinjeplanet körs. Ett subtilt fall: möter skanningen från glyph A en matchande glyph som ritades före A är A kopian, så A kastas och skanningen stannar, och den tidigare glyfen får fortsätta jämförelsen med allt längre bort. Den regeln är det som får "först ritad vinner" att gälla för trippeltryck likväl som par

Jämförelsen sker på Unicode-värde, inte typsnitt eller teckenkod. Två glyfer som mappar till samma Unicode via olika typsnitt, till exempel ett fyllnadspass i en inbäddad delmängd och ett streckpass i en annan, känns fortfarande igen som ett tecken. Baksidan är att en glyph vars kod löser till Unicode 0 inget pålitligt att jämföra har, vilket är en anledning till att hålla ett öga på det olösta antalet som tas upp i att extrahera text från laddade PDF:er med HotPDF

Vilka HotPDF-extraheringsanrop tar bort dubblettglyfer?

Alla textproducerande anrop i HotPDF tar bort dubbletter, och råa glyph-anropet gör det inte. Dedupliceringen bor i THotPDF.ExtractVisibleLoadedPageGlyphs, steget som också kastar glyfer utanför sidans synliga ruta, och vanlig text-, kolumn-, layout- och semantisk textextraktion läser sina glyfer från den. THotPDF.ExtractLoadedPageGlyphs returnerar fortfarande varje glyph content streamen ritar, övertryck inkluderade, eftersom anropare som bygger markering, svärta eller verktyg för forensik behöver hela registret över vad som ritades

För vanligt textarbete ändras ingenting i anropande kod. Samma loop som extraherade dubblerad text före v2.768.15 extraherar nu varje ord en gång

var
  Pdf: THotPDF;
  PageCount, I: Integer;
  PageText: UnicodeString;
begin
  Pdf := THotPDF.Create(nil);
  try
    PageCount := Pdf.LoadFromFile('brochure.pdf');
    for I := 0 to PageCount - 1 do
      if Pdf.ExtractLoadedPageText(I, PageText) then
        if Pos('Bold words', PageText) > 0 then
          Writeln('Found on page ', I + 1);
  finally
    Pdf.Free;
  end;
end;

Layout- och kolumnutdata gynnas lika mycket, eftersom en duplicerad sekvens förut sköt resten av en rad åt sidan på tecknrutnätet. ExtractLoadedPageTextColumns behåller sin valfria gap-multiplikator; dubblettfiltret körs före kolumnsplittringen, så det samspelar inte med den inställningen

var
  Grid, Columns: UnicodeString;
begin
  if Pdf.ExtractLoadedPageTextLayout(0, Grid) then
    TFile.WriteAllText('page1-layout.txt', Grid, TEncoding.UTF8);
  // Kolumner delas där ett gap överstiger 3x medianglyfens förflyttning
  if Pdf.ExtractLoadedPageTextColumns(0, Columns, 3.0) then
    TFile.WriteAllText('page1-columns.txt', Columns, TEncoding.UTF8);
end;

Vill du veta huruvida en sida överhuvudtaget innehåller övertryckt text, jämför de två glyph-anropen. Var medveten om att skillnaden också räknar glyfer som hamnar utanför crop-rutan, så behandla den som en övre gräns för dubbletter snarare än en exakt siffra

var
  AllGlyphs, Visible: THPDFGlyphArray;
begin
  if Pdf.ExtractLoadedPageGlyphs(0, AllGlyphs) and
     Pdf.ExtractVisibleLoadedPageGlyphs(0, Visible) then
    Writeln(Format('%d painted, %d read as text (%d overprinted or off-page)',
      [Length(AllGlyphs), Length(Visible),
       Length(AllGlyphs) - Length(Visible)]));
end;

Ordavstånds- och radbrytningsreglerna som förvandlar dessa glyfer till text påverkas inte av filtret; de täcks i detalj i hur HotPDF placerar ordmellanslag och radbrytningar i sidtext

Där regeln med flit skiljer sig från andra verktyg

HotPDF kastar exakta dubbletter som vissa andra extraheringsverktyg behåller, och den skillnaden är medveten. När v2.768.15-ändringen kontrollerades mot ett referenskorpus extraherades en sidfot ritad två gånger på exakt samma position en gång i HotPDF och två gånger i en referensextraktor. En glyph ritad ovanpå en identisk glyph är osynlig som eget tecken, så att läsa den en gång är vad sidan faktiskt visar

Den ärliga gränsen går åt andra hållet. Fake bold som förskjuter det andra passet med mer än 0,1 av glyphstorleken längs baslinjen fångas inte, och inte heller ett skuggpass ritat med märkbart annan storlek. De layouterna är sällsynta i praktiken, eftersom den visuella effekten slutar se ut som fetstil när förskjutningen blir så stor, men om du ser dubblerad text från en dekorativ rubrik, jämför glyphernas X-värden från ExtractLoadedPageGlyphs innan du antar att filtret är trasigt. Filtret gör heller ingenting åt dubbletter som inte är geometriskt samlokaliserade, som ett löpande sidhuvud upprepat i ett dolt lager någon annanstans på sidan; det är en innehållsfråga, inte ett övertryck

Varför klipptes radslut av i text med CID-typsnitt?

En separat bugg fixad i HotPDF v2.768.12 tappade radslut i text satt med CID-nycklade (Type 0) typsnitt vars /W-breddarray inte var sorterad efter CID. ISO 32000-1 §9.7.4.3 definierar formatet för /W-arrayen men kräver inte att dess poster står i stigande CID-ordning, och vissa generatorer skriver dem i den ordning glyferna först användes i dokumentet

Textextraheringsavkodaren sökte bredder med binärsökning, som tyst antar sorterad indata. På en osorterad array missade de flesta uppslag och föll tillbaka på standardbredden /DW, som är 1000 enheter, en hel em. Latinsk text i ett sådant typsnitt lades därför ut som om varje bokstav vore fullbredd, rader löp långt förbi sidans högerkant, och de glyfer som landade utanför den synliga rutan kastades, så radsluten försvann ur extraherad text och ur sökning. Ingenting kraschade och ingenting rapporterades, vilket var det som gjorde det svårt att upptäcka

HotPDF sorterar nu /W-posterna med en stabil merge sort direkt efter läsningen, så där två poster överlappar vinner fortfarande den som skrevs först, vilket matchar hur arrayen läses i filordning. Sidrenderaren påverkades aldrig, eftersom den behåller sin egen kopia av bredddatat och skannar det linjärt, och den vertikala breddarrayen /W2 skannas också linjärt. Den spliten är värd att minnas när en sida ser perfekt ut men extraheras dåligt: rendering och extraktion i ett PDF-bibliotek delar ofta mycket mindre kod än man skulle tro

FAQ

Tar HotPDF bort upprepade bokstäver som det dubbla l i "fill"?

Nej. Två riktiga angränsande bokstäver ligger ungefär en förflyttningsbredd isär, medan HotPDF bara slår ihop identiska tecken som ligger inom 0,1 av glyphstorleken längs baslinjen och 0,2 tvärs över den, så legitima upprepningar behålls

Hur får jag varje glyph, inklusive övertryckta dubbletter?

Anropa THotPDF.ExtractLoadedPageGlyphs, som returnerar varje glyph sidan ritar i ritordning. THotPDF.ExtractVisibleLoadedPageGlyphs och alla textextraheringsanrop returnerar den deduplicerade, synliga mängden

Vilken HotPDF-version fixar dubblerad text från fake bold?

HotPDF v2.768.15 lade till dubblettglyf-borttagning i text-, layout-, kolumn- och semantisk textextraktion, och v2.768.12 fixade saknade radslut i CID-typsnitt med en osorterad /W-array

Båda fixarna medföljer nuvarande HotPDF Delphi PDF component, som extraherar text från laddade PDF:er i Delphi och C++Builder bredvid sina funktioner för PDF-skapande, redigering och rendering