Tehnički članak

Geometrija slika HotXLS-a u Delphiju: EMU, cm i skaliranje

U zaglavlje generiranog računa ubacite logotip od 600×400 piksela, na vašem 96-DPI razvojnom monitoru izgleda ispravno, a tjedan dana kasnije kupac na prijenosniku visoke gustoće prijavi da se tiska veličine poštanske marke. Pikseli se nisu promijenili. Promijenila se pretpostavka da broj piksela znači fizičku veličinu, a u OOXML-u to nije slučaj. Slika u proračunskoj tablici nosi svoje dimenzije u EMU-u, i dok god razmišljate u EMU-u - ili u stvarnim jedinicama koje se na njega čisto mapiraju - raspored vam ovisi o bilo kojem DPI-u koji prikazni stroj slučajno pretpostavi

HotXLS je izvorna VCL spreadsheet komponenta za Delphi i C++Builder koja čita i piše XLS i XLSX bez Excela i bez bilo kakve COM ovisnosti. Od v2.91.0 XLSX objekt slike više vas ne tjera da ručno računate jedinice: uz sirovi EMU izlaže širinu i visinu u centimetrima, inčima i točkama, plus Scale metodu koja mijenja veličinu prema postotku uz opcionalno zaključavanje omjera stranica. Ovaj članak govori o tome što je EMU zapravo, zašto ga je DrawingML odabrao i kako koristiti novo geometrijsko sučelje da slike postavljate prema fizičkoj veličini, a ne prema broju piksela kojem ne možete vjerovati

Što je EMU i zašto ga DrawingML koristi

EMU znači English Metric Unit i osnovna je dužinska jedinica DrawingML-a, sloja za crtanje koji je zajednički cijeloj obitelji Office Open XML (ECMA-376, Part 1, §20). Jedan EMU definiran je tako da ih ima točno 914400 EMU po inču i 360000 EMU po centimetru. Te dvije konstante cijeli su razlog zašto jedinica postoji. 914400 je djeljiv s 2, 3, 4, 5, 6, 8, 9, 10, 12 i mnogim drugim brojevima; faktorizira se kao 26 × 32 × 52 × 127. Budući da je 1 inč = 2.54 cm točno, odabir jedinice djeljive i s 360000 i s čistim razlomkom od 914400 omogućuje formatu da izrazi inče, centimetre i točke kao cijele brojeve bez zaokruživanja na granici jedinice. Tamo gdje bi plutajuća vrijednost "1.27 cm" polako driftala, EMU pohrani 457200 i ostaje točan

Druga jedinica koja je ovdje važna jest točka. Tipografska točka iznosi 1/72 inča, pa ima 12700 EMU po točki (914400 / 72). Točke su način na koji Excel iznutra razmišlja o visini redaka, veličini fonta i marginama, zbog čega je izlaganje geometrije slike u točkama korisno kada želite da se slika poravna s metričkim tekstom, a ne s ispisanim ravnalom. HotXLS te četiri veze kodira kao konstante jedinica u biblioteci:

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)

Ta posljednja linija srž je buga s poštanskom markom. Piksel ima fizičku veličinu tek kada fiksirate DPI, a 9525 EMU je veličina piksela pri točno 96 DPI. Excelov zadani DPI za renderiranje iznosi 96, pa slika od 100 piksela završi na 100 × 9525 = 952500 EMU ≈ 2.54 cm u zadanom postavu - ali ništa u datoteci ne jamči da će potrošač koristiti 96. Autorirajte u stvarnim jedinicama i ta dvoznačnost nestaje: 4 cm je 4 cm bez obzira je li zaslon 96 ili 220 DPI

Surface geometrije TXLSXImage

Ugrađena slika u HotXLS-u je TXLSXImage. Njezina kanonska pohrana su dva cjelobrojna polja, WidthEMU i HeightEMU, usidrena na jedan-baziranom Row i Col (ćelija u gornjem lijevom kutu iz koje slika visi). Svojstva za stvarne jedinice izračunati su pogledi nad tim EMU poljima, a ne zasebno stanje - čitanje WidthCM dijeli EMU s 360000, a zapisivanje ga množi i vraća natrag zaokruživanjem. Dakle, svaku dimenziju koju postavite samo je drukčiji zapis iste temeljne EMU vrijednosti:

  • WidthInch / HeightInch — EMU ÷ 914400
  • WidthCM / HeightCM — EMU ÷ 360000
  • WidthPt / HeightPt — EMU ÷ 12700
  • WidthEMU / HeightEMU — izvor istine kao cijeli broj

Sliku dodajete s AddImage(ARow, ACol, AData, AFormat), prosljeđujući sirove kodirane bajtove i TXLSXImageFormat (xlsxImagePng, xlsxImageJpeg, xlsxImageGif, ili xlsxImageBmp); vraća zero-based indeks u kolekciji Images. Postoji i AddImageFromFile(ARow, ACol, AFileName), koja iz nastavka datoteke zaključuje format. Primijetite bazu indeksa: AddImage vraća zero-based, a Images[] je zero-based, što je namjerni kontrast prema jednom-baziranom Cells[Row, Col] gridu, pa nemojte pretpostaviti da su ta dva ista

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;

