Technischer Artikel

Eine Excel-Zellrange als ein Bild mit HotXLS exportieren

Manchmal ist das Ergebnis kein Dokument, sondern ein Bild einer Tabelle. Ein Zusammenfassungsblock in einer Status-E-Mail, ein gerendertes KPI-Panel in einem Dashboard, eine Miniatur neben einem Suchergebnis: Alle wollen die Zellen, und keiner will Papier. TXLSCellImageExporter in HotXLS nimmt ein klassisches oder XLSX-Zellrechteck und produziert ein kompaktes PNG oder JPEG ohne Seitengröße, ohne Ränder, ohne Kopf- oder Fußzeilen, ohne Drucktitel und ohne Seitenumbrüche. Auflösung, Skalierung, Format und JPEG-Qualität sind konfigurierbar, Objekte, Gitterlinien und Zellränder haben eigene Schalter, der Hintergrund kann eine Farbe oder transparent sein, und das Schreiben der Datei läuft über einen atomaren Ersatz im selben Ordner, der ein bestehendes Ziel unangetastet lässt, wenn irgendetwas scheitert

Der Grund, warum das einen eigenen Exporteur braucht statt eines Flags am Druckpfad: Paginierung ist keine optionale Schicht, die man ausschalten könnte. Sie ist der Daseinszweck der Seiten-Pipeline

Warum die Range nicht durch die Druck-Pipeline rendern?

Weil die Druck-Pipeline eine Seite zwischen Sie und die Zellen schiebt. Die Papiergröße entscheidet, wie viel hineinpasst, Ränder drücken den Inhalt nach innen, Kopf- und Fußzeilen belegen Bänder, die Sie nie verlangt haben, Drucktitel wiederholen Zeilen, die Sie bereits haben, und Seitenumbrüche zerschneiden die Range. Ein Zusammenfassungsblock, der zufällig über einen Umbruch hinwegreicht, kommt als zwei Bilder heraus, mit der interessanten Zeile halbiert. Sie können das alles kompensieren, indem Sie eine benutzerdefinierte Seitengröße einrichten, die exakt der Range entspricht, und Leute tun das, aber das bedeutet, die Papiergeometrie bei jeder Range-Änderung neu zu berechnen, und es lässt das Kopfzeilen-Band und die Drucktitel-Logik trotzdem im Pfad

Der Zellexporteur misst das Rechteck, allokiiert ein Bitmap von exakt dieser Größe, zeichnet die Zellen hinein und enkodiert. Es gibt keine Seite, also gibt es nichts, was man wegkonfigurieren könnte. Für die Fälle, in denen Sie doch Papier wollen, ist der PDF-Export-Pfad das richtige Werkzeug und wird behandelt in dem Artikel zum Arbeitsblatt-PDF-Export

TXLSCellImageExporter misst, zeichnet und enkodiert ein Bild je Zellrange, während die Druck-Pipeline die Range an Seitenumbrüchen zerschneidet
Die Seiten-Pipeline schiebt Papiergeometrie zwischen Sie und die Zellen; der Zellexporteur hat nirgendwo im Pfad eine Seite

Messen, bevor Sie rendern

Measure liefert die Pixeldimensionen, die die aktuellen Einstellungen erzeugen würden, ohne irgendetwas zu enkodieren. Das ist aus zwei Gründen wichtig. Eine HTML- oder E-Mail-Vorlage braucht üblicherweise die Bildabmessungen, bevor das Bild existiert, damit sie die Box reservieren und Layout-Shift vermeiden kann. Und ein Dienst, der vom Nutzer gewählte Ranges rendert, braucht einen Weg, eine absurde Anfrage abzulehnen, bevor er dafür allokiert

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 hält dünne Striche scharf
      Exporter.DPI := 96;
      Exporter.Scale := 2.0;             // Ausgabe in Retina-Dichte
      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 und H sind nun bekannt; Layout-Box reservieren vor dem Enkodieren
      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;

Budgets, denn Skalierung multipliziert

MaxPixels und MaxBytes sind keine defensive Dekoration. Die Pixelzahl wächst mit dem Quadrat des Skalierungsfaktors und mit dem Quadrat des Auflösungsverhältnisses, also wird eine Range, die bei 96 DPI vernünftige 1200 mal 800 ist, bei 600 DPI grob 47 Megapixel, und ein Nutzer, der eine ganze benutzte Range statt eines Zusammenfassungsblocks wählt, legt eine weitere Größenordnung obendrauf. Ohne Obergrenze ist der Fehlermodus eine Allokation, die der Prozess nicht erfüllen kann, was alles andere mitreißt, was dieser Prozess tat

Mit einer Obergrenze scheitert die Anfrage, und der Aufrufer darf wählen: ablehnen, die Skalierung reduzieren oder die Range verkleinern. Das ist eine viel bessere Position für einen Berichtsserver, und es ist dieselbe Begründung hinter den expliziten Budgets im Metadatei-Dekodierer, beschrieben in dem Artikel zum begrenzten EMF- und WMF-Dekodierer

