מאמר טכני

ייבוא EMF ב-PDFlibPas: חוקי PolyDraw, Polyline ו-Bezier

PDFlibPas, ספריית ה-PDF של losLab ל-Delphi, ממירה את רשומות ה-Poly* של EMF לנתיבי PDF לפי הגדרת כל רשומה ב-[MS-EMF]: EMR_POLYBEZIER בן 32 ביט מתחיל בנקודה 0, polylines נשארות פתוחות ומקבלות stroke בלבד, PT_CLOSEFIGURE בתוך EMR_POLYDRAW הוא דגל, וכל מניין נקודות נבדק מול גודל הרשומה. הכללים האלה נחתו על פני v3.539.39, v3.539.41 ו-v3.539.43. לפני כן, תרשים בדוח יכול היה לצאת מ-ImportEMFFromFile עם טריז ממולא במקום קו מגמה, עקמת Bezier שמתעקמת לעבר נקודת השליטה הלא נכונה, או קו מתאר סגור שחסרה לו הצלעה האחרונה. אף אחד מאלה לא העלה שגיאה, והכללים תקפים לכל ממיר EMF ל-PDF ב-Delphi ולכל פרסר של רשומות GDI

למה רשומות ה-Poly* של EMF משתבשות בהמרה ל-PDF?

רשומות ה-Poly* של EMF משתבשות כי כל אחת מהן נושאת חלק מהמשמעות שלה מחוץ לנקודות שלה: האם הצורה פתוחה, האם היא מתחילה במיקום הנוכחי, איזה עט ואיזו מברשת חלים, ואיפה ברשומה מתחילות הנקודות. enhanced metafile הוא הקלטה של קריאות GDI מול device context, ולכן ממיר צריך לשחזר גם את מצב אותו device context וגם את הקואורדינטות. ל-PDF אין device context. יש בו נתיב, נקודה נוכחית בתוך הנתיב, ואופרטור ציור שמכריע בין stroke (S), fill (f) ושניהם (B). כל אי-התאמה בין שני המודלים הופכת להבדל רינדור שקט

משפחת ה-Poly* מגיעה גם בשני רוחבים. לכל רשומה בת 32 ביט כמו EMR_POLYLINE יש תאומה בת 16 ביט כמו EMR_POLYLINE16 שמאחסנת נקודות כזוגות SmallInt. GDI בדרך כלל מקליטה את הצורה הקומפקטית כשכל קואורדינטה נכנסת, ולכן ה-handlers בני ה-32 ביט של ממיר יכולים להישאר שגויים שנים בזמן שציורי בדיקה יומיומיים לעולם לא מגיעים אליהם. הביקורת המהירה ביותר היא להזרים את אותן נקודות דרך שתי הרשומות ולהשוות את הנתיבים שיוצאים. הרשומות שמכוסות כאן כולן בקבוצת רשומות הציור של [MS-EMF] (2.3.5 Drawing Record Types)

רשומהמתחילה בסגורה?מיקום נוכחי
EMR_POLYBEZIERנקודה 0לאלא בשימוש, לא מתעדכן
EMR_POLYLINEנקודה 0לא (עט בלבד)לא בשימוש, לא מתעדכן
EMR_POLYLINETOהמיקום הנוכחילא (עט בלבד)בשימוש ומתעדכן
EMR_POLYPOLYLINEהנקודה הראשונה של כל polylineלא (עט בלבד)לא בשימוש, לא מתעדכן
EMR_POLYDRAWה-PT_MOVETO הראשון, או המיקום הנוכחירק היכן ש-PT_CLOSEFIGURE מוגדרבשימוש ומתעדכן

מאיפה עקמת EMR_POLYBEZIER באמת מתחילה?

עקמת EMR_POLYBEZIER מתחילה בנקודה 0, ורק הנקודות מאינדקס 1 והלאה מקובצות בשלשות של נקודת שליטה, נקודת שליטה, נקודת סיום. רשומה עם 7 נקודות מציירת אפוא שני מקטעים קוביים: 0 הוא ההתחלה, 1 עד 3 מרכיבים את המקטע הראשון, 4 עד 6 את השני. ה-handler בן ה-16 ביט ב-PDFlibPas כבר עשה את זה. ה-handler בן ה-32 ביט התחיל לקבץ מנקודה 0, ולכן נקודת ההתחלה נבלעה כנקודת השליטה הראשונה וכל מקטע מאוחר יותר הוסט באחד. העקמת עדיין התרנדרה, רק לא זו הנכונה. מאז v3.539.41 שני הרוחבים פותחים את הנתיב עם m בנקודה 0 ופולטים c אחד לכל שלשה שלמה אחריה

