מאמר טכני

ייצוא עמודי PDF ל-SVG בדלפי עם HotPDF

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

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

למה עמוד PDF לא פשוט הופך ל-SVG?

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

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

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

קריאה אחת למקרה הנפוץ

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

var
  Pdf: THotPDF;
  I: Integer;
  Svg: AnsiString;
  Output: TFileStream;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('statements.pdf', '') <= 0 then
      Exit;                     // LoadFromFile מחזירה את מספר העמודים
    for I := 0 to Pdf.LoadedPageCount - 1 do
    begin
      Svg := Pdf.BuildLoadedPageSVG(I);
      if Length(Svg) = 0 then
        Continue;
      Output := TFileStream.Create(Format('page-%d.svg', [I + 1]), fmCreate);
      try
        Output.WriteBuffer(Svg[1], Length(Svg));
      finally
        Output.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

אותו ייצוא חשוף גם על ידי כלי שורת הפקודה של HotPDF כפקודת export-svg שלו, שימושית בצינורות בנייה ובתסריטי רגרסיה שבהם רוצים ייצוג של עמוד שניתן להשוואה טקסטואלית בלי לכתוב Pascal בכלל. מכיוון ש-SVG הוא טקסט, זהו בן זוג טבעי לנתיב הרסטר המתואר בהצגת עמוד PDF כמפת סיביות: מפת הסיביות מספרת איך העמוד נראה, ה-SVG מספר ממה הוא עשוי

איך טקסט PDF ממופה לאלמנטי טקסט של SVG?

HotPDF מרכיבה את שרשרת מטריצת הטקסט כ-prefix כפול CTM כפול מטריצת טקסט כפול היפוך גליף, כאשר היפוך הגליף הוא כפל מימין במטריצה matrix(1,0,0,-1,0,0). גורם זה מימין קיים אך ורק כדי לבטל את ההיפוך האנכי ברמת העמוד עבור צורות הגליפים, שכן טקסט SVG המצויר במסגרת מקומית הפוכה היה נראה אחרת הפוך. הצבת התיקון במטריצה במקום בקוד מיוחד למקרה פירושה שטקסט מסובב, מוראה ומוטה כולם יוצאים נכון בלי ענפים נוספים

מיקום אופקי משתמש בתחביר ה-x רב-הערכי של אלמנט ה-text של SVG, קואורדינטה אחת לכל תו, נצברת מכל התקדמות גליף בתוספת ריווח התווים Tc וריווח המילים Tw שהיו בתוקף באותו רגע. קנה מידה אופקי Tz מקופל לתוך העמודות a ו-c של מטריצת הטקסט במקום להיפלט בנפרד, כך שצופה שמתעלם מתכונות טקסט אקזוטיות עדיין ממקם כל גליף במקום שבו ה-PDF שם אותו. טקסט המופק באמצעות עיצוב מורכב, המכוסה בעיצוב טקסט בכתבים מורכבים, עובר באותו נתיב, מכיוון שמעצב הכתב כבר פתר אשכולות לגליפים ממוקמים עד שזרם התוכן מתפרש

סיבוב ותמונות: שני היפוכים שקל להפוך בטעות

עמוד עם ערך /Rotate שאינו אפס דורש טרנספורמציה מקדימה המורכבת מהיפוך כנגד גובה הקנבס המסובב וסיבוב המבוטא במרחב תצוגה y-כלפי-מעלה. שלוש מטריצות הסיבוב הן (0,-1,1,0,0,W) ל-90 מעלות, (-1,0,0,-1,W,H) ל-180 ו-(0,1,-1,0,H,0) ל-270, כאשר W ו-H הם מידות העמוד לפני הסיבוב. גזירת אלה ידנית מזמינה שגיאות סימן בדיוק בשלושה מקומות, כך שהמייצא מרכיב אותן דרך אותה שגרת כפל מטריצות המטפלת בכל שאר הטרנספורמציות

תמונות מוטמעות זקוקות להיפוך משלהן, מכיוון שמרחב התמונה של PDF ממקם את שורת הדגימה הראשונה בקצה העליון של ריבוע היחידה בעוד אלמנט ה-image של SVG נושא מסגרת מקומית y-כלפי-מטה. הטרנספורמציה הנפלטת היא אפוא ה-CTM כפול מימין ב-matrix(1,0,0,-1,0,1). טעות בזה מייצרת תצלומים הפוכים אנכית על עמוד מושלם אחרת, וזהו סוג הפגם שסוקר מבחין בו מיד ובדיקה אוטומטית לרוב לא

מה בדיוק שומר התקן מצב הגרפיקה?

HotPDF שולחת את אופרטורי מצב הקו w, J, j, M ו-d דרך ממשק התקן אופציונלי נפרד, כך שנאמנות הקו נוספה בלי לשנות את ה-vtable של התקן התוכן הקיים ובלי לשבור תאימות בינארית לקוד שנבנה כנגד גרסאות קודמות. באופן קונקרטי, ה-SVG המיוצא מקבל מילות מפתח מתורגמות במקום מספרים שלמים גולמיים של PDF:

// מספרים שלמים של PDF הופכים לתכונות מילות-מפתח של SVG
//   קצה קו   0, 1, 2  ->  butt, round, square
//   חיבור קו 0, 1, 2  ->  miter, round, bevel
//
// רוחב קו אפס משמעו קו דק כשערה במרחב ההתקן ב-PDF, ולכן
// המייצא פולט vector-effect="non-scaling-stroke" כדי לשמור על
// הקו נראה וברוחב של כפיקסל התקן אחד לאחר ה-CTM
//
// f* B* b* בוחרים את כלל האי-זוגי-זוגי ופולטים fill-rule="evenodd",
// בעוד f B b שומרים על ברירת המחדל של SVG, ליפוף לא-אפס

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

גבולות שכדאי להכיר לפני שמוציאים לייצור

המייצא כן לגבי היקפו, ולמידת הגבולות מראש זולה יותר מגילוים בייצור. צבע מגיע להתקן ה-SVG דרך אופרטורי rg, RG, g ו-G. מילויים שנקבעו דרך מרחב צבע בתוספת scn, שכך נצבעים צבעי Separation, DeviceN ו-ICCBased, לא מגיעים להתקן כשלישיית RGB פתורה, כך שעמודים המשתמשים בצבעי ספוט בדרך זו מייצאים את הגיאומטריה שלהם אך לא את הצבעים הללו. עבור מקורות מכווני-דפוס, בצעו רסטריזציה במקום או השטיחו את צבעי הספוט תחילה; מודל הצביעה עצמו מכוסה בהצגת צבעי ספוט מסוג Separation ו-DeviceN

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

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