Teknik Makale

HotPDF OCR metin katmanı kayması: CropBox üzerinden eşleme

OCR metin katmanları, barkod sınırları ve yüz maskeleme kutuları, bitmap pixel'leri renderer'ın aslında rasterize ettiği kutu yerine — MediaBox'a kırpılmış CropBox (ISO 32000-1 §14.11.2) yerine — MediaBox üzerinden geri eşlendiğinde kırpılmış PDF sayfalarında kayar. HotPDF bunu ApplyLoadedOCRTextLayer için v2.770.153'te, DecodeLoadedPageBarcodes ile DetectLoadedRedactionFindings için v2.770.154'te düzeltti

Genelde ulaşan hata raporu şöyle görünür. Taranmış bir sözleşme arşivi OCR'dan geçer, çıktı aranabilirdir ve bir madde numarasının arama isabeti, basılı numaranın yarım inç aşağısında ve solunda vurgulanır. Topluluktaki dosyaların çoğu sağlamdır. Bozuk olanların hepsi, platen kenar boşluğunu kırpmak için /CropBox yazan tek bir tarama istasyonundan gelmiştir. O tek ayrıntı, OCR motorunun gördüğü resmi metin katmanının yerleştirildiği çerçeveden ayırır ve aynı uyumsuzluk barkod sınırlarını, daha ciddisi yüz maskeleme kutularını oynatır

OCR metin katmanı taranmış kelimelerden neden uzaklaşır?

Metin katmanı kayar, çünkü hattın iki yarısı bitmap'in hangi dikdörtgeni kapsadığı konusunda anlaşamıyordu. v2.766.64'te HotPDF, rendering'i, SVG export'unu, viewer'ı ve yazdırmayı CropBox'a uyduracak biçimde değiştirdi: bir sayfa, CropBox'ının MediaBox'a kırpılmış hâliyle — ISO 32000-1 §14.11.2'nin öngördüğü gibi — görüntülenir ve o görünür kutuyu döndürmek için GetLoadedPageVisibleBox eklendi. Tanıma özellikleri ise device-to-page dönüşümünü kurmaya GetLoadedPageBox(PageIndex, pbMediaBox, ...) ile devam ediyordu. Raster artık görünür kutuyu kapsıyor, dönüşüm hâlâ MediaBox varsayıyor ve tanınan her konum ikisinin arasındaki boşluk kadar kaymış dönüyordu

Etkilenen pencere bu yüzden kesindir. ApplyLoadedOCRTextLayer, metni v2.766.64'ten v2.770.152'ye dek yanlış yere koydu. Bütün sayfalık DecodeLoadedPageBarcodes ile DetectLoadedRedactionFindings içindeki yüz detection'ı bir derleme daha uzun, v2.770.153'e dek yanlış kaldı. v2.766.64 öncesinde renderer bütün MediaBox'ı çiziyordu; eşleme ile raster hemfikirdi — karşılığında görüntüleyicilerin hiç göstermediği içeriği tanıma pahasına. Düzeltmeler her özellik için üç şeyi birlikte değiştirdi: dönüşümü, pixel bütçesi tahminini ve istek kaydında özel bir motora verilen page box'ı

Birkaç olgu hiç etkilenmedi:

  • /CropBoxı olmayan ya da CropBox'ı MediaBox'a eşit olan sayfalar, düzeltme öncesi ve sonrasında birebir aynı eşlenir
  • HasRegion setli DecodeLoadedPageBarcodes, tam olarak geçirdiğiniz bölgeyi render eder ve aynı bölge üzerinden eşler; dolayısıyla açık bölge decode'u baştan sona doğrudu. Bölgenin sayfa içinde kaldığını denetleyen kontrol hâlâ MediaBox'ı kullanır
  • Desen tabanlı maskeleme bulguları (e-postalar, kart numaraları vb.) user space'te metin extraction'ından gelir, raster'dan değil; dolayısıyla yalnızca yüz detection bulguları oynadı

Üç koordinat çerçevesi ve her birini hangi HotPDF API'leri kullanır?

