Tehnični članak

Geometrija slik HotXLS v Delphi: EMU, cm in skaliranje

V glavo ustvarjenega računa spustite logotip velikosti 600×400 pik, na svojem 96-DPI razvojnem monitorju je videti pravilno, teden dni pozneje pa stranka na prenosniku z visoko gostoto pik sporoči, da se natisne v velikosti poštne znamke. Piksli se niso nikoli spremenili. Spremenila se je predpostavka, da število pik pomeni fizično velikost, v OOXML pa to ne drži. Slika v preglednici nosi svoje mere v EMU in dokler ne razmišljate v EMU ali v resničnih enotah, ki se vanj čisto preslikajo, je vaša postavitev prepuščena temu, kakšen DPI domneva stroj za izris

HotXLS je izvorna komponenta VCL za preglednice za Delphi in C++Builder, ki bere in zapisuje XLS in XLSX brez Excela ali odvisnosti od COM. Od v2.91.0 dalje vas objekt slike XLSX ne sili več v ročno računanje enot: ob surovih vrednostih EMU izpostavi širino in višino v centimetrih, palcih in točkah ter metodo Scale, ki spreminja velikost po odstotkih z neobveznim zaklepom razmerja stranic. Ta članek pojasni, kaj EMU sploh je, zakaj ga je DrawingML izbral in kako novo površino geometrije uporabiti za umeščanje slik po fizični velikosti namesto po številu pik, ki mu ne morete zaupati

Kaj je EMU in zakaj ga uporablja DrawingML

EMU pomeni English Metric Unit in je osnovna dolžinska enota DrawingML, risalne plasti, ki si jo deli celotna družina Office Open XML (ECMA-376, Part 1, §20). En EMU je definiran tako, da je natanko 914400 EMU na palec in 360000 EMU na centimeter. Ti dve konstanti sta celoten razlog, da enota obstaja. Število 914400 je deljivo z 2, 3, 4, 5, 6, 8, 9, 10, 12 in še mnogimi drugimi; razcepi se na 26 × 32 × 52 × 127. Ker je 1 palec natančno 2,54 cm, format z izbiro enote, ki je deljiva z 360000 in s čistim ulomkom 914400, lahko palce, centimetre in točke izraža kot cela števila brez zaokroževanja na meji enote. Kjer bi plavajoča vrednost 1,27 cm začela driftati, EMU shrani 457200 in ostane točen

Druga pomembna enota tukaj je točka. Tipografska točka je 1/72 palca, zato je 12700 EMU na točko (914400 / 72). Točke so enota, v kateri Excel pod pokrovom razmišlja o višinah vrstic, velikostih pisav in robovih, zato je izpostavljanje geometrije slik v točkah uporabno takrat, ko želite sliko poravnati z merami besedila namesto z natisnjenim ravnilom. HotXLS vse štiri zveze zakodira kot konstante enot v knjiž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)

Zadnja vrstica je bistvo hrošča s poštno znamko. Piksel ima fizično velikost šele, ko določite DPI, in 9525 EMU je velikost piksla natančno pri 96 DPI. Privzeti DPI za izris v Excelu je 96, zato slika s 100 pikami pristane pri 100 × 9525 = 952500 EMU ≈ 2,54 cm v privzeti nastavitvi, vendar v datoteki nič ne zagotavlja, da bo porabnik uporabljal 96. Če avtorite v resničnih enotah, ta dvoumnost izgine: 4 cm so 4 cm, ne glede na to, ali ima zaslon 96 ali 220 DPI

Površina geometrije TXLSXImage

