Vložíte logo o rozměru 600×400 pixelů do záhlaví vygenerované faktury, na vývojovém monitoru s 96 DPI vypadá správně a o týden později zákazník na notebooku s vysokým DPI hlásí, že se při tisku zmenšilo na velikost poštovní známky. Pixely se nezměnily. Změnil se předpoklad, že počet pixelů znamená fyzickou velikost, a v OOXML to tak není. Obrázek v tabulce nese své rozměry v EMU a dokud neuvažujete v EMU nebo v reálných jednotkách, které se na něj přímo mapují, je vaše rozvržení vydáno na milost tomu, jaké DPI si zrovna vykreslovací stroj předpokládá
HotXLS je nativní VCL komponenta pro Delphi a C++Builder pro práci s tabulkami, která čte a zapisuje XLS a XLSX bez Excelu a bez jakékoli závislosti na COM. Od verze v2.91.0 už objekt obrázku v XLSX nenutí počítat jednotky ručně: vedle syrového EMU zpřístupňuje šířku a výšku v centimetrech, palcích i bodech, a Scalemetodu, která mění velikost podle procenta s volitelným uzamčením poměru stran. Tento článek vysvětluje, co EMU vlastně je, proč si ho DrawingML zvolilo, a jak novou geometrickou vrstvu použít k umisťování obrázků podle fyzické velikosti místo podle počtu pixelů, kterému nelze věřit
Co je EMU a proč ho DrawingML používá
EMU znamená English Metric Unit a je základní jednotkou délky DrawingML, kreslicí vrstvy sdílené napříč celou rodinou Office Open XML (ECMA-376, Part 1, §20). Jeden EMU je definován tak, že na palec připadá přesně 914400 EMU na palec a 360000 EMU na centimetr. Právě to je celý důvod, proč tato jednotka existuje. 914400 je dělitelné 2, 3, 4, 5, 6, 8, 9, 10, 12 a mnoha dalšími čísly; rozkládá se na 26 × 32 × 52 × 127. Protože 1 palec = 2,54 cm přesně, volba jednotky dělitelné 360000 i čistým zlomkem 914400 umožňuje formátu vyjadřovat palce, centimetry a body jako celá čísla bez zaokrouhlování na hranici jednotky. Tam, kde by desetinné „1,27 cm“ plavalo, EMU uloží 457200 a zůstane přesné
Další jednotka, na které zde záleží, je bod. Typografický bod je 1/72 palce, takže existuje 12700 EMU na bod (914400 / 72). Body jsou to, jak Excel pod kapotou uvažuje o výškách řádků, velikostech písma a okrajích, a proto je zpřístupnění geometrie obrázku v bodech užitečné, když chcete, aby se obrázek zarovnal k metrikám textu, ne k tištěnému pravítku. HotXLS v knihovně kóduje všechny čtyři vztahy jako konstanty jednotek:
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)
Právě ten poslední řádek je jádrem chyby s poštovní známkou. Pixel má fyzickou velikost až ve chvíli, kdy určíte DPI, a 9525 EMU je velikost pixelu při přesně 96 DPI. Výchozí vykreslovací DPI v Excelu je 96, takže obrázek o 100 pixelech skončí na 100 × 9525 = 952500 EMU ≈ 2,54 cm ve výchozím nastavení, ale nic v souboru nezaručuje, že příjemce používá 96. Pište v reálných jednotkách a tato nejasnost zmizí: 4 cm je 4 cm, ať má obrazovka 96 nebo 220 DPI
Plocha geometrie TXLSXImage
V HotXLS je vložený obrázek TXLSXImage. Jeho kanonické uložení tvoří dvě celočíselná pole, WidthEMU a HeightEMU, ukotvená na jedné bázi Row a Col (horní levá buňka, ze které obrázek visí). Vlastnosti v reálných jednotkách jsou vypočtené pohledy nad těmito poli EMU, nikoli samostatný stav - čtení WidthCM dělí EMU 360000, a při zápisu jej násobí a zaokrouhluje zpět. Každý rozměr, který nastavíte, je tedy jen jiný zápis téže podkladové hodnoty EMU:
WidthInch/HeightInch- EMU ÷ 914400WidthCM/HeightCM- EMU ÷ 360000WidthPt/HeightPt- EMU ÷ 12700WidthEMU/HeightEMU- celočíselný zdroj pravdy
Obrázek přidáte pomocí AddImage(ARow, ACol, AData, AFormat), předáním surových zakódovaných bajtů a TXLSXImageFormat (xlsxImagePng, xlsxImageJpeg, xlsxImageGif, nebo xlsxImageBmp); vrátí index od nuly index do kolekce listuImages kolekce. K dispozici je také AddImageFromFile(ARow, ACol, AFileName), která odvozuje formát z přípony souboru. Všimněte si základny indexu: AddImage vrací index od nuly a Images[] je indexovaná od nuly, což je záměrný protiklad k jedničkově indexované Cells[Row, Col] mřížce, takže nepředpokládejte, že se oba způsoby shodují
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;
Nově vytvořený obrázek má ve výchozím stavu 100×100 pixelů, tedy čtverec 952500 EMU, zhruba rámeček o velikosti 2,54 cm při 96 DPI. Tato výchozí hodnota zajišťuje, že bude obrázek viditelný i tehdy, když mu zapomenete nastavit velikost, ale pro jakékoli skutečné rozvržení byste měli nastavit výslovnou fyzickou velikost a nespoléhat na výchozí hodnotu odvozenou z pixelů
Škálování a příznak zachování poměru stran
Když chcete měnit velikost podle aktuálních rozměrů místo podle absolutního cíle - třeba zmenšit obrázek grafu na 60 % toho, s čím byl importován - použijte Scale:
procedure Scale(APercent: Double; AKeepAspect: Boolean = True);
APercent je procento, kde 100 znamená beze změny, 150 zvětší o polovinu a 50 zmenší na polovinu. S AKeepAspect při výchozí hodnotě True, se šířka i výška násobí stejným faktorem, takže proporce zůstanou zachované a obrázek 4×3 cm se po Scale(150).False a škáluje se pouze šířka - výška zůstane přesně tak, jak byla. Tato asymetrie je záměrná: když chcete nezávisle natáhnout jednu osu, správným nástrojem jsou výslovné WidthCM/HeightCM nastavovací metody a větev bez zachování poměru stran u Scale slouží pro užší případ úpravy pouze šířky. Snadno lze číst Scale(150, False) jako "volně natáhnout obě osy" a být překvapeni, proto sáhněte po nastavovacích metodách, když skutečně myslíte dvě nezávislé dimenze
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
Jeden drobný detail chování: Scale(100) se předčasně ukončí a vrátí bez zásahu do kteréhokoli pole, takže jej lze bezpečně volat bez podmínky v cyklu, kde může být procento 100. A protože geometrie je uložena jako integer EMU, každá nastavovací metoda zaokrouhluje. Při obousměrném převodu přes zlomkové centimetry tak může dojít k odchylce o zlomek EMU - hluboko pod jakoukoli viditelnou hranicí, ale je dobré o tom vědět, pokud v testu porovnáváte přesnou shodu. Pro pixelově přesné řízení nastavte WidthEMU a HeightEMU přímo a převod jednotek úplně vynechejte
Zpětné čtení geometrie
Kolekci obrázků lze dotazovat, což je důležité, když načtete existující sešit a potřebujete zkontrolovat nebo upravit to, co už v něm je, místo toho, co jste právě přidali. Images.Count vypisuje každý obrázek na listu, Images[i] je čísluje od nuly a FindAt(ARow, ACol) vrací obrázek ukotvený ke konkrétní buňce, nebo nil pokud žádná není. Existuje také IndexOfCell pro index místo objektu a DeleteAt / DeleteInRange pro odstranění
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;
Protože vlastnosti s reálnými jednotkami jsou živé pohledy, obrázek importovaný z jiného nástroje v nějaké velikosti EMU okamžitě hlásí svou geometrii v centimetrech, bez jakéhokoli převodu z vaší strany. To přirozeně zapadá do širšího kreslicího modelu; pokud vedle rastrových obrázků umisťujete i grafy a tvary, doprovodný průvodce k Grafy, obrázky a kresby Excelu v Delphi s HotXLS popisuje model ukotvení, který tyto objekty sdílejí
Metrické okraje nastavení stránky
Stejné napětí mezi EMU a skutečnými jednotkami se objevuje o úroveň výš, na úrovni stránky. OOXML a Excel ukládají tiskové okraje v palcích, což je nepraktické, pokud jsou vaše šablony sestav zadané v milimetrech jako ve většině světa mimo USA. Verze v2.91.0 přidává nad palcové okraje obálky v centimetrech: MarginLeftCM, MarginRightCM, MarginTopCM, MarginBottomCM, MarginHeaderCM a MarginFooterCM. Každá z nich je jen tenká pohodlná vrstva nad odpovídající palcovou vlastností a převádí přes přesný poměr 1 palec = 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;
Palcové vlastnosti (MarginLeft a další) zůstávají kanonickým úložištěm, takže oba přístupy můžete kombinovat - nastavit horní okraj v centimetrech a přečíst ho zpět v palcích, nebo naopak - a soubor zapsaný na disk bude v obou případech identický. Převod je prosté násobení 2,54, bez zaokrouhlování na hrubou mřížku, takže 2 cm zůstane při plné dvojité přesnosti 2 cm. Je to stejná filozofie metrického pohodlí jako u geometrie obrázku: formát pod kapotou mluví imperiálně a knihovna vám dovolí vytvářet obsah v libovolné jednotce, v jaké je vaše specifikace napsaná. Pro rozvržení okolní sestavy, tedy nadpisů, bloků metadat a součtů, se podívejte na sloučené buňky a rozvržení šablony sestavy v HotXLS, který tyto okraje používá spolu se sloučenými oblastmi a tiskovou oblastí
Poznámka k tomu, co geometrie zaručuje a nezaručuje
Vlastnosti geometrie řídí deklarovanou velikost obrázku v souboru, tedy velikost, v níž jej vykreslí kompatibilní spotřebitel. Nesamplují znovu bajty obrázku; PNG o velikosti 50×50 pixelů nastavené na 8 cm se zvětší a bude působit hranatě, přesně jako v Excelu. Určování velikosti je operace rozvržení, ne zpracování obrazu, takže obrázku dejte dostatečné zdrojové rozlišení pro fyzickou velikost, kterou zamýšlíte. Knihovna také nepřekóduje formáty: bajty, které předáte do AddImage se ukládají a zapisují beze změny, společně s TXLSXImageFormat formátem, který zadáte. Předáte-li bajty JPEG, ale označíte je jako xlsxImagePng a vytvoříte soubor, který Excel neotevře, takže nechte AddImageFromFile odvozovat formát z přípony, kdykoli to jde
Nic z toho není exotické, jakmile si osvojíte jednu myšlenku pod tím vším: v OOXML je fyzická velikost skutečná veličina a pixely jsou její odvozený stín závislý na DPI. Vytvářejte obrázky a okraje v centimetrech, palcích nebo bodech, nechte HotXLS je namapovat na přesné EMU a vaše faktury i sestavy se budou tisknout ve stejné velikosti na každém počítači, který je otevře
API geometrie obrázků, škálování a metrických okrajů popsaná zde jsou součástí komponenty tabulkového procesoru HotXLS pro Delphi, která čte a zapisuje XLS a XLSX z Delphi a C++Builderu bez nutnosti instalace Excelu