Teknik Makale

Delphi'de PDFium QuadPoints ile Metin İşaretleme Açıklamaları

PDFium Bileşeni; vurgulama (highlight), altını çizme (underline), üzerini çizme (strikeout) ve dalgalı çizgi (squiggly) gibi metin işaretleme açıklamalarını TPdf.CreateAnnotation aracılığıyla oluşturur: TPdfAnnotation kaydında HasAttachmentPoints := True olarak ayarlar ve AttachmentPoints dörtgenini doldurursunuz, bileşen de ISO 32000-1 §12.5.6.10'da tanımlanan QuadPoints girdisini yazar. Tüm API yüzeyi bundan ibarettir. Bu makalenin var olma nedeni ise bunun altında yatanlardır; çünkü ham PDFium çağrı zinciri, araç setindeki en az yardımcı olan belirtiyi üreten bir başarısızlık moduna sahiptir: FPDFAnnot_SetAttachmentPoints, yeni oluşturulmuş bir açıklamada her zaman, hata kodu veya ipucu vermeden false döndürür. Bu, mevcut açıklamaları okumak ve gözden geçirmek üzerine olan makalemizin oluşturma tarafındaki tamamlayıcısıdır ve aynı yapılar üzerinden ters yönde ilerler

Hata ayıklama sahnesi her zaman aynıdır. Bir vurgulama açıklaması oluşturursunuz, indeks 0 ile bağlantı noktası belirleyicisini çağırırsınız, işlev false döndürür ve koordinatlarınızı ikinci kez tahmin etmeye başlarsınız. Noktaları aktarır, Y eksenini ters çevirir, sayfa alanını cihaz alanıyla değiştirirsiniz. Bunların hiçbiri yardımcı olmaz çünkü sorun hiçbir zaman koordinatlar değildir. Sorun, C API'sinin indeks semantiğidir ve bunu bir kez gördüğünüzde düzeltme iki satırdan ibarettir

ISO 32000-1'de QuadPoints ne anlama gelir?

QuadPoints, n adet dörtgeni tanımlayan 8×n sayıdan oluşan bir dizidir ve ISO 32000-1 §12.5.6.10 her metin işaretleme açıklamasında bunu zorunlu kılar: her dörtgen vurgulamanın, altı çizilmenin veya üzeri çizilmenin uygulanacağı bir kelimeyi veya bitişik kelimeler grubunu işaretler. Açıklamanın Rect girdisi hala mevcuttur ancak işaretleme alt türleri için yalnızca bölgeyi sınırlar; dörtgenler (quads) ise oluşturucunun (renderer) gerçekte boyadığı şeydir. Dikdörtgen yerine dörtgen kullanılmasının nedeni metnin döndürülebilmesi veya eğrilebilmesidir, bu nedenle dört köşe dört bağımsız nokta olarak saklanır: x1 y1 x2 y2 x3 y3 x4 y4

Bu dört noktanın sırası, spesifikasyon ile kurulu kullanıcı tabanının ayrıldığı yerdir. Spesifikasyon metni noktaları dörtgeni saat yönünün tersine izleyecek şekilde tanımlar ancak Adobe'nin kendi oluşturucusu bunları her zaman bir Z deseninde yorumlamıştır: önce soldan sağa üst kenar, ardından soldan sağa alt kenar. Her yazar Acrobat'a göre test ettiğinden, PDFium dahil hemen hemen her oluşturucu Z desenini takip eder ve spesifikasyonun harfi harfine sözlerine uyan dosyalar bazı görüntüleyicilerde çökmüş veya bükülmüş vurgular olarak oluşturulur. PDFium'un FS_QUADPOINTSF yapısı tam olarak bu kuralı kodlar: (x1,y1) sol üst köşe, (x2,y2) sağ üst, (x3,y3) sol alt, (x4,y4) sağ alt köşedir ve sayfa koordinatlarında Y yukarı doğru büyür. Bu sırayı takip edin ve bitirin; oluşturucular birçok şeye hoşgörü gösterir ancak bozuk bir dörtgen bunlardan biri değildir

FPDFAnnot_SetAttachmentPoints neden false döndürür?

FPDFAnnot_SetAttachmentPoints yeni bir açıklamada başarısız olur çünkü sözleşmesi verilen bir indeksteki dörtgeni değiştirmektir ve yeni oluşturulmuş bir açıklamada değiştirilecek sıfır dörtgen vardır. İmza bir açıklama tanıtıcısı, bir quad_index ve noktaları alır; indeks 0 "gerekirse oluşturarak ilk yuvayı" ifade etmez, "mevcut 0 numaralı dörtgeni" ifade eder ve FPDFAnnot_CountAttachmentPoints 0 bildirdiğinde böyle bir dörtgen yoktur ve çağrı false döndürür. Yuva oluşturan işlev FPDFAnnot_AppendAttachmentPoints işlevidir. FPDFPage_CreateAnnot aracılığıyla oluşturulan her açıklama sıfır sayısıyla başlar, bu nedenle oluşturma yolu önce Append'i çağırmalıdır ve yalnızca sonraki güncellemeler Set'i çağırabilir

