Teknisk artikel

HotXLS-billedgeometri i Delphi: EMU, cm og skalering

Du lægger et 600×400 pixel logo ind i sidehovedet på en genereret faktura, det ser rigtigt ud på din 96-DPI-udviklermonitor, og en uge senere melder en kunde på en høj-DPI-bærbar, at det udskrives på størrelse med et frimærke. Pixelsene ændrede sig aldrig. Det, der ændrede sig, er antagelsen om, at et pixelantal betyder en fysisk størrelse, og i OOXML gør det ikke. Et regnearksbillede bærer sine dimensioner i EMU, og indtil du ræsonnerer i EMU—eller i de virkelige-verden-enheder, der mapper rent over på den—er dit layout prisgivet, hvilken DPI rendermaskinen nu antager

HotXLS er en native VCL spreadsheet-komponent til Delphi og C++Builder, som læser og skriver XLS og XLSX uden Excel eller nogen COM-afhængighed. Fra v2.91.0 holder XLSX-billedobjektet op med at tvinge dig til at regne enhederne ud i hånden: ud over den rå EMU eksponerer det bredde og højde i centimeter, tommer og punkter, plus en Scale-metode, der ændrer størrelsen med en procentdel med valgfri aspect-ratio-lås. Denne artikel handler om, hvad EMU faktisk er, hvorfor DrawingML valgte det, og hvordan du bruger den nye geometri-overflade til at placere billeder efter fysisk størrelse i stedet for et pixelantal, du ikke kan stole på

Hvad en EMU er, og hvorfor DrawingML bruger den

EMU står for English Metric Unit, og det er den grundlæggende længdeenhed i DrawingML, tegnelaget der deles på tværs af hele Office Open XML-familien (ECMA-376, del 1, §20). Én EMU er defineret, så der er præcis 914400 EMU pr. tomme og 360000 EMU pr. centimeter. De to konstanter er hele grunden til, at enheden findes. 914400 er delelig med 2, 3, 4, 5, 6, 8, 9, 10, 12 og mange flere; det faktoriseres som 26 × 32 × 52 × 127. Fordi 1 tomme = 2.54 cm præcist, lader valget af en enhed, der er delelig med både 360000 og en ren brøkdel af 914400, formatet udtrykke tommer, centimeter og punkter som heltal uden afrunding ved enhedsgrænsen. Hvor et flydende-komma-tal "1.27 cm" ville drifte, gemmer EMU 457200 og forbliver eksakt

Den anden enhed, der betyder noget her, er punktet. Et typografisk punkt er 1/72 tomme, så der er 12700 EMU pr. punkt (914400 / 72). Punkter er, hvordan Excel selv tænker om rækkehøjder, skriftstørrelser og margener under motorhjelmen, hvilket er grunden til, at det er nyttigt at eksponere billedgeometri i punkter, når du vil have et billede til at flugte med tekstmetrikker frem for med en trykt lineal. HotXLS koder alle fire relationer som enhedskonstanter i biblioteket:

EMU-længdeenhedskort for HotXLS i Delphi, der viser én English Metric Unit konvertere præcist til 914400 pr. tomme, 360000 pr. centimeter, 12700 pr. punkt og en DPI-afhængig 9525 pr. 96-DPI-pixel
Én EMU afbilder præcis på tomme-, centimeter- og punktenheder, mens 9525-EMU-pixelen er den eneste DPI-afhængige konvertering og kilden til frimærkefejlen
const
  XlsxEmuPerInch  = 914400;  // 1 tomme
  XlsxEmuPerCm    = 360000;  // 1 centimeter
  XlsxEmuPerPoint = 12700;   // 1 punkt (1/72 tomme)
  XlsxEmuPerPixel = 9525;    // 1 pixel ved 96 DPI (914400 / 96)

Den sidste linje er kernen i frimærke-fejlen. En pixel har kun en fysisk størrelse, når du har fastlagt en DPI, og 9525 EMU er størrelsen på en pixel specifikt ved 96 DPI. Excels standard-render-DPI er 96, så et 100-pixel-billede lander på 100 × 9525 = 952500 EMU ≈ 2.54 cm på en standardopsætning—men intet i filen garanterer, at forbrugeren bruger 96. Skriv i virkelige enheder, og den tvetydighed forsvinder: 4 cm er 4 cm, uanset om skærmen er 96 eller 220 DPI

TXLSXImage-geometri-overfladen

Et indlejret billede i HotXLS er et TXLSXImage. Dets kanoniske lagring er to heltalsfelter, WidthEMU og HeightEMU, forankret ved en et-baseret Row og Col (den øverste venstre celle, billedet hænger fra). De rigtige-enheder-egenskaber er beregnede visninger over de EMU-felter, ikke separat tilstand—at læse WidthCM dividerer EMU'en med 360000, og at skrive den ganger og runder tilbage. Så hver dimension, du sætter, er blot en anden stavemåde af den samme underliggende EMU-værdi:

