מאמר טכני

היסט שכבת הטקסט של OCR ב-HotPDF: מיפוי דרך ה-CropBox

שכבות טקסט OCR, גבולות ברקוד ותיבות הסתרת פנים סוטים בעמודי PDF חתוכים כשפיקסלי bitmap ממופים חזרה דרך ה-MediaBox במקום התיבה שה-renderer באמת רסטר: ה-CropBox כפי שהוא נחתך מול ה-MediaBox (ISO 32000-1 §14.11.2). HotPDF תיקן זאת עבור ApplyLoadedOCRTextLayer ב-v2.770.153, ועבור DecodeLoadedPageBarcodes ו-DetectLoadedRedactionFindings ב-v2.770.154

דוח הבאג שבדרך כלל מגיע נראה כך. ארכיון חוזים סרוק עובר OCR, הפלט ניתן לחיפוש, וההדגשה של פגיעת החיפוש עבור מספר סעיף מוצגת חצי אינץ' מתחת ומשמאל למספר המודפס. רוב הקבצים באצווה תקינים. השבורים כולם הגיעו מתחנת סריקה אחת שכותבת /CropBox כדי לחתוך את שולי הפלטה. הפרט האחד הזה מפריד בין התמונה שמנוע ה-OCR ראה לבין המסגרת שבה שכבת הטקסט הוצבה, ואותו אי-התאמה מזיז גם את גבולות הברקוד ו, חמור מכך, את תיבות הסתרת הפנים

למה שכבת הטקסט של ה-OCR מתרחקת מהמילים הסרוקות?

שכבת הטקסט סוטה כי שני חצאי הצינור חלקו על איזה מלבן ה-bitmap מכסה. ב-v2.766.64 HotPDF שינתה את הרינדור, ייצוא ה-SVG, הצופה וההדפסה כדי לכבד את ה-CropBox: עמוד מוצג דרך ה-CropBox שלו כפי שהוא נחתך מול ה-MediaBox שלו, מה ש-ISO 32000-1 §14.11.2 מורה, ו-GetLoadedPageVisibleBox נוסף כדי להחזיר את אותה תיבה נראית. יכולות הזיהוי המשיכו לבנות את הטרנספורם שלהן מ-device ל-page מתוך GetLoadedPageBox(PageIndex, pbMediaBox, ...). הרסטר כיסה עכשיו את התיבה הנראית, הטרנספורם עדיין הניח את ה-MediaBox, וכל מיקום שזוהה חזר מוסט בפער שבין השניים

החלון הנגוע הוא אפוא מדויק. ApplyLoadedOCRTextLayer הציב טקסט במקום הלא נכון מ-v2.766.64 ועד v2.770.152. DecodeLoadedPageBarcodes לעמוד שלם וה-detection של פנים בתוך DetectLoadedRedactionFindings נשארו שגויים build אחד נוסף, עד v2.770.153. לפני v2.766.64 ה-renderer צייר את ה-MediaBox כולו, כך שהמיפוי והרסטר הסכימו, במחיר זיהוי של תוכן שצופים לעולם לא מציגים. התיקונים שינו שלושה דברים יחד עבור כל יכולת: את הטרנספורם, את הערכת תקציב הפיקסלים, ואת תיבת העמוד שנמסרת למנוע מותאם בתוך רשומת הבקשה

כמה מקרים מעולם לא נפגעו:

  • עמודים בלי /CropBox, או שה-CropBox שלהם שווה ל-MediaBox, ממופים זהים לפני ואחרי התיקון
  • DecodeLoadedPageBarcodes עם HasRegion מוגדר מרנדר בדיוק את האזור שמעבירים וממפה דרך אותו אזור, כך שפענוח אזור-מפורש היה נכון לאורך כל הדרך; הבדיקה שהאזור שוכן בתוך העמוד עדיין משתמשת ב-MediaBox
  • ממצאי הסתרה מבוססי תבנית (אימיילים, מספרי כרטיסים וכדומה) מגיעים מחילוץ טקסט במרחב המשתמש, לא מרסטר, ולכן רק ממצאי detection הפנים זזו

שלוש מערכות קואורדינטות, ואילו APIs של HotPDF משתמשים בכל אחת

