Tehnički članak

Excel širina kolone i Max Digit Width (MDW) u Delphi-ju

Izvezeni PDF stavlja svaku granicu kolone pola znaka levo od mesta gde je Excel crta, i svaka omotana ćelija sada lomi na drugom mestu. Excel širina kolone se ne meri u znakovima ni u poenima. Meri se u Max Digit Width (MDW) jedinicama Normal fonta radne sveske, i HotXLS meri taj font GDI-jem pre svakog izgrađivanja paginacije. Režim greške je tih: ništa ne baca izuzetak, sačuvane širine kruže bajt po bajt, i geometrija je i dalje van za nekoliko procenata po koloni dok nakupljeni pomak ne gurne tabelu sa jedne stranice na dve

U kojoj je jedinici Excel širina kolone?

Širina kolone u radnom listu je broj znakova cifara Normal fonta radne sveske, ne apsolutno merenje. ECMA-376 §18.3.1.13 definiše width atribut <col> preko Maximum Digit Width tog fonta na 96 dpi, i daje konverziju sačuvane širine nazad u piksele kao odscajući izraz nad MDW. Za Calibri 11, koji Excel isporučuje kao Normal stil, MDW meri 7 piksela. Propustite podrazumevanu širinu od 8.43 jedinica kroz formulu specifikacije sa MDW 7 i dobijate tačno 64 piksela, što je 48 poena na 96 dpi. To su brojevi koje sam Excel prijavljuje, pa čine korisnu proveru: ako vaša konverzija reprodukuje 8.43 jedinice u 64 piksela, aritmetika je ispravna i samo MDW ulaz može još biti pogrešan

const
  // Maximum digit width (MDW) podrazumevanog fonta teksta u pikselima na 96 dpi.    
  // Calibri 11 meri 7 px, što reprodukuje tačne piksel širine
  // koje Excel čuva (8.43 jedinice -> 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 pikseli -> poeni
  if Result < MinimumColumnWidth then
    Result := MinimumColumnWidth;
end;

HotXLS drži tu aritmetiku u tačno jednoj funkciji, u lxPagination jedinici, pa postoji jedno jedino mesto gde lenjir može pogrešiti. + 5 je punjenje koje Excel dodaje za mrežne linije i margine ćelija, * 0.75 pretvara 96 dpi piksele u PostScript poene, i pod u MinimumColumnWidth postoji da patološki uska kolona i dalje ostavi traku u koju render može da nacrta ivicu. Javna ulazna tačka ColumnWidthToPoints zadržava svoj stari potpis sa jednim argumentom i prosleđuje izmereni MDW ovoj funkciji, što je omogućilo da promena ponašanja sleti bez dodirivanja ijednog mesta poziva

HotXLS lanac konverzije širine kolone u Delphi-ju, uvodeći izmereni Max Digit Width Normal fonta radne sveske u formulu specifikacije tako da sačuvana širina od 8.43 jedinice postane 64 piksela pa onda 48 poena
Sačuvana širina je broj cifara, pa je izmereni MDW Normal fonta ulaz u formulu a ne detalj stila, i kruženje 8.43 u 64 u 48 proverava aritmetiku

Zašto Normal font koji nije Calibri pomera svaku granicu

Pomak je multiplikativan, i zato izgleda kao greška renderovanja a ne greška jedinica. MDW je faktor na širini, ne ofset. Podignite MDW sa 7 na 8 i podrazumevana kolona od 8.43 jedinice prelazi sa 64 na 72 piksela, skok od 8 piksela ili 6 poena na jednoj koloni. Deset takvih kolona i desna ivica tabele se pomerila za skoro ceo inč. Radne sveske koje na ovo naiđu su potpuno obične: sve što je izgenerisao izveštajni alat koji utiskuje Arial ili Segoe UI u Normal stil, sve što je sačuvano iz ERP šablona za izvoz, sve što je kupac jednom prestilio i zaboravio

Dva srodna sistema rasporeda nasleđuju grešku umesto da je prouzrokuju. Spojene regije sabiraju poenske širine svojih članskih kolona, pa spajanje koje je stalo na jednu stranicu u Excel-u može da prekorači posle MDW pomaka, što vredi zapamtiti kad gradite šablone izveštaja sa spojenim ćelijama. Shrink-to-fit poredi izmerenu širinu teksta sa istom širinom kolone, pa pogrešan MDW takođe menja koje se ćelije skupljaju i za koliko. Ista porodica zabune jedinica javlja se u sidrima crteža, gde geometrija slika i EMU razmera ima svoj lanac konverzije koji može da se pokvari

Dva HotXLS lenjira kolona upoređena, jedan meren sa MDW od 7 piksela i drugi sa 8, pokazujući kako se skok po koloni sa 64 na 72 piksela gomila kroz deset kolona dok spojene regije i shrink-to-fit nasleđuju grešku
Pošto MDW množi umesto da pomera, jedno pogrešno merenje pomera svaku granicu kolone, a spojene regije i shrink-to-fit nasleđuju pomak bez ičega što baca izuzetak

Kako HotXLS meri MDW u vreme izvršavanja

HotXLS razrešava MDW iz same radne sveske umesto da pretpostavi konstantu, i dve procedure rade taj posao. PaginationApplyNormalFont čita Normal stil fonta sa radne sveske i izvršava se na vrhu izgrađivanja paginacije, pre nego što se bilo kakva geometrija kolone izračuna; prvo se resetuje na Calibri 11, pa radna sveska bez tabele fontova ne može da nasledi zastarelo stanje od prethodne izgradnje. Normal stil font je fonts[0] u styles.xml, izložen od komponente kao Workbook.Fonts[0]

