Műszaki cikk

Excel oszlopszélesség és Max Digit Width (MDW) Delphiben

Az exportált PDF minden oszlophatárt fél karakterrel az Excel általi rajzolástól balra tesz, és minden tördelt cella most más helyen törik. Az Excel oszlopszélessége nem karakterekben vagy pontokban mérődik. A munkafüzet Normal betűtípusának Max Digit Width (MDW) egységeiben mérődik, és a HotXLS minden lapozási build előtt GDI-vel méri azt a betűtípust. A hibamód csendes: semmi nem dob, a tárolt szélességek bájtonként visszautaznak, és a geometria oszloponként még mindig néhány százalékkal eltér, amíg a felgyülemlett sodródás egy egyoldalas táblát kettőre nem nyom

Milyen egységben mérődik az Excel oszlopszélessége?

Egy munkalap oszlopszélessége a munkafüzet Normal betűtípusának számjegykaraktereinek száma, nem abszolút mérték. Az ECMA-376 §18.3.1.13 a <col> width attribútumát az adott betűtípus 96 dpi-nél mért Maximum Digit Width-jával definiálja, és a tárolt szélességről pixelekre való visszakonverziót MDW feletti lefelé csonkító kifejezésként adja. Calibri 11 esetén — amely az Excel Normal stílusának szállított betűtípusa — az MDW 7 pixel. Ha az alapértelmezett 8,43 egységes szélességet a specifikációs képleten átengedi MDW 7-tel, pontosan 64 pixelt kap, ami 96 dpi-nél 48 pont. Ezek azok a számok, amelyeket maga az Excel jelent, így hasznos ellenőrzést adnak: ha a konverziója 8,43 egységből 64 pixelt reprodukál, az aritmetika helyes, és csak az MDW bemenet lehet még rossz

const
  // Az alapértelmezett szövegtörzs betűtípusának MDW-ja pixelekben 96 dpi-nél.
  // A Calibri 11 7 px, ami reprodukálja a pontos pixel szélességeket,
  // amelyeket az Excel tárol (8.43 egység -> 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 pixelek -> pontok
  if Result < MinimumColumnWidth then
    Result := MinimumColumnWidth;
end;

A HotXLS azt az aritmetikát pontosan egy függvényben tartja, a lxPagination egységben, így egyetlen hely van, ahol a vonalzó tévedhet. A + 5 az a kitöltés, amelyet az Excel a rácsvonalak és cellamargók miatt ad hozzá, a * 0.75 a 96 dpi-s pixeleket PostScript pontokká alakítja, a MinimumColumnWidth-nál lévő plafon pedig azért létezik, hogy egy patológiusan keskeny oszlop is hagyjon sávot, amelybe a renderelő szegélyt rajzolhat. A nyilvános belépési pont, a ColumnWidthToPoints, megtartja régi egyargumentumos szignatúráját, és mért MDW-t továbbít ennek a függvénynek, ami tette lehetővé, hogy a viselkedésváltozás egyetlen hívóhely érintése nélkül landoljon

A HotXLS oszlopszélesség konverziólánc Delphiben, amely a munkafüzet Normal betűtípusának mért Max Digit Width-ját táplálja a specifikációs képletbe, így a 8,43 egységnyi tárolt szélesség 64 pixel lesz, majd 48 pont
A tárolt szélesség számjegyszám, így a Normal betűtípus mért MDW-ja a képlet bemenete, nem stílusrészlet, és a 8,43, 64, 48 közti oda-vissza út ellenőrzi az aritmetikát

Miért mozgat minden határt egy nem Calibri Normal betűtípus?

A sodródás multiplikatív, ezért olvastatik renderelési hibaként, nem egységhibaként. Az MDW a szélesség tényezője, nem eltolása. Tolja az MDW-t 7-ről 8-ra, és az alapértelmezett 8,43 egységes oszlop 64 pixelről 72-re megy, 8 pixel vagy 6 pont ugrás egyetlen oszlopon. Tíz ilyen oszlop, és a tábla jobb széle jóformán egy hüvelyknyit mozdult. Az ezen fennakadó munkafüzetek teljesen hétköznapik: bármi, amelyet olyan jelentéskészítő eszköz generált, amely Arial vagy Segoe UI-t pecsétel a Normal stílusba, bármi, amelyet ERP export sablonból mentettek, bármi, amelyet egy ügyfél egyszer átstyírt, majd elfelejtett

