מאמר טכני

גופנים משתנים ב-PDF: יצירת מופעים סטטיים בדלפי

ל-PDF אין מושג של גופן משתנה. גופן המוטמע בקובץ PDF הוא קבוצה קבועה של קווי מתאר עם מדדים קבועים, כך שגופן משתנה חייב לצומצם למופע סטטי אחד לפני שהוא יכול להיכנס למסמך. HotPDF מבצעת יצירת מופע זו באופן פנימי: בודקים את הצירים של גופן משתנה, בוחרים קואורדינטות כגון משקל 620 או רוחב 87.5, והספרייה אופה את הערכים הללו לתוך תוכנית גופן שלמה ועצמאית שכל קורא PDF תואם יכול להציג

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

מה יצירת המופע באמת צריכה לבנות מחדש?

גופן OpenType משתנה שומר קו מתאר ברירת מחדל אחד לכל גליף בתוספת קבוצת דלתות המאונדקסות לפי מיקום במרחב העיצוב. החלת קואורדינטת ציר אינה עניין של כתיבת מספר בכותרת; היא כרוכה במעבר על טבלת gvar, אינטרפולציה של דלתות עבור המיקום המבוקש, הזזת נקודות, ואז חישוב מחדש של כל מה שנגזר מאותן נקודות. HotPDF בונה מחדש את קווי המתאר של הגליפים, את טבלת loca הארוכה, את מדדי הרוחב והאורך המלאים, את תיבת הגבול הכללית של הגופן ואת התאמת סכום הביקורת של ה-sfnt

חשוב לא פחות מה שמוסר. מופע סטטי אסור שישמור fvar, avar, gvar, HVAR, VVAR, MVAR, STAT או cvar, וגם DSIG מיושן צריך ללכת, מכיוון שהבייטים החתומים כבר לא קיימים. השארת אחד מאלה מייצרת גופן שטוען שהוא משתנה בעודו נושא קווי מתאר שכבר הוזזו, וקוראים שכן מחילים וריאציות יחילו אותן פעם שנייה

נקודות פנטום, ומלכודת ההחלה הכפולה

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

אז כאשר לגופן יש טבלת gvar, HotPDF גוזרת מדדים אופקיים ואנכיים מנקודות הפנטום המאונטרפלות ואינה מחילה בנוסף HVAR או VVAR. הוספת שניהם היא הטעות הקלאסית: אותה וריאציה מוחלת פעמיים וכל רוחב התקדמות יוצא רחב מעט מדי, מה שמתבטא כטקסט הנע בהדרגה ימינה לאורך שורה מיושרת. רק כאשר לגופן אין gvar, הספרייה אופה את מאגר וריאציית המדד ישירות לתוך hmtx או vmtx

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

בדיקת מרחב העיצוב לפני הבחירה

הקריאה הראשונה בכל זרימת עבודה של יצירת מופעים היא InspectVariableFont, המדווחת על הצירים ועל המופעים בעלי השם שיצרן הגופן הגדיר. רשומות צירים נושאות את תג ארבעת הבייטים, ערכי המינימום, ברירת המחדל והמקסימום, דגלים ומזהה שם; מופעים בעלי שם נושאים מזהה שם משפחת משנה, דגלים, מזהה שם PostScript אופציונלי וקואורדינטה אחת לכל ציר:

var
  Pdf: THotPDF;
  Axes: THPDFVariableFontAxisArray;
  Instances: THPDFVariableFontNamedInstanceArray;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.InspectVariableFont('C:\Fonts\Inter.ttf', Axes, Instances) then
    begin
      for I := 0 to High(Axes) do
        Writeln(Format('%s  min=%.1f default=%.1f max=%.1f',
          [string(Axes[I].Tag), Axes[I].MinimumValue,
           Axes[I].DefaultValue, Axes[I].MaximumValue]));
      Writeln(Format('%d named instance(s) defined', [Length(Instances)]));
    end
    else
      Writeln('not a variable font - embed it as an ordinary TrueType face');
  finally
    Pdf.Free;
  end;
end;

דיווח על טווח הציר חשוב מכיוון שערכי צירים נצמדים לטווח שהגופן מכריז עליו, לא לטווח שהממשק שלכם מציע. מחוון שמאפשר למשתמש לבקש משקל 1000 בגופן שציר ה-wght שלו עוצר ב-900 צריך להיות מתוקן בממשק, לא בשקט בשכבת הגופן, אחרת הפלט המודפס יסתור את התצוגה המקדימה

בחירת קואורדינטות ופליטת המסמך

בחירת ציר היא בעלת מצב וחלה על גופנים הנרשמים לאחר מכן. SetVariableFontAxis מקבלת תג ASCII בר-הדפסה בן ארבעה בייטים וערך סופי, ודוחה כל דבר אחר בחריגה במקום להתעלם ממנו בשקט. ClearVariableFontAxes מאפסת את הבחירה, ו-GetVariableFontAxisSelections מדווחת מה תלוי ועומד כרגע, וכדאי לתעד זאת במנועי דוחות שבהם כמה נתיבי קוד עשויים לגעת באותו אובייקט מסמך. המשפחה עצמה נבחרת לפי שם דרך SetFont, בדיוק כמו כל צורת TrueType מוטמעת אחרת:

begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginDoc;

    Pdf.SetVariableFontAxis('wght', 620);   // semibold, לא מופע בעל שם
    Pdf.SetVariableFontAxis('wdth', 87.5);  // מעט דחוס
    Pdf.CurrentPage.SetFont('Inter', [], 11);
    Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');

    Pdf.ClearVariableFontAxes;              // חזרה למופע ברירת המחדל
    Pdf.CurrentPage.SetFont('Inter', [], 10);
    Pdf.CurrentPage.TextOut(72, 700, 0, 'Prepared by the finance team');

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

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

איך יצירת מופעים מקיימת אינטראקציה עם תת-קיבוץ ועיצוב

יצירת המופע רצה לפני תת-הקיבוץ, וזהו הסדר הנכון. הגופן שנוצר לו מופע הוא צורת TrueType סטטית רגילה, כך שתת-הקבוצה הרגיל מתייחס אליו כמו לכל אחר: הוא מחשב את הסגירה של הגליפים, שומר את הגליפים שהמסמך באמת משתמש בהם וזורק את השאר. האינטראקציה שכדאי להיות מודעים לה היא ששתי בחירות צירים שונות של אותה משפחה הן שתי תוכניות גופן שונות, כך שמסמך המשלב משקל 400 ומשקל 620 מטמיע שתי תת-קבוצות, לא צורה אחת משותפת עם שני מופעים

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

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

תמיכה בגופנים משתנים משתלבת באותו צינור גופנים כמו הטמעה, תת-קיבוץ וסגירת גליפים, המתואר בהרחבה בסגירת תת-קבוצת גופן וגליפים מעוצבים. מערך תכונות הטיפוגרפיה המלא לדלפי ו-C++Builder רשום בעמוד רכיב HotPDF Delphi PDF