TXLSXImage geometriflade i HotXLS til Delphi, hvor WidthEMU og HeightEMU er den heltals sandhedskilde, og inch-, centimeter- og punktegenskaber er beregnede visninger, der dividerer ved læsning og ganger og runder ved skrivning
WidthEMU og HeightEMU er den eneste gemte tilstand — hver rigtigenhedsegenskab dividerer EMU ved læsning og multiplicerer tilbage med afrunding ved skrivning
  • WidthInch / HeightInch — EMU ÷ 914400
  • WidthCM / HeightCM — EMU ÷ 360000
  • WidthPt / HeightPt — EMU ÷ 12700
  • WidthEMU / HeightEMU — heltalskilden til sandheden

Du tilføjer et billede med AddImage(ARow, ACol, AData, AFormat), som giver de rå kodede bytes og et TXLSXImageFormat (xlsxImagePng, xlsxImageJpeg, xlsxImageGif, eller xlsxImageBmp); den returnerer det nul-baserede indeks ind i regnearkets Images-samling. Der er også AddImageFromFile(ARow, ACol, AFileName), som udleder formatet fra filendelsen. Bemærk indeksgrundlaget: AddImage returnerer nul-baseret, og Images[] er nul-baseret, hvilket er en bevidst kontrast til det et-baserede Cells[Row, Col]-gitter, så antag ikke, at de to stemmer overens

var
  Sheet: TXLSXWorksheet;
  Img: TXLSXImage;
  Idx: Integer;
begin
  Sheet := Workbook.Sheets.Add('Images');

  // Forankr en PNG ved række 3, kolonne 2; AddImage returnerer et 0-baseret indeks.
  Idx := Sheet.AddImage(3, 2, LogoBytes, xlsxImagePng);

  Img := Sheet.Images[Idx];
  Img.WidthCM := 4.0;    // 4 cm bred  -> 1440000 EMU
  Img.HeightCM := 3.0;   // 3 cm høj  -> 1080000 EMU

  // Samme geometri, læst tilbage i andre enheder.
  // Img.WidthPt  er nu 113.39 pt, Img.WidthInch er 1.5748 in.
end;

Et nyoprettet billede har som standard 100×100 pixels, dvs. 952500 EMU kvadratisk, groft sagt en 2.54 cm boks ved 96 DPI. Den standard findes, så et billede er synligt, selv hvis du glemmer at give det en størrelse, men til ethvert reelt layout bør du sætte en eksplicit fysisk størrelse frem for at stole på den pixel-afledte standard

Skalering og aspect-ratio-flaget

Når du vil ændre størrelse relativt til de aktuelle dimensioner i stedet for til et absolut mål—sig, formindske et diagrambillede til 60% af, hvad det blev importeret ved—brug Scale:

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

APercent er en procentdel, hvor 100 betyder uændret, 150 forstørrer med en halv, 50 halverer. Med AKeepAspect ved dens standard True ganges både bredde og højde med samme faktor, så proportionerne holder, og et 4×3 cm-billede bliver til 6×4.5 cm efter Scale(150). Angiv False, og kun bredden skaleres—højden lades præcis, som den var. Den asymmetri er tilsigtet: når du vil strække én akse uafhængigt, er det rigtige værktøj de eksplicitte WidthCM/HeightCM-settere, og non-aspect-grenen af Scale er der til det snævrere tilfælde at justere bredden alene. Det er let at læse Scale(150, False) som "stræk begge frit" og blive overrasket, så grib efter setterne, når du reelt mener to uafhængige dimensioner

Sammenligning af HotXLS Delphi Scale-metoden på et 4x3 cm billede: Scale(150) med aspect-låsning vokser begge akser til 6x4,5 cm, mens Scale(50, False) kun formindsker bredden til 3,0 cm og lader højden uændret
Med aspect-flaget på dets standard True multipliceres begge dimensioner med samme faktor — at give False skalerer kun bredden og efterlader højden urørt
Img.WidthCM := 4.0;
Img.HeightCM := 3.0;

Img.Scale(150);          // aspect låst: nu 6.0 x 4.5 cm
Img.Scale(100);          // no-op, returnerer med det samme

Img.Scale(50, False);    // kun bredde: 3.0 cm bred, højde uændret ved 4.5 cm