קוד HotPDF שנוגע בזיהוי מתמודד עם שלוש מערכות, ורוב באגי המיפוי מגיעים מערבוב של שתיים מהן

  • פיקסלי bitmap: ראשית שמאל-עליונה, Y גדל כלפי מטה, היחידות פיקסלים ב-DPI של הבקשה. THPDFOCRWord.Left, Top, Right ו-Bottom נמצאים במערכת הזאת, וכן נקודות ה-baseline האופציונליות, התוצאות ש-IHPDFBarcodeDecoder מותאם מחזיר, והתיבות מ-IHPDFFaceDetector מותאם
  • מרחב משתמש PDF של עמוד טעון: ראשית שמאל-תחתונה, Y גדל כלפי מעלה, היחידות נקודות, עם Bottom < Top. GetLoadedPageBox ו-GetLoadedPageVisibleBox מחזירים Left, Bottom, Right, Top במערכת הזאת, וכך גם שדות PageLeft, PageBottom, PageRight ו-PageTop של THPDFOCRRequest, הגבולות ב-THPDFDecodedBarcode והמלבנים ב-THPDFRedactionFinding
  • קואורדינטות ציור עמוד של HotPDF: ה-API שמשתמשים בו לבניית עמודים חדשים (פלט טקסט, צורות, ברקודים, קישורים, שדות טפסים) עובד עם ראשית שמאל-עליונה ו-Y גדל כלפי מטה. המערכת הזאת שייכת לייצור מסמכים ואין לה שום קשר ל-APIs של מסמכים טעונים שלמעלה, ולכן לעולם לא מזינים אליה מלבן במרחב משתמש של עמוד טעון ללא שינוי

רשומת המילה של ה-OCR מבוססת בכוונה על פיקסלים: מנוע מדווח על מה שראה בתמונה, ו-ApplyLoadedOCRTextLayer בבעלותו ההמרה. החלוקה הזאת עובדת רק כשההמרה משתמשת בתיבה הנכונה, מה ש-v2.770.153 השיב

מערכות הקואורדינטות של הזיהוי ב-HotPDF: פיקסלי bitmap עם ראשית שמאל-עליונה שמשמשים תיבות THPDFOCRWord ומפענחים מותאמים, מרחב משתמש PDF עם ראשית שמאל-תחתונה שמוחזר על ידי GetLoadedPageBox ו-GetLoadedPageVisibleBox, ו-API ציור העמוד שמאל-עליונה, שלעולם לא צריך לקבל מלבן של עמוד טעון ללא שינוי
מנועים מדווחים פיקסלים כי זה מה שראו, HotPDF ממפה אותם, וערבוב שתי המערכות הוא הדרך שבה שכבות ותיבות הסתרה סוטים

הטרנספורם מ-device ל-page שמאחורי OCR, ברקודים ופנים

HotPDF ממפה פיקסלי bitmap אל העמוד עם מטריצה אפינית אחת שנבנית מחמישה קלטים: הסיבוב, הקנה המידה DPI / 72, גובה ה-bitmap, וה-Left, Bottom, Right ו-Top של התיבה המרונדרת. OCR, פענוח ברקודים ו-detection של פנים חולקים רוטינה אחת עבור זה, ולכן קלט תיבה שגוי אחד שבר את שלושתם באותו אופן. עבור עמוד לא מסובב מטריצת ה-page-to-device [A B C D E F] היא:

  • A = Scale ו-D = -Scale, כאשר Scale = DPI / 72; ה-D השלילי הופך מרחב משתמש (Y מעלה) אל מרחב bitmap (Y מטה)
  • B = C = 0, כי עמוד לא מסובב אינו מחזיק גזירה או החלפה בין צירים
  • E = -Left * Scale, שמזיז את הקצה השמאלי של התיבה אל עמודת פיקסל 0
  • F = BitmapHeight + Bottom * Scale, שממפה את הקצה התחתון של התיבה אל y = BitmapHeight, הקצה התחתון של ה-bitmap, כך שהקצה העליון נוחת על שורה 0

פיקסלים חוזרים אל העמוד דרך ההופכית של אותה מטריצה. Request.PageRotation נושא את ה-/Rotate של העמוד מנורמל ל-0, 90, 180 או 270 (כל ערך שאינו כפולה של 90 מטופל כ-0), וה-renderer מסובב את העמוד עם כיוון השעון כפי ש-ISO 32000-1 §7.7.3.3 דורש. תחת סיבוב הצירים מתחלפים וזוג קצוות תיבה אחר מקובע אל ראשית ה-bitmap. כתובים כנוסחאות הופכיות, עם S = DPI / 72, x ו-y בפיקסלים ו-H גובה ה-bitmap:

