Sie fügen ein Logo mit 600×400 Pixeln in die Kopfzeile einer generierten Rechnung ein, es sieht auf Ihrem Entwicklungsmonitor mit 96 DPI korrekt aus, und eine Woche später meldet ein Kunde auf einem Laptop mit hoher DPI-Zahl, dass es in der Größe einer Briefmarke gedruckt wird. Die Pixel haben sich nie geändert. Was sich geändert hat, ist die Annahme, dass eine Pixelanzahl eine physische Größe bedeutet, und in OOXML ist das nicht der Fall. Ein Tabellenkalkulationsbild trägt seine Abmessungen in EMU, und solange Sie nicht in EMU denken – oder in den realen Einheiten, die sich sauber darauf abbilden lassen –, ist Ihr Layout der DPI-Zahl ausgeliefert, die der rendernde Rechner gerade annimmt
HotXLS ist eine native VCL-Tabellenkalkulationskomponente für Delphi und C++Builder, die XLS- und XLSX-Dateien ohne Excel oder COM-Abhängigkeiten liest und schreibt. Seit der Version v2.91.0 müssen Sie die Einheitenrechnung beim XLSX-Bildobjekt nicht mehr manuell durchführen: Neben den rohen EMU-Werten stellt es Breite und Höhe in Zentimetern, Zoll und Punkten bereit, plus eine Scale-Methode, die die Größe prozentual mit optionaler Seitenverhältnissperre ändert. In diesem Artikel geht es darum, was EMU eigentlich ist, warum DrawingML diese Einheit gewählt hat und wie Sie die neue Geometrieoberfläche nutzen, um Bilder nach ihrer physischen Größe zu platzieren, anstatt sich auf eine Pixelanzahl zu verlassen, der Sie nicht trauen können
Was eine EMU ist und warum DrawingML sie verwendet
EMU steht für English Metric Unit und ist die Basislängeneinheit von DrawingML, der Zeichnungsebene, die in der gesamten Office Open XML-Familie verwendet wird (ECMA-376, Teil 1, §20). Eine EMU ist so definiert, dass es genau 914400 EMU pro Zoll und 360000 EMU pro Zentimeter gibt. Diese beiden Konstanten sind der einzige Grund für die Existenz dieser Einheit. 914400 ist teilbar durch 2, 3, 4, 5, 6, 8, 9, 10, 12 und viele weitere; seine Primfaktorzerlegung lautet 26 × 32 × 52 × 127. Da 1 Zoll exakt gleich 2,54 cm ist, ermöglicht die Wahl einer Einheit, die sowohl durch 360000 als auch durch einen sauberen Bruchteil von 914400 teilbar ist, dem Format, Zoll, Zentimeter und Punkte als Ganzzahlen ohne Rundungsfehler an den Einheitsgrenzen auszudrücken. Wo ein Floating-Point-Wert von „1,27 cm“ driften würde, speichert EMU exakt 457200 und bleibt präzise
Die andere Einheit, die hier von Bedeutung ist, ist der Punkt (Point). Ein typografischer Punkt entspricht 1/72 Zoll, sodass genau 12700 EMU pro Punkt anfallen (914400 / 72). In Punkten rechnet Excel unter der Haube selbst Zeilenhöhen, Schriftgrößen und Ränder um. Daher ist die Angabe der Bildgeometrie in Punkten nützlich, wenn das Bild an Textmetriken statt an einem gedruckten Lineal ausgerichtet werden soll. HotXLS codiert alle vier Beziehungen als Einheitskonstanten in der Bibliothek:
const
XlsxEmuPerInch = 914400; // 1 inch
XlsxEmuPerCm = 360000; // 1 centimetre
XlsxEmuPerPoint = 12700; // 1 point (1/72 inch)
XlsxEmuPerPixel = 9525; // 1 pixel at 96 DPI (914400 / 96)
Diese letzte Zeile ist der Kern des Briefmarken-Bugs. Ein Pixel hat erst dann eine physische Größe, wenn Sie eine DPI-Zahl festlegen, und 9525 EMU ist die Größe eines Pixels speziell bei 96 DPI. Die Standard-Rendering-DPI von Excel ist 96, sodass ein Bild mit 100 Pixeln bei einer Standardkonfiguration bei 100 × 9525 = 952500 EMU ≈ 2,54 cm landet – aber nichts in der Datei garantiert, dass der Empfänger 96 verwendet. Wenn Sie in realen Einheiten schreiben, verschwindet diese Mehrdeutigkeit: 4 cm sind 4 cm, unabhängig davon, ob der Bildschirm 96 oder 220 DPI hat
Die TXLSXImage-Geometrieoberfläche
Ein eingebettetes Bild in HotXLS ist ein TXLSXImage. Seine kanonische Speicherung besteht aus zwei Ganzzahlfeldern, WidthEMU und HeightEMU, verankert an einer einsbasierten Row und Col (der Zelle oben links, an der das Bild hängt). Die Real-Unit-Eigenschaften sind berechnete Ansichten über diese EMU-Felder, kein separater Zustand – das Lesen von WidthCM teilt die EMU durch 360000, und das Schreiben multipliziert und rundet zurück. Jede von Ihnen festgelegte Dimension ist also nur eine andere Schreibweise desselben zugrunde liegenden EMU-Werts:
WidthInch/HeightInch— EMU ÷ 914400WidthCM/HeightCM— EMU ÷ 360000WidthPt/HeightPt— EMU ÷ 12700WidthEMU/HeightEMU— die Ganzzahl als Quelle der Wahrheit
Sie fügen ein Bild mit AddImage(ARow, ACol, AData, AFormat) hinzu, wobei Sie die rohen kodierten Bytes und ein TXLSXImageFormat (xlsxImagePng, xlsxImageJpeg, xlsxImageGif oder xlsxImageBmp) übergeben. Die Funktion gibt den nullbasierten Index in der Images-Sammlung des Arbeitsblatts zurück. Es gibt auch die Funktion AddImageFromFile(ARow, ACol, AFileName), die das Format aus der Dateiendung ableitet. Beachten Sie die Indexbasis: AddImage gibt nullbasiert zurück und Images[] is nullbasiert, was ein bewusster Kontrast zum einsbasierten Raster Cells[Row, Col] ist – gehen Sie also nicht davon aus, dass beide übereinstimmen
var
Sheet: TXLSXWorksheet;
Img: TXLSXImage;
Idx: Integer;
begin
Sheet := Workbook.Sheets.Add('Images');
// Anchor a PNG at row 3, column 2; AddImage returns a 0-based index.
Idx := Sheet.AddImage(3, 2, LogoBytes, xlsxImagePng);
Img := Sheet.Images[Idx];
Img.WidthCM := 4.0; // 4 cm wide -> 1440000 EMU
Img.HeightCM := 3.0; // 3 cm tall -> 1080000 EMU
// Same geometry, read back in other units.
// Img.WidthPt is now 113.39 pt, Img.WidthInch is 1.5748 in.
end;
Ein neu erstelltes Bild ist standardmäßig 100×100 Pixel groß, d. h. 952500 EMU im Quadrat, was in etwa einer Box von 2,54 cm bei 96 DPI entspricht. Dieser Standardwert existiert, damit ein Bild auch dann sichtbar ist, wenn Sie vergessen, seine Größe anzugeben. Bei jedem echten Layout sollten Sie jedoch eine explizite physische Größe festlegen, anstatt sich auf den von Pixeln abgeleiteten Standardwert zu verlassen
Skalierung und die Flagge für das Seitenverhältnis
Wenn Sie die Größe relativ zu den aktuellen Abmessungen anstelle eines absoluten Ziels ändern möchten – beispielsweise ein Diagrammbild auf 60 % der Größe verkleinern, mit der es importiert wurde –, verwenden Sie Scale:
procedure Scale(APercent: Double; AKeepAspect: Boolean = True);
APercent ist ein Prozentsatz, bei dem 100 unverändert bedeutet, 150 um die Hälfte vergrößert, 50 halbiert. Wenn AKeepAspect auf dem Standardwert True steht, werden sowohl Breite als auch Höhe mit demselben Faktor multipliziert, sodass die Proportionen erhalten bleiben und ein 4×3 cm großes Bild nach Scale(150) zu 6×4,5 cm wird. Übergeben Sie False, wird nur die Breite skaliert – die Höhe bleibt genau so, wie sie war. Diese Asymmetrie ist beabsichtigt: Wenn Sie eine Achse unabhängig dehnen möchten, sind die expliziten Setter WidthCM/HeightCM das richtige Werkzeug. Der Zweig von Scale ohne Seitenverhältnis ist für den engeren Fall gedacht, nur die Breite anzupassen. Man kann Scale(150, False) leicht als „beide frei dehnen“ missverstehen und eine Überraschung erleben. Nutzen Sie daher die Setter, wenn Sie tatsächlich zwei unabhängige Dimensionen meinen
Img.WidthCM := 4.0;
Img.HeightCM := 3.0;
Img.Scale(150); // aspect locked: now 6.0 x 4.5 cm
Img.Scale(100); // no-op, returns immediately
Img.Scale(50, False); // width only: 3.0 cm wide, height unchanged at 4.5 cm
Ein kleines Detail zum Verhalten: Scale(100) bricht sofort ab und kehrt zurück, ohne eines der Felder zu berühren. Es ist also sicher, den Aufruf bedingungslos in einer Schleife auszuführen, in der der Prozentsatz 100 sein kann. Und da die Geometrie als Ganzzahl-EMU gespeichert wird, rundet jeder Setter. Das Hin- und Herrechnen über gebrochene Zentimeter kann daher um einen Bruchteil einer EMU abweichen – weit unter allem Sichtbaren, aber gut zu wissen, falls Sie jemals exakte Gleichheit in einem Test prüfen. Für pixelgenaue Kontrolle setzen Sie WidthEMU und HeightEMU direkt und überspringen die Einheitenumrechnung ganz
Geometrie wieder auslesen
Die Bildersammlung ist abfragbar, was wichtig ist, wenn Sie eine vorhandene Arbeitsmappe laden und prüfen oder anpassen müssen, was bereits vorhanden ist, anstatt was Sie gerade hinzugefüg haben. Images.Count listet jedes Bild auf dem Blatt auf, Images[i] indiziert sie nullbasiert, und FindAt(ARow, ACol) gibt das Bild zurück, das an einer bestimmten Zelle verankert ist – oder nil, wenn keines vorhanden ist. Es gibt auch IndexOfCell für den Index anstelle des Objekts sowie DeleteAt / DeleteInRange zum Entfernen
var
i: Integer;
Img: TXLSXImage;
begin
for i := 0 to Sheet.Images.Count - 1 do
begin
Img := Sheet.Images[i];
Writeln(Format('[%d] R%dC%d %.2f x %.2f cm (%d x %d EMU)',
[i, Img.Row, Img.Col, Img.WidthCM, Img.HeightCM,
Img.WidthEMU, Img.HeightEMU]));
end;
Img := Sheet.Images.FindAt(3, 2); // nil-check before use
if Img <> nil then
Img.Scale(80);
end;
Da die Real-Unit-Eigenschaften Live-Ansichten sind, meldet ein Bild, das mit einer bestimmten EMU-Größe aus einem anderen Werkzeug importiert wurde, seine Geometrie sofort in Zentimetern – ohne Umrechnungsschritt Ihrerseits. Dies passt hervorragend zum breiteren Zeichnungsmodell; wenn Sie Diagramme und Formen sowie Rasterbilder platzieren, behandelt der Begleitfaden über HotXLS-Diagramme, Bilder und Excel-Zeichnungen in Delphi das Anker-Modell, das diese Objekte gemeinsam nutzen
Metrische Seiteneinstellungen-Ränder
Das gleiche Spannungsfeld zwischen EMU und realen Einheiten zeigt sich eine Ebene höher, bei der Seite. OOXML und Excel speichern Druckränder in Zoll (Inches), was unpraktisch ist, wenn Ihre Berichtsvorlagen in Millimetern spezifiziert sind, wie fast überall außerhalb der USA. v2.91.0 fügt Zentimeter-Wrapper über die Zoll-Ränder hinzu: MarginLeftCM, MarginRightCM, MarginTopCM, MarginBottomCM, MarginHeaderCM und MarginFooterCM. Jeder ist eine bequeme Hilfsfunktion über der entsprechenden Zoll-Eigenschaft, die im genauen Verhältnis 1 Zoll = 2,54 cm umrechnet
Sheet.MarginLeftCM := 2.0; // 2 cm == 0.7874 inch
Sheet.MarginRightCM := 2.0;
Sheet.MarginTopCM := 2.5;
Sheet.MarginBottomCM := 2.5;
Sheet.MarginHeaderCM := 1.0;
Sheet.MarginFooterCM := 1.0;
Die Zoll-Eigenschaften (MarginLeft und Verwandte) bleiben der kanonische Speicher, sodass Sie beide mischen können – legen Sie einen oberen Rand in Zentimetern fest und lesen Sie ihn in Zoll aus, oder umgekehrt. Die auf die Festplatte geschriebene Datei ist in beiden Fällen identisch. Die Umrechnung ist eine einfache Multiplikation mit 2,54 ohne Rundung auf ein grobes Raster, sodass 2 cm mit voller Double-Präzision 2 cm bleiben. Dies ist dieselbe Philosophie des metrischen Komforts wie bei der Bildgeometrie: Das Format spricht unter der Haube imperial, und die Bibliothek lässt Sie in der Einheit schreiben, in der Ihre Spezifikation verfasst ist. Informations zum Layouten des umgebenden Berichts – Titel, Metadatenblöcke, Summen – finden Sie unter Verbundene Zellen und Berichtsvorlagen-Layout in HotXLS, das diese Ränder zusammen mit verbundenen Bereichen und einem Druckbereich verwendet
Ein Hinweis darauf, was die Geometrie garantiert und was nicht
Die Geometrieeigenschaften steuern die deklarierte Größe des Bildes in der Datei – die Größe, in der ein konformer Empfänger es rendern wird. Sie führen kein Resampling der Bildbytes durch; ein PNG mit 50×50 Pixeln, das auf 8 cm vergrößert wird, wird hochskaliert und pixelig aussehen, genau wie in Excel. Die Größenänderung ist eine Layout-Operation, keine Bildbearbeitung. Stellen Sie dem Bild also genügend Ausgangsauflösung für die beabsichtigte physische Größe zur Verfügung. Die Bibliothek kodiert auch keine Formate neu: Die Bytes, die Sie an AddImage übergeben, werden so gespeichert und weitergegeben, wie sie sind, mit dem von Ihnen deklarierten TXLSXImageFormat. Wenn Sie JPEG-Bytes übergeben, diese aber als xlsxImagePng kennzeichnen, erzeugen Sie eine Datei, die Excel nicht öffnen kann. Lassen Sie also AddImageFromFile das Format nach Möglichkeit aus der Dateiendung ableiten
Nichts davon ist ungewöhnlich, wenn man die eine zugrunde liegende Idee verinnerlicht hat: In OOXML ist die physische Größe die reale Größe, und Pixel sind nur ein abgeleiteter, DPI-abhängiger Schatten davon. Schreiben Sie Bilder und Ränder in Zentimetern, Zoll oder Punkten, lassen Sie HotXLS diese auf exakte EMU abbilden, und Ihre Rechnungen und Berichte werden auf jedem Rechner, der sie öffnet, in derselben Größe gedruckt
Die hier beschriebenen APIs für Bildgeometrie, Skalierung und metrische Ränder werden mit der HotXLS Delphi spreadsheet component ausgeliefert, die XLS- und XLSX-Dateien in Delphi und C++Builder liest und schreibt, ohne dass eine Excel-Installation erforderlich ist