PDFlibPas מזרים קטע טקסט שמור לתוך עמודה אחת עד 64 עמודות שוות רוחב עם DrawTextFlowColumns, ושובר שורות בהפרדת הברות מוגבלת ומודעת-שפה ברגע שקוראים ל-SetTextFlowLanguage ול-SetTextFlowHyphenation. תשע שפות נתמכות, והשפה יכולה לרשת מהערך /Lang של ה-Catalog של המסמך במקום להיקבע לכל זרימה בנפרד
שתי התכונות קיימות מאותה סיבה: עמודה צרה היא המקום שבו שבירת שורות נאיבית מפסיקה להיראות כסידור טיפוגרפי ומתחילה להיראות כדוח באג
מדוע טקסט מיושר משני הצדדים מתפרק בעמודות צרות?
מפני שיישור לשני הצדדים מפזר את המרחב הנותר לתוך רווחי המילים של השורה, וכמות הנותר תלויה במה שנכנס. במידה רחבה הנותר קטן והעין אף פעם לא שמה לב. חצה את הרוחב ומילה ארוכה אחת שלא נכנסת נדחפת לשורה הבאה, ומשאירה את קודמותיה לספוג את כל אותו מרחב. שלוש שורות כאלה ברצף מייצרות את הערוצים הלבנים האנכיים שטיפוגרפים מכנים נהרות, וקוראים חווים אותם כטקסט שקשה לעקוב אחריו מבלי לדעת למה
הפרדת הברות מתקנת את הסיבה ולא את התסמין, על ידי מתן אפשרות לשבור בתוך המילה. תרכובות בגרמנית ובהולנדית הופכות את זה לבלתי משתמע לשני פנים: לשם בן 24 תווים בעמודה ברוחב 60 מילימטר אין תוצאה טובה ללא נקודת שבירה. אנגלית סובלת את ההיעדר טוב יותר, וזו הסיבה שמוצרים המיועדים לאנגלית בראש ובראשונה שולחים לעיתים קרובות קוד פריסה שקורס בפעם הראשונה שלקוח גרמני מריץ אותו
אילו שפות, ומאיפה מגיעה השפה?
הפרדת ההברות מכסה אנגלית, גרמנית, הולנדית, צרפתית, ספרדית, איטלקית, פורטוגזית, רוסית וטורקית. אפשר לקבוע אותה במפורש לכל זרימה עם SetTextFlowLanguage, או לתת לה להתקבל בירושה מהערך /Lang של ה-Catalog של המסמך, שהוא הערך שמסמך מתויג ונגיש כבר נושא
שווה להשתמש בירושה הזו ולא לדרוס אותה. מסמך שמצהיר על שפתו ב-Catalog מודיע לקוראי מסך, למנועי אינדוקס חיפוש ולהפרדת ההברות את אותה עובדה ממקום אחד, ומקום אחד הוא היכן שעובדה צריכה לחיות. אם כבר מפיקים פלט מתויג כמתואר בתיוג אוטומטי למסמכי PDF נגישים, ערך השפה כבר מוגדר והזרימה יכולה פשוט לעקוב אחריו
var
Lib: TPDFlib;
Flow, Drawn: Integer;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.AddTrueTypeFont('Georgia', 1);
Lib.SetTextSize(10.5);
Flow := Lib.NewTextFlow(ArticleBody);
try
Lib.SetTextFlowLanguage(Flow, 'de');
// הפעלה, לפחות 3 תווים לפני השבירה, 3 אחריה
Lib.SetTextFlowHyphenation(Flow, 1, 3, 3);
Lib.SetTextFlowMinLines(Flow, 2); // לעולם לא להשאיר שורה בודדת תלויה
repeat
// שלוש עמודות באזור של 480 pt, מרווחי 18 pt, מאוזן
Drawn := Lib.DrawTextFlowColumns(Flow, 72, 720, 480, 620, 3, 18, 1);
if (Drawn = 0) or (Lib.TextFlowFinished(Flow) = 1) then
Break;
Lib.NewPage;
until False;
finally
Lib.ReleaseTextFlow(Flow);
end;
Lib.SaveToFile('newsletter.pdf');
finally
Lib.Free;
end;
end;
MinPrefix ו-MinSuffix הם עניין טיפוגרפי, לא ולידציה
שני המספרים השלמים שאחרי דגל ההפעלה קובעים את המספר המינימלי של תווים שחייבים להישאר לפני השבירה ואחריה. שלושה ושלושה הם ברירת מחדל שמרנית שרוב סגנונות הבית מקבלים. שניים ושניים מפיקים יותר הזדמנויות שבירה ותוצאות מכוערות בבירור, מפני ששבר של שני תווים תלוי בסוף שורה נקרא כמו שגיאת כתיב
העלה את המינימומים כשהגופן גדול, שם כל שבר בולט לעין, והורד אותם רק כשהעמודה צרה באמת והחלטת שמידה צפופה חשובה יותר ממידה נקייה. זו החלטת סגנון בית ולא החלטה טכנית, וזו בדיוק הסיבה שמדובר בפרמטר ולא בקבוע
מה "מאוזן" באמת אומר כאן?
הפרמטר Balance משנה התנהגות רק בסוף הקטע. כשהאיזון פעיל, העמודות מקוצרות למספר שורות שווה בדיוק כאשר כל מה שנותר נכנס בתוך האזור, וזה מה שמונע מהעמוד האחרון להציג שתי עמודות מלאות ושלישית שמחזיקה שורה בודדת ובודדת. כשהקטע לא נכנס, כל עמודה שומרת על גובהה המלא כך שהעמוד נושא כמה שיותר טקסט, וההמשך ממשיך לעמוד הבא
אסימטריה זו היא ברירת המחדל הנכונה למסמכים רציפים. איזון באמצע כתבה זורמת היה מבזבז מרחב אנכי בכל עמוד לטובת אפקט קוסמטי שאף אחד לא רואה, מפני שהעמודות מלאות ממילא. איזון בסוף הוא המקום שבו העין באמת שמה לב, ובדיוק שם הוא חל
שבירת שורות מודדת מילים שלמות
אלגוריתם השבירה מודד מילים שלמות במקום לצבור רוחבי תווים, ושומר חיפוש מוגבל לטוקנים גדולים מדי שלא נכנסים לשורה כלל, כגון כתובת URL או מספר אסמכתא. זה שומר על המקרה הרגיל מהיר ועל המקרה הפתולוגי מוגבל, ולא להיפך
מקפים רשות ומקפים אוטומטיים מוצגים רק כשהשבירה שהם מסמנים היא השבירה שבסופו של דבר נבחרה. זה נשמע מובן מאליו וזה פגם קלאסי: מימוש נאיבי כותב את תו המקף בזמן המדידה, ואם השבירה זזה, המקף נשאר מאחור באמצע שורה. שום דבר לא נראה יותר כמו מנוע טקסט שבור ממקף תועה בתוך מילה
var
Lib: TPDFlib;
Flow, Needed: Integer;
begin
// החלט על הפריסה לפני שמציירים משהו
Flow := Lib.NewTextFlow(ArticleBody);
try
Lib.SetTextFlowLanguage(Flow, 'fr');
Lib.SetTextFlowHyphenation(Flow, 1, 3, 3);
// מספר השורות שיתרת הקטע צריכה ברוחב עמודה אחת
Needed := Lib.MeasureTextFlow(Flow, 148);
if Needed > 3 * LinesPerColumn then
UseTwoPageSpread
else
UseSinglePage;
Lib.DrawTextFlowColumns(Flow, 72, 720, 480, 620, 3, 18, 1);
if Lib.TextFlowFinished(Flow) <> 1 then
CarryOver(Lib.GetTextFlowRemaining(Flow));
finally
Lib.ReleaseTextFlow(Flow);
end;
end;
שמרו על הגדרות הגופן זהות בין התיבות
כלל אחד שולט בכל פריסה מבוססת-זרימה וכדאי לומר אותו במפורש: DrawTextFlow, DrawTextFlowColumns ו-MeasureTextFlow כולם שוברים שורות באמצעות הגופן שנבחר ברגע הקריאה. שנה את הגופן או את הגודל בין שתי תיבות של אותה זרימה, או התחל עמוד חדש מבלי לבחור גופן מחדש, והתיבה השנייה תישבר אחרת ממה שהראשונה מדדה
התסמין מטריף דווקא מפני שהוא נראה לסירוגין: טקסט שנכנס בעמוד הראשון גולש בעמוד השני, או שספירת שורות שנמדדה לא תואמת את מה שצויר. בחר את הגופן פעם אחת לפני הלולאה, בחר אותו מחדש אחרי כל NewPage, והזרימה תתנהג כראוי. כשכתבים מעורבים מופיעים באותו קטע, הפתרון המתואר בגיבוי גופנים אוטומטי לטקסט CJK ואימוג'י חל הן על המדידה והן על הציור, כך שהרוחבים נשארים עקביים גם לאורך ריצות הגיבוי
בפריסות דוח שבהן הזרימה היא רכיב אחד מבין כותרות, כותרות תחתונות ובלוקים מונעי-נתונים, דפוסי ההרכבה במנוע דוחות מבוסס-Dataset משתלבים היטב עם זרימות עמודות: מדוד קודם, מקם את הרהיטים הקבועים, ואז תן לזרימה את האזור שנותר
PDFlibPas היא ספריית PDF ל-Delphi, ל-C++Builder ול-Lazarus, ומחזור החיים המלא של TextFlow — יצירה, ציור, מדידה, בדיקה, הרצה לאחור ושחרור — חשוף גם דרך ממשקי ה-DLL וה-ActiveX. תיעוד מלא נמצא בדף ספריית PDFlibPas ל-Delphi