Viety PDF asettaa jokaisen sarakerajan puoli merkkiä vasemmalle siitä, missä Excel sen piirtää, ja jokainen rivitetty solu katkeaa nyt eri kohtaa. Excel-sarakkeen leveyttä ei mitata merkeissä eikä pisteissä. Se mitataan työkirjan Normal-fontin Max Digit Width (MDW) -yksiköinä, ja HotXLS mittaa kyseisen fontin GDI:llä ennen jokaista sivutusrakennetta. Vikatila on hiljainen: mikään ei heitä, tallennetut leveydet round-tripataan tavu tavulta, ja geometria on silti muutaman prosentin väärin saraketta kohden, kunnes kertynyt ajautuminen työntää yhden sivun taulukon kahdelle
Mikä yksikkö Excel-sarakkeen leveys on?
Arkin sarakeleveys on laskuri työkirjan Normal-fontin numeromerkeistä, ei absoluuttinen mittaus. ECMA-376 §18.3.1.13 määrittelee <col>-elementin width-attribuutin kyseisen fontin Maximum Digit Widthin kautta 96 dpi:ssä, ja antaa muunnoksen tallennetusta leveydestä takaisin pikseleiksi katkaisevana lausekkeena MDW:n yli. Calibri 11:lle, jonka Excel toimittaa Normal-tyylinä, MDW mittaa 7 pikseliä. Syötä oletusleveys 8.43 yksikköä määrittelykaavan läpi MDW 7:llä ja saat täsmälleen 64 pikseliä, joka on 48 pistettä 96 dpi:ssä. Nuo ovat luvut, joita Excel itse raportoi, joten ne tekevät hyödyllisen tarkistuksen: jos muunnosesi toistaa 8.43 yksikköä 64 pikseliksi, aritmetiikka on oikein ja vain MDW-syöte voi vielä olla väärin
const
// Oletusrunkofontin maksiminumeroleveys (MDW) pikseleinä 96 dpi:ssä.
// Calibri 11 mittaa 7 px, mikä toistaa täsmälliset pikselileveydet,
// jotka Excel tallentaa (8.43 yksikköä -> 64 px -> 48 pt).
DefaultMDW = 7;
MinimumColumnWidth = 24.0;
function ColumnWidthToPointsMdW(Value: Double; MdW: Integer): Double;
var
Pixels: Integer;
begin
if Value <= 0 then
Value := 8.43;
if MdW <= 0 then
MdW := DefaultMDW;
Pixels := Trunc(((256 * Value + Trunc(128 / MdW)) / 256) * MdW) + 5;
Result := Pixels * 0.75; // 96 dpi:n pikselit -> pisteet
if Result < MinimumColumnWidth then
Result := MinimumColumnWidth;
end;
HotXLS pitää kyseisen aritmetiikan täsmälleen yhdessä funktiossa, lxPagination-yksikössä, joten on yksi paikka, jossa mittanauha voi olla väärin. + 5 on täyte, jonka Excel lisää ruudukon viivoille ja solujen marginaaleille, * 0.75 muuntaa 96 dpi:n pikselit PostScript-pisteiksi, ja lattia MinimumColumnWidthissa on olemassa, jotta patologisesti kapea sarake jättää silti kaistan, johon rendereri voi piirtää reunan. Julkinen sisääntulopiste ColumnWidthToPoints pitää vanhan yksiargumenttisen allekirjoituksensa ja välittää mitatun MDW:n tälle funktiolle, mikä antoi käyttäytymismuutoksen laskeutua koskettamatta yhtään kutsupaikkaa
Miksi muu kuin Calibri Normal -fonti siirtää jokaisen rajan
Ajautuminen on multiplikatiivista, minkä vuoksi se luetaan renderöintivirheeksi eikä yksikkövirheeksi. MDW on kerroin leveydellä, ei siirtymä. Työnnä MDW arvosta 7 arvoon 8, ja oletusarvoinen 8.43-yksikön sarake kasvaa 64 pikselistä 72:een, hyppy 8 pikseliä eli 6 pistettä yhdellä sarakkeella. Kymmenellä tällaisella sarakkeella taulukon oikea reuna on liikkunut lähes tuuman verran. Tähän kompastuvat työkirjat ovat täysin tavallisia: mikä tahansa raportointityökalun tuottama, joka leimaa Arial- tai Segoe UI -fontin Normal-tyyliin, mikä tahansa ERP-vientimallista tallennettu, mikä tahansa, jonka asiakas kerran uudelleentyylitteli ja unohti
Kaksi toisiinsa liittyvää asettelujärjestelmää perii virheen sen sijaan, että aiheuttaisivat sen. Yhdistetyt alueet summaavat jäsensarakkeidensa pisteleveydet, joten yhdistetty alue, joka mahtui yhdelle sivulle Excelissä, voi vuotaa yli MDW-ajautumisen jälkeen, mikä kannattaa muistaa, kun rakennat yhdistettyjen solujen raporttipohjia. Shrink-to-fit vertaa mitattua tekstin leveyttä samaan sarakeleveyteen, joten väärä MDW muuttaa myös sen, mitkä solut kutistuvat ja kuinka paljon. Sama yksikkösekavennusten perhe ilmestyy esiin piirrosankkureissa, joissa kuvageometrialla ja EMU-skaalauksella on oma muunnosketjunsa, jonka voi saada väärin
Kuinka HotXLS mittaa MDW:n ajonaikaisesti
HotXLS ratkaisee MDW:n itse työkirjasta vakion olettamisen sijaan, ja kaksi proseduuria tekee työn. PaginationApplyNormalFont lukee Normal-tyylin fontin työkirjasta ja ajaa sivutusrakennuksen alussa, ennen kuin mitään sarakegeometriaa lasketaan; se nollautuu ensin Calibri 11:een, joten fonttitauluton työkirja ei voi periä vanhentunutta tilaa edellisestä rakennuksesta. Normal-tyylin fontti on fonts[0] kohteessa styles.xml, jonka komponentti näyttää muodossa Workbook.Fonts[0]
// Lukee fonts[0]:n eli Normal-tyylin fontin työarkin työkirjasta.
// Klassiset fonttitauluttomat työarkit säilyttävät Calibri 11 -oletuksen.
procedure PaginationApplyNormalFont(Worksheet: TObject);
var
Sh: TXLSXWorksheet;
Fnt: TXLSXFont;
begin
PaginationNormalFontName := 'Calibri';
PaginationNormalFontSize := 11;
if not (Worksheet is TXLSXWorksheet) then
Exit;
Sh := TXLSXWorksheet(Worksheet);
if (Sh.Workbook = nil) or (Sh.Workbook.Fonts.Count < 1) then
Exit;
Fnt := Sh.Workbook.Fonts[0];
if Fnt.Name <> '' then
PaginationNormalFontName := Fnt.Name;
if Fnt.Size > 0 then
PaginationNormalFontSize := Fnt.Size;
end;
Toinen proseduuri, PaginationMeasureMdW, kysyy GDI:ltä yksittäisen merkin '0' laajuuden funktiolla GetTextExtentPoint32W jaetulla näytön ulkopuolisella bittikarttakankaalla, palautuu kohteen GetTextMetricsW tmAveCharWidth-arvoon, kun laajuuskutsu epäonnistuu, ja palautuu DefaultMDW:hen, kun kumpikaan ei ole käytettävissä. Sen välimuisti on yksi paikka, jonka avain on (name, size), mikä kuulostaa karkealta, kunnes katsot käyttömallia: sivutusrakennus kysyy samaa Normal-fonttia jokaisen sivun jokaiselle sarakkeelle, joten yhdellä paikalla on lähes täydellinen osumaprosentti ja hinta on kolme vertailua per kutsu
Mitä tapahtuu, kun fonttitaulua, GUI:a tai fonttia ei ole?
HotXLS degradeaa Calibri 11 -vakioon jokaisessa tapauksessa, jossa todellista Normal-fonttia ei voi määrittää, ja se tekee niin hiljaisesti suunnitellusti. Klassiset BIFF-työarkit ovat yleisin tapaus: perintämuodoissa ei ole XLSX-fonttipoolia, johon fonts[0] voisi viitata, joten tyyppitarkistus poistuu aikaisin ja oletusarvoinen MDW 7 pysyy voimassa. Se ei ole korjaus, vaan aiempi käyttäytyminen, joka on tarkoituksella säilytetty, jotta mittauksen lisääminen XLSX-polkuun ei voisi heikentää klassisen muodon tuotosta
GDI-riippuvuus on rehellinen varaus. Mittaus ajaa Windows-laitekontekstia vasten, joten polku olettaa Windows-isäntän, jossa fontti on asennettuna. Palvelussa tai headless-rakennusagentissa GDI-tekstimetriikat ratkeavat yleensä yhä, mutta koneelta puuttuva fontti korvataan fonttimaaparilla ja mittaat korviketta. Se ei koskaan epäonnistu äänekkäästi; se palauttaa uskottavan luvun väärälle kirjasintyypille. Jos palvelinpuolen vientien on vastattava työpöytäreferenssiä, asenna vienti-isäntään fontit, joita mallisi nimeävät, tai kiinnitä Normal-fontti ennen työarkin PDF-vientipolun kutsumista
var
Book: TXLSXWorkbook;
Exporter: TXLSPDFExport;
begin
Book := TXLSXWorkbook.Create;
Exporter := TXLSPDFExport.Create;
try
Book.Open('quarterly-report.xlsx');
// Kiinnitä Normal-fontti, jotta tällä isännällä mitattu MDW on se,
// jota vasten asettelu suunniteltiin, ei fonttimaaparikorviketta.
if Book.Fonts.Count > 0 then
begin
Book.Fonts[0].Name := 'Calibri';
Book.Fonts[0].Size := 11;
end;
Exporter.UseWorksheetPageSetup := True;
Exporter.SaveAsPDF(Book, 'quarterly-report.pdf');
finally
Exporter.Free;
Book.Free;
end;
end;
Mittausvälimuistit ja se, joka kaatui Win64:llä
Kun tekstin mittaus on GDI round trip kertolaskun sijaan, se pitää välimuistittaa, ja välimuistitus renderöintikierroksen sisällä on kohta, jossa tämä työ vuoti verta. Shrink-to-fit-silmukka laskee fonttikokoa 0.5 pisteen askelin ja mittaa uudelleen jokaisen askeleen jälkeen, joten yksi solu voi kutsua PaginationMeasureTextWidth:ia tusina kertaa samalla merkkijonolla, ja sanojen rivitys kutsuu sitä uudelleen jokaista kandidaattiriviä kohden. Muistio, jonka avain on fontin nimi, koko ja teksti, kutistaa tämän yhteen GDI-kutsuun per erillinen merkkijono, tallennettuna TStringList:iin nimi/arvo-pareina
Toinen sen rinnalle lisätty välimuisti ei ollut yhtä siisti. Renderöintikierros 5 ratkaisee fonttipoolin per solu FontIndex:llä, ja sen muistio käytti rinnakkaisia dynaamisia taulukoita käsin ylläpidetyllä FontMemoCount:illa. Ensimmäinen versio unohti kutsua ResetFontMemo:a jokaisen sivun alussa, joten laskuri jatkoi kiipeämistä sivujen yli, vaikka taulukot eivät, ja koodi kirjoitti kaikkien niiden loppujen yli. Win32:lla se raaputti hiljaa viereiseen kekoon ja loppui; Win64:llä se nosti access violationin heti kirjoituksessa kohteeseen 0x538. Yleistettävä oppitunti: yksikkötason muuttujassa pidetty taulukkopohjainen välimuisti pitää nollata jokaisen sen käyttävän kierroksen alussa, koska merkkijonolista tai sanakirja antaa puuttuvan nollauksen anteeksi kasvamalla, ja rinnakkaiset taulukot eivät
Oman muunnosesi tarkistaminen
Et tarvitse komponenttia varmentaaksesi mitään tästä. Ota työkirja, jonka Normal-fontti ei ole Calibri 11, lue leveys kohteesta <col width="..."/> ja aja se määrittelykaavan läpi kahdesti, kerran MDW 7:llä ja kerran sillä MDW:llä, jonka renderöijäsi oikeasti mittaa kyseiselle fontille; jos vastaukset eroavat ja tulostesi täsmää ensimmäiseen, olet löytänyt ajautumisen. Sarakegeometria on yksi niistä taulukkolaskentamoottorin osista, joka on joko näkymätön tai ainoa, jonka joku huomaa, ja sen saaminen oikein tarkoittaa Normal-fontin käsittelyä asettelun syötteenä tyylittelyn yksityiskohdan sijaan. Jos rakennat Delphi- tai C++Builder-sovelluksia, jotka lukevat, kirjoittavat, renderöivät ja tulostavat Excel-työkirjoja ilman asennettua Officea, HotXLS Delphi Excel -komponentti hoitaa MDW-mittauksen, sivutusmallin ja PDF-putken yhden VCL-luokkajoukon takana