/RotateX של העמודY של העמודקצוות התיבה שהמיפוי נשען עליהם
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

העמודה האחרונה מסבירה למה הבאג נראה אקראי בייצור. CropBox שחותך רק את ראש העמוד משאיר את Left ו-Bottom ללא מגע, ולכן עמודים זקופים יצאו מושלמים ורק עמודים שנושאים /Rotate 270 סטו. הסיבוב גם מחליף את ממדי ה-bitmap: ב-90 וב-270 ה-bitmap הוא ברוחב (Top - Bottom) * S פיקסלים ובגובה (Right - Left) * S פיקסלים

טבלת המיפוי ההופכי של HotPDF עבור סיבוב עמוד: ב-/Rotate 0 ו-90 הטרנספורם מקבע את קצוות Left ו-Bottom של התיבה המרונדרת, ב-180 הוא מקבע את Right ו-Bottom, ב-270 את Right ו-Top, ולכן עמוד חתוך סוטה לכיוון אחר עבור כל אוריינטציה במסמך מעורב
אותו חיתוך של חצי אינץ' נראה כמו שלושה באגים שונים ברגע שעמודים נושאים ערכי /Rotate שונים, כי כל אוריינטציה מקבעת זוג קצוות תיבה אחר

מה משתבש עם MediaBox [0 0 612 792] ו-CropBox [36 36 576 756]?

עם חיתוך של חצי אינץ' מכל צד, שכבת הטקסט של עמוד לא מסובב נוחתת בדיוק 36 נקודות משמאל ו-36 נקודות מתחת למילים הסרוקות כשה-MediaBox בשימוש. קחו עמוד US Letter שה-CropBox שלו חותך 36 נקודות (0.5 אינץ') מכל קצה. התיבה הנראית היא 540 על 720 נקודות, ולכן ברזולוציית ה-OCR המוגדרת של 300 DPI הקנה הוא 300 / 72 ≈ 4.1667 וה-bitmap הוא 2250 על 3000 פיקסלים

נניח שהמנוע מדווח על מילה עם תיבת פיקסלים Left 450, Top 600, Right 900, Bottom 660 וללא בסיס. HotPDF אז מציב את הבסיס 20 אחוזים מגובה המילה מעל הקצה התחתון, בשורת פיקסלים 648, וממפה את נקודת ההתחלה (450, 648):

  • דרך התיבה הנראית: 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) נקודות
אנטומיית הסטייה של ה-CropBox ב-HotPDF על עמוד US Letter עם MediaBox 0 0 612 792 ו-CropBox 36 36 576 756: ה-renderer מרסטר את התיבה הנראית ב-300 DPI, ולכן מיפוי פיקסל המילה 450 דרך GetLoadedPageVisibleBox נותן 144.0 ו-600.48 בזמן שהטרנספורם של ה-MediaBox נוחת ב-108.0 ו-564.48
הרסטר מכסה את ה-CropBox, ולכן כל טרנספורם שנבנה מה-MediaBox מזיז כל מילה שזוהתה בדיוק בשולי החיתוך

מסובבים את אותו עמוד וכיוון השגיאה משתנה, כי קצוות אחרים מעורבים. ב-/Rotate 180 האיבר של ה-X משתמש ב-Right, ו-612 במקום 576 דוחף את השכבה 36 נקודות ימינה בזמן ש-Bottom עדיין גורר אותה 36 נקודות מטה. ב-/Rotate 270 גם Right וגם Top גדולים מדי, ולכן השכבה זזה 36 נקודות ימינה ו-36 נקודות מעלה. מסמך עם אוריינטציות מעורבות יכול להציג את הסטייה בשלושה כיוונים, טביעת אצבע אמינה לבאג הזה. קוד שנכתב ביד שגוזר את הקנה מהתיבה, כמו Bitmap.Width / (Right - Left), גם מותח כל קואורדינטה ב-612 / 540, קרוב ל-13 אחוזים, מעל ההזזה

אילו מהמסמכים שלכם נגועים?

