Teknik Makale

Delphide PDF metni çıkarma: boşluklar ve satır sonları

HotPDF Delphi Component, THotPDF.ExtractLoadedPageTextte kelime boşluklarını ve satır sonlarını glyph geometrisinden yeniden kurar; space karakterlerinden değil. Bir glyphin kendi genişliğinden sonraki boşluk, text heightin 0.15ini aşınca bir space girer ve yeni satır, yalnızca text origin yazma yönü boyunca text heightin yarısından fazlasını katettiğinde başlar. v2.768.3ten beri sayfa metni Form XObjectler üzerinden boyanan metni de içerir ve görünür crop alanının dışındaki glyphleri bırakır. Bu yazının gerisi, her kuralın neden bu şekilde göründüğünü anlatır; çünkü her biri, gerçek belgelerde inandırıcı ama yanlış çıktı üreten daha basit bir kuralın yerini aldı

Belirtiler, PDF metnini bir arama indeksine yedirmiş herkese tanıdıktır. Bir kapak sayfası PDFReferenceManualNovember4,1998 olarak çıkar, bir vergi formu 156 satıra bölünür, çapraz bir filigran satır başına bir karakter gelir ve kırpılmış bir prova, hiçbir görüntüleyicinin göstermediği yazıcının slug satırıyla başlar. Bu dosyaların hiçbiri bozuk değildir. Her biri, naif bir extractorın yanlış okuduğu, tamamen meşru bir metin yerleştirme yolu kullanır

Çıkarılan PDF metni kelime boşluklarını neden kaybeder?

Çıkarılan metin kelime boşluklarını kaybeder, çünkü bir PDFin onları içermesi asla zorunlu değildir. Bir üretici kelimeleri space karakteri göstererek ayırabilir; ama aynı işi TJ dizisi içindeki bir sayıyla (ISO 32000-1 §9.4.3) ya da taze bir Tdyle (§9.4.2) kalemi oynatarak da yapabilir; TeX çıktısı, pek çok Distiller dosyası ve çoğu iki yana yaslanmış düzen tam olarak bunu yapar. v2.766.76dan önce HPDFAssemblePageText yalnızca dikey harekete bakıyordu; konumlandırmayla yapılan kelime ayrımı bu yüzden sessizce kayboluyordu. Assembler artık, önceki glyphin yazma yönü boyunca, o glyphin kendi genişliğinin sonundan mevcut glyphin başlangıcına olan mesafeyi ölçer ve mesafe, user spacete ascentten descente ölçülen mevcut glyphin kutu yüksekliğinin 0.15ini aştığında bir space ekler. İki taraftan biri hâlihazırda boşken space eklenmez; iki CJK karakteri arasında da eklenmez, çünkü iki yana yaslanma ideografları, o açıklık kelime sınırı demeden açar. Glyph kayıtları aynı geometriyi açığa verir; yani belirli bir dosya şaşırttığında kararı yeniden üretebilirsiniz

uses
  SysUtils, HPDFDoc, HPDFContentStream;

procedure DumpWordGaps(Pdf: THotPDF; PageIndex: Integer);
var
  Glyphs: THPDFGlyphArray;
  I: Integer;
  Height, Gap: Double;
begin
  if not Pdf.ExtractLoadedPageGlyphs(PageIndex, Glyphs) then
    Exit;
  for I := 1 to High(Glyphs) do
  begin
    // glyph kutusunun ascent-descent yüksekliği, user spacete
    Height := Sqrt(Sqr(Glyphs[I].QuadX[3] - Glyphs[I].QuadX[0]) +
      Sqr(Glyphs[I].QuadY[3] - Glyphs[I].QuadY[0]));
    // yatay metin: önceki glyphin kendi genişliği sonundan boşluk
    Gap := Glyphs[I].BaselineStartX - Glyphs[I - 1].GlyphEndX;
    if (Height > 0) and (Gap > 0.15 * Height) then
      Writeln(Format('U+%.4x gap %.2f height %.2f: space',
        [Glyphs[I].Unicode, Gap, Height]));
  end;
end;

Neden kalem konumundan değil glyphin kendi genişliğinden ölçülür?

HotPDF kelime boşluklarını GlyphEndX / GlyphEndYden ölçer, çünkü bir glyphden sonraki kalem konumu, boşluk olmayan aralıkları çoktan taşır. ISO 32000-1 §9.4.4 yatay yer değiştirmeyi, glyph genişliği çarpı font boyutu artı karakter aralığı Tc artı kelime aralığı Tw olarak tanımlar; hepsi Tzyle ölçeklenir. BaselineEndX / BaselineEndY o tam yer değiştirmeyi tutar; GlyphEndX / GlyphEndY ise yalnızca font advanceini ve Tzyi tutar. Fark, negatif bir Tcyle trackingi sıkılaştırıp her glyphden sonra o alanı bir TJ ayarıyla geri veren üreticiler için önem taşır: kalem konumundan ölçüldüğünde geri verilen alan boşluk gibi görünür ve Çince "95后" terimi "9 5 后" olarak çıkardı. Eşik benzer bir sebeple Tf boyutu yerine glyph kutusu yüksekliğine bağlıdır. Word exportları sık sık 1 Tf yazıp gerçek boyutu ölçeklenmiş bir Tmde taşır; Tfs 1 derken metin 10 punto boyundadır ve Tfse bağlanan bir kural aynı sayfanın iki yazımını farklı işlerdi