Vdelana slika v HotXLS je TXLSXImage. Njeno kanonično shranjevanje sta dve celoštevilski polji, WidthEMU in HeightEMU, sidrani na enoindeksnem Row in Col (zgornja leva celica, s katere slika visi). Lastnosti v realnih enotah so izračunani pogledi čez polja EMU, ne ločeno stanje. Branje WidthCM deli EMU z 360000, zapis pa vrednost pomnoži in zaokroži nazaj. Vsaka dimenzija, ki jo nastavite, je torej le drugačen zapis iste osnovne vrednosti EMU:

  • WidthInch / HeightInch - EMU ÷ 914400
  • WidthCM / HeightCM - EMU ÷ 360000
  • WidthPt / HeightPt - EMU ÷ 12700
  • WidthEMU / HeightEMU - celoštevilski vir resnice

Sliko dodate z AddImage(ARow, ACol, AData, AFormat), pri čemer podate surove kodirane bajte in TXLSXImageFormat (xlsxImagePng, xlsxImageJpeg, xlsxImageGif ali xlsxImageBmp); vrne ničelno indeksiran indeks v zbirki Images delovnega lista. Obstaja tudi AddImageFromFile(ARow, ACol, AFileName), ki obliko sklepa iz končnice datoteke. Upoštevajte osnovo indeksov: AddImage vrne ničelno indeksirano vrednost in Images[] je ničelno indeksiran, kar je namenoma v nasprotju z enoindeksno mrežo Cells[Row, Col], zato ne predpostavljajte, da se ujemata

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;

Na novo ustvarjena slika privzeto meri 100×100 pik, to je kvadrat 952500 EMU oziroma približno 2,54 cm pri 96 DPI. Ta privzetost obstaja, da je slika vidna tudi, če jo pozabite določiti po velikosti, toda za vsako resno postavitev morate nastaviti izrecno fizično velikost, namesto da se zanašate na privzeto vrednost, izpeljano iz pik

Skaliranje in zastavica razmerja stranic

Kadar želite spreminjati velikost glede na trenutne mere namesto na absolutni cilj, na primer skrčiti sliko grafikona na 60 % velikosti, s katero je bila uvožena, uporabite Scale:

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

APercent je odstotek, kjer 100 pomeni brez spremembe, 150 pomeni povečanje za polovico, 50 pa razpolovi. Če je AKeepAspect pri privzeti vrednosti True, se širina in višina pomnožita z istim faktorjem, zato razmerja ostanejo enaka in slika 4×3 cm po Scale(150) postane 6×4,5 cm. Če podate False, se spremeni samo širina, višina pa ostane natanko takšna, kot je bila. Ta asimetrija je namerna: kadar želite neodvisno raztegniti eno os, je pravo orodje izrecna nastavitev WidthCM/HeightCM, veja brez ohranjanja razmerja v Scale pa je tam za ožji primer prilagajanja samo širine. Scale(150, False) je lahko hitro razumeti kot "prosto raztegni oboje", zato, kadar res mislite dve neodvisni dimenziji, raje posezite po setterjih

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

Majhna vedenjska podrobnost, ki jo je dobro poznati: Scale(100) naredi short-circuit in se vrne brez spremembe katerega koli polja, zato ga lahko varno kličete brez pogoja v zanki, kjer je odstotek morda 100. Ker je geometrija shranjena kot celoštevilski EMU, vsak setter tudi zaokroži. Round-trip skozi delne centimetre lahko zato odtava za delček EMU, kar je daleč pod mejo vidnega, vendar je dobro vedeti, če v testu kdaj preverjate strogo enakost. Če želite popoln nadzor po pikah, nastavite WidthEMU in HeightEMU neposredno ter povsem preskočite pretvorbo enot

Branje geometrije nazaj

Zbirka slik je poizvedljiva, kar je pomembno, ko naložite obstoječi delovni zvezek in morate pregledati ali prilagoditi to, kar je že v njem, ne pa tega, kar ste pravkar dodali. Images.Count našteje vse slike na listu, Images[i] jih indeksira z ničlo in FindAt(ARow, ACol) vrne sliko, zasidrano na določeno celico, oziroma nil, če je tam ni. Obstajata tudi IndexOfCell za indeks namesto objekta ter DeleteAt / DeleteInRange za odstranjevanje

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;

