บางครั้งผลลัพธ์ที่ต้องส่งมอบไม่ใช่เอกสาร แต่คือภาพของตาราง บล็อกสรุปในอีเมลสถานะ พาเนล KPI ที่ render ใน dashboard ภาพย่อข้างผลค้นหา: ทั้งหมดต้องการเซลล์ และไม่มีตัวไหนต้องการกระดาษ TXLSCellImageExporter ใน HotXLS รับสี่เหลี่ยมเซลล์แบบ classic หรือ XLSX แล้วผลิต PNG หรือ JPEG กะทัดรัดหนึ่งภาพ โดยไม่มีขนาดหน้า ไม่มีขอบ ไม่มี header หรือ footer ไม่มี print title และไม่มีการแบ่งหน้า ความละเอียด สเกล รูปแบบและคุณภาพ JPEG ตั้งค่าได้ วัตถุ เส้นกริดและขอบเซลล์มีสวิตช์แยกกัน พื้นหลังเป็นสีหรือโปร่งใสก็ได้ และการเขียนไฟล์ผ่านการแทนที่แบบ atomic ในโฟลเดอร์เดียวกัน ซึ่งปล่อยเป้าหมายเดิมไว้ไม่ถูกแตะหากมีอะไรพลาด
เหตุที่สิ่งนี้ต้องมี exporter เป็นของตัวเอง แทนที่จะเป็น flag บนเส้นทางพิมพ์ คือการแบ่งหน้าไม่ใช่ชั้นเสริมที่ปิดได้ตามใจ มันคือสิ่งที่ pipeline หน้าถูกสร้างมาเพื่อทำ
ทำไมไม่ render ช่วงนี้ผ่าน print pipeline
เพราะ print pipeline แทรกหน้าเข้ามาคั่นระหว่างคุณกับเซลล์ ขนาดกระดาษตัดสินว่าใส่ได้เท่าไร ขอบดันเนื้อหาเข้าด้านใน header และ footer ยึดแถบที่คุณไม่ได้ขอ print title ทำซ้ำแถวที่คุณมีอยู่แล้ว และการแบ่งหน้าผ่าช่วงออก บล็อกสรุปที่บังเอิญคร่อมจุดแบ่งจะออกมาเป็นสองภาพพร้อมแถวที่น่าสนใจโดนตัดครึ่ง คุณชดเชยทั้งหมดนี้ได้ด้วยการตั้งขนาดกระดาษเฉพาะที่ตรงกับช่วงพอดี และหลายคนทำ แต่นั่นหมายถึงการคำนวณเรขาคณิตกระดาษใหม่ทุกครั้งที่ช่วงเปลี่ยน และยังทิ้งตรรกะแถบ header กับ print title ค้างอยู่ในเส้นทาง
ตัว exporter ระดับเซลล์วัดสี่เหลี่ยม จัดสรร bitmap ขนาดพอดีนั้น วาดเซลล์ลงไป แล้วเข้ารหัส ไม่มีหน้า จึงไม่มีอะไรต้องตั้งค่าให้เมินออกไป กรณีที่คุณต้องการกระดาษจริง ๆ เส้นทาง export PDF คือเครื่องมือที่ถูก และอธิบายไว้ในบทความ export เวิร์กชีตเป็น PDF
วัดก่อนแล้วจึง render
Measure คืนมิติพิกเซลที่การตั้งค่าปัจจุบันจะผลิต โดยไม่เข้ารหัสอะไรเลย เรื่องนี้สำคัญสองเหตุ เทมเพลต HTML หรืออีเมลมักต้องการมิติของภาพก่อนภาพจะเกิด เพื่อจองกล่องและเลี่ยง layout shift และบริการที่ render ช่วงที่ผู้ใช้เลือกต้องมีวิธีปฏิเสธคำขอที่ไร้สาระก่อนจะจัดสรรทรัพยากรให้มัน
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 แล้ว จองกล่อง layout ก่อนเข้ารหัส
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 และผู้ใช้ที่เลือก used range ทั้งหมดแทนบล็อกสรุปจะบวกเพิ่มอีกหนึ่งอันดับขนาดบนนั้น ไม่มีเพดานแล้ว failure mode คือการจัดสรรที่ process ตอบสนองไม่ได้ ซึ่งลากสิ่งอื่นที่ process กำลังทำลงไปด้วย
มีเพดานแล้วคำขอจะล้มเหลวและผู้เรียกได้เลือก: ปฏิเสธ ลดสเกล หรือกระชับช่วง นั่นเป็นจุดยืนที่ดีกว่ามากสำหรับ report server และเป็นการเหตุผลชุดเดียวกับงบประมาณแบบชัดเจนใน metafile decoder ตามที่อธิบายไว้ในบทความ decoder EMF และ WMF แบบมีขอบเขต
การแทนที่แบบ atomic และเหตุที่โฟลเดอร์สำคัญ
Save ลงชื่อไฟล์ไม่ได้เขียนตรงเข้าเป้าหมาย มันเขียนไฟล์ชั่วคราวในโฟลเดอร์เดียวกัน เข้ารหัสลงไป แล้วจึงแทนที่เป้าหมาย ถ้าการเข้ารหัสล้ม ถ้างบประมาณถูกเกินกลางทาง หรือถ้า process ถูกฆ่า ภาพเดิมยังอยู่ที่นั่นและยังใช้ได้ dashboard ที่สร้าง tile ใหม่ตามตารางเวลาจึงไม่เคยแสดง PNG ที่ถูกตัดขาด ซึ่งเป็นอาการทั่วไปของการเขียนแบบไร้เดียงสาที่เปิดปลายทางแล้วส่งข้อมูลไหลเข้า
รายละเอียดโฟลเดอร์เดียวกันไม่ใช่เรื่องบังเอิญ การแทนที่แบบ atomic เป็น atomic ได้เฉพาะภายในวอลุ่มเดียวกัน เพราะข้ามวอลุ่มระบบปฏิบัติการต้อง copy แล้ว delete ซึ่งนำหน้าต่างที่คุณพยายามจะปิดกลับเข้ามา การ implement รูปแบบนี้แบบใดที่วางไฟล์ชั่วคราวใน system temp directory จะไม่เป็น atomic บนเครื่องที่ผลลัพธ์อยู่คนละไดรฟ์
เหตุการณ์วาดภาพทำงานบน canvas จริง
ทั้ง range exporter และ page exporter เปิดเผยเหตุการณ์วาดช่วงต้นและช่วงท้าย และพวกมันได้รับ context แบบอ่านอย่างเดียวฉบับเต็ม ไม่ใช่แค่ handle ของ canvas TXLSPagePaintContext แบก canvas ที่มีชีวิต ขอบเขตพิกเซล ขนาดหน้าเป็น point, ความละเอียดและสเกลที่ใช้จริง เลขหน้าระดับเอกสาร เลขหน้าระดับชีต จำนวนหน้ารวม ชื่อชีต และเวิร์กชีตต้นทางทั้งรูป classic และ 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;
มีสามพฤติกรรมที่คุณพึ่งพาได้ เหตุการณ์ยิงครั้งเดียวต่อหนึ่งเฟรมที่ถูก render รวมถึงทุกเฟรมของ TIFF หลายหน้า ตัวนับที่เพิ่มใน handler จึงเชื่อถือได้ พวกมันเงียบระหว่างการวัด -handler ที่มี side effect จึงไม่รันซ้ำสองครั้งต่อหนึ่งผลลัพธ์ และถ้าเหตุการณ์ช่วงต้น raise ขึ้น เหตุการณ์ช่วงท้ายจะไม่ยิง และไบต์ของภาพบางส่วนจะไม่ถูกเขียน exception ในโค้ดวาดของคุณจึงผลิตไฟล์ที่ประทับครึ่ง ๆ กลาง ๆ ไม่ได้
การเลือกรูปแบบ
PNG สำหรับสิ่งใดก็ตามที่ข้อความหนาแน่น JPEG ใช้ transform แบบบล็อกที่ให้เสียงกริ่งรอบเส้นบางคมชัด ซึ่งก็คือขอบเซลล์และข้อความขนาดเล็กนั่นเอง และสิ่งแปลกปลอมเหล่านี้รอดอยู่ที่การตั้งค่าคุณภาพที่ภาพถ่ายดูสมบูรณ์แบบ JPEG คุ้มตำแหน่งเมื่อช่วงดังกล่าวถูกครอบด้วยภาพถ่ายที่ฝังมา และขนาดไฟล์สำคัญกว่าความคมของขอบ พื้นหลังโปร่งใสต้องใช้ PNG เพราะ JPEG ไม่มีช่อง alpha tile ที่ตั้งใจให้วางบนพื้นผิวมีสีจึงได้เลือกแทนคุณไปแล้ว
ถ้าช่วงของคุณมีเซลล์ที่ merge ไว้ ให้เช็กผลลัพธ์กับชีต: region ที่ merge ทำปฏิกิริยากับความกว้างคอลัมน์ในแบบที่ทำให้หลายคนแปลกใจ กฎ layout อธิบายไว้ในบทความเซลล์ merge และเทมเพลตรายงาน HotXLS อ่านและเขียน XLS, XLSX, ODS และ CSV จาก Delphi และ C++Builder โดยไม่พึ่ง Excel และพื้นผิว exporter ฉบับเต็มถูกจัดทำเป็นเอกสารไว้บนหน้าผลิตภัณฑ์HotXLS Delphi spreadsheet component