Besedilna stran PDF izpostavi znake in škatle, nikoli vrstic. PDFium Component zgradi vizualno vrstico tako, da grozdi škatle znakov, katerih navpična sredina pade znotraj polovice višine sejnega znaka, in se sprehodi navzven od kliknjenega znaka, dokler toleranca ni presežena. Vsaka pot izbire v pregledovalniku pokliče tega enega pomočnika, tako da se miška, tipkovnica in koda strinjajo
Simptom, ki vas pripelje sem, je specifičen in neprijeten. Uporabnik trikrat klikne odstavek v dvostolpčnem poročilu in dobi polovico strani. Ali pa trikrat klikne celico tabele in izbira požre celotno vrstico plus številko strani v nogi. Pregledovalnik ni pokvarjen; postavlja vprašanje, na katerega datoteka ne more odgovoriti. V PDF-ju ni vrstice za izbiro, katera koli implementacija, ki se dela, da je, pa uganja. Ta članek se ukvarja s tem, da to ugibanje naredi namerno in dosledno. Če dejansko potrebujete izvleček besedila iz dokumenta, glejte izvlečenje besedila iz PDF dokumentov s PDFium; če postavljate besedilo in potrebujete širine, glejte merjenje besedila in prelom vrstic. Tukaj je tema ožja: odločitev, kje se vizualna vrstica začne in konča, ter izbira natanko tega
Zakaj besedilna stran PDF nima objektov vrstic?
Ker vsebinski tok PDF opisuje risanje, ne strukture. ISO 32000-1 §9.4 opredeljuje besedilni objekt kot par BT / ET, ki vsebuje operatorje postavljanja in prikazovanja. Operatorji postavljanja iz §9.4.2 (Td, TD, Tm, T*) premikajo besedilno matriko po strani, operatorji prikazovanja iz §9.4.3 (Tj, TJ, ', ") pa pobarvajo glifе, kamor koli ta matrika trenutno kaže. Nič v tem modelu ne pravi "ta niz glifov je vrstica". Vrstica je tisto, kar človek vidi, ko je risanje končano
Producenti to poslabšajo na načine, ki jih ne morete nadzorovati. Poravnan odstavek je lahko izdan kot eno polje TJ na vrstico, ali kot en Tj na besedo z izrecnim Tm pred vsako, ali kot ena sama operacija prikaza s prilagoditvami razmikanja, ki nosijo razmik. Dvostolpčna postavitev lahko izda levi stolpec od zgoraj navzdol in nato desnega, ali pa ju prepleta, če je producent sprehodil svoj lasten notranji seznam objektov v drugačnem vrstnem redu. Zaporedje znakov, ki vam ga da PDFium, sledi vsebinskemu toku, vsebinski tok pa sledi temu, kar se je ustvarjajoči aplikaciji zdelo primerno storiti. Tako sta funkciji, ki ju dejansko dobite, FPDFText_CountChars, ki poroča, koliko znakov stran nosi, in FPDFText_GetCharBox, ki vrne mejno škatlo enega znaka v prostoru strani. To je celoten surov besednjak. Vse nad tem, besede, vrstice, odstavki, stolpci, je sklepanje, ki ga izvedete na geometriji
Zakaj je zaznavanje CR in LF napačen test?
Ker znaki, proti katerim bi testirali, niso zanesljivo prisotni, ko pa so prisotni, niso zanesljivo vaši. PDFium vbrizga sintetične znake v besedilno stran, da naredi izvlečeno besedilo berljivo: presledek, kjer sta dva teka vizualno ločena, CR ali LF, kjer se naslednji tek začne na novi osnovni črti. FPDFText_IsGenerated obstaja natanko zato, da jih lahko ločite od znakov, ki so prišli iz datoteke, PDFium Component pa to izpostavi kot lastnost CharacterGenerated
Razdelite na te znake, in podedujete vsako presojo, ki jo je PDFium naredil med njihovim sintetiziranjem. Trd prelom vrstice znotraj prelomljenega odstavka in mehak prelom po sintezi izgledata enako. Vrstica tabele, ki jo je producent izdal celico za celico, morda med zadnjo celico in prvo celico naslednje vrstice sploh ne dobi preloma, ker se osnovne črte naključno dovolj približajo. Medtem naslov, ki mu sledi telo besedila v drugačni velikosti, morda dobi dva preloma, kjer človek vidi enega. Generirani znaki so prikazna udobnost za izvleček cele strani; niso model vrstice, degradirajo pa se natanko v tistih dokumentih, kjer je izbira najpomembnejša
Grozdenje škatel znakov po navpični sredini
Zanesljiv signal je geometrija. Vzemite znak, ki ga je uporabnik kliknil, kot seme, izračunajte navpično sredino njegove škatle in se sprehodite navzven v obe smeri, dokler sosednje škatle obdržijo svoje navpične sredine znotraj tolerance. PDFium Component uporabi polovico višine sejne škatle kot to toleranco, s tlemi 0.5 enot strani, tako da degenerirane škatle, pika, tanek presledek, glif s skoraj ničelno škatlo višine, ne strnejo tolerance na nič in prekinejo vrstico po enem znaku
function TPdfView.LineRangeAt(TxtPage: FPDF_TEXTPAGE; CharIndex: Integer;
out StartIndex, Count: Integer): Boolean;
var
Lo, Hi, Total: Integer;
SeedBox, Box: TPdfRectangle;
SeedYMid, BoxYMid, HalfH: Double;
begin
Result := False;
StartIndex := -1;
Count := 0;
Total := FPDFText_CountChars(TxtPage);
if (CharIndex < 0) or (CharIndex >= Total) then
Exit;
if FPDFText_GetCharBox(TxtPage, CharIndex, SeedBox.Left, SeedBox.Right,
SeedBox.Bottom, SeedBox.Top) = 0 then
Exit;
SeedYMid := (SeedBox.Top + SeedBox.Bottom) / 2;
HalfH := Abs(SeedBox.Top - SeedBox.Bottom) / 2;
if HalfH < 0.5 then // floor for degenerate boxes
HalfH := 0.5;
Lo := CharIndex;
Hi := CharIndex;
while Lo > 0 do
begin
if FPDFText_GetCharBox(TxtPage, Lo - 1, Box.Left, Box.Right,
Box.Bottom, Box.Top) = 0 then
Break;
BoxYMid := (Box.Top + Box.Bottom) / 2;
if Abs(BoxYMid - SeedYMid) > HalfH then
Break;
Dec(Lo);
end;
while Hi < Total - 1 do
begin
if FPDFText_GetCharBox(TxtPage, Hi + 1, Box.Left, Box.Right,
Box.Bottom, Box.Top) = 0 then
Break;
BoxYMid := (Box.Top + Box.Bottom) / 2;
if Abs(BoxYMid - SeedYMid) > HalfH then
Break;
Inc(Hi);
end;
StartIndex := Lo;
Count := Hi - Lo + 1;
Result := True;
end;
Tri podrobnosti v tej zanki si zaslužijo svoje mesto. Toleranca izvira iz semena namesto iz konstante, tako da 24pt naslov dobi širok pas, 7pt besedilo opombe pod črto pa ozkega, nobeden pa ne krade znakov drugemu. Primerjava uporablja navpične sredine namesto osnovnih črt ali vrhov škatel, kar drži nadpisan tek, vrivan tek druge velikosti ali mešano-pisavni stavek na isti vrstici kot njegove sosede. In neuspel FPDFText_GetCharBox konča pregled namesto da bi ga preskočil, ker znak brez pridobljive geometrije ne da nobenega dokaza v katero koli smer, nadaljevanje mimo njega pa bi sprehodu dovolilo preskočiti pravo mejo na moč znaka dlje po vrsti
Zakaj mora vsaka pot izbire deliti enega pomočnika?
Ker se tri poti kode, ki vsaka implementira "vrstico", razidejo, in razidejo se tiho. V PDFium Component trikliktna razširitev, Shift+Home, Shift+End in javna metoda SelectLineAt vse razrešijo svoje meje prek istega klica LineRangeAt. Trojni klik ga sejе iz sidra izbire; tipki shift ga sejeta iz kurzorja izbire in premakneta le ta konec; SelectLineAt ga sejе iz indeksa znaka, ki ga poda klicatelj, in rezultat izroči SelectTextRange, istemu validatorju obsega, ki ga uporablja pot miške. Podvojite logiko namesto tega, in odpoved ni sesutje, temveč počasno drsenje. Nekdo prilagodi toleranco trojnega klika, da popravi poročilo s tesnim razmikom, zdaj pa se Shift+End ustavi en znak prekratko od tega, kjer se ustavi trojni klik na istem odstavku. Uporabnik izbere vrstico z miško, jo razširi s tipkovnico in gleda, kako se izbira krči. Ker SelectLineAt hrani navaden cevovod izbire, tudi programska izbira ostaja neodvisna od tega, ali je vnos miške omogočen, in vseeno zastonj dobi preverjanje obsega, ponovno risanje in obvestilo OnSelectionChange
// Select the visual line under a client-space point, then read it back
procedure TForm1.SelectLineUnderCursor(X, Y: Integer);
var
CharIndex: Integer;
begin
CharIndex := PdfView1.CharacterIndexAtPos(X, Y, 6.0, 6.0);
if CharIndex < 0 then
Exit;
if PdfView1.SelectLineAt(PdfView1.CurrentPage, CharIndex) then
Memo1.Lines.Add(PdfView1.SelectedText);
end;
Upoštevajte argumente tolerance na CharacterIndexAtPos. Testiranje zadetkov ima svojo lastno ohlapnost, izraženo v enotah strani, kar je ločena skrb od tolerance vrstice. Klik, ki pristane v razmiku med dvema vrsticama, se razreši na kateri koli znak je najbližji znotraj te škatle; pregled vrstice nato teče od katerega koli znaka se je to izkazalo. Podajanje pregrešno velikodušne tolerance zadetka v seme je eden lažjih načinov, da izberete vrstico, na katero uporabnik ni kazal
Dva prostora indeksov: indeks znaka in indeks besedila
Ko imate obseg, se uprite skušnjavi, da bi ga uporabili kot niz odmikov. FPDFText_GetText vrne besedilo strani kot medpomnilnik UTF-16, njegovi indeksi pa niso isti prostor indeksov kot indeksi znakov, ki jih uporabljata FPDFText_GetCharBox in FPDFText_CountChars. Generirani znaki, obravnavani prej, sedijo v besedilnem medpomnilniku, medtem ko zasedajo reže znakov brez uporabne geometrije, dve oštevilčenji pa se čez stran razideta. Mostova sta FPDFText_GetTextIndexFromCharIndex in FPDFText_GetCharIndexFromTextIndex, ki ju PDFium Component ovije kot CharacterIndexToTextIndex in TextIndexToCharacterIndex
var
TextStart, TextEnd: Integer;
begin
// char-index range from LineRangeAt -> offsets into the page text buffer
TextStart := Pdf.CharacterIndexToTextIndex(StartIndex);
TextEnd := Pdf.CharacterIndexToTextIndex(StartIndex + Count - 1);
if (TextStart >= 0) and (TextEnd >= TextStart) then
Caption := Pdf.Text(TextStart, TextEnd - TextStart + 1);
end;
Smer, ki najbolj grize, je obratna. Iskanje, implementirano nad izvlečenim nizom, vam da indekse besedila, njihovo neposredno posredovanje API-ju škatle ali izbire pa tiho naslovi napačne znake, z napako, ki raste dlje, ko greste po strani navzdol. Pretvorite z TextIndexToCharacterIndex, preden se karkoli geometrijskega dotakne številke. Nadomestni pari dodajo drugo, neodvisno težavo odmika na to, kar je pokrito v članku o emojijih, CJK in nadomestnih parih
Kje se hevristika upogne
Bodite iskreni glede meja, ker so resnične in dosegljive. Obrnjeno besedilo je najjasnejši primer: škatla znaka je osi poravnan pravokotnik v prostoru strani, zato imajo za besedilo, obrnjeno za 90 stopinj, škatle ene vizualne vrstice navpične sredine razpršene čez stran, pregled pa se ustavi skoraj takoj. Kar dobite, je kratka izbira namesto napačne, kar je boljši način odpovedi, vseeno pa je še vedno odpoved. Navpični načini pisanja se obnašajo enako iz istega razloga. Dvostolpčne postavitve delujejo, kadar sta stolpca navpično zamaknjena drug od drugega, in odpovejo, kadar nista. Če si oba stolpca delita osnovno mrežo, znaki desnega stolpca sedijo znotraj tolerance vrstice levega stolpca, pregled pa bo tekel naravnost čez žleb, ker v čisti geometriji ni ničesar, kar bi ga ustavilo. Zaznavanje tega potrebuje test vodoravne vrzeli poleg navpičnega grozdenja, izbira praga vrzeli pa je lastna presoja o tem, za katere dokumente ste pripravljeni biti napačni. Mešane velikosti pisav so primer, ki ga toleranca, relativna na seme, dobro obravnava: vrivan 8pt kodni segment znotraj 11pt telesa besedila obdrži svojo sredino znotraj pasu, 24pt naslov na naslednji osnovni črti pa ne potegne vrstice telesa vase
Semantika izbire vrstic, opisana tukaj, je del PDFium Component za Delphi in C++Builder, skupaj s testiranjem zadetkov, obsegom izbire in API-ji indeksov besedila, uporabljenimi v primerih; stran izdelka nosi celoten referenčni pregled besedilne strani in modela izbire