text layer ของ OCR, ขอบเขตของ barcode และกล่อง redaction ของใบหน้าเหลื่อมตำแหน่งบนหน้า PDF ที่ถูก crop เมื่อ pixel ของ bitmap ถูกแมปกลับผ่าน MediaBox แทนกล่องที่ renderer rasterize จริง ๆ ซึ่งก็คือ CropBox ที่ถูก clip ด้วย MediaBox (ISO 32000-1 §14.11.2) HotPDF แก้เรื่องนี้ให้ ApplyLoadedOCRTextLayer ใน v2.770.153 และให้ DecodeLoadedPageBarcodes กับ DetectLoadedRedactionFindings ใน v2.770.154
รายงานบั๊กที่มักไหลเข้ามาหน้าตาแบบนี้ คลังสัญญาที่สแกนถูกส่งเข้า OCR ไฟล์ output ค้นหาได้ และ search hit ของเลขมาตราถูก highlight หลุดไปครึ่งนิ้วด้านล่างกับด้านซ้ายของเลขที่พิมพ์ไว้ ไฟล์ส่วนใหญ่ในล็อตไม่มีปัญหา ตัวที่พังทั้งหมดมาจากสถานีสแกนเครื่องเดียวที่เขียน /CropBox ตัดขอบของแท่นแก้ว รายละเอียดจุดเดียวนี้แหละที่ทำให้ภาพที่ OCR engine เห็นกับกรอบที่ text layer ถูกวางแยกจากกัน และความไม่ตรงกันแบบเดียวกันนี้ยังขยับขอบเขตของ barcode และที่จริงจังกว่านั้นคือกล่อง redaction ของใบหน้า
ทำไม text layer ของ OCR ถึงเหลื่อมหลุดจากคำที่สแกนมา
text layer เหลื่อมเพราะครึ่งซ้ายกับครึ่งขวาของ pipeline เห็นต่างกันว่า bitmap ครอบสี่เหลี่ยมไหน ใน v2.766.64 HotPDF เปลี่ยนการ render, การ export SVG, viewer และการพิมพ์ให้ยึด CropBox: หน้าถูกแสดงผ่าน CropBox ที่ถูก clip ด้วย MediaBox ตามที่ ISO 32000-1 §14.11.2 กำหนด และ GetLoadedPageVisibleBox ถูกเพิ่มเข้ามาคืนกล่องที่มองเห็นนั้น ส่วนฟีเจอร์ฝั่ง recognition ยังสร้าง device-to-page transform จาก GetLoadedPageBox(PageIndex, pbMediaBox, ...) อยู่ raster ตอนนี้ครอบ visible box transform ยังสมมติ MediaBox ตำแหน่งที่จดจำได้ทุกตำแหน่งจึงกลับมาเหลื่อมเท่าช่องว่างระหว่างสองกล่อง
ช่วงที่ได้รับผลกระทบจึงเป๊ะมาก ApplyLoadedOCRTextLayer วาง text ผิดที่ตั้งแต่ v2.766.64 ถึง v2.770.152 DecodeLoadedPageBarcodes แบบทั้งหน้ากับ face detection ข้างใน DetectLoadedRedactionFindings ผิดต่ออีกหนึ่ง build ถึง v2.770.153 ก่อน v2.766.64 renderer วาด MediaBox ทั้งกล่อง การแมปกับ raster จึงตรงกัน แลกกับการจดจำเนื้อหาที่ viewer ไม่เคยโชว์ การแก้เปลี่ยนสามอย่างพร้อมกันให้แต่ละฟีเจอร์: transform, การประมาณงบ pixel และ page box ที่ส่งให้ engine แบบ custom ใน request record
บางเคสไม่เคยได้รับผลเลย:
- หน้าที่ไม่มี
/CropBoxหรือหน้าที่ CropBox เท่ากับ MediaBox แมปเหมือนเดิมก่อนและหลังการแก้ DecodeLoadedPageBarcodesที่เซ็ตHasRegionจะ render เฉพาะ region ที่ส่งให้พอดี แล้วแมปผ่าน region เดียวกัน การถอดแบบระบุ region จึงถูกต้องมาตลอด ส่วนเช็คว่า region อยู่ข้างในหน้ายังใช้ MediaBox อยู่- redaction finding แบบอิงรูปแบบ (อีเมล เลขบัตร ฯลฯ) มาจากการ extract text ใน user space ไม่ใช่จาก raster มีแต่ finding ของ face detection เท่านั้นที่ขยับ
สามกรอบพิกัด กับ API ของ HotPDF ที่ใช้กรอบไหน
โค้ด HotPDF ที่แตะฝั่ง recognition ต้องจัดการกับสามกรอบ และบั๊กการแมปส่วนใหญ่มาจากการเอาสองกรอบมาคละกัน
- pixel ของ bitmap: origin บนซ้าย Y โตลงล่าง หน่วยเป็น pixel ที่ DPI ของคำขอ
THPDFOCRWord.Left,Top,RightกับBottomอยู่ในกรอบนี้ จุด baseline แบบเลือกได้ก็เช่นกัน รวมถึงผลลัพธ์ที่IHPDFBarcodeDecoderแบบ custom คืนมา และกล่องจากIHPDFFaceDetectorแบบ custom - PDF user space ของหน้าที่โหลด: origin ล่างซ้าย Y โตขึ้นบน หน่วยเป็นพอยต์ โดย
Bottom < TopGetLoadedPageBoxกับGetLoadedPageVisibleBoxคืน Left, Bottom, Right, Top ในกรอบนี้ fieldPageLeft,PageBottom,PageRightกับPageTopของTHPDFOCRRequestก็เช่นกัน รวมถึงขอบเขตในTHPDFDecodedBarcodeและสี่เหลี่ยมในTHPDFRedactionFinding - พิกัดวาดหน้าของ HotPDF: API ที่ใช้สร้างหน้าใหม่ (ข้อความ รูปทรง barcode ลิงก์ form field) ทำงานด้วย origin บนซ้ายและ Y โตลงล่าง กรอบนี้เป็นของการสร้างเอกสาร ไม่เกี่ยวกับ API ของเอกสารที่โหลดด้านบนเลย ห้ามยัดสี่เหลี่ยม user space ของหน้าที่โหลดเข้ามันโดยไม่แปลงเด็ดขาด
record ของคำ OCR ถูกออกแบบให้อิง pixel ตั้งใจ engine รายงานสิ่งที่มันเห็นในภาพ และ ApplyLoadedOCRTextLayer เป็นเจ้าของการแปลงเอง การแบ่งงานแบบนี้ทำงานได้ก็ต่อเมื่อการแปลงใช้กล่องที่ถูกตัว ซึ่งคือสิ่งที่ v2.770.153 กู้คืนมา
device-to-page transform ที่อยู่เบื้องหลัง OCR, barcode และใบหน้า
HotPDF แมป pixel ของ bitmap กลับสู่หน้าด้วย affine matrix ตัวเดียวที่ประกอบจาก input ห้าตัว: การหมุน, อัตราส่วน DPI / 72, ความสูงของ bitmap และ Left, Bottom, Right กับ Top ของกล่องที่ render OCR, การถอด barcode และ face detection แชร์ routine ตัวเดียวกันร่วมกัน กล่อง input ผิดหนึ่งตัวจึงทำให้ทั้งสามพังแบบเดียวกัน สำหรับหน้าที่ไม่หมุน matrix แบบ page-to-device [A B C D E F] เป็น:
A = ScaleกับD = -ScaleโดยScale = DPI / 72Dที่ติดลบพลิก user space (Y ขึ้น) ไปเป็นพื้นที่ bitmap (Y ลง)B = C = 0เพราะหน้าที่ไม่หมุนไม่มีการเฉือนหรือสลับแกนE = -Left * Scaleซึ่งเลื่อนขอบซ้ายของกล่องไปที่ column 0 ของ pixelF = BitmapHeight + Bottom * Scaleซึ่งแมปขอบล่างของกล่องไปที่ y = BitmapHeight ขอบล่างของ bitmap ขอบบนจึงตกลงที่แถว 0
pixel กลับสู่หน้าผ่าน inverse ของ matrix นั้น Request.PageRotation หอบ /Rotate ของหน้ามาแบบ normalize เป็น 0, 90, 180 หรือ 270 (ค่าใดที่ไม่ใช่ผลคูณของ 90 ถือเป็น 0) และ renderer หมุนหน้าตามเข็มนาฬิกาตามที่ ISO 32000-1 §7.7.3.3 กำหนด ภายใต้การหมุนแกนจะสลับกันและคู่ขอบกล่องคนละคู่ถูกตรึงกับ origin ของ bitmap เขียนเป็นสูตร inverse โดย S = DPI / 72, x กับ y หน่วย pixel และ H คือความสูงของ bitmap:
| /Rotate | Page X | Page Y | ขอบกล่องที่การแมปพึ่งพา |
|---|---|---|---|
| 0 | Left + x / S | Bottom + (H - y) / S | Left, Bottom |
| 90 | Left + y / S | Bottom + x / S | Left, Bottom |
| 180 | Right - x / S | Bottom + y / S | Right, Bottom |
| 270 | Right - y / S | Top - x / S | Right, Top |
column สุดท้ายอธิบายว่าทำไมบั๊กถึงดูเหมือนสุ่มใน production CropBox ที่ตัดแค่ส่วนบนของหน้าปล่อย Left กับ Bottom ไว้เหมือนเดิม หน้าตั้งตรงจึงออกมาสมบูรณ์ และมีแต่หน้าที่แปะ /Rotate 270 เท่านั้นที่เหลื่อม การหมุนยังสลับขนาดของ bitmap ด้วย ที่ 90 กับ 270 bitmap กว้าง (Top - Bottom) * S pixel และสูง (Right - Left) * S pixel
MediaBox [0 0 612 792] กับ CropBox [36 36 576 756] พังตรงไหน
ด้วยการ crop ครึ่งนิ้วทุกด้าน text layer ของหน้าที่ไม่หมุนจะตกเหลื่อมพอดี 36 พอยต์ไปทางซ้ายและ 36 พอยต์ลงล่างจากคำที่สแกนเมื่อใช้ MediaBox ยกหน้า US Letter ที่ CropBox ตัด 36 พอยต์ (0.5 นิ้ว) ออกจากทุกขอบ visible box คือ 540 คูณ 720 พอยต์ ที่ความละเอียด OCR เริ่มต้น 300 DPI อัตราส่วนคือ 300 / 72 ≈ 4.1667 และ bitmap คือ 2250 คูณ 3000 pixel
สมมติ engine รายงานคำหนึ่งพร้อม box เป็น pixel ที่ Left 450, Top 600, Right 900, Bottom 660 และไม่มี baseline HotPDF จะวาง baseline ไว้สูงจากขอบล่าง 20 เปอร์เซ็นต์ของความสูงคำ ที่แถว pixel 648 แล้วแมปจุดเริ่ม (450, 648):
- ผ่าน visible box: x = 36 + 450 / 4.1667 = 144.0 และ y = 36 + (3000 - 648) / 4.1667 = 600.48 ซึ่งคือที่ที่คำถูกพิมพ์จริง
- ผ่าน MediaBox: x = 0 + 108.0 = 108.0 และ y = 0 + 564.48 = 564.48 เหลื่อมเป็นชุดเดียว (-36, -36) พอยต์
หมุนหน้าเดิมแล้วทิศของความผิดพลาดเปลี่ยน เพราะขอบที่เกี่ยวข้องไม่เหมือนเดิม ที่ /Rotate 180 พจน์ X ใช้ Right และ 612 แทน 576 ดัน layer ไปทางขวา 36 พอยต์ ขณะที่ Bottom ยังลากมันลง 36 พอยต์ ที่ /Rotate 270 ทั้ง Right กับ Top ใหญ่เกิน layer จึงไปขวา 36 พอยต์และขึ้นบน 36 พอยต์ เอกสารที่ orientation ปนกันสามารถโชว์การเหลื่อมสามทิศ ซึ่งเป็นลายนิ้วมือที่เชื่อถือได้ของบั๊กตัวนี้ โค้ดที่เขียนมือซึ่ง derive อัตราส่วนจากกล่อง อย่าง Bitmap.Width / (Right - Left) ยังดึงพิกัดทุกตัวยืดเพิ่ม 612 / 540 หรือราว 13 เปอร์เซ็นต์ ทับไปบนการเหลื่อมอีกชั้น
เอกสาร PDF ของคุณตัวไหนได้รับผลกระทบ
เอกสาร PDF เสี่ยงเมื่อมีหน้าอย่างน้อยหนึ่งหน้าที่ visible box ต่างจาก MediaBox ของมัน และ HotPDF บอกได้ในไม่กี่บรรทัด เทียบ GetLoadedPageBox ด้วย pbMediaBox กับ GetLoadedPageVisibleBox ทีละหน้า พร้อมพิมพ์ GetLoadedPageRotation คู่ไว้ด้วยเพื่อทำนายทิศการเหลื่อมจากตารางข้างบน THPDFPageBoundary ยังมี pbCropBox, pbBleedBox, pbTrimBox กับ pbArtBox ให้ด้วย แต่ GetLoadedPageBox(pbCropBox) จะย้อนไปใช้ MediaBox เมื่อไม่มี crop box และไม่ clip ให้ visible box จึงเป็นสิ่งที่ถูกต้องที่สุดที่จะเอามาเทียบ
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;
// array ที่เก็บไว้อาจลิสต์มุมสลับลำดับกันได้
if MR < ML then begin Tmp := ML; ML := MR; MR := Tmp; end;
if MT < MB then begin Tmp := MB; MB := MT; MT := Tmp; end;
// normalize และ clip ด้วย MediaBox ไว้เรียบร้อยแล้ว
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 สำคัญกับ script แบบนี้ ฟังก์ชันปล่อย out parameters ไว้เหมือนเดิมเมื่อล้ม การตั้งค่าขนาดหน้าค่าเริ่มต้นไว้ก่อนเรียกจึงเป็น pattern ที่ปลอดภัย และเมื่อ CropBox ที่เสียรูปไม่ตัดกับ MediaBox เลย ฟังก์ชันจะคืน MediaBox แทนสี่เหลี่ยมว่าง ถ้ารายงานลิสต์หน้าและ build ที่ deploy อยู่เก่ากว่า v2.770.153 สำหรับ OCR หรือ v2.770.154 สำหรับ barcode กับใบหน้า ให้รัน recognition ซ้ำกับหน้าพวกนั้นหลังอัปเกรด layer ของ OCR ที่ build ที่ได้รับผลกระทบ commit ไว้จะค้างอยู่ในไฟล์ที่ save และตัวเลือก SkipPagesWithText ค่าเริ่มต้นจะข้ามหน้าพวกนั้นในรอบที่สอง เว้นแต่คุณจะปิดมันหรือถอด layer เก่าออกก่อน
IHPDFOCREngine แบบ custom ควรแมป pixel กลับสู่พื้นที่ PDF อย่างไร
IHPDFOCREngine แบบ custom ควรคืน box ของคำเป็น pixel ของ bitmap แล้วให้ HotPDF ทำการแมป แปลงเป็น user space เฉพาะเพื่อการตัดสินใจของคุณเอง และตอนนั้นให้ใช้กล่องจาก request ห้ามใช้ MediaBox ตั้งแต่ v2.770.153 field PageLeft, PageBottom, PageRight กับ PageTop ของ request บรรยาย visible box ที่ render แล้ว จึงตรงกับ Request.Bitmap เป๊ะ ๆ helper ด้านล่างเป็น inverse ของ transform ของ library รวมถึงการใช้ความสูง bitmap จริงกับหน้าที่ตั้งตรง จึงเห็นตรงกับ HotPDF ระดับ pixel
uses
System.SysUtils, System.Math, Vcl.Graphics, HPDFDoc;
// pixel ของ bitmap (origin บนซ้าย Y ลง) ไปหา PDF user space
// (origin ล่างซ้าย Y ขึ้น) ผ่านกล่องที่ bitmap ถูก render ออกมา
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;
เหตุผลที่สมจริงในการต้องการ user space ข้างใน engine คือกฎเขตงาน: ใบแจ้งหนี้ที่ไม่ต้องการให้ letterhead ค้นหาได้ หรือพื้นที่ตราประทับที่รบกวน recognizer engine ด้านล่างเขียนด้วย TInterfacedObject เพื่อให้ reference counting ดูแลอายุของมัน กรองคำตามตำแหน่งกึ่งกลางที่ตกบนหน้า แล้วคืนตัวรอดชีวิตกลับไปเฉย ๆ ในพิกัด pixel RunRecognizer เป็นตัวแทนของ call recognizer ของคุณ
type
TZoneFilterOCREngine = class(TInterfacedObject, IHPDFOCREngine)
private
FSkipLeft, FSkipBottom, FSkipRight, FSkipTop: Single; // user space
function RunRecognizer(Bitmap: TBitmap; MaxWords: Integer;
out Words: THPDFOCRWords): boolean; // recognizer ของคุณ ให้ box เป็น pixel
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]; // ยังเป็น pixel อยู่ ให้ HotPDF แมปเอง
Inc(Count);
end;
SetLength(Words, Count);
Result := True;
end;
ส่ง engine เข้า ApplyLoadedOCRTextLayer(PageIndices, Engine, Options, Info) เหมือน engine อื่น ๆ library จะตรวจสิ่งที่ไหลกลับมาก่อนเชื่อมัน คำหนึ่งจะถูกทิ้งและนับเข้า Info.DroppedWordCount เมื่อ box ของมันหลุดออกนอก bitmap เมื่อ Right <= Left หรือ Bottom <= Top หรือเมื่อ Confidence อยู่นอก 0..1 หรือต่ำกว่า MinimumConfidence การคืนคำเกิน MaxWordsPerPage หรือดันยอดสะสมเกิน MaxTotalWords ทำให้ call ทั้งชุด fail ด้วย budget error จึงควรเคารพ Request.MaxWords ใน engine ห้ามแปลง box ของคำเป็น user space ก่อนคืนค่า ไม่งั้น HotPDF จะถือค่าพอยต์เป็น pixel และ layer จะหุบยุบไปกอด origin ของ bitmap
แมป output ของ detector ที่เขียนเอง
helper ตัวเดียวกันเสิร์ฟ pipeline ที่ปั้นเองบน RenderLoadedPageToBitmap ซึ่ง render กล่องที่มองเห็นและใช้ /Rotate แบบเดียวกับที่ฟีเจอร์ฝั่ง recognition ทำ อ่านกล่องด้วย GetLoadedPageVisibleBox normalize การหมุนแบบเดียวกับที่ HotPDF ทำ แล้วแมปมุมตรงข้ามกันสองจุดของ box เป็น pixel แกน Y ถูกพลิก และที่ 90 กับ 270 องศาแกนสลับกัน มุมที่แมปแล้วจึงออกมาไม่มีลำดับตายตัว ให้หยิบค่าต่ำสุดกับสูงสุดของจุดที่แมปแล้ว ซึ่งเป็นวิธีเดียวกับที่ HotPDF สร้างขอบเขตของ barcode
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); // โค้ดของคุณ ให้ box เป็น pixel
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;
พฤติกรรมการหมุนเล่าลึกกว่านี้ในการ flatten page rotation โดยไม่ทำ page box พัง และ pipeline ถอด barcode ที่กิน transform ตัวเดียวกันในการถอด QR code ที่หมุนจากหน้า PDF ถ้า engine ของคุณห่อ recognizer ภายนอก adapter ของ Tesseract OCR สำหรับ searchable PDFโชว์ฝั่งการแยก process กับ cancellation ของ interface เดียวกันนี้
สรุปด่วน: การแมปพิกัดที่ปลอดภัยต่อ CropBox
- renderer rasterize กล่องที่มองเห็น คือ CropBox ที่ถูก clip ด้วย MediaBox (ISO 32000-1 §14.11.2) การแมป pixel ไปหน้าทุกชุดต้องใช้กล่องนั้น อ่านด้วย
GetLoadedPageVisibleBox - HotPDF v2.770.153 แก้
ApplyLoadedOCRTextLayerv2.770.154 แก้DecodeLoadedPageBarcodesแบบทั้งหน้ากับ face finding จากDetectLoadedRedactionFindingsbuild ตั้งแต่ v2.766.64 ถึงเวอร์ชันก่อนทั้งคู่ได้รับผล - box ของ
THPDFOCRWordเป็น pixel ของ bitmap ที่มี origin บนซ้ายGetLoadedPageBoxกับGetLoadedPageVisibleBoxคืน PDF user space ที่มี origin ล่างซ้ายโดยBottom < Top - อัตราส่วนคือ
DPI / 72derive มันจาก DPI ห้าม derive จากการเอา page box ไปหารความกว้าง bitmap เด็ดขาด - /Rotate ตัดสินว่าขอบไหนมีผล: Left กับ Bottom ที่ 0 กับ 90, Right กับ Bottom ที่ 180, Right กับ Top ที่ 270
- คืนคำของ OCR เป็น pixel แล้วให้ HotPDF แมปต่อ แปลงเองเฉพาะสำหรับ logic กรองของคุณ
- รัน OCR ซ้ำกับหน้าที่ถูก crop ซึ่งผ่าน build ที่ได้รับผล และจำไว้ว่า
SkipPagesWithTextจะข้ามหน้าที่พา layer เก่าไว้แล้ว
ฟีเจอร์ฝั่ง recognition, การ query page box และการ render เอกสารที่โหลดแล้วที่ใช้ในนี้ทั้งหมดมาพร้อม HotPDF component สำหรับ Delphi กับ C++Builder การ license, ดาวน์โหลดเวอร์ชันทดลองและรายการฟีเจอร์เต็มอยู่ที่หน้า product ของ HotPDF Delphi PDF component