Delphide ExtractLoadedPageText için HotPDF kelime boşluğu kuralı: space yalnızca önceki glyphin GlyphEndXinden sonraki glyphin BaselineStartXine olan mesafe ascent-descent kutu yüksekliğinin 0.15ini aştığında girer, çünkü BaselineEndXdeki kalem konumu Tc, Tw ile Tzyi çoktan taşır ve iki yana yaslanmış tracking geri vermelerini 9 5 后 gibi sahte boşluklara çevirir
Kelimelerin nerede bittiğine space karakterleri değil geometri karar verir — glyph kayıtları aynı ölçümleri açığa verir, yani şaşırtıcı herhangi bir dosya için kararı yeniden oynatabilirsiniz

Kuralın dürüst sınırları vardır. Çok gevşek trackingle set edilmiş bir başlık, Tcnin tek başına harfler arasında text heightin 0.15inden fazlasını açtığı durumlarda her harfin arasına bir space girerek çıkar; sayfanın göründüğü de budur ama muhtemelen indekslemek istediğiniz de değil. Aynı baseline üzerinde sıra dışı çizilen parçalar negatif boşluk üretir ve spacesiz birleşir. İki durum da gövde metninde nadirdir ve test korpusunda değişiklik, hiçbirini düşürmeden bir referans extractora karşı 28 sayfada kelime eşleşmelerini yükseltti

HotPDF çıkarılan metinde yeni satıra ne zaman başlar?

v2.766.79dan beri yeni satır, önceki glyphin başlangıcından mevcut glyphin başlangıcına olan hareketin, önceki yazma yönünün normaline izdüşümü, iki glyphin daha büyük kutu yüksekliğinin yarısını aştığında başlar. Daha eski kural, ham Y hareketini Tfsin yarısıyla karşılaştırıyordu ve iki yönde başarısız oluyordu. 1 Tf ve ölçeklenmiş bir Tm ile eşik yarım birime indi; 0.4lik bir text rise ile yükseltilmiş bir üst simge ya da sıradan baseline titremesi satırı kırıyordu. Kural ayrıca Xiyi tamamen yok sayıyordu; döndürülmüş bir Tm altındaki metin her glyphle sayfada aşağı adımlıyor ve satır başına bir glyph çıkıyordu. Yön normaline izdüşüm, döndürülmüş koşuları yataylar gibi davranıştırır; iki yükseklikten büyüğünü almak ise baseline paylaştıklarında büyük bir örnek kelimeyle küçük altyazısını aynı satırda tutar. Yukarıda sözü edilen vergi formunda satır sayısı 156dan 97ye indi. Yazma modu 1deki (§9.7.4.3) dikey metin ayrı bir yol izler: o glyphler kolonlara gruplanır, sağdan sola ve yukarıdan aşağı okunur, her kolon değişiminde satır sonu gelir

HotPDFin ExtractLoadedPageTexti Delphide satır sonlarına nasıl karar verir: glyph başlangıçları arasındaki hareket yazma yönünün normaline izdüşürülüp daha büyük kutu yüksekliğinin yarısıyla karşılaştırılır; böylece 1 Tf fontu altında küçük bir text rise ile yükseltilmiş üst simge ile döndürülmüş bir Tm altında sayfada aşağı adımlayan metin artık satır başına bir glyphe bölünmez
İzdüşüm döndürülmüş koşuları yataylar gibi davranıştırır ve iki kutu yüksekliğinden büyüğünü almak, büyük örnek kelimeyle küçük altyazısını aynı satırda tutar

ExtractLoadedPageText hangi metni alır, hangisini bırakır?

ExtractLoadedPageText, bir görüntüleyicinin gösterdiği metni döndürür. v2.766.80den beri yalnızca görünür glyphlerden çalışır; kutu merkezi GetLoadedPageVisibleBoxın — MediaBoxa kırpılmış CropBoxın (§14.11.2) — dışına düşen her glyphi düşürür. Bu, trim alanının dışında metin olarak set edilmiş slug satırlarını ve diğer yazıcı işaretlerini kaldırır. ExtractLoadedPageGlyphs ise bilinçli olarak sayfa content streaminin her glyphini döndürmeye devam eder; o malzemeye ihtiyacınız olduğunda hâlâ bulabilirsiniz. Filtre kutu testidir, görünürlük testi değil: clipping yoluyla gizlenmiş, beyaz boyanmış ya da bir image altında kalmış metin yine de çıkar