דיאגרמת PDFlibPas של רשומת EMR_POLYBEZIER עם שבע נקודות, שבה נקודה אפס פותחת את הנתיב עם m ונקודות אחת עד שלוש וארבע עד שש מרכיבות כל אחת מקטע c קובי אחד, בניגוד ל-handler בן ה-32 ביט המתוקן מאז v3.539.41 אל מול הקיבוץ הישן שבלע את נקודת ההתחלה כנקודת שליטה
נקודה 0 היא נקודת ההתחלה ורק שלשות שלמות אחריה הופכות למקטעים קוביים, כך ש-PolyBezier בן שבע נקודות מתרנדר כ-m ועוד שני אופרטורי c

לפרסר שלכם: מניין שאינו 1 בתוספת כפולה של 3 פירושו קלט פגום, ואת הנקודות העודפות כדאי להתעלם מהן ולא לתפור אותן לתוך עקמת

PolyDraw: PT_CLOSEFIGURE הוא דגל, לא סוג נקודה

בתוך EMR_POLYDRAW, PT_CLOSEFIGURE (ערך 1) הוא ביט שמשולב עם PT_LINETO (2) או PT_BEZIERTO (4), ולכן בייט סוג תקין יכול להיות 3 או 5. סוג הנקודה הוא הבייט כשהביט הזה ממוסך ממנו, והדגל אומר: סגרו את הצורה אחרי המקטע שנגמר בנקודה הזאת. ה-handler הישן של PDFlibPas השווה את הבייט מול ערכים בודדים בפקודת case, ולכן נקודות מסוג 3 ו-5 לא התאימו לכלום ודולגו כליל. מלבן שצויר עם PolyDraw איבד את הצלעה הסוגרת שלו, ושלשת Bezier שהנקודה האחרונה שלה נשאה את הדגל איבדה את הנקודה הזאת, מה שהוציא את כל השלשות שאחר כך מהקצב

מאז v3.539.39 הסוג נקרא כ-Types[i] and not PT_CLOSEFIGURE, והסגירה נפלטת רק אחרי מקטע שלם: אחרי הקו עבור PT_LINETO סגור, ואחרי הנקודה השלישית של קבוצת Bezier. קובץ פגום שמגדיר את הדגל על הנקודה הראשונה או השנייה של שלשה לא סוגר את הצורה מוקדם מדי. שני תיקונים נלווים יצאו באותה מהדורה:

  • כל PT_MOVETO בתוך EMR_POLYDRAW16 בן ה-16 ביט התחיל מחדש את כל הנתיב, ולכן רשומה שהכילה שלוש צורות שמרה רק על האחרונה; עכשיו המעבר הראשון פותח את הנתיב ומעברים מאוחרים פותחים תתי-נתיב
  • רשומת PolyDraw שלא מתחילה ב-PT_MOVETO מתחילה במיקום הנוכחי, כפי שהגדרת הרשומה אומרת, במקום לכתוב אופרטור l או c בלי m שלפניו
אנטומיה של בייט סוג ב-EMR_POLYDRAW לפי PDFlibPas, שבה PT_CLOSEFIGURE הוא ביט דגל אפס שמקודד ב-OR לתוך PT_LINETO או PT_BEZIERTO, ולכן בייטי סוג תקינים 3 ו-5 חייבים מיסוך עם and not PT_CLOSEFIGURE לפני הניתוב; הפקודת case הישנה דילגה על שני הבייטים וצורות סגורות איבדו את הצלעה האחרונה
מסכים את דגל הסגירה לפני הניתוב ופולטים את הסגירה רק אחרי קו שהושלם או שלשת Bezier, אחרת PolyDraw מפילה נקודות בשקט

למה polyline של EMF חייב להישאר ללא מילוי ב-PDF?

