Tehnični članak

Širina stolpca Excel in Max Digit Width (MDW) v Delphiju

Izvoženi PDF postavi vsako mejo stolpca za pol znaka levo od tistega, kjer jo nariše Excel, vsaka ovita celica pa se zdaj prelomi na drugem mestu. Širina stolpca v Excelu se ne meri v znakih ali točkah. Meri se v enotah Max Digit Width (MDW) pisave Normal delovnega zvezka, HotXLS pa to pisavo izmeri z GDI, preden vsakič zgradi paginacijo. Način odpovedi je tih: nič ne sproži izjeme, shranjene širine preživijo pot naprej in nazaj bajt za bajtom, geometrija pa je vseeno odmaknjena za nekaj odstotkov na stolpec, dokler nakopičen zdrs enostrani tabele ne potisne na dve strani

V kakšni enoti je širina stolpca v Excelu?

Širina stolpca v delovnem listu je štetje znakov števk pisave Normal delovnega zvezka, ne absolutna meritev. ECMA-376 §18.3.1.13 definira atribut width elementa <col> prek Maximum Digit Width te pisave pri 96 dpi in poda pretvorbo iz shranjene širine nazaj v slikovne pike kot odrezni izraz nad MDW. Za Calibri 11, ki ga Excel dobavlja kot stil Normal, MDW meri 7 slikovnih pik. Privzeto širino 8.43 enot pognite skozi formulo specifikacije z MDW 7 in dobite točno 64 slikovnih pik, kar je 48 točk pri 96 dpi. To so številke, ki jih poroča sam Excel, zato so koristen preizkus: če vaša pretvorba ponovi 8.43 enot v 64 slikovnih pik, je aritmetika prava in narobe je lahko le še vhod MDW

const
  // Max digit width (MDW) privzete pisave telesa, v slikovnih pikah pri 96 dpi.
  // Calibri 11 meri 7 px, kar ponovi točne širine v slikovnih pikah,
  // ki jih shrani Excel (8.43 enot -> 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 slikovne pike -> točke
  if Result < MinimumColumnWidth then
    Result := MinimumColumnWidth;
end;

HotXLS hrani to aritmetiko v natančno eni funkciji, v enoti lxPagination, zato obstaja en sam prostor, kjer se lahko ravnilo pokaže napačno. + 5 je oblazinjenje, ki ga Excel doda za črte mreže in robove celic, * 0.75 pretvori 96 dpi slikovne pike v točke PostScript, spodnja meja pri MinimumColumnWidth pa obstaja zato, da patološko ozek stolpec še vedno pusti pas, v katerega izrisovalnik lahko nariše obrobo. Javna vstopna točka ColumnWidthToPoints ohrani star podpis z enim argumentom in tej funkciji posreduje izmerjen MDW, kar je omogočilo, da je sprememba vedenja pristala, ne da bi se dotaknila enega samega mesta klica

Veriga pretvorb širine stolpcev HotXLS v Delphiju, ki izmerjeni Max Digit Width pisave Normal delovnega zvezka vpelje v formulo specifikacije, tako da shranjena širina 8.43 enot postane 64 slikovnih pik in nato 48 točk
Shranjena širina je štetje števk, zato je izmerjeni MDW pisave Normal vhod v formulo in ne podrobnost oblikovanja, povratna pot 8.43 v 64 v 48 pa preveri aritmetiko

Zakaj pisava Normal, ki ni Calibri, premakne vsako mejo

Zdrs je multiplikativen, zato se bere kot hrošč izrisovanja in ne kot hrošč enot. MDW je faktor na širini, ne odmik. Pomaknite MDW s 7 na 8 in privzeti stolpec 8.43 enot gre s 64 slikovnih pik na 72, skok 8 slikovnih pik ali 6 točk na enem stolpcu. Deset takih stolpcev in desni rob tabele se je premaknil za skoraj palec. Delovni zvezki, ki to sprožijo, so povsem običajni: vse, kar je ustvarilo orodje za poročila, ki v stil Normal odtisne Arial ali Segoe UI, vse, kar je shranjeno iz predloge za izvoz ERP, vse, kar je stranka enkrat preoblikovala in pozabila