Ker so lastnosti v realnih enotah živi pogledi, slika, uvožena iz drugega orodja z neko velikostjo EMU, svojo geometrijo takoj poroča v centimetrih. Z vaše strani ni potreben noben pretvorbeni korak. To se naravno ujame s širšim modelom risanja; če poleg rastrskih slik umeščate tudi grafikone in oblike, spremljevalni vodnik o grafikonih, slikah in risbah Excela v Delphi z HotXLS pokriva model sidranja, ki si ga ti objekti delijo

Metrični robovi nastavitev strani

Ista napetost med EMU in realnimi enotami se pokaže še eno raven višje, na strani. OOXML in Excel robove za tisk shranjujeta v palcih, kar je nerodno, če so vaše predloge poročil zapisane v milimetrih, kot je običajno v večjem delu sveta zunaj ZDA. V2.91.0 doda centimetrske ovoje okoli palčnih robov: MarginLeftCM, MarginRightCM, MarginTopCM, MarginBottomCM, MarginHeaderCM in MarginFooterCM. Vsak je tanek priročni sloj nad ustrezno lastnostjo v palcih in pretvarja po natančnem razmerju 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;

Palčne lastnosti (MarginLeft in sorodne) ostajajo kanonično shranjevanje, zato lahko oba sistema mešate: zgornji rob nastavite v centimetrih in ga berete nazaj v palcih ali obratno, datoteka, zapisana na disk, pa bo v obeh primerih enaka. Pretvorba je preprosto množenje z 2,54 brez zaokroževanja na grobo mrežo, zato 2 cm ostaneta 2 cm s polno natančnostjo tipa double. To je ista filozofija metričnega udobja kot pri geometriji slik: format pod pokrovom govori imperialno, knjižnica pa vam omogoča avtorstvo v kateri koli enoti, v kateri je napisana vaša specifikacija. Za postavitev okoliškega poročila, naslovov, blokov metapodatkov in seštevkov glejte združene celice in postavitev predlog poročil v HotXLS, kjer so ti robovi uporabljeni skupaj z združenimi območji in območjem tiskanja

Opomba o tem, kaj geometrija zagotavlja in česa ne

Lastnosti geometrije nadzorujejo deklarirano velikost slike v datoteki, torej velikost, s katero jo bo skladen odjemalec izrisal. Ne vzorčijo znova bajtov slike; PNG velikosti 50×50 pik, nastavljen na 8 cm, se bo povečal in videti kockast, natanko tako kot v Excelu. Določanje velikosti je operacija postavitve, ne obdelave slike, zato sliki priskrbite dovolj izvorne ločljivosti za fizično velikost, ki jo nameravate uporabiti. Knjižnica tudi ne prekodira formatov: bajti, ki jih podate v AddImage, se shranijo in zapišejo naprej takšni, kot so, z TXLSXImageFormat, ki ga določite. Če podate bajte JPEG, jih pa označite kot xlsxImagePng, boste ustvarili datoteko, ki je Excel ne more odpreti, zato, kadar je mogoče, pustite AddImageFromFile, da obliko sklepa iz končnice

Nič od tega ni eksotično, ko ponotranjite eno samo osnovno idejo: v OOXML je fizična velikost prava količina, piksli pa so iz nje izpeljana senca, odvisna od DPI. Slike in robove avtorite v centimetrih, palcih ali točkah, prepustite HotXLS, da jih preslika na natančne EMU, in vaši računi ter poročila se bodo na vsakem računalniku, ki jih odpre, natisnili v isti velikosti

Tukaj opisani API-ji za geometrijo slik, skaliranje in metrične robove so del komponente HotXLS za preglednice v Delphi, ki bere in zapisuje XLS in XLSX iz Delphi in C++Builder brez potrebe po namestitvi Excela