polyline של EMF אסור שתמולא כי EMR_POLYLINE ו-EMR_POLYPOLYLINE הן צורות פתוחות שמצוירות בעט בלבד, ומילוי של נתיב פתוח ב-PDF סוגר אותו בעקיפין. ISO 32000-1 §8.5.3 קובע שאופרטורי המילוי סוגרים כל תת-נתיב פתוח לפני שהם מציירים אותו. ממיר שפולט B או f עבור polyline בת שלוש נקודות מצייר אפוא משולש ממולא בצבע המברשת הנוכחית: הטריז הממולא מתחת לקו המגמה בתרשים. לפני v3.539.41, PDFlibPas מילאה את שני רוחבי ה-polyline במברשת, והרשומה בת ה-32 ביט גם נסגרה במפורש. היום שני הרוחבים מסתיימים ב-stroke בלבד, וההבחנה של GDI נשמרת: Polygon סוגר וממלא, Polyline לעולם לא

השוואה של PDFlibPas בין polyline פתוח בצורת V שיוצא מ-EMR_POLYLINE: ממיר נכון מסיים את הנתיב באופרטור ה-stroke S ומתעלם מהמברשת הנבחרת, בזמן שפליטת f או B סוגרת בעקיפין את תת-הנתיב הפתוח לפי ISO 32000-1 8.5.3 ומציירת את באג הטריז הממולא בתרשים
אופרטור מילוי סוגר כל תת-נתיב פתוח לפני הציור, ולכן polylines חייבות להסתיים ב-S בלי h, f או B על תת-הנתיב

PolylineTo מתחיל במיקום הנוכחי

EMR_POLYLINETO מצייר מהמיקום הנוכחי דרך כל נקודה ברשומה, נשאר פתוח, ומשאיר את המיקום הנוכחי בנקודה האחרונה. ה-handler הישן הכיל גם מקרה פרטי שכיבה את העט כששתי הנקודות הראשונות חולקות קואורדינטת y, ושום דבר לא הדליק אותו בחזרה, כך שכל רשומה מאוחרת בקובץ איבדה את קו המתאר שלה. מצב העט שייך ל-EMR_SELECTOBJECT ול-EMR_CREATEPEN; ל-handler של רשומת ציור אין עסק בשינוי שלו. מקרה הפרטי הזה הוסר ב-v3.539.41, והצורה בת הנקודה האחת של הרשומה כבר לא קוראת מעבר לנקודות של עצמה (תוקן ב-v3.539.39)

הנקודות של PolyPolyline מתחילות אחרי מערך המניינים

EMR_POLYPOLYLINE בן ה-32 ביט מאחסן nPolys מניינים ואז cptl נקודות, והנקודות מתחילות בהיסט בייטים של 32 + nPolys * 4. המלכודת היא ב-RTL: היחידה Windows מצהירה על TEMRPolyPolyline עם aPolyCounts ו-aptl בתור מערכים של אלמנט אחד, ולכן aptl[0] היא הנקודה הראשונה רק כש-nPolys שווה 1. קוד שמאנדקס ישירות את aptl קורא ערכי מניין כקואורדינטות בכל רשומה מרובת קווים. ה-handler הישן של PDFlibPas גם ביסס את בדיקת הגבולות שלו על הפריסה השגויה הזאת, ולכן רשומות תקינות מרובות קווים נדחו ואלה של קו אחד לא ציירו כלום. מאז v3.539.41 PDFlibPas מאתרת את מערך הנקודות מההיסט המחושב, בדיוק כפי שה-handler של PolyPolygon שלה עשה תמיד, ומציירת כל polyline בתור תת-נתיב פתוח משלו עם stroke אחד בסוף. ב-v3.539.43 התאומה בת ה-16 ביט קיבלה את אותו טיפול; היא ציירה מקטע-מקטע, מה ששבר חיבורי קווים והתעלם מ-NULL_PEN נבחר

עט ומברשת ברירת המחדל, וסוגרי הנתיב

שני כללי מצב משלימים את תיקוני ה-polyline ב-v3.539.43:

  • device context טרי של GDI כבר בוחר ב-BLACK_PEN וב-WHITE_BRUSH, ולכן metafile שמצייר בלי שום EMR_SELECTOBJECT עדיין מצייר קווי מתאר שחורים; הממיר נהג להתחיל בלי עט ובלי מילוי ולכתוב n (סיום נתיב, בלי ציור) עבור רשומות כאלה
  • בתוך סוגריים של BeginPath / EndPath, Polyline לא משתמשת במיקום הנוכחי ולא מעדכנת אותו, ולכן היא חייבת לפתוח תת-נתיב חדש בנקודה הראשונה שלה במקום להתחבר לצורה הקודמת, ושום דבר לא רשאי להיצייר עד שהסוגריים מקבלים stroke או מילוי

