Teknisk artikel

HotXLS-bildgeometri i Delphi: EMU, cm och skalning

Du släpper in en 600×400-pixelslogotyp i sidhuvudet på en genererad faktura, den ser rätt ut på din 96-DPI-utvecklingsskärm, och en vecka senare rapporterar en kund på en bärbar dator med hög DPI att den skrivs ut i frimärksstorlek. Pixelvärdena ändrades aldrig. Det som ändrades var antagandet att ett pixelantal betyder en fysisk storlek, och i OOXML gör det inte det. En kalkylbladsbild bär sina mått i EMU, och tills du tänker i EMU, eller i de verkliga enheter som mappar rent mot dem, ligger layouten i händerna på den DPI som renderingsmaskinen råkar anta

HotXLS är en inbyggd VCL-kalkylbladskomponent för Delphi och C++Builder som läser och skriver XLS och XLSX utan Excel eller något COM-beroende. Från och med v2.91.0 slutar XLSX-bildobjektet att tvinga dig att räkna enheter för hand: vid sidan av rå EMU visar det bredd och höjd i centimeter, tum och punkter, plus en Scale metod som ändrar storleken med en procentandel och valfri låsning av aspektförhållandet. Den här artikeln handlar om vad EMU faktiskt är, varför DrawingML valde det och hur du använder den nya geometrivyn för att placera bilder efter fysisk storlek i stället för efter ett pixelantal du inte kan lita på

Vad en EMU är och varför DrawingML använder den

EMU står för English Metric Unit och är DrawingML:s basenhet för längd, den ritningsnivå som delas av hela Office Open XML-familjen (ECMA-376, Part 1, §20). En EMU är definierad så att det finns exakt 914400 EMU per tum och 360000 EMU per centimeter. Dessa två konstanter är hela anledningen till att enheten finns. 914400 är delbart med 2, 3, 4, 5, 6, 8, 9, 10, 12 och många fler; det faktoriseras som 26 × 32 × 52 × 127. Eftersom 1 inch = 2.54 cm exakt, gör valet av en enhet som är delbar både med 360000 och med en ren faktor av 914400 att formatet kan uttrycka tum, centimeter och punkter som heltal utan avrundning vid enhetsgränsen. Där ett flyttal som "1.27 cm" skulle driva iväg, lagrar EMU 457200 och förblir exakt

Den andra enheten som spelar roll här är punkten. En typografisk punkt är 1/72 tum, så det finns 12700 EMU per punkt (914400 / 72). Punkter är hur Excel själv tänker om radhöjder, teckenstorlekar och marginaler under huven, vilket är varför det är användbart att exponera bildgeometri i punkter när du vill att en bild ska linjera med textmått snarare än med en utskriven linjal. HotXLS kodar alla fyra relationerna som enhetskonstanter i biblioteket:

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)

Den sista raden är kärnan i frimärksbuggen. En pixel får bara en fysisk storlek när du låser en DPI, och 9525 EMU är storleken på en pixel vid just 96 DPI. Excels standard-DPI för rendering är 96, så en bild på 100 pixlar hamnar på 100 × 9525 = 952500 EMU ≈ 2.54 cm i en standardmiljö, men inget i filen garanterar att mottagaren använder 96. Författar du i verkliga enheter försvinner den tvetydigheten: 4 cm är 4 cm oavsett om skärmen har 96 eller 220 DPI

TXLSXImage-geometrivyn

En inbäddad bild i HotXLS är en TXLSXImage. Dess kanoniska lagring består av två heltalsfält, WidthEMU och HeightEMU, förankrade vid en baserad på ett Row och Col (som förankrar bilden i cellen uppe till vänster). Egenskaperna för verkliga enheter är beräknade vyer ovanpå dessa EMU-fält, inte separat tillstånd - läser du WidthCM divideras EMU med 360000, och när du skriver tillbaka multipliceras värdet och avrundas tillbaka. Så varje dimension du sätter är bara en annan benämning för samma underliggande EMU-värde:

  • WidthInch / HeightInch - EMU ÷ 914400
  • WidthCM / HeightCM - EMU ÷ 360000
  • WidthPt / HeightPt - EMU ÷ 12700
  • WidthEMU / HeightEMU - den heltalsbaserade sanningen

Du lägger till en bild med AddImage(ARow, ACol, AData, AFormat), och skickar de rå kodade bytena samt en TXLSXImageFormat (xlsxImagePng, xlsxImageJpeg, xlsxImageGif, eller xlsxImageBmp); den returnerar den nollbaserade index i arbetsbladets Images samling. Det finns också AddImageFromFile(ARow, ACol, AFileName), som härleder formatet från filändelsen. Notera indexbasen: AddImage returnerar nollbaserat och Images[] är nollbaserat, vilket är en avsiktlig kontrast mot det Cells[Row, Col] rutnätet med bas ett, så anta inte att de två överensstämmer

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;

En nyss skapad bild har som standard 100×100 pixlar, alltså en kvadrat på 952500 EMU, ungefär en ruta på 2.54 cm vid 96 DPI. Standardvärdet finns så att bilden syns även om du glömmer att ange storlek, men för all riktig layout bör du sätta en uttrycklig fysisk storlek i stället för att lita på det pixelhärledda standardvärdet

Skalning och flaggan för aspektförhållande