Tanımayla temas eden HotPDF kodu üç çerçeveyle uğraşır ve eşleme hatalarının çoğu ikisini karıştırmaktan doğar

  • Bitmap pixel'leri: sol-üst köken, Y aşağı büyür, birimler istek DPI'ındaki pixel'lerdir. THPDFOCRWord.Left, Top, Right ve Bottom bu çerçevededir; isteğe bağlı taban noktaları, özel bir IHPDFBarcodeDecoder'ın döndürdüğü sonuçlar ve özel bir IHPDFFaceDetector'dan gelen kutular da öyledir
  • Yüklenen bir sayfanın PDF user space'i: sol-alt köken, Y yukarı büyür, birimler point, Bottom < Top. GetLoadedPageBox ile GetLoadedPageVisibleBox Left, Bottom, Right, Top'ı bu çerçevede döndürür; THPDFOCRRequest'in PageLeft, PageBottom, PageRight ve PageTop alanları, THPDFDecodedBarcode içindeki sınırlar ve THPDFRedactionFinding içindeki dikdörtgenler de öyledir
  • HotPDF page-drawing koordinatları: yeni sayfalar kurmak için kullandığınız API (metin çıktısı, şekiller, barkodlar, linkler, form alanları) sol-üst kökenle ve Y aşağı büyüyerek çalışır. O çerçeve belge üretimine aittir ve yukarıdaki yüklenen-belge API'leriyle hiçbir ilgisi yoktur; dolayısıyla yüklenen-sayfa user-space dikdörtgenini değiştirmeden ona asla beslemeyin

OCR kelime kaydı kasıtlı olarak pixel tabanlıdır: bir motor image'de gördüğünü bildirir ve dönüşümü ApplyLoadedOCRTextLayer sahiplenir. O bölünme yalnızca dönüşüm doğru kutuyu kullandığında çalışır; v2.770.153'ün geri getirdiği de buydu

HotPDF tanıma koordinat çerçeveleri: THPDFOCRWord kutularının ve özel decoder'ların kullandığı sol-üst kökenli bitmap pixel'leri; GetLoadedPageBox ile GetLoadedPageVisibleBox'ın döndürdüğü sol-alt kökenli PDF user space'i; ve yüklenen-sayfa dikdörtgenini asla değiştirmeden almaması gereken sol-üst kökenli page drawing API'si
motorlar pixel bildirir çünkü gördükleri oydu, HotPDF onları eşler ve iki çerçeveyi karıştırmak katmanların ve maskeleme kutularının kayma biçimidir

OCR, barkod ve yüzlerin ardındaki device-to-page dönüşümü

HotPDF, bitmap pixel'lerini beş girdiden kurulan tek bir affine matrisle sayfaya eşler: rotasyon, DPI / 72 ölçeği, bitmap yüksekliği ve render edilen kutunun Left, Bottom, Right, Top'ı. OCR, barkod decode'u ve yüz detection bunun için tek bir yordamı paylaşır; tek bir yanlış kutu girdisinin üçünü de aynı biçimde bozması bu yüzden. Döndürülmemiş bir sayfa için page-to-device matrisi [A B C D E F] şudur:

  • A = Scale ve D = -Scale, Scale = DPI / 72 iken; negatif D, user space'i (Y yukarı) bitmap space'ine (Y aşağı) çevirir
  • B = C = 0, çünkü döndürülmemiş bir sayfanın eksenler arasında shear'ı ya da takası yoktur
  • E = -Left * Scale, kutunun sol kenarını 0 numaralı pixel sütununa taşır
  • F = BitmapHeight + Bottom * Scale, kutunun alt kenarını bitmap'in alt kenarı olan y = BitmapHeight'a eşler; böylece üst kenar 0. satıra düşer

