Verilen PDF, her sütun sınırını Excelin çizdiği yerin yarım karakter soluna koyar ve artık kaydırılan her hücre farklı bir yerden kırılır. Excel sütun genişliği karakter ya da punto cinsinden ölçülmez. Çalışma kitabının Normal yazı tipinin Maksimum Rakam Genişliği (MDW) birimiyle ölçülür ve HotXLS, her sayfalama kurulumundan önce o yazı tipini GDI ile ölçer. Hata biçimi sessizdir: hiçbir şey özel durum fırlatmaz, depolanan genişlikler bayt bayt gidiş dönüş yapar ve yine de geometri sütun başına yüzde birkaç sapar; ta ki biriken kayma, tek sayfalık bir tabloyu iki sayfaya itene kadar
Excel sütun genişliği hangi birimdedir?
Bir çalışma sayfasındaki sütun genişliği, mutlak bir ölçüm değil, çalışma kitabının Normal yazı tipinin rakam karakterlerinin sayısıdır. ECMA-376 §18.3.1.13, <col> öğesinin width özniteliğini o yazı tipinin 96 dpi karşısındaki Maksimum Rakam Genişliği cinsinden tanımlar ve depolanan genişlikten piksele dönüşü MDW üzerinde kesmeli bir ifade olarak verir. Excelin Normal stili olarak sunduğu Calibri 11 için MDW 7 piksel ölçülür. Varsayılan genişlik olan 8,43 birimi MDW 7 ile belirtim formülünden geçirdiğinizde tam olarak 64 piksel elde edersiniz; bu, 96 dpide 48 puntodur. Bunlar Excelin kendisinin bildirdiği sayılardır, bu yüzden kullanışlı bir denetim olurlar: dönüşümünüz 8,43 birimi 64 piksele yeniden üretiyorsa aritmetik doğrudur ve yalnızca MDW girdisi hâlâ yanlış olabilir
const
// Varsayılan gövde yazı tipinin Maksimum Rakam Genişliği (MDW),
// 96 dpide piksel cinsinden. Calibri 11, 7 px ölçülür ve Excelin
// depoladığı tam piksel genişliklerini yeniden üretir (8,43 birim -> 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 pikselleri -> punto
if Result < MinimumColumnWidth then
Result := MinimumColumnWidth;
end;
HotXLS bu aritmetiği tam olarak tek bir işlevde, lxPagination biriminde tutar; böylece cetvelin yanlış olabileceği tek bir yer vardır. + 5, Excelin kılavuz çizgileri ve hücre kenar boşlukları için eklediği dolgudur; * 0.75, 96 dpi piksellerini PostScript puanlarına çevirir ve MinimumColumnWidth tabanı, patolojik derecede dar bir sütunun yine de işleyicinin içine kenarlık çizebileceği bir şerit bırakması için vardır. Ortak giriş noktası ColumnWidthToPoints eski tek bağımsız değişkenli imzasını korur ve ölçülen bir MDW değerini bu işleve iletir; davranış değişikliğinin tek bir çağrı noktasına bile dokunmadan hayata geçmesini sağlayan da budur
Calibri dışı bir Normal yazı tipi neden her sınırı oynatır?
Kayma çarpımsaldır; bu yüzden bir birim hatası değil, işleme hatası gibi okunur. MDW genişlik üzerinde bir öteleme değil, bir çarpandır. MDW 7 pikselden 8 piksele çıktığında varsayılan 8,43 birimlik sütun 64 pikselden 72 piksele gider; tek sütunda 8 piksel ya da 6 puntoluk bir sıçrama. Bundan on sütun olduğunda tablonun sağ kenarı bir inçin çoğu kadar kaymış olur. Bunu tetikleyen çalışma kitapları tamamen sıradandır: Normal stiline Arial ya da Segoe UI damgalayan bir raporlama aracının ürettiği her şey, bir ERP dışa aktarım şablonundan kaydedilen her şey, bir müşterinin bir kez stil verip unuttuğu her şey
İki ilişkili yerleşim sistemi hataya neden olmak yerine onu miras alır. Birleştirilmiş bölgeler üye sütunların punto genişliklerini toplar; bu yüzden Excelde tek sayfaya sığan bir birleştirme, MDW kaymasından sonra taşabilir; birleştirilmiş hücreli rapor şablonları kurarken bunu hatırlamaya değer. Sığdırmak için küçült, ölçülen metin genişliğini aynı sütun genişliğiyle karşılaştırır; bu yüzden yanlış MDW hangi hücrelerin ne kadar küçüleceğini de değiştirir. Aynı birim karışıklığı ailesi çizim çapalarında da karşımıza çıkar; görsel geometrisi ve EMU ölçekleme orada yanlış yapılabilecek kendi dönüşüm zincirine sahiptir
HotXLS MDW değerini çalışma zamanında nasıl ölçer?
HotXLS MDW değerini bir sabit varsaymak yerine çalışma kitabının kendisinden çözer ve işi iki yordam yapar. PaginationApplyNormalFont, Normal stil yazı tipini çalışma kitabından okur ve herhangi bir sütun geometrisi hesaplanmadan önce, sayfalama kurulumunun başında çalışır; önce Calibri 11 değerine sıfırlar, böylece yazı tipi tablosu olmayan bir çalışma kitabı önceki kurulumdan bayat durumu miras alamaz. Normal stil yazı tipi, styles.xml içindeki fonts[0] konumudur ve bileşen tarafından Workbook.Fonts[0] olarak sunulur
// fonts[0] yazı tipini (Normal stil yazı tipi) çalışma sayfasının çalışma kitabından okur.
// Yazı tipi tablosu olmayan klasik çalışma sayfaları Calibri 11 varsayılanını korur.
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;
İkinci yordam olan PaginationMeasureMdW, paylaşılan bir ekran dışı bit eşlem tuvali üzerinde GetTextExtentPoint32W aracılığıyla GDI üzerinden tek '0' karakterinin kapsamını ister; kapsam çağrısı başarısız olduğunda GetTextMetricsW içindeki tmAveCharWidth değerine, ikisi de kullanılamadığında DefaultMDW değerine geri düşer. Önbelleği, anahtarı (name, size) olan tek bir yuvadır; erişim örüntüsüne bakana kadar kulağa kaba gelir: bir sayfalama kurulumu her sayfanın her sütununda aynı Normal yazı tipini ister, bu yüzden tek yuva neredeyse kusursuz bir isabet oranına sahiptir ve çağrı başına üç karşılaştırmaya mal olur
Yazı tipi tablosu, GUI ya da yazı tipi olmadığında ne olur?
HotXLS, gerçek Normal yazı tipinin belirlenemediği her durumda Calibri 11 sabitine geri düşer ve bunu tasarım gereği sessizce yapar. Klasik BIFF çalışma sayfaları yaygın olan durumdur: eski biçimler fonts[0] konumunun başvuracağı bir XLSX yazı tipi havuzu taşımaz, bu yüzden tür koruması erken çıkar ve varsayılan MDW olan 7 geçerli kalır. Bu bir onarım değildir; ölçümün XLSX yoluna eklenmesinin klasik biçim çıktısını geriletememesi için bilinçli olarak korunan önceki davranıştır
GDI bağımlılığı dürüst uyarıdır. Ölçüm bir Windows aygıt bağlamı üzerinde çalışır; bu yüzden yol, yazı tipinin kurulu olduğu bir Windows konağı varsayar. Bir hizmette ya da başsız bir kurulum aracısında GDI metin ölçümleri genellikle yine çözülür, ancak o makinede kurulu olmayan bir yazı tipi yazı tipi eşleyici tarafından ikame edilir ve onun yerine ikame olanı ölçersiniz. Asla yüksek sesle başarısız olmaz; yanlış yazı tipi için makul bir sayı döndürür. Sunucu tarafı dışa aktarımlar bir masaüstü başvurusuyla eşleşmek zorundaysa, şablonlarınızın adlandırdığı yazı tiplerini dışa aktarım konağına kurun ya da çalışma sayfası PDF dışa aktarım yolunu çağırmadan önce Normal yazı tipini sabitleyin
var
Book: TXLSXWorkbook;
Exporter: TXLSPDFExport;
begin
Book := TXLSXWorkbook.Create;
Exporter := TXLSPDFExport.Create;
try
Book.Open('quarterly-report.xlsx');
// Normal yazı tipini sabitleyin; bu konakta ölçülen MDW, yerleşimin
// ona göre tasarlandığı değer olsun, yazı tipi eşleyici ikamesi değil.
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;
Ölçüm önbellekleri ve Win64 üzerinde çöken tanesi
Metin ölçümü bir çarpma yerine bir GDI gidiş dönüşü olduğunda, önbelleğe alınması gerekir ve bir işleme geçişi içinde önbelleğe alma, bu işin kan döktüğü yerdir. Sığdırmak için küçült döngüsü yazı tipi boyutunu 0,5 pt adımlarla düşürür ve her adımdan sonra yeniden ölçer; bu yüzden tek bir hücre PaginationMeasureTextWidth işlevini aynı dizgeyle bir düzine kez çağırabilir ve sözcük kaydırma onu her aday satır için yeniden çağırır. Anahtarı yazı tipi adı, boyutu ve metni olan bir memo, bunu her ayrı dizge için tek bir GDI çağrısına indirger ve name/value çiftleri olarak bir TStringList içinde depolanır
Yanına eklenen diğer önbellek o kadar düzenli değildi. İşleme geçişi 5, yazı tipi havuzunu hücre başına FontIndex ile çözer ve memosu, elle bakımı yapılan FontMemoCount ile paralel dinamik diziler kullanıyordu. İlk sürüm her sayfanın başında ResetFontMemo çağırmayı unuttu; bu yüzden sayfalar ilerledikçe sayaç tırmanırken diziler tırmanmadı ve kod hepsinin sonunu aşarak yazdı. Win32 üzerinde bu sessizce komşu yığına karalayıp bitiriyordu; Win64 üzerinde ise 0x538 adresine yazmada anında bir erişim ihlali yükseltiyordu. Genelleştirilebilir ders: birim düzeyi değişkende tutulan dizi destekli bir önbellek, onu kullanan her geçişin girişinde sıfırlanmalıdır; çünkü bir dizge listesi ya da sözlük eksik sıfırlamayı büyüyerek bağışlar ama paralel diziler bağışlamaz
Kendi dönüşümünüzü denetleme
Bunların hiçbirini doğrulamak için bileşene gereksiniminiz yok. Normal yazı tipi Calibri 11 olmayan bir çalışma kitabı alın, <col width="..."/> içinden bir genişlik okuyun ve belirtim formülünden iki kez geçirin: bir kez MDW 7 ile, bir kez işleyicinizin o yazı tipi için gerçekte ölçtüğü MDW ile; yanıtlar farklıysa ve çıktınız birinciyle eşleşiyorsa kaymayı buldunuz demektir. Sütun geometrisi, elektronik tablo motorunun ya görünmez olan ya da herkesin fark ettiği tek şey olan parçalarından biridir ve onu doğru yapmak, Normal yazı tipini bir stil ayrıntısı yerine yerleşimin girdisi saymak demektir. Office kurulu olmadan Excel çalışma kitaplarını okuyan, yazan, işleyen ve yazdıran Delphi ya da C++Builder uygulamaları geliştiriyorsanız, HotXLS Delphi Excel bileşeni MDW ölçümünü, sayfalama modelini ve PDF işlem hattını tek bir VCL sınıf kümesinin ardında yönetir