Bu durum PDFium Bileşeni'nin kendisini de etkiledi. v1.79.0 sürümüne kadar CreateAnnotation ve SetAnnotation tarafından paylaşılan dahili rutin, mevcut bir işaretleme açıklamasını güncellemek için doğru olan ve yeni bir açıklama için başarısız olması garanti edilen FPDFAnnot_SetAttachmentPoints(Annotation, 0, ...) çağrısını sabit kodlamıştı ve bu durum 'Cannot set attachment points' mesajıyla bir EPdfException olarak ortaya çıkıyordu. v1.79.1'de sunulan düzeltme sayıya göre dallanır:

// Inside the component's annotation writer (v1.79.1+):
// a new annotation has no quad slots yet, so Append creates
// the first one; Set only replaces a slot that already exists
if FPDFAnnot_CountAttachmentPoints(Annotation) = 0 then
  Check(FPDFAnnot_AppendAttachmentPoints(Annotation, QuadPoints) <> 0,
    'Cannot set attachment points')
else
  Check(FPDFAnnot_SetAttachmentPoints(Annotation, 0, QuadPoints) <> 0,
    'Cannot set attachment points');

Aynı şablon, tüm FPDFAnnot_* giriş noktaları PDFium.pas içinde sunulduğundan bileşenin yapmanıza izin verdiği C işlevlerini doğrudan çağırdığınızda da geçerlidir. Bir FPDF_ANNOTATION tanıtıcısı tuttuğunuzda ve dörtgen yazmak istediğinizde, önce FPDFAnnot_CountAttachmentPoints işlevine sorun ve ona göre yönlendirin. "FPDFAnnot_SetAttachmentPoints returns false" şeklinde bir arama yapıyorsanız, bu count-then-append dallanması neredeyse kesinlikle cevabınızdır

TPdf.CreateAnnotation ile vurgulama oluşturma

Bileşen Append-Set yönlendirmesini sizin için yaparken, vurgulama oluşturmak bir kaydı doldurmaya indirgenir. Aşağıdaki örnek bir A4 sayfası oluşturur ve 200×20 noktalık bir bölge üzerine yarı saydam sarı bir vurgu bırakır; dörtgenin yukarıda açıklanan Z sırasını takip ettiğine ve Rectangle alanının dörtgeni çevreleyecek şekilde ayarlandığına dikkat edin, bu durum Rect'e göre isabet testi (hit-test) yapan görüntüleyicilerin mantıklı davranmasını sağlar

var
  Pdf: TPdf;
  A: TPdfAnnotation;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.CreateDocument;
    Pdf.AddPage(0, 595, 842);

    FillChar(A, SizeOf(A), 0);
    A.Subtype := anHighlight;
    A.HasColor := True;
    A.Color := clYellow;
    A.ColorAlpha := $80;                     // 50% opacity
    A.HasAttachmentPoints := True;
    A.AttachmentPoints[1].X := 50;  A.AttachmentPoints[1].Y := 700; // top-left
    A.AttachmentPoints[2].X := 250; A.AttachmentPoints[2].Y := 700; // top-right
    A.AttachmentPoints[3].X := 50;  A.AttachmentPoints[3].Y := 680; // bottom-left
    A.AttachmentPoints[4].X := 250; A.AttachmentPoints[4].Y := 680; // bottom-right
    A.Rectangle.Left := 50;  A.Rectangle.Top := 700;
    A.Rectangle.Right := 250; A.Rectangle.Bottom := 680;
    A.ContentsText := 'Highlighted region';
    Pdf.CreateAnnotation(A);

    Pdf.SaveAs('highlighted.pdf');
  finally
    Pdf.Free;
  end;
end;

Alt türleri değiştirmek bir satıra mal olur. anUnderline, anStrikeout ve anSquiggly aynı kayıt şeklini, dörtgenleri ve her şeyi alır çünkü ISO 32000-1 her dördünü de yalnızca dörtgen bölgesinin nasıl süslendiğiyle ayrılan aynı açıklama ailesi olarak ele alır. Metin işaretleme olmayan anSquare, anCircle ve anText gibi alt türler kendilerini yalnızca Rectangle alanına göre konumlandırır; bunlarda HasAttachmentPoints değerini False olarak bırakın, böylece dörtgen mekanizması hiçbir zaman çalışmaz

