Do hlavičky generovanej faktúry vložíte logo 600×400 pixelov, na vašom 96-DPI vývojovom monitore vyzerá správne a o týždeň zákazník na notebooku s vysokým DPI hlási, že sa vytlačí vo veľkosti poštovej známky. Pixely sa nezmenili. Zmenil sa predpoklad, že počet pixelov znamená fyzickú veľkosť, a v OOXML to neplatí. Obrázok v tabuľke nesie svoje rozmery v EMU a kým nezačnete uvažovať v EMU, alebo v reálnych jednotkách, ktoré sa naň mapujú bez zvyšku, vaše rozloženie závisí od DPI, ktoré si vykresľujúci stroj práve zvolí
HotXLS je natívny komponent VCL pre Delphi a C++Builder, ktorý číta a zapisuje XLS a XLSX bez Excelu a bez závislosti na COM. Od verzie v2.91.0 vás objekt obrázka v XLSX už nenúti robiť jednotkovú aritmetiku ručne: popri surovom EMU sprístupňuje šírku a výšku v centimetroch, palcoch a bodoch, plus metódu Scale, ktorá mení veľkosť percentom s voliteľným zachovaním pomeru strán. Tento článok vysvetľuje, čo EMU naozaj je, prečo si ho DrawingML zvolilo a ako nový geometrický povrch používať tak, aby ste obrázky umiestňovali podľa fyzickej veľkosti, nie podľa počtu pixelov, ktorému sa nedá veriť
Čo je EMU a prečo ho DrawingML používa
EMU znamená English Metric Unit a je základnou dĺžkovou jednotkou DrawingML, kresliacej vrstvy zdieľanej naprieč celou rodinou Office Open XML. Jedno EMU je definované tak, aby existovalo presne 914400 EMU per inch and 360000 EMU per centimetre. Those two constants are the entire reason the unit exists. 914400 is divisible by 2, 3, 4, 5, 6, 8, 9, 10, 12, and many more; it factors as 26 × 32 × 52. Tieto dve konštanty sú celý dôvod existencie tejto jednotky. Keďže 1 inch = 2.54 cm presne, jednotka deliteľná hodnotami 360000 aj 914400 umožňuje formátu vyjadriť palce, centimetre aj body ako celé čísla bez zaokrúhľovania na hranici jednotiek. Kde by hodnota ako „1.27 cm“ vo floating pointe driftovala, EMU uloží 457200 a zostane presné
Ďalšia dôležitá jednotka je bod. Typografický bod je 1/72 palca, takže existuje 12700 EMU per point (914400 / 72). Excel práve v bodoch interne uvažuje o výškach riadkov, veľkostiach písma a okrajoch, preto je geometria obrázka v bodoch užitočná vtedy, keď chcete obrázok zarovnať na textové metriky, nie na tlačené pravítko. HotXLS kóduje všetky štyri vzťahy ako konštanty jednotiek v knižnici:
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)
Posledný riadok je jadrom chyby s poštovou známkou. Pixel má fyzickú veľkosť až vtedy, keď zafixujete DPI, a 9525 EMU je veľkosť jedného pixelu at 96 DPI specifically. Predvolené vykresľovacie DPI Excelu je 96, takže 100-pixelový obrázok skončí ako 100 × 9525 = 952500 EMU, približne 2.54 cm v štandardnom nastavení, ale nič v súbore negarantuje, že konzument použije práve 96. Tvorte v reálnych jednotkách a táto nejednoznačnosť zmizne: 4 cm sú 4 cm, či má obrazovka 96 alebo 220 DPI
Geometrický povrch TXLSXImage
Vložený obrázok v HotXLS je TXLSXImage. Its canonical storage is two integer fields, WidthEMU and HeightEMU, anchored at a one-based Row and Col. Vlastnosti v reálnych jednotkách sú len odvodené pohľady na tieto polia EMU, nie samostatný stav. Pri čítaní WidthCM sa EMU delí hodnotou 360000 a pri zápise sa násobí a zaokrúhli späť. Každý rozmer, ktorý nastavíte, je preto len iný zápis tej istej podkladovej hodnoty EMU:
WidthInch/HeightInch— EMU ÷ 914400WidthCM/HeightCM— EMU ÷ 360000WidthPt/HeightPt— EMU ÷ 12700WidthEMU/HeightEMUceločíselný zdroj pravdy
Obrázok pridáte cez AddImage(ARow, ACol, AData, AFormat), passing the raw encoded bytes and a TXLSXImageFormat (xlsxImagePng, xlsxImageJpeg, xlsxImageGif, or xlsxImageBmp; výsledkom je zero-based kolekcie worksheetu. K dispozícii je aj Images collection. There is also AddImageFromFile(ARow, ACol, AFileName), ktorý odvodí formát z prípony súboru. Pozor na bázu indexu: AddImage returns zero-based and Images[] is zero-based, which is a deliberate contrast with the one-based Cells[Row, Col] grid, so do not assume the two agree
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;
. Novo vytvorený obrázok má predvolene 100×100 pixelov, teda štvorec 952500 EMU, približne 2.54 cm pri 96 DPI. Toto predvolené nastavenie existuje preto, aby bol obrázok viditeľný aj vtedy, keď zabudnete nastaviť veľkosť, ale pre akékoľvek reálne rozloženie by ste mali nastaviť explicitnú fyzickú veľkosť, nie sa spoliehať na hodnotu odvodenú od pixelov
Zmena mierky a príznak pomeru strán
Keď chcete meniť veľkosť relatívne k aktuálnym rozmerom namiesto absolútneho cieľa, napríklad zmenšiť obrázok grafu na 60 % toho, v čom bol importovaný, použite Scale:
procedure Scale(APercent: Double; AKeepAspect: Boolean = True);
APercent je percento, kde 100 znamená bez zmeny, 150 zväčšenie o polovicu a 50 zmenšenie na polovicu. Pri AKeepAspect at its default True, both width and height multiply by the same factor, so the proportions hold and a 4×3 cm image becomes 6×4.5 cm after Scale(150) sa šírka aj výška násobia rovnakým faktorom, takže proporcie zostanú zachované. Ak odovzdáte False and only the width scales—the height is left exactly as it was. That asymmetry is intentional: when you want to stretch one axis independently, the right tool is the explicit WidthCM<, zmení sa len šírka a výška zostane presne taká, aká bola. Táto asymetria je zámerná: keď chcete jeden rozmer natiahnuť nezávisle od druhého, správnym nástrojom sú explicitné settery code>/HeightCM setters, and the non-aspect branch of Scale, a neproporcionálna vetva Scale(150, False) slúži len na užší prípad úpravy samotnej šírky. Pri skutočne nezávislých rozmeroch siahnite po setteroch
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
Jedno drobné správanie sa oplatí poznať: Scale(100) short-circuits and returns without touching either field, so it is safe to call unconditionally in a loop where the percentage might be 100. And because the geometry is stored as integer sa skratovo vráti bez zmeny oboch polí, takže ho môžete bezpečne volať aj bez podmienky. Keďže geometria je uložená ako WidthEMUceločíselnéHeightEMU EMU, každý setter zaokrúhľuje. Pri round-tripe cez zlomkové centimetre preto môže vzniknúť odchýlka o zlomok EMU. Ak potrebujete pixelovo presnú kontrolu, nastavte
Čítanie geometrie späť
priamo a konverziu jednotiek vynechajte.Images.CountČítanie geometrie späťImages[i] indexes them zero-based, and FindAt(ARow, ACol) returns the image anchored at a specific cell—or nil if none is. There is also IndexOfCell for the index rather than the object, and DeleteAt / DeleteInRange na odstránenie
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;
Keďže vlastnosti v reálnych jednotkách sú živé pohľady, obrázok importovaný z iného nástroja s určitým rozmerom EMU okamžite hlási svoju geometriu v centimetroch, bez ďalšieho prevodu z vašej strany. Ak okrem rastrových obrázkov umiestňujete aj grafy a tvary, sprievodný článok HotXLS charts, images, and Excel drawings in Delphi opisuje model ukotvenia, ktorý tieto objekty zdieľajú
Okraje nastavenia strany v metrických jednotkách
To isté napätie medzi EMU a reálnymi jednotkami sa objaví o úroveň vyššie, na stránke. OOXML a Excel ukladajú tlačové okraje v palcoch, čo je nepraktické, ak sú vaše šablóny reportov definované v milimetroch. Verzia v2.91.0 pridáva centimetrové obaly nad palcovými okrajmi: MarginLeftCM, MarginRightCM, MarginTopCM, MarginBottomCM, MarginHeaderCM, and MarginFooterCM. Každý z nich je tenká pohodlná vrstva nad zodpovedajúcou vlastnosťou v palcoch s presným prevodom 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;
Vlastnosti v palcoch (MarginLeft a podobné) zostávajú kanonickým uložením, takže ich môžete miešať. Je to tá istá filozofia metrického pohodlia ako pri geometrii obrázka: formát pod kapotou hovorí imperiálne a knižnica vám dovolí tvoriť v jednotkách, v ktorých je napísaná vaša špecifikácia. Ak rozkladáte zvyšok reportu, pozrite si merged cells and report template layout in HotXLS
Poznámka k tomu, čo geometria garantuje a čo nie
Vlastnosti geometrie riadia deklarovanú veľkosť obrázka v súbore, teda veľkosť, pri ktorej ho kompatibilný konzument vykreslí. Nevzorkujú znovu bajty obrázka. PNG s rozlíšením 50×50 pixelov nastavený na 8 cm sa zväčší a bude kockatý presne tak, ako v Exceli. Knižnica tiež formáty znova nekóduje: bajty, ktoré odovzdáte do AddImage, sa uložia a zapíšu tak, ako sú. Ak odovzdáte JPEG bajty, ale označíte ich ako TXLSXImageFormat you declare. Pass JPEG bytes but tag them xlsxImagePng, vytvoríte súbor, ktorý Excel neotvorí, preto keď môžete, nechajte AddImageFromFile odvodiť formát z prípony
V OOXML je skutočnou veličinou fyzická veľkosť a pixely sú len odvodený tieň závislý od DPI. Tvorte obrázky aj okraje v centimetroch, palcoch alebo bodoch, nechajte HotXLS namapovať ich na presné EMU a vaše faktúry aj reporty sa budú tlačiť rovnako veľké na každom stroji, ktorý ich otvorí
API pre geometriu obrázkov, zmenu mierky a metrické okraje opísané tu sú súčasťou HotXLS Delphi spreadsheet component, ktorá číta a zapisuje XLS a XLSX z Delphi a C++Buildera bez potreby inštalácie Excelu