Techninis straipsnis

Excel stulpelio plotis ir Max Digit Width (MDW) Delphi

Eksportuotas PDF kiekvieną stulpelio ribą pastato per pusę simbolio į kairę nuo ten, kur piešia Excel, ir kiekvienas laužomas langelis dabar laužiasi kitoje vietoje. Excel stulpelio plotis nematuojamas simboliais ar taškais. Jis matuojamas darbo knygos Normal šrifto Max Digit Width (MDW) vienetais, o HotXLS tą šriftą su GDI pamatuoja prieš kiekvieną puslapiavimo buildą. Gedimo režimas tylus: niekas nemeta, saugojami pločiai grįžta baitas po baito, o geometrija vis tiek klysta keliais procentais per stulpelį, kol sukauptas dreifas nustumia vieno puslapio lentelę ant dviejų

Kokiais vienetais matuojamas Excel stulpelio plotis?

Stulpelio plotis darbalapyje yra darbo knygos Normal šrifto skaitmenų simbolių skaičius, o ne absoliutus matas. ECMA-376 §18.3.1.13 apibrėžia <col> atributą width per to šrifto 96 dpi Maximum Digit Width ir duoda konversiją iš saugoto pločio atgal į pikselius kaip apipjaustančią išraišką ant MDW. Kalbant apie Calibri 11, kurį Excel siunčia kaip Normal stilių, MDW yra 7 pikseliai. Įleiskite numatytąjį 8.43 vienetų plotį per specifikacijos formulę su MDW 7 ir gausite tiksliai 64 pikselius, tai yra 48 taškus 96 dpi. Tie skaičiai yra tai, ką pats Excel praneša, tad jie naudinga patikra: jei jūsų konversija atkuria 8.43 vienetus į 64 pikselius, aritmetika teisinga, ir tik MDW įvestis dar gali būti bloga

const
  // Numatytojo pagrindinio šrifto Maximum digit width (MDW) pikseliais 96 dpi.
  // Calibri 11 yra 7 px, kas atkuria tikslius pikselių pločius,
  // kuriuos saugojo Excel (8.43 vienetai -> 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 pikseliai -> taškai
  if Result < MinimumColumnWidth then
    Result := MinimumColumnWidth;
end;

HotXLS laiko tą aritmetiką tiksliai vienoje funkcijoje, lxPagination modulyje, tad yra vienintelė vieta, kur liniuotė gali klysti. + 5 yra tarpas, kurį Excel prideda tinkleliams ir langelio paraštėms, * 0.75 konvertuoja 96 dpi pikselius į PostScript taškus, o grindis ties MinimumColumnWidth egzistuoja tam, kad patologiškai siauras stulpelis vis tiek paliktų juostą, į kurią rendereris gali nupiešti rėmelį. Viešas įėjimo taškas ColumnWidthToPoints išlaiko seną vieno argumento signatūrą ir perduoda pamatuotą MDW šiai funkcijai, kas leido elgesio pokyčiui atsirasti neliečiant nė vieno kvietimo vietos

HotXLS stulpelio pločio konversijos grandinė Delphi, įleidžianti pamatuotą darbo knygos Normal šrifto Max Digit Width į specifikacijos formulę, tad saugotas 8.43 vienetų plotis tampa 64 pikseliais ir paskui 48 taškais
Saugotas plotis yra skaitmenų skaičius, tad pamatuotas Normal šrifto MDW yra formulės įvestis, o ne stiliavimo detalė, ir 8.43 į 64 į 48 grįžtamasis kelias patikrina aritmetiką

Kodėl ne Calibri Normal šriftas pastumia kiekvieną ribą

Dreifas multiplikatyvus, todėl jis skaitomas kaip atvaizdavimo klaida, o ne vienetų klaida. MDW yra daugiklis ant pločio, o ne poslinkis. Pastumkite MDW nuo 7 iki 8, ir numatytasis 8.43 vienetų stulpelis eina nuo 64 pikselių iki 72 — šuolis 8 pikseliais arba 6 taškais viename stulpelyje. Dešimt tokių stulpelių, ir dešinysis lentelės kraštas pajudėjo beveik coliu. Darbo knygos, kurios užkabina tai, visiškai paprastos: bet kas, sugeneruota ataskaitų įrankio, kuris įspaudžia Arial ar Segoe UI į Normal stilių, bet kas, išsaugota iš ERP eksporto šablono, bet kas, ką klientas kartą perstilizavo ir užmiršo