Novostvorena slika zadano je 100×100 piksela, tj. 952500 EMU kvadrat, otprilike kutija od 2.54 cm pri 96 DPI. Ta zadana vrijednost postoji kako bi slika bila vidljiva i ako joj zaboravite postaviti veličinu, ali za svaki stvarni raspored trebate eksplicitnu fizičku veličinu umjesto da se oslanjate na zadanu vrijednost izvedenu iz piksela

Skaliranje i zastavica omjera stranica

Kad želite mijenjati veličinu relativno prema trenutačnim dimenzijama umjesto prema apsolutnom cilju - recimo, smanjiti sliku grafikona na 60% od veličine u kojoj je uvezena - koristite Scale:

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

APercentAPercentAKeepAspectAKeepAspectTrueTrueScale(150)Scale(150)FalseFalseWidthCMWidthCMHeightCM/ScaleScaleScale(150, False)Scale(150, False)

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

Scale(100)Scale(100)Scale(100)integerintegerWidthEMUWidthEMUHeightEMUHeightEMU

Čitanje geometrije natrag

Kolekcija slika može se ispitivati, što je važno kada učitate postojeću radnu knjigu i trebate pregledati ili prilagoditi ono što je već ondje umjesto onoga što ste tek dodali. Images.CountImages.CountImages[i] indeksira ih zero-based, a FindAt(ARow, ACol) vraća sliku usidrenu za određenu ćeliju - ili nil ako je nema. Postoji i IndexOfCell za indeks umjesto objekta, te DeleteAt / DeleteInRange za uklanjanje

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;

Budući da su svojstva stvarnih jedinica živi prikazi, slika uvezena s nekom EMU veličinom iz drugog alata odmah prijavljuje svoju geometriju u centimetrima - bez ikakvog koraka pretvorbe s vaše strane. To se prirodno uklapa u širi model crtanja; ako postavljate i grafikone i oblike uz rasterske slike, prateći vodič o HotXLS grafikonima, slikama i Excel crtežima u Delphiju pokriva model usidrenja koji ti objekti dijele

Metričke margine stranice

Ista napetost između EMU-a i stvarnih jedinica pojavljuje se jednu razinu dalje, na stranici. OOXML i Excel pohranjuju margine ispisa u inčima, što je nezgodno ako su vaši predlošci izvještaja zadani u milimetrima kao u većini svijeta izvan SAD-a. v2.91.0 dodaje centimetarske omotače oko inčnih margina: MarginLeftCM, MarginRightCM, MarginTopCM, MarginBottomCM, MarginHeaderCM, i MarginFooterCM. Svaki je tanka pogodnost nad odgovarajućim inčnim svojstvom, uz pretvorbu točno po omjeru 1 inč = 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;

Inčna svojstva (MarginLeft i ostala) ostaju kanonska pohrana, pa možete miješati oba - postaviti gornju marginu u centimetrima i vratiti je u inčima, ili obrnuto - a datoteka zapisana na disk identična je u oba slučaja. Pretvorba je običan množitelj s 2.54, bez zaokruživanja na grubu mrežu, pa 2 cm ostaje 2 cm do pune double preciznosti. To je ista filozofija metričke pogodnosti kao i geometrija slike: format iznutra govori imperijalno, a biblioteka vam dopušta da pišete u jedinici u kojoj je napisana vaša specifikacija. Za raspoređivanje okolnog izvještaja - naslovi, blokovi metapodataka, zbrojevi - pogledajte spojene ćelije i raspored predloška izvještaja u HotXLS-u, koji ove margine koristi zajedno sa spojenim rasponima i područjem ispisa

Napomena o tome što geometrija jamči, a što ne

Geometrijska svojstva kontroliraju deklariranu veličinu slike u datoteci - veličinu na kojoj će je usklađeni potrošač renderirati. Ona ne resamplira bajtove slike; PNG od 50×50 piksela postavljen na 8 cm povećat će se i izgledati blokasto, baš kao u Excelu. Veličina je operacija rasporeda, a ne obrade slike, pa sliku hranite dovoljno visokom izvorne rezolucijom za fizičku veličinu koju želite. Biblioteka također ne ponovno kodira formate: bajtovi koje proslijedite u AddImage se pohranjuju i zapisuju takvi kakvi jesu, uz TXLSXImageFormat koji ste deklarirali. Proslijedite JPEG bajtove, ali ih označite kao xlsxImagePng i dobit ćete datoteku koju Excel ne može otvoriti, pa pustite AddImageFromFile da format zaključi iz nastavka kad god možete

Ništa od ovoga nije egzotično kad usvojite jednu ideju ispod svega: u OOXML-u je fizička veličina stvarna veličina, a pikseli su izvedena, DPI-ovisna sjena te veličine. Pišite slike i margine u centimetrima, inčima ili točkama, pustite HotXLS da ih preslika u točan EMU, i vaši će računi i izvještaji ispisivati istu veličinu na svakom računalu koje ih otvori

API za geometriju slika, skaliranje i metričke margine opisan ovdje dolazi s HotXLS Delphi spreadsheet component, koja čita i piše XLS i XLSX iz Delphija i C++Buildera bez potrebe za instaliranim Excelom