מאמר טכני

ציור בבד הציור של HotPDF ב-Delphi: נתיבים וקטוריים וצבע

HotPDF מצייר גרפיקה וקטורית על ידי בניית נתיב על העמוד הנוכחי ולאחר מכן מבקש לצבוע אותו. אין שלב מפת סיביות (bitmap) באמצע. קו שאתה מצייר עם MoveTo ו-LineTo מסיים כאופרטורי נתיב של PDF בזרם התוכן, כך שהוא נשאר וקטור אמיתי: חד ב-50% זום, חד ב-1600%, ושבריר מהגודל שגרסת רסטר (rasterized) הייתה עולה. עבור תרשימים, קווי טבלה, צירי תרשים וקישוטי טפסים, זה בדיוק מה שאתה רוצה, וה-API שמאחוריו קטן מספיק כדי ללמוד בישיבה אחת

כל משטח הציור חי על THotPDF.CurrentPage. בין BeginDoc ל-EndDoc אתה מגדיר צבע ורוחב קו באותו אובייקט עמוד, מניח גיאומטריה, וקורא לאופרטור צביעה כדי לקבע אותו (commit). ארבעת הפרימיטיבים שתשתמש בהם לרוב הם MoveTo ו-LineTo עבור נתיבים שרירותיים, Rectangle לתיבות, Circle לדיסקות, ושני אופרטורי הצביעה Stroke ו-Fill

מערכת הקואורדינטות היא שמאלית-תחתונה