Két kapcsolódó elrendezési rendszer örökli a hibát, nem okozza. Az összevont régiók tagoszlopaik pontszélességeit összegzik, így egy Excelben egy oldalra férő összevonás MDW-sodródás után túllőhet, ami emlékezetre érdemes, amikor összevont cellás jelentéssablonokat épít. A zsugorításhoz igazítás a mért szövegszélességet ugyanahhoz az oszlopszélességhez méri, így a rossz MDW azt is megváltoztatja, mely cellák zsugorodnak és mennyit. Ugyanez az egységzavar család rajzolási horgonyoknál bukkan fel, ahol a képgeometria és EMU skálázás saját konverziólánccal rendelkezik, amelyet el lehet rontani

Két HotXLS oszlopvonalzó összehasonlítva, az egyik 7 pixel MDW-vel, a másik 8-cal mérve, bemutatva, hogyan halmozódik az oszloponkénti ugrás 64-ről 72 pixelre tíz oszlopon át, miközben az összevont régiók és a zsugorítás örökli a hibát
Mivel az MDW szorz, nem eltol, egyetlen rossz mérés minden oszlophatárt mozgat, és az összevont régiók és a zsugorítás öröklik a sodródást, anélkül hogy bármi dobna

Hogyan méri a HotXLS az MDW-t futásidőben

A HotXLS az MDW-t magából a munkafüzetből oldja fel, konstans feltételezése helyett, és két eljárás végzi a munkát. A PaginationApplyNormalFont leolvassa a Normal stílus betűtípusát a munkafüzetről, és a lapozási build tetején fut, bármilyen oszlopgeometria kiszámítása előtt; először Calibri 11-re áll vissza, így egy betűtípus-tábla nélküli munkafüzet nem örökölhet elavult állapotot egy korábbi buildből. A Normal stílus betűtípusa a styles.xml fonts[0]-ja, amelyet a komponens Workbook.Fonts[0]-ként tár fel

// Leolvassa a fonts[0]-t (a Normal stílus betűtípusát) a munkalap-munkafüzetről.
// A betűtípus-tábla nélküli klasszikus munkalapok megtartják a Calibri 11 alapértelmezést.
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;

A második eljárás, a PaginationMeasureMdW, a GDI-től kéri az egyetlen '0' karakter kiterjedését a GetTextExtentPoint32W-n át egy megosztott, képernyőn kívüli bitképvásznon, a tmAveCharWidth-ra esik vissza a GetTextMetricsW-ből, amikor a kiterjedési hívás elbukik, és a DefaultMDW-ra, amikor egyik sem elérhető. A gyorsítótára egyetlen hely, amelyet a (name, size) kulcsol, ami durvának hangzik, amíg meg nem nézi a hozzáférési mintát: egy lapozási build minden oldal minden oszlopánál ugyanazt a Normal betűtípust kéri, így egy hely közel tökéletes találati aránnyal bír, és hívásonként három összehasonlításba kerül

Mi történik betűtípus-tábla, GUI vagy betűtípus nélkül?

A HotXLS minden olyan esetben a Calibri 11 konstansra degradálódik, amikor a valódi Normal betűtípus nem határozható meg, és ezt tervezetten csendesen teszi. A klasszikus BIFF munkalapok a gyakori eset: a legacy formátumok nem hordoznak XLSX betűtípus-készletet, amelyre a fonts[0] hivatkozhat, így a típusőr korán kilép, és a 7-es alapértelmezett MDW áll. Ez nem javítás, hanem a korábbi viselkedés szándékos megőrzése, hogy a mérés XLSX útvonalra vétele ne regresszálhassa a klasszikus formátumú kimenetet

A GDI-függés az őszinte kikötés. A mérés Windows eszközkontextus ellen fut, így az útvonal telepített betűtípussal rendelkező Windows gazdát tételez fel. Szolgáltatásban vagy fej nélküli build agenten a GDI szövegmetrikák általában még feloldódnak, de egy azon a gépen telepítetlen betűtípust a betűtípus-leképező helyettesít, és Ön a helyettesítőt méri. Soha nem hibázik hangosan; hihető számot ad vissza rossz betűtípusra. Ha a kiszolgáló oldali exportnak asztali referenciához kell illeszkednie, telepítse a sablonjai által nevezett betűtípusokat az export gazdagépre, vagy rögzítse a Normal betűtípust, mielőtt meghívja a munkalap PDF export útvonalát

