Du setter inn en logo på 600×400 piksler i toppteksten i en generert faktura, den ser riktig ut på utviklingsskjermen din med 96 DPI, og en uke senere melder en kunde på en laptop med høy DPI at den skriver ut på størrelse med et frimerke. Pikslene endret seg aldri. Det som endret seg, er antakelsen om at et pikselantall betyr en fysisk størrelse, og i OOXML gjør det ikke det. Et regnearkbilde lagrer dimensjonene sine i EMU, og før du tenker i EMU, eller i de virkelige enhetene som mapper direkte til dem, er layouten din overlatt til hvilken DPI gjengivelsesmotoren tilfeldigvis antar.
HotXLS er en native VCL-regnearkkomponent for Delphi og C++Builder som leser og skriver XLS og XLSX uten Excel eller noen COM-avhengighet. Fra v2.91.0 slipper XLSX-bildeobjektet deg å regne om enhetene manuelt: ved siden av rå EMU eksponerer det bredde og høyde i centimeter, tommer og punkter, pluss en Scale-metode som endrer størrelse med en prosent og valgfri låsing av aspektforholdet. Denne artikkelen handler om hva EMU faktisk er, hvorfor DrawingML valgte det, og hvordan du bruker det nye geometrilaget til å plassere bilder etter fysisk størrelse i stedet for etter et pikselantall du ikke kan stole på.
Hva en EMU er og hvorfor DrawingML bruker den
EMU står for English Metric Unit, og er grunnlengdeenheten i DrawingML, tegnelaget som deles av hele Office Open XML-familien (ECMA-376, del 1, §20). Én EMU er definert slik at det er nøyaktig 914400 EMU per tomme og 360000 EMU per centimeter. Disse to konstantene er hele grunnen til at enheten finnes. 914400 er delelig med 2, 3, 4, 5, 6, 8, 9, 10, 12 og mange flere; den faktoriseres som 26 × 32 × 52 × 127. Siden 1 tomme = 2,54 cm helt nøyaktig, gjør valget av en enhet som er delelig med både 360000 og en pen brøkdel av 914400 at formatet kan uttrykke tommer, centimeter og punkter som heltall uten avrunding ved enhetsgrensen. Der en flyttallsverdi som "1,27 cm" ville drive, lagrer EMU 457200 og forblir nøyaktig.
Den andre enheten som betyr noe her, er punktet. Et typografisk punkt er 1/72 tomme, så det er 12700 EMU per punkt (914400 / 72). Punkter er måten Excel selv tenker på radhøyder, skriftstørrelser og marger internt, og derfor er det nyttig å eksponere bildegeometrien i punkter når du vil at et bilde skal linje opp med tekstmål i stedet for med en trykt linjal. HotXLS koder alle de fire sammenhengene som enhetskonstanter i biblioteket:
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)
Den siste linjen er kjernen i frimerkefeilen. Et piksel har bare en fysisk størrelse når du låser en DPI, og 9525 EMU er størrelsen på et piksel ved 96 DPI spesifikt. Excels standard-DPI er 96, så et bilde på 100 piksler havner på 100 × 9525 = 952500 EMU ≈ 2,54 cm i et standardoppsett, men ingenting i filen garanterer at mottakeren bruker 96. Skriv i virkelige enheter, og denne tvetydigheten forsvinner: 4 cm er 4 cm enten skjermen er 96 eller 220 DPI.
Geometrioverflaten i TXLSXImage
Et innebygd bilde i HotXLS er en TXLSXImage. Den kanoniske lagringen er to heltallsfelt, WidthEMU og HeightEMU, forankret i en én-basert Row og Col som bildet er festet til. Egenskapene for virkelige enheter er beregnede visninger over disse EMU-feltene, ikke separat tilstand, og når du leser WidthCM deles EMU på 360000, mens skriving multipliserer og runder tilbake. Hver dimensjon du setter, er derfor bare en annen måte å skrive den samme underliggende EMU-verdien på:
WidthInch/HeightInch: EMU ÷ 914400WidthCM/HeightCM: EMU ÷ 360000WidthPt/HeightPt: EMU ÷ 12700WidthEMU/HeightEMU: den heltallsbaserte sannhetskilden
Du legger til et bilde med AddImage(ARow, ACol, AData, AFormat), der du sender inn rå kodede bytes og et TXLSXImageFormat (xlsxImagePng, xlsxImageJpeg, xlsxImageGif, eller xlsxImageBmp); den returnerer den nullbaserte indeksen inn i regnearkets Images-samling. Det finnes også AddImageFromFile(ARow, ACol, AFileName), som utleder formatet fra filendelsen. Legg merke til indeksgrunnlaget: AddImage returnerer nullbasert og Images[] er nullbasert, noe som bevisst står i kontrast til det én-baserte Cells[Row, Col]-rutenettet, så ikke anta at de to stemmer overens.
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;
Et nyopprettet bilde får standardstørrelsen 100×100 piksler, altså 952500 EMU i begge retninger, omtrent en boks på 2,54 cm ved 96 DPI. Den standarden finnes for at et bilde skal være synlig selv om du glemmer å angi størrelse, men for enhver reell layout bør du sette en eksplisitt fysisk størrelse i stedet for å stole på den pikselbaserte standarden.
Skalering og aspektforholdsflagget
Når du vil endre størrelse relativt til de nåværende dimensjonene i stedet for til et absolutt mål, for eksempel krympe et diagrambilde til 60 % av det det ble importert med, bruker du Scale:
procedure Scale(APercent: Double; AKeepAspect: Boolean = True);
APercent er en prosent der 100 betyr uendret, 150 gjør objektet halvannen gang større, 50 halverer. Med AKeepAspect på standardverdien True blir både bredde og høyde multiplisert med samme faktor, så proporsjonene beholdes og et bilde på 4×3 cm blir 6×4,5 cm etter Scale(150). Send False, og bare bredden skaleres, høyden blir stående nøyaktig som den var. Den asymmetrien er bevisst: når du vil strekke én akse uavhengig, er de riktige verktøyene de eksplisitte setterne WidthCM og HeightCM, og den ikke-aspektbevarende grenen av Scale er der for det smalere tilfellet der du bare justerer bredden. Det er lett å lese Scale(150, False) som "strekk begge fritt" og bli overrasket, så bruk setterne når du faktisk mener to uavhengige dimensjoner.
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
En liten detalj å kjenne til: Scale(100) kortslutter og returnerer uten å røre noen av feltene, så det er trygt å kalle den ubetinget i en løkke der prosentene kan være 100. Og fordi geometrien lagres som heltalls-EMU, runder hver setter. Runding frem og tilbake gjennom brøkdeler av centimeter kan derfor drive med en brøkdel av en EMU, langt under noe som er synlig, men verdt å vite hvis du noen gang bruker eksakt likhet i en test. For pikselpresis kontroll kan du sette WidthEMU og HeightEMU direkte og hoppe helt over enhetskonverteringen.
Lese geometrien tilbake
Bildekolleksjonen kan forespørres, og det er nyttig når du åpner en eksisterende arbeidsbok og trenger å inspisere eller justere det som allerede ligger der, i stedet for det du nettopp la til. Images.Count teller hvert bilde på arket, Images[i] indekserer dem nullbasert, og FindAt(ARow, ACol) returnerer bildet som er forankret i en bestemt celle, eller nil hvis ingen finnes. Det finnes også IndexOfCell for indeksen i stedet for objektet, og DeleteAt / DeleteInRange for fjerning.
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;
Fordi egenskapene for virkelige enheter er levende visninger, rapporterer et bilde som er importert i en eller annen EMU-størrelse fra et annet verktøy geometrien sin i centimeter med én gang, uten noe konverteringstrinn fra din side. Dette passer naturlig sammen med den bredere tegnemodellen; hvis du plasserer både diagrammer og figurer i tillegg til rasterbilder, dekker den tilhørende veiledningen om HotXLS-diagrammer, bilder og Excel-tegninger i Delphi anker-modellen disse objektene deler.
Metriske marger i sideoppsettet
Den samme spenningen mellom EMU og virkelige enheter dukker opp ett nivå ut, på siden. OOXML og Excel lagrer utskriftsmarger i tommer, noe som er tungvint hvis rapportmalene dine er spesifisert i millimeter, slik det er for de fleste utenfor USA. v2.91.0 legger til centimeteromslag rundt tommemargene: MarginLeftCM, MarginRightCM, MarginTopCM, MarginBottomCM, MarginHeaderCM og MarginFooterCM. Hver av dem er en tynn bekvemmelighet over den tilsvarende egenskapen i tommer, med konvertering etter det eksakte forholdet 1 tomme = 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;
Tommeegenskapene (MarginLeft og de andre) er fortsatt den kanoniske lagringen, så du kan blande de to, sette en toppmarg i centimeter og lese den tilbake i tommer, eller omvendt, og filen som skrives til disk blir identisk uansett. Konverteringen er bare et rent multiplum med 2,54, uten avrunding til et grovt rutenett, så 2 cm forblir 2 cm med full dobbel presisjon. Dette er den samme metrikkvennlige filosofien som i bildegeometrien: formatet snakker imperial under panseret, og biblioteket lar deg skrive i den enheten spesifikasjonen din bruker. For å legge opp den omkringliggende rapporten, titler, metadatafelt og summer, se sammenslåtte celler og oppsett av rapportmaler i HotXLS, som bruker disse margene sammen med sammenslåtte områder og et utskriftsområde.
Et notat om hva geometrien garanterer og ikke garanterer
Geometriegenskapene styrer den oppgitte størrelsen på bildet i filen, altså størrelsen en kompatibel mottaker vil gjengi det med. De resampler ikke bildebytesene; en 50×50-pikslers PNG som settes til 8 cm vil skaleres opp og se blokkete ut, akkurat som i Excel. Størrelsesangivelse er en layoutoperasjon, ikke en bildebehandlingsoperasjon, så gi bildet nok kildoppløsning til den fysiske størrelsen du faktisk vil ha. Biblioteket koder heller ikke formatene på nytt: bytesene du sender til AddImage lagres og skrives videre som de er, med TXLSXImageFormat-typen du oppgir. Sender du JPEG-bytes, men merker dem xlsxImagePng, ender du opp med en fil Excel ikke kan åpne, så la AddImageFromFile utlede formatet fra filendelsen når du kan.
Alt dette er lite eksotisk når du først internaliserer den ene ideen under det hele: i OOXML er fysisk størrelse den virkelige størrelsen, og piksler er bare en avledet, DPI-avhengig skygge av den. Skriv bilder og marger i centimeter, tommer eller punkter, la HotXLS mappe dem til eksakte EMU, og fakturaene og rapportene dine skrives ut i samme størrelse på hver maskin som åpner dem.
API-ene for bildegeometri, skalering og metriske marger som er beskrevet her, følger med HotXLS Delphi-regnearkkomponent, som leser og skriver XLS og XLSX fra Delphi og C++Builder uten at Excel må være installert.