När du vill ändra storlek relativt till de nuvarande måtten i stället för till ett absolut mål, säg att du vill krympa en diagrambild till 60% av det den importerades med, använd Scale:

procedure Scale(APercent: Double; AKeepAspect: Boolean = True);

APercent är en procentandel där 100 betyder oförändrat, 150 ökar med hälften och 50 halverar. Med AKeepAspect som standardvärde True, multipliceras både bredd och höjd med samma faktor, så proportionerna bevaras och en bild på 4×3 cm blir 6×4.5 cm efter Scale(150). Ange False och bara bredden skalas - höjden lämnas exakt som den var. Den asymmetrin är avsiktlig: när du vill sträcka en axel oberoende är rätt verktyg de uttryckliga WidthCM/HeightCM inställningarna, och den aspektfria grenen av Scale finns där för det smalare fallet att justera bredden ensam. Det är lätt att läsa Scale(150, False) som "sträck båda fritt" och bli överraskad, så ta till inställningarna när du verkligen menar två oberoende dimensioner

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

En liten detalj att känna till: Scale(100) avbryter tidigt och returnerar utan att röra något av fälten, så det är säkert att anropa den ovillkorligt i en loop där procenten kan vara 100. Och eftersom geometrin lagras som integer EMU, avrundar varje setter. När du går fram och tillbaka genom bråkdelar av centimeter kan det därför driva med en bråkdel av en EMU - långt under det som är synligt, men värt att känna till om du någon gång verifierar exakt likhet i ett test. För pixelperfekt kontroll, sätt WidthEMU och HeightEMU direkt och hoppa över enhetskonverteringen helt

Läsa tillbaka geometrin

Bildsamlingen går att fråga mot, vilket spelar roll när du laddar en befintlig arbetsbok och behöver inspektera eller justera det som redan finns där i stället för det du just lade till. Images.Count räknar upp varje bild på bladet, Images[i] indexerar dem nollbaserat, och FindAt(ARow, ACol) returnerar bilden som är förankrad i en viss cell, eller nil om ingen är det. Det finns också IndexOfCell för indexet i stället för objektet, och DeleteAt / DeleteInRange för borttagning

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;

Eftersom egenskaperna för verkliga enheter är levande vyer rapporterar en bild som importerats i en viss EMU-storlek från ett annat verktyg sin geometri i centimeter direkt - inget konverteringssteg behövs från din sida. Det passar naturligt ihop med den bredare ritmodellen; om du placerar diagram och former såväl som rasterbilder täcker den kompletterande guiden om HotXLS-diagram, bilder och Excel-ritningar i Delphi ankarmodellen som dessa objekt delar

Metriska marginaler för sidinställningar

Samma spänning mellan EMU och verkliga enheter dyker upp ett steg ut, på sidnivå. OOXML och Excel lagrar utskriftsmarginaler i tum, vilket är besvärligt om dina rapportmallar är angivna i millimeter som i större delen av världen utanför USA. v2.91.0 lägger till centimeteromslag runt tum-marginalerna: MarginLeftCM, MarginRightCM, MarginTopCM, MarginBottomCM, MarginHeaderCM, och MarginFooterCM. Var och en är en tunn bekvämlighet ovanpå motsvarande tum-egenskap och konverterar med det exakta förhållandet 1 inch = 2.54 cm

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;

Tum-egenskaperna (MarginLeft och liknande) förblir den kanoniska lagringen, så du kan blanda de två - sätt en toppmarginal i centimeter och läs tillbaka den i tum, eller tvärtom - och filen som skrivs till disk är identisk oavsett väg. Konverteringen är en enkel multiplikation med 2.54, ingen avrundning till ett grovt rutnät, så 2 cm förblir 2 cm med full dubbel precision. Det här är samma filosofi för metrisk bekvämlighet som i bildgeometrin: formatet talar imperialt under huven, och biblioteket låter dig skriva i den enhet som din specifikation är skriven i. För att lägga ut den omgivande rapporten - rubriker, metadatablock, totaler - se sammanfogade celler och rapportmallslayout i HotXLS, som använder dessa marginaler tillsammans med sammanfogade områden och ett utskriftsområde

En not om vad geometrin garanterar och inte garanterar

Geometriegenskaperna styr den angivna storleken på bilden i filen - den storlek en kompatibel läsare renderar den i. De samplas inte om; en PNG på 50×50 pixlar som anges till 8 cm kommer att skalas upp och se blockig ut, precis som i Excel. Storleksanpassning är en layoutåtgärd, inte bildbehandling, så ge bilden tillräcklig källupplösning för den fysiska storlek du tänker dig. Biblioteket kodar inte om format heller: bytena du skickar till AddImage lagras och skrivs igenom som de är, med den TXLSXImageFormat du anger. Skickar du JPEG-byten men märker dem xlsxImagePng och du kommer att skapa en fil som Excel inte kan öppna, så låt AddImageFromFile härleda formatet från filändelsen när du kan

Ange bilder och marginaler i centimeter, tum eller punkter, låt HotXLS mappa dem till exakta EMU, och dina fakturor och rapporter skrivs ut i samma storlek på varje maskin som öppnar dem

Bildgeometri-, skalnings- och metriska marginal-API:erna som beskrivs här levereras med HotXLS Delphi spreadsheet component, som läser och skriver XLS och XLSX från Delphi och C++Builder utan att Excel behöver vara installerat