Pixeller o matrisin tersiyle sayfaya geri gider. Request.PageRotation, sayfanın /Rotate'ini 0, 90, 180 ya da 270'e normalize edilmiş taşır (90'ın katı olmayan her değer 0 sayılır) ve renderer, ISO 32000-1 §7.7.3.3'ün istediği gibi sayfayı saat yönünde döndürür. Rotasyon altında eksenler takas olur ve kutu kenarlarından başka bir çift bitmap kökenine sabitlenir. Ters formüller olarak yazılmış hâliyle, S = DPI / 72, pixel cinsinden x ile y ve bitmap yüksekliği H iken:

/RotateSayfa XSayfa YEşlemenin dayandığı kutu kenarları
0Left + x / SBottom + (H - y) / SLeft, Bottom
90Left + y / SBottom + x / SLeft, Bottom
180Right - x / SBottom + y / SRight, Bottom
270Right - y / STop - x / SRight, Top

Son sütun, hatanın üretimde neden rastgele göründüğünü açıklar. Yalnızca sayfanın üstünü kırpılan bir CropBox, Left ile Bottom'a dokunmaz; dik sayfalar kusursuz çıkar ve yalnızca /Rotate 270 taşıyan sayfalar kayardı. Rotasyon ayrıca bitmap boyutlarını takas eder: 90 ile 270'te bitmap (Top - Bottom) * S pixel genişliğinde ve (Right - Left) * S pixel yüksektir

Sayfa rotasyonu için HotPDF ters eşleme tablosu: /Rotate 0 ile 90'da dönüşüm render edilen kutunun Left ile Bottom kenarlarını sabitler, 180'de Right ile Bottom'u, 270'te Right ile Top'u; karışık yönlü bir belgede kırpılmış sayfanın her yönelim için farklı yöne kayması bundandır
sayfalar farklı /Rotate değerleri taşıdığında aynı yarım inçlik kırpma üç ayrı hata gibi görünür; çünkü her yönelim farklı bir kutu kenarı çiftini sabitler

MediaBox [0 0 612 792] ile CropBox [36 36 576 756]'da ne yanlış gider?

Her kenarda yarım inçlik bir kırpma iken, MediaBox kullanıldığında döndürülmemiş bir sayfanın metin katmanı tam olarak taranmış kelimelerin 36 point soluna ve 36 point altına düşer. Her kenardan 36 point (0.5 inç) kırpan CropBox'ı olan bir US Letter sayfası alın. Görünür kutu 540'a 720 point'tir; dolayısıyla varsayılan 300 DPI OCR çözünürlüğünde ölçek 300 / 72 ≈ 4,1667'dir ve bitmap 2250'ye 3000 pixeldir

Motorun, taban çizgisi olmadan Left 450, Top 600, Right 900, Bottom 660 pixel kutulu bir kelime bildirdiğini varsayın. HotPDF sonra taban çizgisini alt kenarın %20 yukarısına, 648. pixel satırına koyar ve başlangıç noktasını (450, 648) eşler:

  • Görünür kutu üzerinden: x = 36 + 450 / 4,1667 = 144,0 ve y = 36 + (3000 - 648) / 4,1667 = 600,48 — kelimenin basılı olduğu yer
  • MediaBox üzerinden: x = 0 + 108,0 = 108,0 ve y = 0 + 564,48 = 564,48 — (-36, -36) pointlik düzgün bir kayma
MediaBox 0 0 612 792 ve CropBox 36 36 576 756'lı bir US Letter sayfasında HotPDF CropBox kayma anatomisi: renderer görünür kutuyu 300 DPI'da rasterize eder; dolayısıyla kelime pixel 450'sini GetLoadedPageVisibleBox üzerinden eşlemek 144,0 ile 600,48 verirken MediaBox dönüşümü 108,0 ile 564,48'e düşer
raster CropBox'ı kapsar; dolayısıyla MediaBox'tan kurulan her dönüşüm tanınan her kelimeyi tam olarak kırpma kenar boşluğu kadar kaydırır

Aynı sayfayı döndürün, hatanın yönü değişir; çünkü işin içinde farklı kenarlar vardır. /Rotate 180'de X terimi Right'ı kullanır ve 576 yerine 612, katmanı 36 point sağa iterken Bottom onu hâlâ 36 point aşağı çeker. /Rotate 270'te Right ile Top ikisi de fazla büyüktür; katman 36 point sağa ve 36 point yukarı gider. Karışık yönelimli bir belge kaymayı üç yönde gösterebilir — bu hata için güvenilir bir parmak izi. Ölçeği kutudan türeyen elle yazılmış kod, örneğin Bitmap.Width / (Right - Left), ofsetin üstüne her koordinatı 612 / 540, kabaca %13, kadar da gerer

Hangi PDF belgeleriniz etkilenir?

Bir PDF belgesi, en az bir sayfası MediaBox'ından farklı bir görünür kutu taşıdığında maruziyet altındadır ve HotPDF bunu size birkaç satırda söyleyebilir. Her sayfa için pbMediaBoxlu GetLoadedPageBox'ı GetLoadedPageVisibleBox ile karşılaştırın ve kayma yönünü yukarıdaki tablodan öngörebilmeniz için yanına GetLoadedPageRotation'ı da yazdırın. THPDFPageBoundary ayrıca pbCropBox, pbBleedBox, pbTrimBox ve pbArtBox sunar; ama GetLoadedPageBox(pbCropBox), crop kutusu yokken MediaBox'a döner ve kırpma yapmaz — dolayısıyla karşılaştırılacak doğru şey görünür kutudur

uses
  System.SysUtils, HPDFDoc;

procedure ReportCroppedPages(const FileName: string);
var
  Pdf: THotPDF;
  I: Integer;
  ML, MB, MR, MT, VL, VB, VR, VT, Tmp: Single;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile(FileName) < 1 then
      raise Exception.Create('Cannot load ' + FileName);
    for I := 0 to Pdf.LoadedPageCount - 1 do
    begin
      if not Pdf.GetLoadedPageBox(I, pbMediaBox, ML, MB, MR, MT) then
        Continue;
      // saklanan dizi köşelerini herhangi bir sırada listeliyor olabilir
      if MR < ML then begin Tmp := ML; ML := MR; MR := Tmp; end;
      if MT < MB then begin Tmp := MB; MB := MT; MT := Tmp; end;
      // hâlihazırda normalize edilmiş ve MediaBox'a kırpılmış
      if not Pdf.GetLoadedPageVisibleBox(I, VL, VB, VR, VT) then
        Continue;
      if (Abs(VL - ML) > 0.01) or (Abs(VB - MB) > 0.01) or
         (Abs(VR - MR) > 0.01) or (Abs(VT - MT) > 0.01) then
        Writeln(Format('Page %d  MediaBox [%g %g %g %g]  visible [%g %g %g %g]  /Rotate %d',
          [I + 1, ML, MB, MR, MT, VL, VB, VR, VT,
           Pdf.GetLoadedPageRotation(I)]));
    end;
  finally
    Pdf.Free;
  end;
end;

GetLoadedPageVisibleBox'in iki ayrıntısı, bunun gibi script'ler için önemlidir. Fonksiyon, başarısız olduğunda out parametrelerine dokunmaz; dolayısıyla çağrıdan önce varsayılan bir sayfa boyutu önlemek güvenli bir örüntüdür. Ve bozuk bir CropBox MediaBox'la hiç kesişmediğinde fonksiyon, boş bir dikdörtgen yerine MediaBox'ı döndürür. Rapor sayfa listeliyorsa ve dağıtımdaki derlemeniz OCR için v2.770.153'ten, barkod ve yüzler için v2.770.154'ten eskiyse, yükselttikten sonra o sayfalarda tanımayı yeniden koşturun. Etkilenen bir derlemenin işlediği OCR katmanı kaydedilen dosyada kalır ve varsayılan SkipPagesWithText option'ı, kapatmadığınız ya da eski katmanı önce kaldırmadığınız sürece ikinci geçişte o sayfaları atlar

Özel bir IHPDFOCREngine pixel'leri PDF space'ine nasıl geri eşlemeli?

Özel bir IHPDFOCREngine, kelime kutularını bitmap pixel'leri olarak döndürmeli ve eşlemeyi HotPDF'e bırakmalı; user space'e yalnızca kendi kararları için çevirmeli ve o zaman da isteğin kutusunu kullanmalı, asla MediaBox'ı değil. v2.770.153'ten beri isteğin PageLeft, PageBottom, PageRight ve PageTop'u render edilen görünür kutuyu tanımlar; dolayısıyla Request.Bitmap ile tam olarak uyuşurlar. Aşağıdaki yardımcı, kütüphanenin dönüşümünün tersidir — dik sayfalar için gerçek bitmap yüksekliğini kullanması dahil — dolayısıyla HotPDF'le pixel pixel hemfikirdir

uses
  System.SysUtils, System.Math, Vcl.Graphics, HPDFDoc;

// Bitmap pixeli (sol-üst köken, Y aşağı) PDF user space'ine
// (sol-alt köken, Y yukarı), bitmap'in render edildiği kutu üzerinden
procedure HotPixelToPage(Rotation, DPI, BitmapHeight: Integer;
  Left, Bottom, Right, Top: Single; X, Y: Double;
  out PageX, PageY: Double);
var
  S: Double;
begin
  S := DPI / 72.0;
  case Rotation of
    90:  begin PageX := Left + Y / S;  PageY := Bottom + X / S; end;
    180: begin PageX := Right - X / S; PageY := Bottom + Y / S; end;
    270: begin PageX := Right - Y / S; PageY := Top - X / S; end;
  else
    PageX := Left + X / S;
    PageY := Bottom + (BitmapHeight - Y) / S;
  end;
end;

Bir motorun user space'e gerçekten ihtiyaç duymasının gerçekçi bir nedeni bölge kuralıdır: antetinin asla aranabilir olmasını istemediğiniz faturalar ya da tanıyıcıyı şaşırtan bir damga alanı. Aşağıdaki motor — ömrünü reference counting'in yönetmesi için TInterfacedObject ile yazılmış — kelimeleri merkezlerinin sayfaya düştüğü yere göre filtreler ve kalanları pixel koordinatlarında dokunmadan döndürür. RunRecognizer, kendi tanıyıcı çağrınızın yerini tutar

type
  TZoneFilterOCREngine = class(TInterfacedObject, IHPDFOCREngine)
  private
    FSkipLeft, FSkipBottom, FSkipRight, FSkipTop: Single;  // user space
    function RunRecognizer(Bitmap: TBitmap; MaxWords: Integer;
      out Words: THPDFOCRWords): boolean;  // kendi tanıyıcınız, pixel kutuları
  public
    constructor Create(SkipLeft, SkipBottom, SkipRight, SkipTop: Single);
    function GetName: AnsiString;
    function Recognize(const Request: THPDFOCRRequest;
      out Words: THPDFOCRWords; out Diagnostic: AnsiString): boolean;
  end;

function TZoneFilterOCREngine.Recognize(const Request: THPDFOCRRequest;
  out Words: THPDFOCRWords; out Diagnostic: AnsiString): boolean;
var
  Raw: THPDFOCRWords;
  I, Count: Integer;
  CX, CY: Double;
begin
  Diagnostic := '';
  SetLength(Words, 0);
  if not RunRecognizer(Request.Bitmap, Request.MaxWords, Raw) then
  begin
    Diagnostic := 'recognizer failed';
    Exit(False);
  end;
  SetLength(Words, Length(Raw));
  Count := 0;
  for I := 0 to High(Raw) do
  begin
    HotPixelToPage(Request.PageRotation, Request.DPI,
      Request.Bitmap.Height, Request.PageLeft, Request.PageBottom,
      Request.PageRight, Request.PageTop,
      (Raw[I].Left + Raw[I].Right) / 2, (Raw[I].Top + Raw[I].Bottom) / 2,
      CX, CY);
    if (CX >= FSkipLeft) and (CX <= FSkipRight) and
       (CY >= FSkipBottom) and (CY <= FSkipTop) then
      Continue;
    Words[Count] := Raw[I];  // hâlâ pixel: HotPDF kendisi eşler
    Inc(Count);
  end;
  SetLength(Words, Count);
  Result := True;
end;

Motoru, diğer her motor gibi ApplyLoadedOCRTextLayer(PageIndices, Engine, Options, Info)'a verin. Kütüphane, ona güvenmeden önce geri döneni doğrular: kutusu bitmap'ten çıkan, Right <= Left ya da Bottom <= Top olan ya da Confidence'i 0-1 aralığının dışında ya da MinimumConfidence'in altında olan bir kelime düşürülür ve Info.DroppedWordCount'te sayılır. MaxWordsPerPage'den çok kelime döndürmek ya da yürüyen toplamı MaxTotalWords'ün ötesine itmek, bütün çağrıyı bir bütçe hatasıyla düşürür; o yüzden motor içinde Request.MaxWords'e saygı gösterin. Kelime kutularını döndürmeden önce user space'e çevirmeyin; HotPDF point değerlerini pixel sayar ve katman bitmap kökenine doğru çökerdi

Kendi detector çıktınızı eşlemek

Aynı yardımcı, RenderLoadedPageToBitmap üzerine kurulu yerli bir hataya da hizmet eder; o da tanıma özellikleri gibi görünür kutuyu render eder ve /Rotate'i uygular. Kutuyu GetLoadedPageVisibleBox ile okuyun, rotasyonu HotPDF'in yaptığı gibi normalize edin ve her pixel kutusunun birbirine ters iki köşesini eşleyin. Y ekseni döner ve 90 ile 270 derecede eksenler takas olur; dolayısıyla eşlenen köşeler sabit bir sırada çıkmaz. Eşlenen noktaların en küçüğü ile en büyüğünü alın — HotPDF barkod sınırlarını da böyle kurar

const
  DPI = 200;
var
  Pdf: THotPDF;
  Bmp: TBitmap;
  VL, VB, VR, VT: Single;
  Rotation: Integer;
  PxL, PxT, PxR, PxB, X1, Y1, X2, Y2: Double;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('scanned-ids.pdf');
    if not Pdf.GetLoadedPageVisibleBox(0, VL, VB, VR, VT) then Exit;
    Rotation := Pdf.GetLoadedPageRotation(0) mod 360;
    if Rotation < 0 then Inc(Rotation, 360);
    if (Rotation <> 90) and (Rotation <> 180) and (Rotation <> 270) then
      Rotation := 0;
    Bmp := Pdf.RenderLoadedPageToBitmap(0, DPI);
    if Bmp = nil then Exit;
    try
      MyDetector(Bmp, PxL, PxT, PxR, PxB);  // kendi kodunuz, pixel kutusu
      HotPixelToPage(Rotation, DPI, Bmp.Height, VL, VB, VR, VT,
        PxL, PxT, X1, Y1);
      HotPixelToPage(Rotation, DPI, Bmp.Height, VL, VB, VR, VT,
        PxR, PxB, X2, Y2);
      Writeln(Format('User-space box [%.2f %.2f %.2f %.2f]',
        [Min(X1, X2), Min(Y1, Y2), Max(X1, X2), Max(Y1, Y2)]));
    finally
      Bmp.Free;
    end;
  finally
    Pdf.Free;
  end;
end;

Rotasyon davranışı page box'ları bozmadan sayfa rotasyonunu düzleştirmekte, aynı dönüşümü tüketen barkod decode hattı ise PDF sayfalarından döndürülmüş QR kodlarını decode etmekte daha derin ele alınır. Motorunuz harici bir tanıyıcıyı sarıyorsa, aranabilir PDF için Tesseract OCR adaptörü aynı arayüzün süreç izolasyonu ile iptal tarafını gösterir

Hızlı başvuru: CropBox'a uygun koordinat eşleme

  • Renderer görünür kutuyu — MediaBox'a kırpılmış CropBox'ı (ISO 32000-1 §14.11.2) — rasterize eder; her pixel-to-page eşlemesi o kutuyu, GetLoadedPageVisibleBox ile okuyarak kullanmalıdır
  • HotPDF v2.770.153 ApplyLoadedOCRTextLayer'ı düzeltti; v2.770.154 bütün sayfalık DecodeLoadedPageBarcodes ile DetectLoadedRedactionFindings'ten gelen yüz bulgularını düzeltti; v2.766.64'ten o sürümlere kadarki derlemeler etkilenir
  • THPDFOCRWord kutuları sol-üst kökenli bitmap pixel'leridir; GetLoadedPageBox ile GetLoadedPageVisibleBox sol-alt kökenli, Bottom < Top olan PDF user space'i döndürür
  • Ölçek DPI / 72'dir; onu DPI'dan türetin, bitmap genişliğine bölünmüş bir page box'tan asla
  • /Rotate hangi kenarların önemli olduğuna karar verir: 0 ile 90'da Left ile Bottom, 180'de Right ile Bottom, 270'te Right ile Top
  • OCR kelimelerini pixel olarak döndürün ve eşlemeyi HotPDF'e bırakın; yalnızca kendi filtre mantığınız için çevirin
  • Etkilenen bir derlemenin işlediği kırpılmış sayfalarda OCR'ı yeniden koşturun ve SkipPagesWithText'in eski katmanı hâlihazırda taşıyan sayfaları atlattığını unutmayın

Burada kullanılan tanıma özellikleri, page-box sorguları ve yüklenen-belge rendering'i, Delphi ve C++Builder için HotPDF bileşeninde gelir; lisanslama, deneme indirmeleri ve tam özellik listesi HotPDF Delphi PDF component sayfasındadır