בניית קובץ EMF לבדיקה עם TMetafileCanvas

הדרך המהירה ביותר לבדוק ממיר מול הכללים האלה היא להקליט את שלוש הקריאות המסוכנות לתוך enhanced metafile אחד עם TMetafileCanvas. הציור להלן מקליט את העקמות עם מברשת חלולה ואז בוחר בכוונה מברשת מוצקה צהובה עבור ה-polyline: ממיר נכון חייב להתעלם מהמברשת הזאת עבור ה-polyline, ולכן כל צהוב ב-PDF שיוצא הוא באג. ל-PolyDraw אין wrapper של TCanvas, ולכן קוראים לה דרך ה-Windows API עם handle של הקנבס, תוך שימוש בבייטי סוג 3 ו-5 כדי לממש את דגל הסגירה

uses
  Winapi.Windows, System.Types, Vcl.Graphics;

procedure BuildPolyTestEmf(const FileName: string);
const
  // ריבוע סגור (3 = LINETO + CLOSEFIGURE), ואחריו צורת Bezier סגורה
  // שהשלשת השליטה האחרונה שלה מסתיימת עם 5 = BEZIERTO + CLOSEFIGURE
  DrawPts: array[0..7] of TPoint = (
    (X: 300; Y: 40), (X: 380; Y: 40), (X: 380; Y: 120), (X: 300; Y: 120),
    (X: 420; Y: 120), (X: 440; Y: 40), (X: 520; Y: 40), (X: 540; Y: 120));
  DrawTypes: array[0..7] of Byte = (
    PT_MOVETO, PT_LINETO, PT_LINETO, PT_LINETO or PT_CLOSEFIGURE,
    PT_MOVETO, PT_BEZIERTO, PT_BEZIERTO, PT_BEZIERTO or PT_CLOSEFIGURE);
var
  Mf: TMetafile;
  Canvas: TMetafileCanvas;
begin
  Mf := TMetafile.Create;
  try
    Mf.Enhanced := True;
    Mf.Width := 600;
    Mf.Height := 260;
    Canvas := TMetafileCanvas.Create(Mf, 0);
    try
      Canvas.Pen.Color := clNavy;
      Canvas.Pen.Width := 2;
      Canvas.Brush.Style := bsClear;    // קווי מתאר בלבד עבור העקמות
      // נקודה 0 היא ההתחלה; 1..3 ו-4..6 הם שני מקטעים קוביים
      Canvas.PolyBezier([Point(20, 120), Point(60, 20), Point(100, 220),
        Point(140, 120), Point(180, 20), Point(220, 220), Point(260, 120)]);
      PolyDraw(Canvas.Handle, DrawPts[0], DrawTypes[0], Length(DrawPts));
      // צורת V פתוחה עם מברשת מוצקה נבחרת: מקבלת stroke, לעולם לא נסגרת
      // למשולש צהוב
      Canvas.Brush.Style := bsSolid;
      Canvas.Brush.Color := clYellow;
      Canvas.Polyline([Point(20, 240), Point(120, 160), Point(220, 240)]);
    finally
      Canvas.Free;   // מסיים את ההקלטה
    end;
    Mf.SaveToFile(FileName);
  finally
    Mf.Free;
  end;
end;

כי הקואורדינטות האלה נכנסות לתוך SmallInt, GDI בדרך כלל תאחסן את הגרסאות בנות ה-16 ביט. כדי להגיע ל-handlers בני ה-32 ביט צריך מפיק שכותב אותן, או רשומות שבונים ביד. קבצים שנבנים ביד מגיעים עם מלכודת משלהם: TMetafile.LoadFromStream של VCL מתייחס ל-stream בתור EMF רק כשהאורך הנותר גדול ממש מ-108 הבייטים של TEnhMetaHeader. EMF שנכתב ביד בצורה מזערית עם כותרת קצרה, או כזה ריק שאורכו בדיוק 108 בייטים, מתקבל בטעות בתור WMF ונדחה עם "Metafile is not valid". תמיד כתבו את הכותרת המלאה של 108 הבייטים, כולל שדות ההרחבה, לפני רשומות הבדיקה שלכם

ייבוא ה-EMF לתוך PDF עם PDFlibPas