Én lille adfærd at kende: Scale(100) kortslutter og returnerer uden at røre nogen af felterne, så det er sikkert at kalde ubetinget i en løkke, hvor procentdelen kan være 100. Og fordi geometrien gemmes som heltals-EMU, runder hver setter. At runde tur-retur gennem brøkdele af centimeter kan derfor drifte med en brøkdel af en EMU—langt under noget synligt, men værd at vide, hvis du nogensinde tester eksakt lighed. For pixel-perfekt kontrol skal du sætte WidthEMU og HeightEMU direkte og springe enhedskonverteringen helt over

Læs geometrien tilbage igen

Billedsamlingen kan forespørges, hvilket betyder noget, når du indlæser en eksisterende projektmappe og skal inspicere eller justere det, der allerede er der, frem for det, du lige har tilføjet. Images.Count opregner hvert billede på arket, Images[i] indekserer dem nul-baseret, og FindAt(ARow, ACol) returnerer billedet forankret ved en bestemt celle—eller nil, hvis der ingen er. Der er også IndexOfCell for indekset frem for objektet, og DeleteAt / DeleteInRange til fjernelse

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-tjek før brug
  if Img <> nil then
    Img.Scale(80);
end;

Fordi de rigtige-enheder-egenskaber er levende visninger, rapporterer et billede, importeret ved en eller anden EMU-størrelse fra et andet værktøj, straks sin geometri i centimeter—intet konverteringstrin fra din side. Det spiller naturligt sammen med den bredere tegnemodel; placerer du diagrammer og former såvel som rasterbilleder, dækker følgeguiden om HotXLS-diagrammer, billeder og Excel-tegninger i Delphi ankermodellen, de objekter deler

Cm-baserede sideopsætningsmargener

Den samme spænding mellem EMU og rigtige enheder dukker op ét niveau ud, på siden. OOXML og Excel gemmer printmargener i tommer, hvilket er akavet, hvis dine rapportskabeloner er specificeret i millimeter som det meste af verden uden for USA. v2.91.0 tilføjer centimeter-wrappere over tomme-margenerne: MarginLeftCM, MarginRightCM, MarginTopCM, MarginBottomCM, MarginHeaderCM og MarginFooterCM. Hver er en tynd bekvemmelighed over den tilsvarende tomme-egenskab, der konverterer ved det eksakte forhold 1 tomme = 2.54 cm

Sheet.MarginLeftCM := 2.0;     // 2 cm  == 0.7874 tomme
Sheet.MarginRightCM := 2.0;
Sheet.MarginTopCM := 2.5;
Sheet.MarginBottomCM := 2.5;
Sheet.MarginHeaderCM := 1.0;
Sheet.MarginFooterCM := 1.0;

Tomme-egenskaberne (MarginLeft og lignende) forbliver den kanoniske lagring, så du kan blande de to—sætte en topmargin i centimeter og læse den tilbage i tommer, eller omvendt—og filen skrevet til disk er identisk begge veje. Konverteringen er en simpel gange-med-2.54, ingen afrunding til et groft gitter, så 2 cm forbliver 2 cm med fuld double-præcision. Det er den samme metric-bekvemmeligheds-filosofi som billedgeometrien: formatet taler imperial under motorhjelmen, og biblioteket lader dig skrive i hvilken som helst enhed, din specifikation er skrevet i. For at layoute den omgivende rapport—titler, metadatablokke, totaler—se flettede celler og skabelonlayout til rapporter i HotXLS, som bruger disse margener sammen med flettede områder og et printområde

En note om, hvad geometrien gør og ikke garanterer

Geometriegenskaberne styrer den deklarerede størrelse af billedet i filen—den størrelse, en konform forbruger vil rendre det ved. De resampler ikke billedbytesene; en 50×50 pixel PNG sat til 8 cm skalerer op og ser blokagtig ud, præcis som den ville i Excel. Størrelsesfastsættelse er en layoutoperation, ikke en billedbehandlingsoperation, så giv billedet nok kildeopløsning til den fysiske størrelse, du sigter mod. Biblioteket genkoder heller ikke formater: de bytes, du giver til AddImage, gemmes og skrives igennem som de er, med det TXLSXImageFormat, du erklærer. Giv JPEG-bytes, men tag dem xlsxImagePng, og du producerer en fil, Excel ikke kan åbne, så lad AddImageFromFile udlede formatet fra filendelsen, når du kan

Intet af det er eksotisk, når du først har fået den ene idé under det ind under huden: i OOXML er fysisk størrelse den reelle størrelse, og pixels er en afledt, DPI-afhængig skygge af den. Skriv billeder og margener i centimeter, tommer eller punkter, lad HotXLS mappe dem på eksakt EMU, og dine fakturaer og rapporter printer i samme størrelse på hver maskine, der åbner dem

Billedgeometri-, skalerings- og metrisk-margin-API'erne beskrevet her følger med i HotXLS Delphi regnearkskomponent, som læser og skriver XLS og XLSX fra Delphi og C++Builder uden behov for Excel-installation