Dva sorodna postavitvena sistema napako podedujeta in je ne povzročata. Združena območja seštejejo točkovne širine svojih članskih stolpcev, zato se lahko združitev, ki je v Excelu stala na eni strani, po zdrsu MDW prelije — tega je vredno pomniti, ko gradite predloge poročil z združenimi celicami. Shrink-to-fit primerja izmerjeno širino besedila z isto širino stolpca, zato napačni MDW spremeni tudi to, katere celice se skrčijo in za koliko. Isti družini zmede enot se približa pri sidrih risb, kjer ima geometrija slik in merilo EMU svojo verigo pretvorb, v kateri se gre zmotiti

Primerjava dveh ravnil stolpcev HotXLS, enega izmerjenega z MDW 7 slikovnih pik in enega z 8, ki prikazuje, kako se skok s 64 na 72 slikovnih pik na stolpec nabere čez deset stolpcev, združena območja in shrink-to-fit pa napako podedujeta
Ker MDW množi in ne odmika, ena napačna meritev premakne vsako mejo stolpcev, združena območja in shrink-to-fit pa zdrs podedujeta, ne da bi kaj sprožilo izjemo

Kako HotXLS meri MDW med tekom

HotXLS razreši MDW iz delovnega zvezka samega, namesto da bi domneval konstanto, delo pa opravita dve proceduri. PaginationApplyNormalFont prebere pisavo stila Normal z delovnega zvezka in teče na vrhu gradnje paginacije, preden se izračuna katera koli geometrija stolpcev; najprej se ponastavi na Calibri 11, zato delovni zvezek brez tabele pisav ne more podedovati zastarelega stanja prejšnje gradnje. Pisava stila Normal je fonts[0] v styles.xml, komponenta pa jo izpostavi kot Workbook.Fonts[0]

// Prebere fonts[0] (pisavo stila Normal) z delovnega zvezka delovnega lista.
// Klasični delovni listi brez tabele pisav obdržijo privzeti 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;

Druga procedura, PaginationMeasureMdW, vpraša GDI po obsegu enega samega znaka '0' prek GetTextExtentPoint32W na skupnem platnu bitne slike izven zaslona, ob neuspehu tega klica se vrne k tmAveCharWidth iz GetTextMetricsW, kadar pa ni na voljo nič od tega, k DefaultMDW. Njen predpomnilnik je en sam slot s ključem (name, size), kar zveni grobo, dokler ne pogledate vzorca dostopov: gradnja paginacije sprašuje po isti pisavi Normal na vsakem stolpcu vsake strani, zato ima en slot skoraj popolno stopnjo zadetkov in stane tri primerjave na klic

Kaj se zgodi brez tabele pisav, brez GUI ali z manjkajočo pisavo?

HotXLS propade do konstante Calibri 11 v vsakem primeru, ko prave pisave Normal ni mogoče določiti, in to po zasnovi tiho. Klasični delovni listi BIFF so pogosti primer: zapuščinski formati ne nosijo zbirk pisav XLSX, na katero bi se skliceval fonts[0], zato tipski varoval zgodaj izstopi in privzeti MDW 7 ostane veljaven. To ni popravek, to je prejšnje vedenje, namenoma ohranjeno, tako da dodajanje meritve na pot XLSX ni moglo regresirati izhoda klasičnega formata