Budget-Ablauf für TXLSCellImageExporter in HotXLS: Measure liefert zuerst die Pixelgröße, dann begrenzen MaxPixels und MaxBytes Allokation und Ausgabegröße
Die Ablehnung geschieht vor der Allokation, und ein Byte-Budget-Fehler lässt das vorherige Bild für den Aufrufer unangetastet

Atomarer Ersatz und warum der Ordner zählt

Save auf einen Dateinamen schreibt nicht ins Ziel. Es schreibt eine temporäre Datei in denselben Ordner, enkodiert hinein und ersetzt erst dann das Ziel. Scheitert die Enkodierung, wird das Budget unterwegs überschritten oder der Prozess getötet, ist das vorherige Bild noch da und weiterhin gültig. Ein Dashboard, das seine Kacheln nach Zeitplan regeneriert, zeigt daher nie ein abgeschnittenes PNG, das übliche Symptom eines naiven Schreibvorgangs, der das Ziel öffnet und zu streamen beginnt

Das Detail mit demselben Ordner ist nicht zufällig. Ein atomarer Ersatz ist nur innerhalb eines Volumes atomar, denn über Volumes hinweg muss das Betriebssystem kopieren und dann löschen, was das Fenster wieder öffnet, das Sie zu schließen versuchten. Jede Implementierung dieses Musters, die ihre temporäre Datei ins System-Temp-Verzeichnis legt, ist auf einer Maschine, auf der die Ausgabe auf einem anderen Laufwerk lebt, nicht atomar

TXLSCellImageExporter Save enkodiert in eine Temp-Datei im selben Ordner und ersetzt dann das Ziel atomar; Fehler lassen das vorherige Bild gültig
Die temporäre Datei muss neben dem Ziel liegen, denn ein atomarer Ersatz funktioniert nur innerhalb eines Volumes

Paint-Events zeichnen auf dem echten Canvas

Sowohl der Range-Exporteur als auch der Seitenexporteur legen führende und abschließende Paint-Events offen, und sie empfangen einen vollständigen schreibgeschützten Kontext statt nur eines Canvas-Handles. TXLSPagePaintContext trägt den live Canvas, die Pixelgrenzen, die Seitengröße in Points, die tatsächlich verwendete Auflösung und Skalierung, die Dokumentseitennummer, die Seitennummer im Blatt, die Gesamtzahl der Seiten, den Blattnamen und das ursprüngliche Arbeitsblatt in sowohl klassischer als auch XLSX-Variante. Das reicht, um ein Wasserzeichen zu zeichnen, das korrekt skaliert, oder einen Seitenstempel, der weiß, wo er im Lauf ist

procedure TReportJob.StampDraft(Sender: TObject;
  const AContext: TXLSPagePaintContext);
begin
  // Skalierungsbewusst, damit der Stempel bei 1x und 3x gleich aussieht
  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;

Drei Verhaltensweisen sind es wert, sich darauf zu verlassen. Die Events feuern exakt einmal je gerendertem Frame, einschließlich jedes Frames eines mehrseitigen TIFF, also ist ein im Handler inkrementierter Zähler vertrauenswürdig. Sie bleiben während der Messung still, also läuft ein Handler mit Seiteneffekt nicht zweimal für eine Ausgabe. Und wenn das führende Event auslöst, feuert das abschließende nicht, und es werden keine unvollständigen Bild-Bytes geschrieben, also kann eine Exception in Ihrem eigenen Zeichencode keine halb gestempelte Datei erzeugen

Das Format wählen

PNG für alles Textlastige. JPEG wendet eine Blocktransformation an, die sichtbares Ringing um dünne kontrastreiche Striche erzeugt, was genau Zellränder und kleiner Text sind, und die Artefakte überleben bei Qualitätseinstellungen, bei denen ein Foto perfekt aussieht. JPEG verdient seinen Platz, wenn eingebettete Fotografien die Range dominieren und die Dateigröße mehr zählt als die Kantentreue. Transparente Hintergründe verlangen PNG, da JPEG keinen Alphakanal hat, also hat eine Kachel, die auf einer farbigen Fläche sitzen soll, die Entscheidung für Sie getroffen

Enthält Ihre Range verbundene Zellen, prüfen Sie die Ausgabe gegen das Blatt: Verbundene Bereiche interagieren auf Arten mit Spaltenbreiten, die Leute überraschen, und die Layoutregeln behandelt der Artikel zu verbundenen Zellen und Berichtsvorlagen. HotXLS liest und schreibt XLS, XLSX, ODS und CSV aus Delphi und C++Builder ohne Excel-Abhängigkeit, und die vollständige Exporteur-Oberfläche ist auf der Produktseite der HotXLS Delphi Tabellenkomponente dokumentiert