AttachmentPoints[0] Delphi'de derlenirken neden FPC'de başarısız olur?

TQuadrilateralPoint, 1 tabanlı bir dizi olan array [1..4] of TPdfPoint olarak tanımlanmıştır ve bu durum parmakları varsayılan olarak sıfır tabanlı indekslemeye alışkın olan herkesi yanıltır. A.AttachmentPoints[0] yazdığınızda Delphi'nin dcc32'si aralık kontrolü varsayılan olarak kapalı olduğu için bunu şikayet etmeden derler; çalışma zamanında ifade dizi sınırının hemen öncesindeki belleği okur veya yazar ki bu da TPdfAnnotation kaydında bitişik bir alandır. Vurgulamanız bir çöp köşe alır veya komşu bir alan bozulur ve hiçbir şey hata fırlatmaz. Free Pascal, Lazarus portu sırasında kendi demo kaynaklarımızda tam olarak bu hatayı yakaladı: fpc sabit indekslerde derleme zamanı aralık kontrolü gerçekleştirir ve AttachmentPoints[0..3] kullanımını doğrudan reddetti, bu sayede kütüphanedeki sınır aşımı ve Set-Append hatası birlikte ortaya çıkarıldı

Bunu iki alışkanlık takip eder. Dörtgeni, yukarıdaki koddaki köşe sırasına uyacak şekilde 1 ila 4 arasında indeksleyin ve açıklama kodunuzu güvenmeden önce Delphi'de {$R+} veya herhangi bir fpc derlemesi ile aralık kontrolü etkinleştirilmiş olarak en az bir kez derleyin. Varsayılan bir dcc32 derlemesinin geçmesi, indekslerin doğru olduğunun kanıtı değildir; yalnızca orada bulunan bellekte hiçbir şeyin çökmediğinin kanıtıdıra

Gerçek metinden dörtgen koordinatları alma

Sabit kodlanmış dikdörtgenler bir demo için iyidir ancak üretim vurguları gerçek glifleri izler ve koordinatlar tahmin yerine PDFium'un metin sayfası geometrisinden gelmelidir. PDFium Bileşeni ile metin çıkarma kılavuzumuzda kapsanan rutinler, size dörtgenlerin kullandığı aynı sayfa koordinat alanında karakter başına sınır sınırlayıcı kutular (bounding boxes) verir, böylece bir arama sonucu doğrudan köşe noktalarına dönüşür: ilk karakterin solu, sonuncunun sağı, çizginin uzantılarından üst ve alt. Metni kendiniz oluşturuyorsanız ve satırların var olmadan önce nereye düşeceğini bilmeniz gerekiyorsa, metin ölçümü ve sözcük kaydırma makalesi bu uzantıların önceden hesaplanmasını kapsar

Dürüstçe belirtilmesi gereken bir sınır: TPdfAnnotation kaydı tek bir TQuadrilateralPoint taşır, bu nedenle bir CreateAnnotation çağrısı tek bir dörtgen yazar. Üç satıra yayılan bir seçim, §12.5.6.10 uyarınca satır başına bir tane olmak üzere üç dörtgen gerektirir ve oraya ulaşmanın iki yolu vardır. Basit yol satır başına bir açıklama oluşturmaktır, bu her yerde doğru şekilde oluşturulur ve bileşen düzeyindeki API'yi korur. Kompakt yol, üç dörtgen taşıyan tek bir açıklama oluşturmaktır; bu, bileşen aracılığıyla açıklamayı oluşturup ardından ikinci ve üçüncü dörtgenler için ihraç edilen FPDFAnnot_AppendAttachmentPoints işlevini kendiniz çağırmak anlamına gelir, bu işlem Append işlevinin yuvaları değiştirmek yerine oluşturması nedeniyle çalışır. Çoklu dörtgen yapısına tekrarlanan SetAttachmentPoints çağrılarıyla ulaşmaya çalışmayın; mevcut sayıyı aşan her indeks, indeks 0'ın yeni açıklamada döndürdüğü aynı nedenle yalnızca false döndürür

Yazdıktan sonra, dönüş kodlarına güvenmek yerine gerçek bir görüntüleyicide doğrulayın: dosyayı Acrobat veya PDFium tabanlı herhangi bir görüntüleyicide açın ve işaretlemenin metnin üzerine denk geldiğini, istenen opaklıkta okunduğunu ve kaydetme-yeniden yükleme çift yönlü döngüsünden zarar görmeden çıktığını onaylayın. Açıklama türleri, dörtgen işleme ve burada gösterilen sayıya duyarlı yazar; ürün sayfası Delphi ve C++Builder için tüm açıklama API referansını kütüphanenin geri kalanıyla birlikte belgeleyen güncel Delphi PDFium Bileşeni'nin bir parçasıdır