כל מחלץ טקסט גאומטרי מנחש. הוא קורא את ה-glyphs שהעמוד מצייר, ממיין אותם לפי קו בסיס ומיקום אופקי, ומקווה שהסידור החזותי תואם את הסדר שבן אדם היה קורא. על דוח עמודה אחת הניחוש נכון. על מאמר כתב עת בן שתי עמודות, טופס עם סרגל צד, או טבלה שתאיה נפלטו עמודה-עמודה, הוא שגוי באופנים שקשה לשים לב אליהם ויקר לגלות במורד הזרם. HotPDF עונה על זה עם ExtractLoadedPageStructureText, שמתעלם מהגאומטריה לגמרי: הוא עובר על עץ המבנה של המסמך בסדר החיבור כפי שהוגדר ב-ISO 32000-1 §14.8.4, ואז מרכיב מחדש את glyphs העמוד לפי מזהה ה-marked-content שלהם. עבור PDF מתויג זו איננה היוריסטיקה, זה הסדר שהאפליקציה המייצרת הכריזה
הפונקציה מחזירה False כשלעמוד אין עץ מבנה שמיש, וזה האות לחזור אל המחלץ הגאומטרי ולא להיכשל. עיצוב שני-הנתיבים הזה חשוב יותר מהאלגוריתם: קליטת מסמכים אמיתית רואה טפסים ממשלתיים מתויגים ופלט סורק באותה תיקייה, וצינורית שמטפלת רק באחד מהם אינה צינורית
למה חילוץ גאומטרי מקבל את סדר הקריאה שגוי?
כי stream תוכן של PDF לא נושא שום סדר קריאה. הוא רצף של אופרטורים לציור, ולמייצר חופשי לפלוט אותם בכל רצף שמתאים למנוע הפריסה שלו עצמו. מעבדי תמלילים בדרך כלל פולטים בסדר זרימה והמיון הגאומטרי נראה תקין. כלי פריסה, מעצבי טפסים ומחוללי דוחות לעיתים קרובות לא: כותרת תחתונה של עמוד יכולה להיפלט לפני הגוף, טבלה יכולה להתמלא עמודה-ראשית, ועמוד בן שתי עמודות יכול לשזור שורות משתי העמודות כי המרכיב פתר אותן יחד
מצב הכשל שקט. מחלץ גאומטרי לעולם לא מדווח על שגיאה, הוא פשוט מוסר חזרה פרוזה שמשפטיה תפורים משתי עמודות. כל צרכן של אותו טקסט, אינדקס חיפוש, ממפה שדות של חשבוניות אלקטרוניות, צינורית אחזור שמזינה מודל שפה, יורש את הנזק בלי אזהרה. HotPDF גם משלחת את המחלצים הגאומטריים למסמכים טעונים, והם נשארים הכלי הנכון לקבצים לא מתויגים; העניין בנתיב לפי סדר מבנה הוא להפסיק לנחש כשהמסמך כבר נושא את התשובה
מה עץ המבנה באמת מאחסן
PDF מתויג מחזיק תיאור שני, מקביל, של העמוד. הקטלוג מצביע על /StructTreeRoot, שהצאצאים של /K שלו יוצרים עץ של אלמנטי מבנה: /Document, /Sect, /P, /Table, /TR, /TD וכך הלאה. העלים של אותו עץ הם הפניות marked-content, מספרים שלמים שקוראים בשם טווח מ-stream התוכן של העמוד. בצד התוכן, אותם טווחים נפתחים עם אופרטור BDC שנושא /MCID ונסגרים עם EMC. כל אלמנט מבנה גם נושא ערך /Pg שקורא בשם העמוד שאליו הוא שייך, וזה מה שהופך מעבר פר-עמוד לאפשרי במסמך שעץ המבנה שלו משתרע על מאות עמודים
HotPDF עובר על אותו עץ עם תקרת עומק של 128 רמות ומסנן על /Pg כך שרק העמוד הנוכחי תורם. הפלט של המעבר אינו טקסט, הוא רשימה מסודרת של ערכי MCID: סדר החיבור של טווחי ה-marked-content בעמוד הזה. הרכבת הטקסט מחדש היא אז עניין של נגינת ה-glyphs מחדש באותו סדר
ה-MCID נרשם במהלך חילוץ ה-glyphs, לא נחפש אחר כך
זה פרט המימוש שהופך את התכונה לזולה. HotPDF כבר רושם את מזהה ה-marked-content הפעיל על כל glyph שהוא מחלץ, בשדה ה-MCID של THPDFGlyphRecord, כי המפענח של ה-content stream יודע איזה תחום BDC פתוח ברגע שהוא מעבד כל אופרטור Tj או TJ. חילוץ לפי סדר מבנה לכן אינו צריך מעבר שני על ה-content stream. הוא אוסף את רצף ה-MCID מעץ המבנה, ואז מחלק את ה-glyphs שכבר חולצו לקבוצות לפי MCID ופולט אותם באותו רצף
var
Pdf: THotPDF;
PageCount, I, Untagged: Integer;
PageText, AllText: UnicodeString;
Report: TStrings; // sink אבחון בבעלות הקורא
begin
Pdf := THotPDF.Create(nil);
try
PageCount := Pdf.LoadFromFile('accessible-form.pdf');
AllText := '';
for I := 0 to PageCount - 1 do
begin
if Pdf.ExtractLoadedPageStructureText(I, PageText, Untagged) then
begin
// סדר חיבור ישר מעץ המבנה
if Untagged > 0 then
Report.Add(Format('page %d: %d glyphs outside the structure tree',
[I, Untagged]));
end
else
// אין עץ מבנה שמיש בעמוד הזה: fallback גאומטרי
Pdf.ExtractLoadedPageText(I, PageText);
AllText := AllText + PageText + #13#10;
end;
finally
Pdf.Free;
end;
end;
glyphs לא מתויגים נספרים, לעולם לא נזרקים בשקט
עמוד יכול להיות מתויג חלקית. מייצרים מוסיפים קו דקורטיבי, מספר עמוד, או סימן מים משלב מאוחר מחוץ לכל תחום BDC, וה-glyphs האלה לא שייכים לשום MCID. לזרוק אותם היא המימוש המסודר והשגוי, כי אותו פער מופיע גם כשמייצר מתייג את הגוף אבל שוכח את הטבלה, והייתם מאבדים את הטבלה בלי לשים לב
HotPDF מצמיד glyphs לא נתבעים כזנב גאומטרי אחרי הטקסט המסודר לפי מבנה ומדווח על מספרם דרך פרמטר הפלט UntaggedGlyphCount. המספר הזה הוא אות איכות שאפשר לפעול על פיו. קומץ glyphs בעמוד של אלפיים הוא ריהוט עמוד ואפשר להתעלם ממנו. ארבעים אחוז מהעמוד מחוץ לעץ המבנה פירושם שהתיוג דקורטיבי והמחלץ הגאומטרי הוא התשובה הכנה יותר עבור אותו קובץ
function ExtractPageBestEffort(Pdf: THotPDF; PageIndex: Integer;
out AText: UnicodeString; out UsedStructure: Boolean): Boolean;
var
Untagged, TotalGlyphs: Integer;
Glyphs: THPDFGlyphArray;
begin
UsedStructure := False;
if Pdf.ExtractLoadedPageStructureText(PageIndex, AText, Untagged) then
begin
TotalGlyphs := 0;
if Pdf.ExtractLoadedPageGlyphs(PageIndex, Glyphs) then
TotalGlyphs := Length(Glyphs);
// סמכו על עץ המבנה רק כשהוא טוען לרוב העמוד
if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
begin
UsedStructure := True;
Result := True;
Exit;
end;
end;
Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;
מה גורם לפונקציה להחזיר False
שלושה מקרים, ושווה להבחין ביניהם כי רק אחד מהם הוא פגם במסמך. הראשון הוא PDF רגיל לא מתויג: אין /StructTreeRoot, אין מה לעבור עליו, ו-False הוא פשוט האמת. השני הוא עמוד סרוק שהטקסט שלו בא משכבת OCR שמעולם לא תויגה. השלישי הוא המעניין: תוכן שנושא אופרטורי BDC עם ערכי /MCID אבל לעמוד שלו אין ערך /StructParents ועץ המבנה שלו מעולם לא מפנה לאותם מזהים. ה-marked content קיים, הצד המבני לא, ואין סדר לשחזר. HotPDF מדווח False במקום להמציא אחד
המקרה האחרון מופיע בקבצים שנערכו ידנית ובפלט מכלים שפולטים marked content למטרות optional-content או artifact בלי לבנות עץ מבנה. אם אתם מפיקים בעצמכם PDF מתויג, אותו חוסר סימטריה הוא מה שאימות PDF/UA בודק, והמקבילה בצד הכתיבה מכוסה בה-layout DOM שפולט פלט מתויג ומדופדף
איפה סדר מבנה משלם את עצמו
ביקורת נגישות היא הברורה: אם אתם מאשרים מסמך מול PDF/UA, סדר הקריאה שקורא מסך יקריא הוא בדיוק סדר המבנה, ולכן חילוצו הוא הדרך לסקור אותו בלי קורא מסך. לכידת נתונים היא המקרה המסחרי הגדול יותר. טפסים ממשלתיים מתויגים, גילויים מוסדרים וצרופות של חשבוניות אלקטרוניות נושאים תוויות שדות וערכים בסדר שהוכרז, וקריאתם באותו סדר מסירה מחלקה שלמה של באגי מיפוי שחילוץ גאומטרי יוצר על פריסות מרובות עמודות
הצרכן החדש ביותר הוא אחזור עבור מודלי שפה. חיתוך מסמך ל-chunks עבור embedding הוא טוב ככל שסדר הטקסט טוב, ו-chunk שתופר שתי עמודות מפיק משפטים שמעולם לא התקיימו. חילוץ לפי סדר מבנה הוא התיקון הזול ביותר הזמין לזה, כי עבור מסמכים מתויגים הסדר הנכון כבר בקובץ ורק צריך לקרוא אותו
HotPDF הוא רכיב VCL מקורי עבור Delphi ו-C++Builder, ולכן גם המעבר על עץ המבנה וגם נגינת ה-glyphs מחדש רצים בתוך התהליך מול מסמך טעון בלי מרנדר חיצוני מעורב. פרטי ה-API המלאים למשפחת החילוץ של מסמכים טעונים נמצאים בדף המוצר של HotPDF Delphi PDF component