HotPDF מעבד טבלאות HTML דרך פרופיל ה-paged media שלו ל-HTML5 באמצעות רשת תפוסה אמיתית ל-rowspan ול-colspan, גבהים שנמדדו לשורות במקום הערכות מספר-תווים, ושורות כותרת שחוזרות על כל עמוד המשך. שתי מצבים גורמים לו לסרב לחזור על כותרת, והכרתם מראש זולה יותר מניפוי תא כפול אחרי העובדה
מעמד המסמכים שמכתיב את זה הוא זה שכל צוות דוחות משלח בסופו של דבר: חשבונית או דוח תאימות שבהם מקור האמת הוא HTML, הטבלה רצה על פני ארבעה עמודים, והכותרת צריכה להיות קריאה בכל אחד מהם. כל דבר פחות מפריסת טבלה אמיתית מניב את שני הכישלונות שהקוראים מבחינים בהם מיד, כותרת שמופיעה פעם אחת בעמוד הראשון ושורות שגבהיהן נוחשו מספירת תווים
מדוע יכולת הטבלה עברה לתוך המעבד של HTML?
כי האלטרנטיבה מאבדת טקסט עשיר, וטקסט עשיר הוא הסיבה שהתוכן הוא HTML מלכתחילה. התוכנית המובנת מאליה נשמעת כמו שימוש חוזר: HotPDF כבר מחזיק אובייקט טבלה ב-DOM הפריסה עם רשת תקינה, ולכן מגשרים את מנתח ה-HTML אליו ומקבלים spanning במתנה. הבעיה היא במה אותו אובייקט טבלה מצייר. התאים שלו נושאים טקסט וסגנון, ונתיב הציור שלו פולט פלט טקסט פשוט, כך שכל מה שה-HTML באמת הכיל מעבר לגופן ולצבע, קישורים, כתב עילי, שינויי גודל בתוך שורה, צבע לכל ריצת טקסט, נעלם עד שהוא מגיע לעמוד
הכיוון ששורד מגע עם מסמכים אמיתיים הוא ההפוך. מעבירים את יכולות מנוע הטבלה, הרשת התפוסה, מדידה אמיתית, חזרת כותרות ותמיכה במשקלי עמודות, לתוך מעבד ה-HTML, ומשאירים את עיבוד הטקסט העשיר היכן שהוא כבר עובד. זה שינוי גדול יותר מהגישור, והוא השינוי ששומר על hyperlink בתוך תא טבלה כ-hyperlink
rowspan בלי union-find
תאים מתפרשים יוצרים קבוצות שורות אטומיות, אבל הסגירה על הקבוצות האלה לא צריכה מבנה disjoint-set כללי, כי התפוסה היא תמיד מרווח רצוף. תא עם rowspan="3" שמתחיל בשורה K תופס את השורות K עד K+2 ולא עוד, ולכן מידע הקבוצה מצטמצם למסמן סיום פר-שורה
האלגוריתם הוא שתי שורות של כוונה. כשמציבים תא מתפרש שמתחיל ב-K ונגמר ב-E, רושמים GroupEnd[K] := Max(GroupEnd[K], E). ואז הולכים על השורות פעם אחת בכיוון הפוך ומחילים G[R] := G[G[R]], מה שמפיץ כל סיום שורה אחורה דרך ה-spanning החופפים ומניב את הסגירה הטרנזיטיבית במעבר אחד. מה שמקבלים הוא, לכל שורה, את השורה האחרונה שחייבת להישאר באותו עמוד איתה, וזה בדיוק מה שצעד ה-pagination צריך כדי להחליט היכן שבירה מותרת ליפול
חלוקת הגובה היא החצי השני. כשתא מתפרש צריך יותר מקום אנכי ממה שהשורות שהוא מכסה מספקות כרגע, העודף הולך לשורה האחרונה של ה-span, ולא מפוזר באופן שווה על פניהן. מעבדים תאים מתפרשים אחרי שגבהי השורות הרגילים התיישבו, ואז משלימים את השורה הסופית של כל span. פיזור שווה של העודף נשמע הוגן יותר ומניב פלט שגוי בעין: שורות שמכילות רק תאים קצרים של שורה אחת מתנפחות כי תא לא קשור שלוש שורות מעלה המקרה להיות גבוה
var
Pdf: THotPDF;
Importer: THPDFHTMLImporter;
Stats: THPDFHTMLImportStatistics;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'audit-report.pdf';
Pdf.BeginDoc;
Importer := THPDFHTMLImporter.Create(Pdf);
try
Importer.Margin := 48;
Importer.BaseFontName := 'Arial';
Importer.BaseFontSize := 10;
Importer.MaxDOMNodes := 200000;
Importer.MaxLayoutOperations := 2000000;
if Importer.RenderHTML5(SourceHtml, PrintStyleSheet) then
begin
Stats := Importer.Statistics;
Writeln('tables ', Stats.TableCount,
' page breaks ', Stats.PageBreakCount);
end;
finally
Importer.Free;
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
RenderHTML5 מקבל גיליון סגנון של כותב אופציונלי כארגומנט שני, וזה המקום של כללי ההדפסה. שומרים את גיליון הסגנון של המסך החוצה משם. הפרופיל מנוהל לפי גרסאות, ו-HTML5ProfileMilestones מדווח אילו קבוצות יכולת הבנייה הנוכחית מממשת, himParserCascade, himPagedLayout, himTablesForms ו-himBoundedResources, כך שיישום יכול להתדרדר בכוונה במקום לגלות חור בפרודקשן
המדידה חייבת להסכים עם הציור, בדיוק
גובה שורה נכון רק כשהקוד שמודד שורות עטופות עוטף אותן לפי אותו כלל כמו הקוד שמצייר אותן. זה נשמע מובן מאליו והוא המקור הנפוץ ביותר לטבלאות שהגבולות שלהן לא מתיישרים עם התכנים. HotPDF מודד עם מונה שורות חמדני, ואותו מונה חייב להתאים לסמנטיקת העטיפה של נתיב פלט הטקסט העשיר בשלושה היבטים מדויקים: הוא שובר רק ברווחים, הוא לעולם לא חותך מילה, ומילה רחבה מהעמודה מקבלת שורה משלה
הדרישה השנייה היא הגופן. המדידה חייבת לרוץ עם הגופן של התא עצמו, שנקבע דרך SetFont עם השם האמיתי, סט הסגנון והגודל לפני קריאת פונקציית הרוחב, ולא עם הגופן שמקרה להיות פעיל. טקסט מודגש באופן שגרתי רחב יותר מעשרה אחוזים מהרגיל באותו גודל, וזה מספיק להפוך תא של שלוש שורות לתא של ארבע. טבלה שבה תאי הכותרת מודגשים ותאי הגוף לא, שנמדדה בגופן אחד, תהיה שגויה בדיוק בשורות שהקוראים מביטים בהן קודם
עשיית זה נכון משנה מה אפשר לטעון בבדיקה. האפקט הנצפה של מדידה מדויקת הוא ריווח שורות, ולא ספירת גליפים: שורה של שורה אחת גבוהה כעשרים נקודות, בזמן שהערכת ספירת תווים לאותו תוכן חוזה שתי שורות וכ-35. טוענים על המרחק האנכי בין שורות. וזוכרים שבמרחב המשתמש של PDF ציר Y גדל כלפי מעלה, כך שכותרת שיושבת מעל שורת גוף אומרת שערך ה-Y של הכותרת הוא הגדול מבין השניים, ההפך ממה שאינסטינקט קואורדינטות-מסך כותב
מתי HotPDF מסרב לחזור על כותרת?
בשני מקרים, ושניהם היו מניבים פלט שגוי בעין אם היה ממשיך. הראשון הוא בלוק כותרת שמכיל תא מתפרש שנמשך מעבר לכותרת אל שורות הגוף. חזרה על הכותרת הייתה מציירת את תוכן התא הזה בפעם השנייה במקום שבו הוא כבר לא שייך, ולכן הכותרת נציירת פעם אחת והטבלה ממשיכה בלעדיה. השני הוא כותרת גבוהה מתשעים אחוז מגובה העמוד השמיש, שם חזרה הייתה משאירה כמעט אפס מקום לנתונים והטבלה לא הייתה מתקדמת
שני הסירובים מכוונים ושקטים על פי תכנון, כי האלטרנטיבה גרועה. אם הכותרת שלך לא חוזרת וציפית שתחזור, בודקים את ה-markup לפני שחושדים במנוע: האם יש rowspan שחוצה את גבול ה-thead. תבנית markup אחת זו מסבירה את רוב ההפתעות
// משקלי העמודות מגיעים מה-markup, ולכן גיליון הסגנון להדפסה הוא
// המקום לשלוט בהם. רוחבים מטופלים כמשקלים, לא כפיקסלים
const
PrintStyleSheet =
'table { width: 100%; }' +
'thead th { font-weight: bold; background: #eee; }' +
'td.amount { text-align: right; }';
// שורת כותרת שנושאת rowspan החוצה אל הגוף מדכאת את
// חזרת הכותרות. שומרים spans בתוך מקטע אחד:
// <thead><tr><th rowspan="2">Item</th>...</tr></thead> ok
// <tr><th rowspan="3">Item</th>... crossing into tbody, no repeat
רוחבי עמודות מתנהגים כמשקלים ולא כמדידות אבסולוטיות, וזו ההתנהגות ששומרת טבלה שמישה כשהתוכן לא תואם את ההערכה של הכותב. עמודה שהוכרזה בשלושים אחוז מקבלת בערך שלושים אחוז מהרוחב הזמין, אבל ההתפלגות מכבדת את רוחב המינימום שכל עמודה באמת צריכה, כך שעמודה צרה שמחזיקה token ארוך ובלתי ניתן לשבירה לא גולשת בשקט מחוץ לתיבת הטבלה
איפה זה יושב ב-pipeline של מסמך
עבודת הטבלאות יושבת בתוך פרופיל ה-paged media הרחב יותר, וכללי ה-pagination, תקציבי המשאבים וטיפול ה-CSS שמתוארים בנתיב הייבוא של paged media ל-HTML5 חלים ללא שינוי גם על מסמכים שמכילים טבלאות. אם הנתונים שלך לא מתחילים כ-HTML, הנתיב הבנוי-ישירות בבניית טבלאות ישר ל-PDF עוקף את שכבת הניתוח לגמרי ונותן לך את אותה התנהגות רשת דרך API. ומאחר שגובה שורה תלוי בסופו של דבר בהיכן שורות נשברות, הדיון המדידה בישור טקסט ושבירת שורות הוא ההשלמה לכל מי שמכוונן פלט טבלאי צפוף
הלקח הניתן לשימוש חוזר כאן אינו על טבלאות בכלל. כשתת-מערכת חדשה צריכה יכולת שתת-מערכת ישנה כבר מחזיקה, שואלים מי מהשניים בבעלותו את הדבר שהכי קשה לממש מחדש. אריתמטיקת הרשת היא כמה תריסרי שורות ועוברת בקלות. עיבוד טקסט עשיר עם קישורים בתוך שורה, כתב עילי וסגנון לכל ריצה הוא לא, ולכן הרשת עברה והטקסט נשאר. HotPDF משלח את שני הנתיבים כחלק מרכיב ה-PDF של HotPDF ל-Delphi, כך שהבחירה בין קלט HTML לבנייה ישירה היא החלטה של פרויקט ולא של ספרייה