Tehnički članak

Excel širina stupca i Max Digit Width (MDW) u Delphiju

Izvezeni PDF postavlja svaku granicu stupca pola znaka lijevo od mjesta gdje je Excel iscrtava, a svaka omotana ćelija sada se lomi na drugom mjestu. Širina stupca u Excelu ne mjeri se u znakovima ni u točkama. Mjeri se u jedinicama Max Digit Width (MDW) Normalnog fonta radne knjige, a HotXLS taj font mjeri putem GDI-ja prije svake izgradnje paginacije. Način otkazivanja je tih: ništa ne baca iznimku, pohranjene širine vraćaju se bajt-po-bajt netaknute, a geometrija je ipak odmaknuta za nekoliko postotaka po stupcu sve dok nakupljeni pomak tablicu s jedne stranice ne pogurne na dvije

U kojim se jedinicama mjeri širina stupca u Excelu?

Širina stupca u radnom listu jest broj znamenkastih znakova Normalnog fonta radne knjige, a ne apsolutna mjera. ECMA-376 §18.3.1.13 definira atribut width elementa <col> preko Maximum Digit Width tog fonta pri 96 dpi i daje pretvorbu iz pohranjene širine natrag u piksele kao odsječajući izraz nad MDW-om. Za Calibri 11, koji Excel isporučuje kao Normalni stil, MDW iznosi 7 piksela. Ako zadanu širinu od 8.43 jedinice provedete kroz formulu iz specifikacije s MDW-om 7, dobijete točno 64 piksela, što je 48 točaka pri 96 dpi. To su brojevi koje sam Excel prijavljuje, pa služe kao korisna provjera: ako vaša pretvorba reproducira 8.43 jedinice u 64 piksela, aritmetika je ispravna i jedino ulaz MDW još može biti kriv

const
  // Najveća širina znamenke (MDW) zadanog fonta teksta u pikselima pri 96 dpi.
  // Calibri 11 iznosi 7 px, što reproducira točne pikselne širine
  // koje Excel pohranjuje (8.43 jedinica -> 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 -> točke
  if Result < MinimumColumnWidth then
    Result := MinimumColumnWidth;
end;

HotXLS tu aritmetiku drži u točno jednoj funkciji, u jedinici lxPagination, pa postoji samo jedno mjesto na kojem mjerilo može biti krivo. + 5 je dopuna koju Excel dodaje za mrežne linije i margine ćelija, * 0.75 pretvara piksele pri 96 dpi u PostScript točke, a pod od MinimumColumnWidth postoji zato patološki uzak stupac i dalje ostavlja traku u koju iscrtivač može nacrtati rub. Javna ulazna točka ColumnWidthToPoints zadržava svoj stari potpis s jednim argumentom i prosljeđuje izmjereni MDW ovoj funkciji, što je promjenu ponašanja omogućilo bez diranja ijednog mjesta poziva

Lanac pretvorbe širine stupca u HotXLS-u u Delphiju koji izmjereni Max Digit Width Normalnog fonta radne knjige uvodi u formulu iz specifikacije tako pohranjena širina od 8.43 jedinice postaje 64 piksela, a zatim 48 točaka
Pohranjena širina jest broj znamenki, pa je izmjereni MDW Normalnog fonta ulaz u formulu, a ne detalj oblikovanja, a kružni put 8.43 u 64 u 48 provjerava aritmetiku

Zašto Normalni font koji nije Calibri pomjera svaku granicu

Pomak je multiplikativan, pa se stoga čita kao greška iscrtavanja, a ne greška jedinica. MDW je faktor na širinu, a ne pomak. Gurnite li MDW sa 7 na 8, zadani stupac od 8.43 jedinice ide s 64 piksela na 72, skok od 8 piksela ili 6 točaka na jednom stupcu. Deset takvih stupaca i desni rub tablice pomaknuo se za gotovo cijeli palec. Radne knjige koje ovo okidaju posve su obične: sve što je generirao alat za izvještaje koji u Normalni stil utiskuje Arial ili Segoe UI, sve što je spremljeno iz ERP predloška za izvoz, sve što je kupac jednom preoblikovao pa zaboravio

