HotPDF Delphi Component fjerner siden v2.768.15 de duplikerede tegn, som fake bold og overprinted logoer efterlader i udtrakt PDF-tekst. Når en producer maler samme glyf to gange en brøkdel af et point fra hinanden, læser ExtractLoadedPageText, ExtractLoadedPageTextLayout og ExtractLoadedPageTextColumns nu det tegn én gang, så "Bold words" ikke længere kommer ud som "Bold wordsBold words"
Det er en af de bugs, der ligner en afkoderfejl, men ikke er det. Hver glyf afkoder korrekt; teksten ligger virkelig to gange på siden. Fixet skal beslutte, hvilken af to helt gyldige glyffer der er kopien, og gøre det uden at spise legitime gentagne bogstaver som det dobbelte l i "fill" eller de identiske celler i en tabel. Resten af artiklen forklarer reglen, HotPDF bruger, tærsklerne bag, hvor den bevidst afviger fra andre ekstraktionsværktøjer, og en anden, urelateret ekstraktionsbug fra samme release-train, der også skar tekst af linjeender
Hvorfor giver fake bold duplikeret tekst i PDF-ekstraktion?
Fake bold fordobler udtrakt tekst, fordi producenten tegner strengen to gange, og begge kopier er ægte text-showing-operationer. Har en fontfamilie ikke en bold-face, eller gider authoring-værktøjet simpelthen ikke, emulerer mange generatorer bold ved at vise samme streng med Tj eller TJ, skubbe tekstpositionen lidt til højre med et par tiendedele point og vise den igen. På skærmen smelter de to kopier sammen til en tungere streg. I content stream'en er der to komplette runs af tegnkoder, og ISO 32000-1 §9.4 giver en ekstraktor intet flag, der siger "denne her er dekoration"
Overprinted logoer rammer samme problem fra en anden vinkel. En logotype kan males én gang som fill og igen som stroke eller en let forskudt skygge, med samme font og samme tegn. En naiv ekstraktor, der går content stream'en igennem i male-rækkefølge, udsender hele det første run og derefter hele det andet, hvilket er derfor, resultatet lyder "Bold wordsBold words" frem for "BBoolldd". Før v2.768.15 beholdt HotPDFs tekstsamling begge kopier, og duplikatet ødelagde det, de fleste kaldere faktisk bryder sig om: en søgning efter "Bold words" på sideteksten kunne fejle, fordi ordgrænsen mellem de to runs var væk
Ægte text rendering mode 2 (fill og så stroke, ISO 32000-1 §9.3.6) har ikke dette problem, for det er én text-showing-operation, der frembringer ét sæt glyffer. Duplikaterne dukker kun op, når producenten udsender teksten to gange
Hvordan beslutter HotPDF, hvilken glyf der er duplikatet?
HotPDF behandler to glyffer som ét malet tegn, når de bærer samme Unicode-værdi, har samme skriveretning, ligger inden for 5% af hinanden i størrelse og næsten oven på hinanden: højst 0,1 af glyf-størrelsen fra hinanden langs baseline og højst 0,2 af glyf-størrelsen på tværs. Af hvert sådant par bevares den først malede glyf, og den senere droppes. Det er værdierne i v2.768.15-implementeringen, og hver af dem er der af en grund
- Samme Unicode, samme retning. Kun identiske tegn kan være duplikater, og en horisontal glyf sammenlignes aldrig med en vertikal (WMode 1)
- Størrelse inden for 5%. Et stort overskriftsbogstav og et lille billedtekstbogstav, der tilfældigt overlapper, er ikke samme tekst, så et størrelses-mismatch udelukker et par
- 0,1 af størrelsen langs baseline. Fake bold-forskydninger er et par tiendedele point, et par procent af en normal tekststørrelse. To ægte nabo-bogstaver, selv et stramt kerned "ll", er adskilt af omtrent en fuld advance-bredde, en størrelsesorden mere end denne tolerance
- 0,2 af størrelsen på tværs af baseline. Skygger og outline-gennemløb forskubber ofte nedad såvel som til højre, så den vertikale tolerance er løsere, men stadig langt under afstanden til næste linje
- Først malet vinder. At bevare den tidligere glyf bevarer den første runs læserækkefølge, så den overlevende tekst stadig flyder, som producenten skrev den
Afstande måles i glyfens egen baseline-frame frem for rå side-X og -Y, så roteret tekst og tekst på en skæv baseline bruger samme tolerancer som almindelige vandrette linjer. Den størrelse, der bruges, er glyfens størrelse, som den er malet på siden, afledt af dens quad, ikke størrelses-operanden givet til Tf; det betyder noget for dokumenter, der sætter Tf til 1 og skalerer alt gennem tekstmatricen, hvor en rå fontstørrelse ville gøre enhver tolerance ubrugelig
Mellemrum er aldrig kandidater. Code points op til U+0020, no-break space U+00A0 og det ideografiske mellemrum U+3000 springes over, fordi blanke glyffer ikke bærer synligt blæk at duplikere, og at droppe én ville sammensmelte ord
At holde tjekket hurtigt på tætte sider
Duplikat-tjekket i HotPDF kører i omtrent n log n-tid, ikke n i anden, fordi det sorterer først og kun sammenligner glyffer inden for et lille vindue. At sammenligne hver glyf med alle andre ville være korrekt og ubrugeligt: en tæt side med småt kan let bære titusindvis af glyffer, og tjekket kører ved hvert tekst-ekstraktionskald
Den interne rutine sorterer de ikke-mellemrum glyffer efter Unicode-værdi, derefter efter origo-X og derefter efter male-rækkefølge med en stabil merge sort. Efter sorteringen ligger enhver potentiel duplikat af en glyf umiddelbart efter den i rækkefølgen, så scanningen kun går fremad, mens Unicode-værdien matcher, og X-forskellen holder sig inden for 0,25 af glyf-størrelsen. Vinduet på 0,25 er bevidst bredere end tolerance på 0,1 langs baseline, så et par på en let skæv baseline stadig lander inden for, før den præcise baseline-frame-test kører. Én subtil sag: møder scanningen fra glyf A en matchende glyf, der blev malet før A, er A kopien, så A droppes, og scanningen stopper, hvorved den tidligere glyf fortsætter sammenligningen med alt længere fremme. Den regel er, der får "først malet vinder" til at holde for trestrejere ligesom par
Sammenligningen er på Unicode-værdi, ikke font eller tegnkode. To glyffer, der mapper til samme Unicode gennem forskellige fonte, for eksempel et fill-gennemløb i én indlejret subset og et stroke-gennemløb i en anden, genkendes stadig som ét tegn. Bagsiden er, at en glyf, hvis kode opløses til Unicode 0, ikke har noget pålideligt at sammenligne, hvilket er endnu en grund til at holde øje med det uløste antal, der er dækket i udtræk af tekst fra indlæste PDF'er med HotPDF
Hvilke HotPDF-ekstraktionskald fjerner duplikerede glyffer?
Alle tekstfrembrdende kald i HotPDF fjerner duplikater, og det rå glyf-kald gør ikke. Deduplikeringen ligger i THotPDF.ExtractVisibleLoadedPageGlyphs, trinnet der også kasserer glyffer uden for sidens synlige boks, og udtræk af almindelig tekst, kolonner, layout og semantisk tekst læser alle deres glyffer fra den. THotPDF.ExtractLoadedPageGlyphs returnerer stadig hver glyf, content stream'en maler, overprints inkluderet, for kaldere, der bygger highlighting-, redaction- eller forensic-værktøj, har brug for den fulde registrering af, hvad der blev tegnet
Til almindeligt tekstarbejde ændrer intet sig i kaldende kode. Samme løkke, der udtrak fordoblet tekst før v2.768.15, udtrækker nu hvert ord én gang
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- og kolonne-output nyder lige så meget, for et duplikeret run skubbede tidligere resten af en linje sidelæns på tegngitteret. ExtractLoadedPageTextColumns beholder sin valgfrie gap-multiplikator; duplikat-filteret kører før kolonnesplitning, så det interagerer ikke med den indstilling
var
Grid, Columns: UnicodeString;
begin
if Pdf.ExtractLoadedPageTextLayout(0, Grid) then
TFile.WriteAllText('page1-layout.txt', Grid, TEncoding.UTF8);
// Kolonner splittes, hvor et gap overstiger 3x den mediane glyf-advance
if Pdf.ExtractLoadedPageTextColumns(0, Columns, 3.0) then
TFile.WriteAllText('page1-columns.txt', Columns, TEncoding.UTF8);
end;
Vil man vide, om en side overhovedet indeholder overprinted tekst, så sammenlign de to glyf-kald. Vær opmærksom på, at forskellen også tæller glyffer, der falder uden for crop box'en, så behandl den som en øvre grænse for duplikater snarere end et præcist tal
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;
Ord-mellemrums- og linjeskift-reglerne, der forvandler disse glyffer til tekst, er uændret af filteret; de er gennemgået i detaljer i hvordan HotPDF placerer ordmellemrum og linjeskift i sidetekst
Hvor reglen bevidst er uenig med andre værktøjer
HotPDF dropper eksakte duplikater, som nogle andre ekstraktionsværktøjer beholder, og den forskel er bevidst. Da v2.768.15-ændringen blev tjekket mod et referencekorpus, udtrak en sidefod malet to gange på præcis samme position én gang i HotPDF og to gange i en reference-ekstraktor. En glyf malet oven på en identisk glyf er usynlig som et separat tegn, så at læse den én gang er, hvad siden faktisk viser
Den ærlige grænse løber den anden vej. Fake bold, der forskubber det andet gennemløb med mere end 0,1 af glyf-størrelsen langs baseline, fanges ikke, og heller ikke et skygge-gennemløb malet i en markant anden størrelse. De layouts er sjældne i praksis, for den visuelle effekt holder op med at ligne bold, når forskydningen bliver så stor, men ser man fordoblet tekst fra en dekorativ overskrift, så sammenlign glyf-X-værdierne fra ExtractLoadedPageGlyphs, før man antager, at filteret er i stykker. Filteret gør heller intet for duplikater, der ikke er geometrisk samlokeret, som en running header gentaget i et skjult lag et andet sted på siden; det er et indholdsspørgsmål, ikke en overprint
Hvorfor blev linjeender skåret af i CID-font-tekst?
En separat bug, fikset i HotPDF v2.768.12, droppede enderne af linjer i tekst sat med CID-keyed (Type 0)-fonte, hvis /W-bredde-array ikke var sorteret efter CID. ISO 32000-1 §9.7.4.3 definerer /W-array-formatet, men kræver ikke, at dets poster er i stigende CID-rækkefølge, og nogle generatorer skriver dem i den rækkefølge, glyfferne først blev brugt i dokumentet
Tekst-ekstraktions-afkoderen slog bredder op med binær søgning, som stille antager sorteret input. På et usorteret array missede de fleste opslag og faldt tilbage til standardbredden /DW, som er 1000 enheder, en hel em. Latinsk tekst i en sådan font blev derfor sat, som om hvert bogstav var fuldbredde, linjer løb langt forbi sidens højre kant, og glyfferne, der landede uden for den synlige boks, blev kasseret, så linjeenderne forsvandt fra udtrakt tekst og fra søgning. Intet crashede, og intet blev rapporteret, hvilket var det, der gjorde det svært at spotte
HotPDF sorterer nu /W-posterne med en stabil merge sort umiddelbart efter at have læst dem, så hvor to poster overlapper, vinder den først skrevne stadig, i tråd med hvordan arrayet læses i filrækkefølge. Side-rendereren blev aldrig påvirket, for den beholder sin egen kopi af breddedata og scanner den lineært, og det vertikale bredde-array /W2 scannes også lineært. Det split er værd at huske, når en side ser perfekt ud, men udtrækker dårligt: rendering og ekstraktion i et PDF-bibliotek deler ofte langt mindre kode, end man ville forvente
FAQ
Fjerner HotPDF gentagne bogstaver som det dobbelte l i "fill"?
Nej. To ægte nabo-bogstaver er cirka en advance-bredde fra hinanden, mens HotPDF kun fusionerer identiske tegn, der ligger inden for 0,1 af glyf-størrelsen langs baseline og 0,2 på tværs, så legitime gentagelser bevares
Hvordan får man hver glyf, inklusive overprinted-duplikater?
Kald THotPDF.ExtractLoadedPageGlyphs, som returnerer hver glyf, siden maler, i male-rækkefølge. THotPDF.ExtractVisibleLoadedPageGlyphs og alle tekst-ekstraktionskaldene returnerer det deduplikerede, synlige sæt
Hvilken HotPDF-version fixer fordoblet tekst fra fake bold?
HotPDF v2.768.15 tilføjede fjernelse af duplikerede glyffer til tekst-, layout-, kolonne- og semantisk tekst-ekstraktion, og v2.768.12 fixede manglende linjeender i CID-fonte med et usorteret /W-array
Begge fixes følger med i den aktuelle HotPDF Delphi PDF component, som udtrækker tekst fra indlæste PDF'er i Delphi og C++Builder ved siden af dens PDF-oprettelses-, redigerings- og renderingsfunktioner