Dvi susijusios maketo sistemos paveldi klaidą, o ne sukelia jos. Sujungti regionai sumuoja savo nario stulpelių taškų pločius, tad sujungimas, kuris tilpo viename puslapyje Excel, gali perpilti po MDW dreifo — verta prisiminti, kai statote sujungtų langelių ataskaitų šablonus. Shrink-to-fit lygina pamatuotą teksto plotį su tokiu pačiu stulpelio pločiu, tad blogas MDW taip pat keičia, kurie langeliai traukiasi ir kiek. Ta pati vienetų sumaišies šeima atsiranda piešimo inkaruose, kur vaizdo geometrija ir EMU mastelis turi savą konversijos grandinę, kurią galima sugadinti

Du HotXLS stulpelių liniuotės palyginimai, vienas pamatuotas su 7 pikselių MDW, kitas su 8, rodantys, kaip 64 iki 72 pikselių šuolis per stulpelį kaupiasi per dešimt stulpelių, o sujungti regionai ir shrink-to-fit paveldi klaidą
Kadangi MDW daugina, o ne prideda poslinkį, vienas blogas matavimas pajudina kiekvieną stulpelio ribą, o sujungti regionai ir shrink-to-fit paveldi dreifą be nieko metimo

Kaip HotXLS matuoja MDW vykdymo metu

HotXLS išsprendžia MDW iš pačios darbo knygos, o ne mano konstantą, ir dvi procedūros atlieka darbą. PaginationApplyNormalFont perskaito Normal stiliaus šriftą iš darbo knygos ir suveikia puslapiavimo buildo viršuje, prieš skaičiuojant bet kokią stulpelių geometriją; jis pirmiausia atstatomas į Calibri 11, tad darbo knyga be šriftų lentelės negali paveldėti pasenusios būsenos iš ankstesnio buildo. Normal stiliaus šriftas yra fonts[0] styles.xml, komponento atskleistas kaip Workbook.Fonts[0]

// Skaito fonts[0] (Normal stiliaus šriftą) iš darbalapio darbo knygos.
// Klasikiniai darbalapiai be šriftų lentelės laiko Calibri 11 numatytąjį.
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;

Antra procedūra, PaginationMeasureMdW, klauso GDI dėl vieno simbolio '0' išmatavimo per GetTextExtentPoint32W ant bendros už ekrano esančios bitmap drobės, grįžta prie tmAveCharWidthGetTextMetricsW, kai išmatavimo iškvietimas kliesta, ir grįžta prie DefaultMDW, kai nė vieno nėra. Jo talpykla yra vienas lizdas, raktuotas pagal (name, size), kas skamba primityviai, kol nepažiūrite į prieigos modelį: puslapiavimo buildas klauso to paties Normal šrifto ant kiekvieno kiekvieno puslapio stulpelio, tad vienas lizdas turi beveik tobulą pataikymo dažnį ir kainuoja tris lyginimus per iškvietimą

Kas nutinka be šriftų lentelės, be GUI ar su trūkstamu šriftu?

HotXLS sunyksta iki Calibri 11 konstantos kiekvienu atveju, kai tikrojo Normal šrifto negalima nustatyti, ir daro tai tyliai pagal dizainą. Klasikiniai BIFF darbalapiai yra dažniausias: paveldėti formatai neneša XLSX šriftų baseino, į kurį fonts[0] galėtų rodyti, tad tipo apsauga išeina anksti ir numatytasis MDW 7 lieka galiojęs. Tai ne pataisymas, tai ankstesnis elgesys, sąmoningai išsaugotas, kad matavimo pridėjimas į XLSX kelią negalėtų regresuoti klasikinio formato išvesties