Dva povezana sustava rasporeda nasljeđuju grešku umjesto da je uzrokuju. Spojene regije zbrajaju širine u točkama svojih sastavnih stupaca, pa spoj koji je u Excelu stao na jednu stranicu može se prelijeti nakon MDW pomaka, što vrijedi zapamtiti kad gradite predloške izvještaja sa spojenim ćelijama. Shrink-to-fit uspoređuje izmjerenu širinu teksta s istom širinom stupca, pa pogrešan MDW mijenja i to koje se ćelije sažimaju i za koliko. Ista obitelj zabune jedinica pojavljuje se u sidrima crteža, gdje geometrija slika i EMU skaliranje ima svoj lanac pretvorbe u kojem se može pogriješiti

Usporedba dvaju HotXLS mjerila stupaca, jedno izmjereno s MDW-om 7 piksela, drugo s 8, prikazuje kako se skok po stupcu sa 64 na 72 piksela nakuplja kroz deset stupaca dok spojene regije i shrink-to-fit nasljeđuju grešku
Budući da MDW množi a ne pomjera, jedno pogrešno mjerenje pomjera svaku granicu stupca, a spojene regije i shrink-to-fit nasljeđuju pomak bez da išta baci iznimku

Kako HotXLS mjeri MDW tijekom izvođenja

HotXLS razrješuje MDW iz same radne knjige umjesto da pretpostavi konstantu, a dva postupka obavljaju posao. PaginationApplyNormalFont čita font Normalnog stila iz radne knjige i izvodi se na vrhu izgradnje paginacije, prije nego što se bilo koja geometrija stupca izračuna; prvo se vraća na Calibri 11, pa radna knjiga bez tablice fontova ne može naslijediti zastarjelo stanje od prethodne izgradnje. Font Normalnog stila jest fonts[0] u styles.xml, kojega komponenta izlaže kao Workbook.Fonts[0]

// Čita fonts[0] (font Normalnog stila) iz radne knjige lista.
// Klasični listovi bez tablice fontova zadržavaju zadanu vrijednost Calibri 11.
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;

Drugi postupak, PaginationMeasureMdW, od GDI-ja traži opseg pojedinačnog znaka '0' putem GetTextExtentPoint32W na dijeljenom izvanekranskom platnu bitmape, vraća se na tmAveCharWidth iz GetTextMetricsW kad poziv opsega ne uspije, a na DefaultMDW kad ništa od toga nije dostupno. Njegova predmemorija jest jedan utor ključan po (name, size), što zvuči grubo dok ne pogledate obrazac pristupa: izgradnja paginacije traži isti Normalni font na svakom stupcu svake stranice, pa jedan utor ima gotovo savršenu stopu pogodaka i košta tri usporedbe po pozivu

Što se događa bez tablice fontova, bez GUI-ja ili uz font koji nedostaje?

HotXLS se u svakom slučaju u kojem stvarni Normalni font ne može utvrditi vraća na konstantu Calibri 11, i to po dizajnu tiho. Klasični BIFF listovi uobičajeni su slučaj: naslijeđeni formati nemaju XLSX bazen fontova na koji bi se fonts[0] mogao pozvati, pa provjera tipa rano izlazi i zadani MDW od 7 ostaje na snazi. To nije popravak, to je namjerno sačuvano prethodno ponašanje, tako da dodavanje mjerenja XLSX putanji nije moglo regresirati izlaz klasičnog formata