var
  Pdf: THotPDF;
  Glyphs: THPDFGlyphArray;
  PageText: UnicodeString;
  L, B, R, T: Single;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('trimmed-proof.pdf');
    if Pdf.GetLoadedPageVisibleBox(0, L, B, R, T) then
      Writeln(Format('Visible box: %.1f %.1f %.1f %.1f', [L, B, R, T]));
    // sayfa content streaminin her glyphi, slug satırı dahil
    if Pdf.ExtractLoadedPageGlyphs(0, Glyphs) then
      Writeln(Length(Glyphs), ' glyphs in the page content stream');
    // yalnızca sayfanın gösterdiği, Form XObject metni araya eklenmiş hâlde
    if Pdf.ExtractLoadedPageText(0, PageText) then
      Writeln(PageText);
  finally
    Pdf.Free;
  end;
end;

Form XObjectler üzerinden boyanan metin, v2.768.3ten beri sayfa metninin parçasıdır. Başlıklar, damgalar ve filigranlar çoğu zaman formlarda yaşar ve bazı standart belgelerinde değişiklikten önce karakterlerin yüzde 30 ila 35i kayboluyordu. THotPDF.InterpretContentWithForms, her Doyu etkilenen CTMle birlikte kaydeder, formu /Matrixi çarpı o CTMde (§8.10.1) yorumlar ve formun glyphlerini Dounun konumuna ekleyerek iç içe formlara iner. Kendi /Resourcesu olmayan bir form, onu boyayan streaminkileri ödünç alır; §7.8.3 buna izin verir. Form glyphleri TokenIndex = -1 taşır ve ExtractLoadedPageGlyphs hâlâ yalnızca page-stream glyphleri döndürür, çünkü arama, değiştirme ve redaction değişiklikleri TokenIndex üzerinden geri yazar ve bir form glyphi sızarsa yanlış baytları düzenlerdi. Bilinmesi gereken iki sadeleştirme vardır: form metni formun /BBoxına kırplmaz ve döngü tespiti yerine özyineleme 12. seviyede durur; yani kendini boyayan bozuk bir form, tavana ulaşana dek metnini tekrar eder

HotPDF Delphide PDF sayfa metni çıkarırken hangi glyphleri alır: ExtractLoadedPageText yalnızca kutu merkezi GetLoadedPageVisibleBoxa — MediaBoxa kırpılmış CropBoxa — düşen glyphleri tutar, yazıcı slug satırları kaybolur; InterpretContentWithForms her Do konumuna Form XObject glyphlerini TokenIndex -1 ile ekler ve glyph düzeyi API hâlâ her şeyi döndürür
Glyph merkezindeki kutu testi görünürlük testi değildir — beyaz metin, kırpılmış metin ve örtülü metin yine de çıkar ve form metni v2.768.3ten beri sayılır

Q operatöründen sonraki metin neden çöp olarak çözülüyordu?

Qdan sonraki metin v2.766.73ten önce yanlış çözülebiliyordu, çünkü extractor q üzerinde yalnızca CTMyi kaydediyordu. Font, boyut, Tc, Tw, Tz, TL, render modu ve rise gibi text state parameterleri grafik durumuna aittir (§9.3.1); dolayısıyla Q onları yığındaki her şeyin geri kalanıyla birlikte geri yüklemek zorundadır (§8.4.2). Bir sektör raporu, q … Q içinde iki baytlık bir Identity-H fontu seçiyor ve ardından kendi Tfsi olmayan tek baytlık WinAnsi metin gösteriyordu. Extractor iç fontu tutuyor, içindekiler tablosundaki leaderları ve "Adobe" kelimesini iki baytlık kodlar olarak okuyor ve sayfanın karakterlerinin yüzde 15ini düşürüyordu. Interpreterın q/Q yığını artık tam text durumunu tutar. Buradaki çıkarma kuralları her sayfa için geçerlidir; yani bütün bir belge tek çağrıda bir dosyaya gider

var
  Output: TFileStream;
  Pages: Integer;
begin
  Output := TFileStream.Create('report.txt', fmCreate);
  try
    // boş aralık = bütün sayfalar; sayfalar arası form feed; UTF-8 BOM
    Pages := Pdf.ExtractLoadedPagesTextToStream(Output, '', #12, True);
    Writeln(Pages, ' pages extracted');
  finally
    Output.Free;
  end;
end;

Hangi HotPDF text APIsinı kullanmalısınız?

ExtractLoadedPageText content-stream sırasında kalır; arama ve indeksleme için doğru varsayılan budur ve altındaki decoding zinciri HotPDF ile yüklenmiş PDFlerden metin çıkarmada kapsanır. Yazım sırasının önemli olduğu etiketli belgeler için yapı sıralı metin çıkarma geometriden tahmin etmek yerine yapı ağacında yürür; tablolara kilitlenmiş veriler için sayfa kesmelerinde tipli tablo çıkarma satır yerine hücre döndürür. Tam API referansı ve deneme indirmesi HotPDF Delphi PDF Component ürün sayfasındadır