var
  Book: TXLSXWorkbook;
  Exporter: TXLSPDFExport;
begin
  Book := TXLSXWorkbook.Create;
  Exporter := TXLSPDFExport.Create;
  try
    Book.Open('quarterly-report.xlsx');

    // Rögzítse a Normal betűtípust, hogy az ezen a gazdagépen mért MDW az legyen,
    // amelyre az elrendezést tervezték, nem betűtípus-leképező helyettesítő.
    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;

Mérési gyorsítótárak, és az, amely Win64-en összeomlott

Amint a szövegmérés GDI oda-vissza út, nem szorzás, gyorsítótárazni kell, és a gyorsítótárazás egy render meneten belül az a hely, ahol ez a munka vért ejtett. A zsugorításhoz igazítás ciklusa 0,5 pt lépésekben csökkenti a betűméretet, és minden lépés után újra mér, így egyetlen cella tucatszor hívhatja a PaginationMeasureTextWidth-et ugyanazzal a sztringgel, és a szótördelés jelöltsoronként újra hívja. Egy memo, amelyet betűtípusnév, méret és szöveg kulcsol, ezt egyetlen GDI hívásra zsugorolja különálló sztringenként, TStringList-ben tárolva név/érték párokként

A mellette hozzáadott másik gyorsítótár nem volt ilyen rendben. Az 5. render menet cellánként oldja fel a betűkészletet FontIndex alapján, és a memoja párhuzamos dinamikus tömböket használt kézzel karbantartott FontMemoCount-tal. Az első verzió elfelejtette meghívni a ResetFontMemo-t minden oldal elején, így a számláló oldalakon át tovább mászott, miközben a tömbök nem, és a kód mindet túlírt a végén. Win32-n ez csendben a szomszédos kupacba kacsintott be, és befejeződött; Win64-en azonnal hozzáférési sértést dobott egy 0x538-ra íráskor. A általánosítható tanulság: egységszintű változóban tartott tömbalapú gyorsítótár minden őt használó menet belépésénél visszaállítandó, mert egy sztringlista vagy szótár a hiányzó visszaállítást növekedéssel megbocsátja, a párhuzamos tömbök nem

Hogyan oldja fel a HotXLS a munkafüzet Normal betűtípusát, méri meg annak Max Digit Width-ját GDI-n át két tartalékkal, és gyorsítótározza az eredményt, a két render menet memoja mellett és a párhuzamos tömbös gyorsítótár visszaállítási szabályával
Az MDW a munkafüzetből oldódik fel, betűtípusonként egyszer GDI-vel mérődik, majd kulcs szerint gyorsítótárazódik, a render menet memojai pedig megmutatják, miért kell a párhuzamos tömbös gyorsítótárat minden menet belépésénél visszaállítani

A saját konverziójának ellenőrzése

Nincs szüksége a komponensre, hogy bármelyikét ellenőrizze. Végyen egy munkafüzetet, amelynek Normal betűtípusa nem Calibri 11, olvasson egy szélességet a <col width="..."/>-ből, és futtassa át a specifikációs képleten kétszer, egyszer MDW 7-tel, egyszer azzal az MDW-vel, amelyet a renderelője ténylegesen mér az adott betűtípusra; ha a válaszok eltérnek, és a kimenete az elsőt egyezi, megtalálta a sodródást. Az oszlopgeometria a táblázatkezelő motor azon részei közé tartozik, amelyek vagy láthatatlanok, vagy az egyetlen dolog, amelyet bárki észrevesz, és helyesen eltalálni azt jelenti, hogy a Normal betűtípust az elrendezés bemeneteként kezeli, nem stílusrészletként. Ha olyan Delphi vagy C++Builder alkalmazásokat épít, amelyek Office telepítése nélkül olvasnak, írnak, renderelnek és nyomtatnak Excel munkafüzeteket, a HotXLS Delphi Excel komponens kezeli az MDW mérést, a lapozási modellt és a PDF pipeline-t egy VCL osztálykészlet mögött