לפעמים התוצר אינו מסמך אלא תמונה של טבלה. בלוק סיכום במייל סטטוס, פאנל KPI מרונדר בדשבורד, תמונה ממוזערת ליד תוצאת חיפוש: כולם רוצים את התאים ואף אחד לא רוצה נייר. TXLSCellImageExporter ב-HotXLS לוקח מלבן תאים קלאסי או של XLSX ומפיק תמונת PNG או JPEG קומפקטית אחת בלי גודל עמוד, בלי שוליים, בלי כותרות עליונות או תחתונות, בלי כותרות הדפסה ובלי מעברי עמוד. רזולוציה, קנה מידה, פורמט ואיכות JPEG ניתנים להגדרה, objects, קווי רשת וגבולות תאים להם מתגים עצמאיים, הרקע יכול להיות צבע או שקוף, וכתיבת הקובץ עוברת החלפה אטומית באותה תיקייה שמשאירה יעד קיים ללא מגע אם משהו נכשל
הסיבה שזה זקוק למייצא משלו ולא לדגל על נתיב ההדפסה היא שדפדוף אינו שכבה אופציונלית שאפשר לכבות. הוא הדבר שצינורית העמודים קיימת כדי לעשות
למה לא לרנדר את הטווח דרך צינורית ההדפסה?
כי צינורית ההדפסה מכניסה עמוד בינכם לבין התאים. גודל הנייר מכריע כמה נכנס, שוליים דוחפים את התוכן פנימה, כותרות עליונות ותחתונות תופסות פסים שלא ביקשתם, כותרות הדפסה חוזרות על שורות שכבר יש לכם, ומעברי עמוד מפצלים את הטווח. בלוק סיכום שנופל על מעבר יוצא כשתי תמונות עם השורה המעניינת חתוכה בחצי. אפשר לפצות על כל זה בהגדרת גודל עמוד מותאם שתואם בדיוק את הטווח, ואנשים עושים זאת, אבל זה אומר לחשב מחדש גאומטריית נייר בכל פעם שהטווח משתנה וזה עדיין משאיר את פס הכותרת ואת לוגיקת כותרות ההדפסה בנתיב
מייצא התאים מודד את המלבן, מקצה bitmap בגודל הזה בדיוק, מצייר אליו את התאים, ומקודד. אין עמוד, ולכן אין מה לכבות בהגדרות. למקרים שבהם דווקא רוצים נייר, נתיב ייצוא ה-PDF הוא הכלי הנכון ומכוסה במאמר ייצוא PDF של גיליון
מדדו לפני שאתם מרנדרים
Measure מחזיר את ממדי הפיקסלים שההגדרות הנוכחיות היו מפיקות בלי לקודד דבר. זה חשוב משתי סיבות. תבנית HTML או מייל בדרך כלל צריכה את ממדי התמונה לפני שהתמונה קיימת, כדי שתוכל לשריין את התיבה ולהימנע מהזזת פריסה. ושירות שמרנדר טווחים שנבחרו על ידי משתמשים צריך דרך לדחות בקשה אבסורדית לפני שמקצים עבורה
uses
lxHandleX, lxPagination;
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Exporter: TXLSCellImageExporter;
Summary: TXLSXRange;
W, H, Bytes: Integer;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarter.xlsx');
Sheet := Book.Sheets.ByPos[0];
Summary := Sheet.Range['A1:F20'];
Exporter := TXLSCellImageExporter.Create;
try
Exporter.ImageFormat := xpifPng; // PNG שומר קווים דקים חדים
Exporter.DPI := 96;
Exporter.Scale := 2.0; // פלט בצפיפות retina
Exporter.IncludeGridlines := False;
Exporter.IncludeCellBorders := True;
Exporter.TransparentBackground := True;
Exporter.MaxPixels := 40 * 1000 * 1000;
Exporter.MaxBytes := 8 * 1024 * 1024;
if not Exporter.Measure(Summary, W, H) then
raise Exception.Create('range exceeds the configured budget');
// W ו-H ידועים עכשיו; שריינו את תיבת הפריסה לפני הקידוד
Bytes := Exporter.Save(Summary, 'summary.png');
if Bytes <= 0 then
raise Exception.Create('image export failed, previous file kept');
finally
Exporter.Free;
end;
finally
Book.Free;
end;
end;
תקציבים, כי קנה המידה כופל
MaxPixels ו-MaxBytes אינם קישוט הגנתי. מניין הפיקסלים גדל עם ריבוע מקדם קנה המידה ועם ריבוע יחס הרזולוציה, כך שטווח שהוא סביר של 1200 על 800 ב-96 DPI הופך לערך ל-47 מגהפיקסלים ב-600 DPI, ומשתמש שבוחר טווח נציל מלא במקום בלוק סיכום מוסיף סדר גודל נוסף מעל זה. בלי תקרה מצב הכשל הוא הקצאה שהתהליך אינו יכול לספק, שמפילה כל מה שהתהליך הזה עשה אחרת
עם תקרה הבקשה נכשלת והקורא בוחר: לסרב, להפחית את קנה המידה, או לצמצם את הטווח. זו עמדה הרבה יותר טובה עבור שרת דוחות, וזהו אותו היגיון מאחורי התקציבים המפורשים במפענח ה-metafile המתואר במאמר מפענח ה-EMF וה-WMF המסוגר
החלפה אטומית, ולמה התיקייה חשובה
Save אל שם קובץ אינו כותב אל היעד. הוא כותב קובץ זמני באותה תיקייה, מקודד אליו, ורק אז מחליף את היעד. אם הקידוד נכשל, אם התקציב נחרג באמצע הדרך, או אם התהליך נהרג, התמונה הקודמת עדיין שם ועדיין תקפה. דשבורד שמחדש את האריחים שלו לפי לוח זמנים לכן לעולם לא מציג PNG קטוע, שהוא התסמין הרגיל של כתיבה נאיבית שפותחת את היעד ומתחילה להזרים
פרט אותה-התיקייה אינו מקרי. החלפה אטומית היא אטומית בתוך כרך אחד בלבד, כי בין כרכים מערכת ההפעלה צריכה להעתיק ואז למחוק, מה שמחזיר את החלון שניסיתם לסגור. כל מימוש של התבנית הזאת שמציב את הקובץ הזמני שלו בתיקיית ה-temp של המערכת אינו אטומי במחשב שבו הפלט חי על כונן אחר
אירועי ציור מציירים על ה-canvas האמיתי
גם מייצא הטווחים וגם מייצא העמודים חושפים אירועי ציור פותחים וסוגרים, והם מקבלים הקשר קריאה-בלבד מלא ולא רק handle של canvas. TXLSPagePaintContext נושא את ה-canvas החי, את גבולות הפיקסלים, את גודל העמוד בנקודות, את הרזולוציה וקנה המידה בפועל, את מספר העמוד במסמך, את מספר העמוד בגיליון, את מניין העמודים הכולל, את שם הגיליון ואת גיליון המקור גם בטעם הקלאסי וגם בטעם XLSX. זה מספיק לצייר סימן מים שמתאים לקנה מידה נכון, או חותמת עמוד שיודעת היכן היא בריצה
procedure TReportJob.StampDraft(Sender: TObject;
const AContext: TXLSPagePaintContext);
begin
// מודע קנה מידה, כך שהחותמת נראית זהה ב-1x וב-3x
AContext.Canvas.Font.Height := Round(-48 * AContext.Scale);
AContext.Canvas.Font.Color := clSilver;
AContext.Canvas.Brush.Style := bsClear;
AContext.Canvas.TextOut(AContext.Bounds.Left + Round(24 * AContext.Scale),
AContext.Bounds.Top + Round(24 * AContext.Scale), 'DRAFT');
end;
Exporter.AfterPaint := Job.StampDraft;
שלוש התנהגויות שוות הסתמכות. האירועים מנפיקים בדיוק פעם אחת לכל פריים מרונדר, כולל כל פריים של TIFF מרובה עמודים, ולכן מונה שמוגדל במטפל אמין. הם שקטים במהלך מדידה, כך שמטפל עם תופעת לוואי לא רץ פעמיים עבור פלט אחד. ואם האירוע הפותח זורק חריגה, האירוע הסוגר לא מנפיק ולא נכתבים בייטים חלקיים של תמונה, כך שחריגה בקוד הציור שלכם אינה יכולה להפיק קובץ חתום למחצה
בחירת הפורמט
PNG לכל דבר עתיר טקסט. JPEG מחיל טרנספורמציית בלוקים שמפיקה צלצול נראה סביב קווים דקים בעלי ניגודיות גבוהה, וגבולות תאים וטקסט קטן הם בדיוק זה, וה-artefacts שורדים בהגדרות איכות שבהן צילום נראה מושלם. JPEG מרוויח את מקומו כשהטווח נשלט על ידי צילומים מוטמעים וגודל הקובץ חשוב יותר מנאמנות קווי המתאר. רקעים שקופים דורשים PNG, מאחר של-JPEG אין ערוץ alpha, ולכן אריח שמיועד לשבת על משטח צבעוני קבע את הבחירה בשבילכם
אם הטווח שלכם מכיל תאים ממוזגים, בדקו את הפלט מול הגיליון: אזורים ממוזגים מתקשרים עם רוחבי עמודות באופנים שמפתיעים אנשים, וכללי הפריסה מכוסים במאמר תאים ממוזגים ותבניות דוחות. HotXLS קורא וכותב XLS, XLSX, ODS ו-CSV מ-Delphi ו-C++Builder בלי תלות ב-Excel, ומשטח המייצא המלא מתועד בדף המוצר של HotXLS Delphi spreadsheet component