GDI priklausomybė yra sąžiningas įspėjimas. Matavimas vyksta prieš Windows device context, tad kelias mano Windows prieglobą su įdiegtu šriftu. Servise arba be GUI buildo agente GDI teksto metrika paprastai vis tiek išsprendžia, bet šriftas, neįdiegtas toje mašinoje, pakeičiamas font mapperio ir jūs matuojate pakaitalą. Tai niekada nesvyla garsiai; jis grąžina tikėtiną skaičių neteisingam šriftui. Jei serverio pusės eksportai turi atitikti darbalaukio atskaitą, įdiekite šriftus, kuriuos įvardija jūsų šablonai, eksporto priegloboje, arba užfiksuokite Normal šriftą prieš kviesdami darbalapio PDF eksporto kelią

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

    // Užfiksuokite Normal šriftą, kad šiame priegloboje pamatuotas MDW būtų
    // tas, prieš kurį buvo suprojektuotas maketas, o ne font mapperio pakaital.
    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;

Matavimo talpyklos ir ta, kuri užgriuvo ant Win64

Kai teksto matavimas tampa GDI apvaliuoju keliu, o ne dauginimu, jį reikia talpinti, o talpinimas atvaizdavimo perėjimo viduje yra vieta, kur šis darbas praliejo kraujo. Shrink-to-fit ciklas žingsniuoja šrifto dydžiu žemyn 0.5 pt žingsniais ir perrašuoja po kiekvieno žingsnio, tad vienas langelis gali kviesti PaginationMeasureTextWidth tuziną kartų su ta pačia eilute, o žodžių laužymas kviečia jį dar kartą kiekvienai kandidatinei eilutei. Memo, raktuotas pagal šrifto vardą, dydį ir tekstą, suslegia tai į vieną GDI iškvietimą skirtingai eilutei, saugomą TStringList kaip name/value poros

Kita šalia pridėta talpykla nebuvo tokia tvarkinga. Atvaizdavimo perėjimas 5 išsprendžia šriftų baseiną per langelį pagal FontIndex, o jo memo naudojo lygiagrečius dinaminius masyvus su rankomis prižiūrimu FontMemoCount. Pirmoji versija užmiršo kviesti ResetFontMemo kiekvieno puslapio pradžioje, tad skaitiklis toliau lipa per puslapius, kol masyvai ne, ir kodas rašė pro visų jų galą. Ant Win32 tai tyliai subraižė į gretimą krūvą ir baigėsi; ant Win64 tai iškart kėlė access violation rašydamas į 0x538. Apibendrinama pamoka: masyvo pagrįsta talpykla, laikoma modulio lygio kintamajame, turi būti atstatoma kiekvieno perėjimo, kuris ją naudoja, įėjime, nes eilučių sąrašas ar žodynas atleidžia trūkstamą atstatymą augdamas, o lygiagretūs masyvai neatleidžia

Kaip HotXLS išsprendžia darbo knygos Normal šriftą, matuoja jo Max Digit Width per GDI su dviem atsarginiais keliais ir talpina rezultatą, šalia dviejų atvaizdavimo perėjimų memų ir atstatymo taisyklės, kurios reikia lygiagrečių masyvų talpyklai
MDW išsprendžiamas iš darbo knygos ir pamatuojamas su GDI kartą per šriftą, paskui talpinamas pagal raktą, o atvaizdavimo perėjimų memos rodo, kodėl lygiagrečių masyvų talpykla turi būti atstatoma kiekvieno perėjimo įėjime

Patikrinkite savo konversiją

Jums nereikia komponento, kad patvirtintumėte bet kurį iš šito. Paimkite darbo knygą, kurios Normal šriftas nėra Calibri 11, perskaitykite plotį iš <col width="..."/> ir paleiskite jį per specifikacijos formulę du kartus — kartą su MDW 7 ir kartą su MDW, kurį jūsų rendereris iš tikrųjų matuoja tam šriftui; jei atsakymai skiriasi ir jūsų išvestis atitinka pirmąjį, radote dreifą. Stulpelių geometrija yra viena iš tų skaičiuoklės variklio dalių, kurios arba nematoma, arba vienintelis dalykas, kurį kas nors pastebi, ir teisingas jos gavimas reiškia Normal šrifto traktavimą kaip maketo įvestį, o ne stiliavimo detalę. Jei statote Delphi ar C++Builder programas, skaitančias, rašančias, atvaizduojančias ir spausdinančias Excel darbo knygas be įdiegto Office, HotXLS Delphi Excel komponentas apdoroja MDW matavimą, puslapiavimo modelį ir PDF konvejerį už vieno VCL klasių rinkinio