PDFlibPas מייבאת EMF עם ImportEMFFromFile או ImportEMFFromStream, שמחזירות מזהה תמונה שאינו אפס בהצלחה ו-0 בכישלון. GeneralOptions = 0 משאיר את נתיב הווקטור שהמאמר הזה עוסק בו; 1 ממיר במקום זאת את ה-metafile ל-bitmap. FontOptions = 1 מוסיף את גופני ה-metafile כגופני TrueType לא מוטמעים. גרסת ה-stream מחזירה את ה-stream למיקום 0 לפני הטעינה, ולכן העבירו stream שמכיל רק את ה-metafile

uses
  System.SysUtils, PDFlibrary;

procedure EmfToPdf(const EmfFile, PdfFile: WideString);
var
  PDF: TPDFlib;
  ImageID: Integer;
  PageOps: AnsiString;
begin
  PDF := TPDFlib.Create;
  try
    PDF.SetOrigin(1);              // ראשית בפינה השמאלית העליונה עבור DrawImage
    PDF.SetMeasurementUnits(0);    // נקודות
    // FontOptions 1 = מוסיף גופנים כ-TrueType לא מוטמעים
    // GeneralOptions 0 = ייבוא וקטורי, 1 = bitmap
    ImageID := PDF.ImportEMFFromFile(EmfFile, 1, 0);
    if ImageID = 0 then
      raise Exception.Create('The metafile could not be imported');
    PDF.SelectImage(ImageID);
    // עבור EMF, ImageWidth / ImageHeight הם גודל המסגרת בנקודות
    PDF.DrawImage(36, 36, PDF.ImageWidth, PDF.ImageHeight);

    // העמוד רק מפעיל את ה-form המיובא: q ... cm /Name Do Q
    PageOps := PDF.GetPageContentToString;
    if Pos(AnsiString(' Do'), PageOps) = 0 then
      raise Exception.Create('Expected a form XObject invocation');

    if PDF.SaveToFile(PdfFile) <> 1 then
      raise Exception.Create('The PDF could not be saved');
  finally
    PDF.Free;
  end;
end;

ייבוא EMF וקטורי הופך ל-form XObject, ולכן GetPageContentToString מחזיר רק את רצף ה-save, ה-transform, ה-Do וה-restore. האופרטורים m, l, c, h ו-S שנוצרים מרשומות ה-Poly* חיים ב-stream של ה-form XObject, שדחוס. כדי לבקר אותם, פרקו את דחיסת הקובץ השמור ב-inspector של אובייקטי PDF וקראו את ה-stream של ה-form: בקובץ הבדיקה שלמעלה אמורים לראות את ה-polyline מסתיים ב-S בלי h לפניו, h אחד ליד כל דגל סגירה בצורות ה-PolyDraw, ואף f או B על אף אחד מתתי-הנתיב האלה. DrawImage גם מותח EMF מיובא באופן אחיד לפי הקטן מבין Width ו-Height, כך שהציור שומר על יחס הממדים שלו גם כשהתיבה שמעבירים לא תואמת אותו

עבור יעדי Free Pascal, ראו איך מייבא ה-EMF הווקטורי של PDFlibPas מתקמפל תחת Free Pascal; סמנטיקת הרשומות זהה בכל מקום שבו המייבא מתקמפל

איך פרסר EMF צריך להתייחס למנייני נקודות שמגיעים מהקובץ?

פרסר EMF צריך להתייחס לכל מניין נקודות בתור קלט לא מהימן ולבדוק אותו מול גודל הרשומה לפני העתקת נקודה אחת. EnumEnhMetaFile מבטיח רק ש-nSize של כל רשומה נשאר בתוך הקובץ. הוא לא בודק ש-cptl תואם ל-nSize, ולכן handler שמעתיק cptl נקודות עם Move יקרא את הרשומות הבאות, או מעבר לסוף ה-metafile, כשהמניין מזויף או פגום. מאז v3.539.39 PDFlibPas בודקת מול nSize את הכותרת הקבועה בתוספת מניין כפול בייטים לנקודה עבור PolyDraw, PolyBezier, PolyBezierTo, Polyline, PolylineTo ו-Polygon בשני הרוחבים, עם בייט נוסף אחד לנקודה עבור בייטי הסוג של PolyDraw. עבור רשומות ה-PolyPoly המניינים לכל צורה חייבים גם להצטבר ללא יותר מהסך המוצהר, וצורות בנות אפס נקודות מדולגות

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