GDI ovisnost jest iskrena napomena. Mjerenje se izvodi nad Windows kontekstom uređaja, pa putanja pretpostavlja Windows računalo s instaliranim fontom. U servisu ili agentu za gradnju bez sučelja GDI metrike teksta u pravilu se i dalje razrješuju, ali font koji nije instaliran na tom stroju zamjenjuje font mapper i vi mjerite zamjenu. Nikad ne otkazuje glasno; vraća uvjerljiv broj za pogrešan tip slova. Ako izvozi na poslužiteljskoj strani moraju odgovarati referenci sa stolnog računala, instalirajte fontove koje vaši predlošci navode na stroj s kojeg izvozite, ili zaključajte Normalni font prije poziva putanje PDF izvoza radnog lista

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

    // Zaključajte Normalni font da MDW izmjeren na ovom stroju bude onaj
    // prema kojemu je raspored dizajniran, a ne zamjena font-mappera.
    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;

Predmemorije mjerenja i ona koja je srušila Win64

Kad je mjerenje teksta odlazak do GDI-ja i natrag, a ne množenje, mora se predmemorirati, a predmemoriranje unutar prolaza iscrtavanja mjesto je na kojem je ovaj posao najviše zabolio. Petlja shrink-to-fit snižava veličinu fonta u koracima od 0.5 pt i ponovno mjeri nakon svakog koraka, pa jedna ćelija može pozvati PaginationMeasureTextWidth desetak puta s istim nizom, a prelamanje riječi poziva ga ponovno za svaki kandidatski redak. Bilješka ključana po imenu fonta, veličini i tekstu to svodi na jedan GDI poziv po različitom nizu, pohranjen u TStringList kao parovi ime/vrijednost

Druga predmemorija dodana uz nju nije bila tako uredna. Prolaz iscrtavanja 5 razrješuje bazen fontova po ćeliji preko FontIndex, a njegova je bilješka koristila paralelna dinamička polja s ručno vođenim FontMemoCount. Prva verzija zaboravila je pozvati ResetFontMemo na početku svake stranice, pa je brojač nastavio rasti kroz stranice dok polja nisu, i kod je pisao preko kraja svih njih. Na Win32 to je tiho šaralo po susjednoj hrpi i završilo; na Win64 odmah je podignuta iznimka pristupa pri pisanju na 0x538. Pouka koja se generalizira: predmemorija oslonjena na polje, držana u varijabli razine jedinice, mora se resetirati na ulazu u svaki prolaz koji je koristi, jer popis nizova ili rječnik izostavljeni reset oprašta rastom, a paralelna polja ne

Kako HotXLS razrješuje Normalni font radne knjige, mjeri njegov Max Digit Width putem GDI-ja s dvama rezervnim putanjama i predmemorira rezultat, uz dvije bilješke prolaza iscrtavanja i pravilo resetiranja koje predmemorija paralelnih polja treba
MDW se razrješuje iz radne knjige i mjeri GDI-jem jednom po fontu, a zatim predmemorira po ključu, dok bilješke prolaza iscrtavanja pokazuju zašto se predmemorija paralelnih polja mora resetirati na ulazu u svaki prolaz

Provjera vlastite pretvorbe

Ne trebate komponentu da biste ovo provjerili. Uzmite radnu knjigu čiji Normalni font nije Calibri 11, pročitajte širinu iz <col width="..."/> i provedite je dvaput kroz formulu iz specifikacije, jednom s MDW-om 7 i jednom s MDW-om koji vaš iscrtivač stvarno mjeri za taj font; ako se odgovori razlikuju i vaš izlaz odgovara prvom, našli ste pomak. Geometrija stupaca jedan je od onih dijelova motora proračunskih tablica koji je ili nevidljiv ili jedina stvar koju itko primijeti, a ispravno postaviti znači tretirati Normalni font kao ulaz u raspored, a ne detalj oblikovanja. Ako gradite Delphi ili C++Builder aplikacije koje čitaju, pišu, prikazuju i ispisuju Excel radne knjige bez instaliranog Officea, HotXLS Delphi Excel komponenta rješava MDW mjerenje, model paginacije i PDF cjevovod iza jednog skupa VCL klasa