זה הדבר האחד שמפיל כל מי שמגיע מ-VCL. ה-TCanvas שאיתו אתה צובע פקדים מציב את נקודת ההתחלה בפינה השמאלית-עליונה עם ציר Y שגדל כלפי מטה. PDF עושה את ההפך. HotPDF מודד מהפינה השמאלית-תחתונה של העמוד בנקודות (1/72 אינץ'), כשציר Y גדל ככל שאתה נע מעלה. נקודה ב-Y := 720 יושבת קרוב לחלק העליון של עמוד US Letter, שגובהו 792 נקודות, ו-Y := 50 יושבת קרוב לחלק התחתון. אם הציור הראשון שלך יוצא משוקף אנכית, זו הסיבה: קוד שהועבר מגרפיקת מסך מניח את הכיוון השגוי ורץ מחוץ לקצה התחתון

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

נתיבים: MoveTo, LineTo, Stroke

נתיב מסורטט (stroked path) הוא עט מורם, מונח ונגרר. MoveTo מרים את העט וקובע את נקודת ההתחלה מבלי לסמן שום דבר. כל LineTo מאריך את הנתיב הנוכחי לנקודה חדשה. שום דבר לא מופיע על העמוד עד שאתה קורא ל-Stroke, המצייר את הנתיב שהצטבר באמצעות צבע ה-stroke ורוחב הקו הנוכחיים, ולאחר מכן מנקה את הנתיב כך שה-MoveTo הבא מתחיל מחדש

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'DrawPaths.pdf';
    Pdf.BeginDoc;

    // Line width is in points and applies until you change it.
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.SetRGBStrokeColor(clBlack);

    // A horizontal rule near the top of the page (Y measured from bottom).
    Pdf.CurrentPage.MoveTo(72, 720);
    Pdf.CurrentPage.LineTo(523, 720);
    Pdf.CurrentPage.Stroke;          // commit the path; nothing drew before this

    // A thicker connected polyline: three segments in one path.
    Pdf.CurrentPage.SetLineWidth(3);
    Pdf.CurrentPage.SetRGBStrokeColor(RGB(30, 90, 200));
    Pdf.CurrentPage.MoveTo(72, 640);
    Pdf.CurrentPage.LineTo(172, 690);
    Pdf.CurrentPage.LineTo(272, 620);
    Pdf.CurrentPage.LineTo(372, 680);
    Pdf.CurrentPage.Stroke;

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

שני פרטים חוסכים זמן ניפוי שגיאות אמיתי. רוחב קו הוא מצב (state), לא ארגומנט: SetLineWidth מגדיר אותו פעם אחת וכל Stroke שלאחריו משתמש בערך זה עד שתשנה אותו שוב, וזו הסיבה שה-polyline למעלה עבה יותר מקו (rule). והנתיב מתאפס לאחר כל Stroke, כך ש-Stroke שנשכח פירושו שהגיאומטריה שפרסת בקפידה רבה לעולם לא מתרנדרת כלל. אם צורה חסרה בפלט, קריאת הצביעה היא המקום הראשון להסתכל בו

הקואורדינטות הן נקודות, ונקודות הן שבריריות (fractional). MoveTo ו-LineTo מקבלים ערכי Single, כך שקו דקיק (hairline) ב-0.5 נקודות או מיקום ב-72.25 הם חוקיים ומשמעותיים, ולא מעוגלים ליחידה השלמה הקרובה ביותר. הדיוק הזה חשוב בשני כיוונים מנוגדים. עובי קו מתחת לכ-0.5 יכול להתרנדר כקו הדק ביותר האפשרי התלוי במכשיר, שנעלם מהמסך ומופיע שוב כשמודפס, ולכן קו גלוי דורש עובי שתגדיר בכוונה ולא כברירת מחדל. בקצה השני, היצמדות (snapping) של קווי טבלה וקווי רשת לקואורדינטות שלמות שומרת על רשת צפופה מלהיראות מעט לא אחידה כאשר קווים סמוכים מתעגלים בצורה שונה. החלט על מרווח הרשת בנקודות מראש ושאר הפריסה יורשת אותו

צורות ממולאות וצבע

פרימיטיבים סגורים ניתן למלא במקום לסמן בקו מתאר. Rectangle מקבל מיקום וגודל, Circle מקבל מרכז ורדיוס, וכל אחד מהם מקובע עם Fill, שצובע את הפנים בצבע המילוי הנוכחי, או עם Stroke עבור קו מתאר בלבד. צבע מילוי וצבע stroke הם פיסות מצב (state) נפרדות, המוגדרות עם SetRGBFillColor ו-SetRGBStrokeColor, אשר שתיהן מקבלות TColor יחיד. זה אומר שאתה יכול לעשות שימוש חוזר בקבועי הצבע של Delphi ובעזר ה-RGB ישירות

// Rectangle(X, Y, Width, Height): X and Y are the lower-left corner.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 500, 160, 90);
Pdf.CurrentPage.Fill;

// Circle(X, Y, Radius): X and Y are the center.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 545, 45);
Pdf.CurrentPage.Fill;

// Outline only: set a stroke color and a width, then Stroke.
Pdf.CurrentPage.SetLineWidth(2);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
Pdf.CurrentPage.Rectangle(72, 400, 160, 60);
Pdf.CurrentPage.Stroke;

שים לב לצורת הארגומנט ב-Rectangle. זהו מיקום-בתוספת-גודל, X, Y, Width, Height, לא שתי פינות נגדיות. ה-TCanvas.Rectangle שמפתחי Delphi מכירים מקבל (Left, Top, Right, Bottom), כך שזיכרון השריר ימסור ל-HotPDF פינה שנייה היכן שהוא מצפה לרוחב ולגובה, והתיבה תצא בגודל שגוי. הזוג (X, Y) הוא הפינה השמאלית-התחתונה, עקבי עם מקור העמוד. עבור עיגול, (X, Y) הוא המרכז והארגומנט השלישי הוא הרדיוס בנקודות

בחירת צבע אחת שהדוגמה המקורית טעתה בה

גרסה ישנה יותר של דוגמה זו זרעה (seeded) צבעים עם Random($FFFFFF) בכל צורה. זה נראה תוסס, וזה האינסטינקט השגוי עבור מסמכים שנוצרו. מסמך PDF שאתה בונה מקוד הוא בדרך כלל משהו שאתה גם רוצה לבדוק, וצבעי מילוי אקראיים הופכים את הפלט לבלתי אפשרי להשוואה מריצה לריצה: השוואת byte-for-byte (בית-מול-בית) מול קובץ ידוע-כטוב (known-good file) נכשלת בכל פעם, ללא שום סיבה אמיתית. בחר צבעים מפורשים. כאשר אתה רוצה גיוון על פני סדרה של צורות, הנע אותו מהנתונים שלך או ממערך פלטה קבוע, כך שאותו קלט תמיד מייצר את אותו קובץ. דטרמיניזם שווה יותר מחידוש כאשר הארטיפקט נע דרך צינור שחרור (release pipeline)

היכן ציור וקטורי משתלם, והיכן לא

שלח יד לקריאות הנתיב והצורה הללו כאשר הגיאומטריה מיוצרת: קווי רשת ועמודות של תרשים, השורות המשורטטות של טבלת חשבונית, תיבות הסבר (callout) על דיאגרמה, סימן לוגו המבוטא כקומץ נתיבים. כל זה משנה קנה מידה ללא טשטוש ולא מוסיף כמעט כלום לגודל הקובץ, מכיוון שמלבן הוא כמה מספרים במקום אלפי פיקסלים. הצד השני (flip side) הוא גם כן כנה. אם מה שיש לך בפועל הוא תצלום או צילום מסך, צייר אותו כתמונה עם AddImage ו-ShowImage במקום זאת; עקיבה (tracing) אחר מפת סיביות (bitmap) עם קריאות וקטוריות לא קונה לך דבר. עקומות מורכבות הן גם מחוץ לתחום כאן. הפרימיטיבים לעיל הם מקטעים ישרים, מלבנים ועיגולים, הנושאים את הרוב המכריע של עבודת הדיווח האמיתית; כל דבר שזקוק לעקומות בזייה (Bezier) חופשיות הוא חלק נפרד של ה-API

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

הקריאות MoveTo, LineTo, Stroke, Fill וצבע המוצגות כאן הן חלק מרכיב HotPDF עבור Delphi ו-C++Builder