uses
  Winapi.Windows;

// מחזיר nil אלא אם הרשומה באמת מחזיקה את הנקודות שהיא מצהירה עליהן.
// הנקודות מתחילות אחרי מערך המניינים: 32 + nPolys * 4 בייטים פנימה, לא ב-
// aptl[0], שה-RTL מצהיר עליו בתור מערך של אלמנט אחד
function PolyPolylinePoints(Rec: PEnhMetaRecord): PPoint;
var
  P: PEMRPolyPolyline;
  Count: PDWORD;
  PointsOffset, Total: Int64;
  I: Cardinal;
begin
  Result := nil;
  if (Rec^.iType <> EMR_POLYPOLYLINE) or (Rec^.nSize < 32) then
    Exit;
  P := PEMRPolyPolyline(Rec);
  if P^.nPolys = 0 then
    Exit;
  PointsOffset := 32 + Int64(P^.nPolys) * SizeOf(DWORD);
  if PointsOffset + Int64(P^.cptl) * SizeOf(TPoint) > Rec^.nSize then
    Exit;                         // מניין מזויף או קטוע
  Total := 0;
  Count := @P^.aPolyCounts[0];    // הליכה במצביע: [0..0] מפעיל בדיקות טווח
  for I := 1 to P^.nPolys do
  begin
    Inc(Total, Count^);
    Inc(Count);
  end;
  if Total > P^.cptl then
    Exit;                         // הצורות טוענות ליותר נקודות ממה שקיים
  Result := PPoint(NativeUInt(Rec) + NativeUInt(PointsOffset));
end;

בדיקת ההתאמה של הנקודות רצה ראשונה, ולכן ידוע שמערך המניינים נמצא בתוך הרשומה עוד לפני שהלולאה הולכת עליו. החשבון הוא Int64 כי nPolys * 4 ו-cptl * 8 שמחושבים ב-32 ביט יכולים להתגלגל ולעבור את ההשוואה

עזר זריז: כללי ה-Poly* של EMF להמרת EMF ל-PDF

  • EMR_POLYBEZIER: נקודה 0 היא נקודת ההתחלה; מקבצים מנקודה 1 בשלשות; תוקן עבור הרשומה בת ה-32 ביט ב-v3.539.41
  • EMR_POLYLINE / EMR_POLYPOLYLINE: צורות פתוחות, stroke עם S, לעולם לא h, f או B, כי מילוי ב-PDF סוגר תתי-נתיב פתוחים
  • EMR_POLYLINETO: מתחיל במיקום הנוכחי, נשאר פתוח, מעדכן את המיקום הנוכחי, לעולם לא נוגע במצב העט
  • EMR_POLYPOLYLINE בן 32 ביט: הנקודות מתחילות בבייט 32 + nPolys * 4, לא ב-aptl[0]
  • EMR_POLYDRAW: מסכים את PT_CLOSEFIGURE לפני הניתוב, סוגר אחרי המקטע שהושלם, מתחיל במיקום הנוכחי כשהנקודה הראשונה אינה PT_MOVETO
  • מצב ברירת המחדל של ה-device context הוא BLACK_PEN בתוספת WHITE_BRUSH; v3.539.43 ואילך מכבדים אותו
  • בתוך BeginPath / EndPath, כל polyline פותחת תת-נתיב משלה ושום דבר לא מצויר עד שהסוגריים נצרכים
  • מאמתים כל cptl / cpts מול nSize בחשבון 64 ביט לפני העתקת נקודות
  • קבצי EMF לבדיקה שנבנים ביד זקוקים לכותרת המלאה של 108 הבייטים, אחרת TMetafile.LoadFromStream קורא אותם בתור WMF

אם הדוחות שלכם עוברים דרך רכיב אחר, אותה סמנטיקת רשומות בתוקף; ייבוא וקטורי של EMF ו-WMF ב-HotPDF מסביר איך הרכיב הזה הופך מברשות gradient ו-hatch ל-patterns של PDF, ו-גרפיקה וקטורית, shaders ו-gradientים ב-PDFlibPas מסביר לצייר את אותן צורות ישירות עם ה-API של הספרייה במקום דרך metafile

PDFlibPas v3.539.43 ומעלה כוללת את כל הכללים שלמעלה. פרטים והורדות ניסיון נמצאים בעמוד המוצר של PDFlibPas Delphi PDF library