מסמך PDF חשוף כשלפחות עמוד אחד יש תיבה נראית ששונה מה-MediaBox שלו, ו-HotPDF יכול לומר לכם את זה בכמה שורות. משווים את GetLoadedPageBox עם pbMediaBox מול GetLoadedPageVisibleBox עבור כל עמוד, ומדפיסים GetLoadedPageRotation לצד, כדי שתוכלו לחזות את כיוון הסטייה מהטבלה שלמעלה. THPDFPageBoundary מציע גם את pbCropBox, pbBleedBox, pbTrimBox ו-pbArtBox, אבל GetLoadedPageBox(pbCropBox) נופל בחזרה אל ה-MediaBox כשאין crop 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;
      // ייתכן שהמערך השמור מפרט את פינותיו בכל סדר
      if MR < ML then begin Tmp := ML; ML := MR; MR := Tmp; end;
      if MT < MB then begin Tmp := MB; MB := MT; MT := Tmp; end;
      // כבר מנורמל ונחתך מול ה-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 משנים עבור סקריפטים כמו זה. הפונקציה משאירה את פרמטרי ה-out שלה ללא מגע כשהיא נכשלת, ולכן הגדרה מראש של גודל עמוד ברירת מחדל לפני הקריאה היא תבנית בטוחה. וכש-CropBox פגום לא חותך כלל את ה-MediaBox, הפונקציה מחזירה את ה-MediaBox ולא מלבן ריק. אם הדוח מפרט עמודים וה-build המופץ שלכם ישן מ-v2.770.153 עבור OCR, או מ-v2.770.154 עבור ברקודים ופנים, מריצים זיהוי מחדש על אותם עמודים אחרי השדרוג. שכבת OCR שנקבעה על ידי build נגוע נשארת בקובץ השמור, ואפשרות SkipPagesWithText כברירת המחדל תדלג על אותם עמודים במעבר שני אלא אם תכבו אותה או תסירו קודם את השכבה הישנה

איך IHPDFOCREngine מותאם אמור למפות פיקסלים חזרה אל מרחב PDF?

IHPDFOCREngine מותאם אמור להחזיר תיבות מילים בפיקסלי bitmap ולתת ל-HotPDF לעשות את המיפוי; ממירים אל מרחב משתמש רק עבור ההחלטות שלכם, ואז משתמשים בתיבה מתוך הבקשה, לעולם לא ב-MediaBox. מאז v2.770.153 ה-PageLeft, PageBottom, PageRight ו-PageTop של הבקשה מתארים את התיבה הנראית המרונדרת, כך שהם תואמים את Request.Bitmap במדויק. ה-helper להלן הוא ההופכי של הטרנספורם של הספרייה, כולל שימושו בגובה ה-bitmap האמיתי עבור עמודים זקופים, ולכן הוא מסכים עם HotPDF עד הפיקסל

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

// פיקסל bitmap (ראשית שמאל-עליונה, Y מטה) אל מרחב משתמש PDF
// (ראשית שמאל-תחתונה, Y מעלה), דרך התיבה שממנה ה-bitmap רונדר
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;

סיבה ריאלית להזדקק למרחב משתמש בתוך מנוע היא כלל אזור: חשבוניות שאת הכותרת המודפסת שלהן אף פעם לא רוצים ניתנת לחיפוש, או אזור חותמת שמבלבל את המזהה. המנוע להלן, שנכתב עם TInterfacedObject כך שמונה ההפניות מטפל במחזור חייו, מסנן מילים לפי המקום שבו המרכזים שלהן נופלים על העמוד, ואז מחזיר את השורדים ללא מגע בקואורדינטות פיקסלים. RunRecognizer עומד במקום קריאת המזהה שלכם

type
  TZoneFilterOCREngine = class(TInterfacedObject, IHPDFOCREngine)
  private
    FSkipLeft, FSkipBottom, FSkipRight, FSkipTop: Single;  // מרחב משתמש
    function RunRecognizer(Bitmap: TBitmap; MaxWords: Integer;
      out Words: THPDFOCRWords): boolean;  // המזהה שלכם, תיבות פיקסלים
  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];  // עדיין פיקסלים: HotPDF ממפה אותם בעצמו
    Inc(Count);
  end;
  SetLength(Words, Count);
  Result := True;
end;