Odvisnost od GDI je iskren pripombnik. Meritev teče proti kontekstu naprave Windows, zato pot domneva gostitelja Windows z nameščeno pisavo. V storitvi ali brezglavem gradbenem agentu se metrike besedil GDI splošno še vedno razrešijo, pisava, ki ni nameščena na tistem stroju, pa jo zamenja mapirnik pisav in vi merite zamenjavo. Nikoli ne odpove glasno; vrne verodostojno številko za napačno pisavo. Če morajo izvozi na strani strežnika ustrezati namizni referenci, namestite na izvoznega gostitelja pisave, ki jih poimenujejo vaše predloge, ali pa pripnite pisavo Normal, preden pokličete pot izvoza delovnega lista v PDF

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

    // Pripni pisavo Normal, da je MDW, izmerjeno na tem gostitelju, tisto,
    // proti kateremu je postavitev zasnovana, in ne zamenjava iz mapirnika pisav.
    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;

Predpomnilniki meritev in tisti, ki je sesul Win64

Ko je merjenje besedila GDI-potovanje naprej in nazaj in ne množenje, ga je treba predpomniti, predpomnjenje znotraj prehoda izrisovanja pa je mesto, kjer je to delo krvavelo. Zanka shrink-to-fit stopnjuje velikost pisave navzdol v korakih 0.5 pt in po vsakem koraku znova meri, zato lahko ena celica PaginationMeasureTextWidth pokliče ducatkrat z istim nizom, prelom besed pa ga pokliče še enkrat na kandidatno vrstico. Zapomnilnik s ključem ime pisave, velikost in besedilo to strne v en GDI-klic na različen niz, shranjen v TStringList kot pari ime/vrednost

Drugi predpomnilnik, dodan ob njem, pa ni bil tako urejen. Prehod izrisovanja 5 razreši zbirko pisav na celico po FontIndex, njegov zapomnilnik pa je uporabljal vzporedna dinamična polja z ročno vzdrževanim FontMemoCount. Prva različica je pozabila poklicati ResetFontMemo na začetku vsake strani, zato je števec čez strani stalno rastel, polja pa ne, koda pa je zapisala čez konec vseh. Na Win32 je to tiho črtalo po sosednji kopici in končalo; na Win64 pa je takoj sprožilo kršitev dostopa ob zapisu na 0x538. Generalizirana lekcija: predpomnilnik, ki ga nosi polje in živi v spremenljivki na ravni enote, je treba ponastaviti na vstopu vsakega prehoda, ki ga uporablja, ker seznam nizov ali slovar manjkajočo ponastavitev odpusti z rastjo, vzporedna polja pa ne

Kako HotXLS razreši pisavo Normal delovnega zvezka, izmeri njen Max Digit Width prek GDI z dvema rezervnima potema in predpomni rezultat, poleg tega dva zapomnilnika prehodov izrisovanja in pravilo ponastavitve, ki ga potrebuje predpomnilnik iz vzporednih polj
MDW se razreši iz delovnega zvezka in izmeri z GDI enkrat na pisavo, nato pa se predpomni po ključu; zapomnilnika prehodov izrisovanja pa prikazujeta, zakaj se mora predpomnilnik iz vzporednih polj ponastaviti na vstopu vsakega prehoda

Preverjanje lastne pretvorbe

Te komponente ne potrebujete, da preverite katero koli od teh stvari. Vzemite delovni zvezek, čigar pisava Normal ni Calibri 11, preberite širino iz <col width="..."/> in jo dvakrat zaženite skozi formulo specifikacije, enkrat z MDW 7 in enkrat z MDW, ki ga vaš izrisovalnik za to pisavo dejansko izmeri; če se odgovora razlikujeta in vaš izhod ustreza prvemu, ste zdrs našli. Geometrija stolpcev je ena tistih delov pogona preglednic, ki je bodisi nevidna bodisi edina stvar, ki jo kdorkoli opazi, pravilna pa pomeni, da pisavo Normal obravnavate kot vhod v postavitev in ne kot podrobnost oblikovanja. Če gradite aplikacije Delphi ali C++Builder, ki berejo, pišejo, izrisujejo in tiskajo delovne zvezke Excel brez nameščenega Officea, komponenta Excel HotXLS za Delphi opravi merjenje MDW, model paginacije in cevovod PDF za enim naborom razredov VCL