procedure PaginationApplyNormalFont(Worksheet: TObject);
var
  Sh: TXLSXWorksheet;
  Fnt: TXLSFont;
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;

Druga procedura, PaginationMeasureMdW, pita GDI za obim jednog znaka '0' kroz GetTextExtentPoint32W na deljenom vanekranskom bitmap platnu, pada nazad na tmAveCharWidth iz GetTextMetricsW kada poziv obima ne uspe, i pada nazad na DefaultMDW kada nijedno nije dostupno. Njen keš je jedan slot sa ključem (ime, veličina), što zvuči grubo dok ne pogledate obrazac pristupa: izgrađivanje paginacije traži isti Normal font za svaku kolonu svake stranice, pa jedan slot ima skoro savršenu stopu pogodaka i košta tri poređenja po pozivu

Šta se dešava bez tabele fontova, bez GUI-ja ili sa fontom koji nedostaje?

HotXLS degradira na Calibri 11 konstantu u svakom slučaju gde se pravi Normal font ne može odrediti, i čini to tiho po dizajnu. Klasični BIFF radni listovi su uobičajeni slučaj: zastareli formati ne nose XLSX bazen fontova na koji fonts[0] može da se pozove, pa tip-guard izlazi rano i podrazumevani MDW od 7 stoji. To nije popravka, to je prethodno ponašanje sačuvano namerno, tako da dodavanje merenja na XLSX putanju nije moglo da regredira izlaz klasičnog formata

GDI zavisnost je iskrena napomena. Merenje se izvršava nad Windows device context, pa putanja pretpostavlja Windows domaćina sa instaliranim fontom. U servisu ili headless build agentu, GDI tekst metrike obično i dalje rešavaju, ali font koji nije instaliran na toj mašini font mapper zamenjuje i vi merite zamenicu. Nikad ne pada glasno; vraća uverljiv broj za pogrešan tip slova. Ako izvozi na strani servera moraju da se poklapaju sa referencom sa radne površine, instalirajte fontove koje vaši šabloni imenuju na export host, ili zakačite Normal font pre poziva putanje izvoza radnog lista u PDF

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

    // Zakačite Normal font da MDW izmeren na ovom domaćinu bude onaj
    // prema kojem je raspored dizajniran, ne font-mapper zamena.
    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;

Keševi merenja, i onaj koji je pukao na Win64

Kada je merenje teksta GDI odlazak i povratak umesto množenja, mora se keširati, a keširanje unutar prolaza renderovanja je mesto gde je ovaj posao prokrvario. Shrink-to-fit petlja spušta veličinu fonta u koracima od 0.5 pt i ponovo meri posle svakog koraka, pa jedna ćelija može da pozove PaginationMeasureTextWidth desetak puta sa istim stringom, i prelom reči ga poziva ponovo za svaku kandidatsku liniju. Memo sa ključem ime fonta, veličina, tekst svodi to na jedan GDI poziv po jedinstvenom stringu, čuvan u TStringList kao parovi ime/vrednost

Drugi keš dodat uz njega nije bio tako uredan. Render prolaz 5 razrešava bazen fontova po ćeliji preko FontIndex, i njegov memo je koristio paralelne dinamičke nizove sa ručno održavanim FontMemoCount. Prva verzija zaboravila je da pozove ResetFontMemo na početku svake stranice, pa je brojač nastavio da raste kroz stranice dok nizovi nisu, i kod je pisao preko kraja svih njih. Na Win32 je to tiho izgrebalo susedni heap i završilo; na Win64 diglo je access violation pri upisu na 0x538 odmah. Generalizovana lekcija: keš podržan nizom držan u unit nivoa promenljivoj mora se resetovati na ulazu u svaki prolaz koji ga koristi, jer string lista ili rečnik oprašta nedostajući reset rastom, a paralelni nizovi ne

Kako HotXLS razrešava Normal font radne sveske, meri njegov Max Digit Width kroz GDI sa dva rezervna puta, i kešira rezultat, pored dva memoa render prolaza i pravila resetovanja koja keš sa paralelnim nizovima traži
MDW se razrešava iz radne sveske i meri GDI-jem jednom po fontu, pa se kešira po ključu, dok memoi render prolaza pokazuju zašto keš sa paralelnim nizovima mora da se resetuje na ulazu u svaki prolaz

Provera sopstvene konverzije

Nije vam potrebna komponenta da biste ovo proverili. Uzmite radnu svesku čiji Normal font nije Calibri 11, pročitajte širinu iz <col width="..."/>, i provucite je kroz formulu specifikacije dva puta, jednom sa MDW 7 i jednom sa MDW koji vaš render stvarno meri za taj font; ako se odgovori razlikuju i vaš izlaz se poklapa sa prvim, našli ste pomak. Geometrija kolone je jedan od onih delova spreadsheet engine-a koji je ili nevidljiv ili jedina stvar koju neko primeti, i njeno ispravno urađivanje znači tretiranje Normal fonta kao ulaza u raspored a ne detalja stila. Ako gradite Delphi ili C++Builder aplikacije koje čitaju, pišu, renderuju i štampaju Excel radne sveske bez instaliranog Office-a, HotXLS Delphi Excel komponenta rukuje MDW merenjem, modelom paginacije i PDF cevovodom iza jednog skupa VCL klasa