מוסרים את המנוע אל ApplyLoadedOCRTextLayer(PageIndices, Engine, Options, Info) כמו עם כל מנוע אחר. הספרייה מאמתת את מה שחוזר לפני שהיא סומכת עליו: מילה מושמטת ונספרת ב-Info.DroppedWordCount כשהתיבה שלה יוצאת מה-bitmap, כש-Right <= Left או Bottom <= Top, או כש-Confidence מחוץ ל-0..1 או מתחת ל-MinimumConfidence. החזרת יותר מילים מ-MaxWordsPerPage, או דחיפת המניין הרץ מעבר ל-MaxTotalWords, מכשילה את הקריאה כולה עם שגיאת תקציב, ולכן נאמנים ל-Request.MaxWords בתוך המנוע. לא ממירים תיבות מילים אל מרחב משתמש לפני ההחזרה; HotPDF היה מתייחס לערכי הנקודות בתור פיקסלים והשכבה הייתה קורסת אל ראשית ה-bitmap

מיפוי הפלט של ה-detector שלכם

אותו helper משרת צינור ביתי שנבנה על RenderLoadedPageToBitmap, שמרנדר את התיבה הנראית ומיישם את /Rotate בדיוק כפי שיכולות הזיהוי עושות. קוראים את התיבה עם GetLoadedPageVisibleBox, מנרמלים את הסיבוב באותו אופן שבו HotPDF עושה זאת, וממפים שתי פינות נגדיות של כל תיבת פיקסלים. ציר ה-Y מתהפך, וב-90 וב-270 מעלות הצירים מתחלפים, כך שהפינות הממופות יוצאות ללא סדר קבוע; לוקחים את המינימום והמקסימום של הנקודות הממופות, מה שהוא גם האופן שבו HotPDF בונה את גבולות הברקוד

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);  // הקוד שלכם, תיבת פיקסלים
      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;

התנהגות הסיבוב מכוסה לעומק רב יותר ב-שיטוח סיבוב עמוד בלי לשבור את תיבות העמוד, וצינור פענוח הברקודים שצורך את אותו טרנספורם ב-פענוח קודי QR מסובבים מעמודי PDF. אם המנוע שלכם עוטף מזהה חיצוני, מתאם ה-Tesseract OCR עבור PDF ניתן לחיפוש מציג את צד בידוד ה-process והביטול של אותו ממשק

עזר זריז: מיפוי קואורדינטות ששומר על ה-CropBox

  • ה-renderer מרסטר את התיבה הנראית, ה-CropBox כפי שהוא נחתך מול ה-MediaBox (ISO 32000-1 §14.11.2); כל מיפוי מפיקסל לעמוד חייב להשתמש באותה תיבה, נקראת עם GetLoadedPageVisibleBox
  • HotPDF v2.770.153 תיקן את ApplyLoadedOCRTextLayer; v2.770.154 תיקן את DecodeLoadedPageBarcodes לעמוד שלם וממצאי פנים מתוך DetectLoadedRedactionFindings; builds מ-v2.766.64 ועד אותן גרסאות נגועים
  • תיבות THPDFOCRWord הן פיקסלי bitmap עם ראשית שמאל-עליונה; GetLoadedPageBox ו-GetLoadedPageVisibleBox מחזירים מרחב משתמש PDF עם ראשית שמאל-תחתונה ו-Bottom < Top
  • הקנה הוא DPI / 72; גוזרים אותו מה-DPI, לעולם לא מתיבת עמוד שמחולקת את רוחב ה-bitmap
  • /Rotate מכריע אילו קצוות חשובים: Left ו-Bottom ב-0 וב-90, Right ו-Bottom ב-180, Right ו-Top ב-270
  • מחזירים מילי OCR בפיקסלים ונותנים ל-HotPDF למפות אותם; ממירים רק עבור לוגיקת הסינון שלכם
  • מריצים OCR מחדש על עמודים חתוכים שעובדו על ידי build נגוע, וזוכרים ש-SkipPagesWithText מדלג על עמודים שכבר נושאים את השכבה הישנה

יכולות הזיהוי, שאילתות תיבות העמוד ורינדור מסמכים טעונים שבשימוש כאן מגיעים כולם ברכיב HotPDF עבור Delphi ו-C++Builder; רישוי, הורדות ניסיון ורשימת היכולות המלאה נמצאים ב-עמוד HotPDF Delphi PDF component