Anda menaruh logo 600×400 piksel di header invoice yang dihasilkan, tampilannya pas di monitor pengembangan 96-DPI, lalu seminggu kemudian seorang pelanggan di laptop high-DPI melaporkan hasil cetaknya sebesar perangko. Pikselnya tidak berubah. Yang berubah adalah asumsi bahwa jumlah piksel sama dengan ukuran fisik, padahal di OOXML tidak demikian. Gambar spreadsheet membawa dimensinya dalam EMU, dan selama Anda belum berpikir dalam EMU, atau dalam satuan dunia nyata yang dipetakan langsung ke sana, tata letak Anda akan bergantung pada DPI apa pun yang diasumsikan mesin rendering
HotXLS adalah komponen spreadsheet VCL native untuk Delphi dan C++Builder yang membaca dan menulis XLS dan XLSX tanpa Excel atau dependensi COM apa pun. Sejak v2.91.0, objek gambar XLSX tidak lagi memaksa Anda menghitung satuan secara manual: selain EMU mentah, objek ini mengekspos lebar dan tinggi dalam sentimeter, inci, dan poin, plus metode Scale yang mengubah ukuran berdasarkan persentase dengan penguncian rasio aspek opsional. Artikel ini membahas apa itu EMU sebenarnya, mengapa DrawingML memilihnya, dan bagaimana memakai permukaan geometri baru untuk menempatkan gambar berdasarkan ukuran fisik, bukan berdasarkan jumlah piksel yang tidak bisa Anda percayai
Apa itu EMU dan mengapa DrawingML memakainya
EMU adalah singkatan dari English Metric Unit, dan itu adalah satuan panjang dasar DrawingML, lapisan gambar yang dipakai bersama di seluruh keluarga Office Open XML (ECMA-376, Part 1, §20). Satu EMU didefinisikan sehingga tepat ada 914400 EMU per inci dan 360000 EMU per sentimeter. Dua konstanta itulah alasan satuan ini ada. 914400 habis dibagi 2, 3, 4, 5, 6, 8, 9, 10, 12, dan banyak lagi; faktornya adalah 26 × 32 × 52 × 127. Karena 1 inci = 2.54 cm tepat, memilih satuan yang habis dibagi baik oleh 360000 maupun oleh pecahan bersih dari 914400 membuat format dapat mengekspresikan inci, sentimeter, dan poin sebagai bilangan bulat tanpa pembulatan di batas satuan. Saat angka floating-point "1.27 cm" akan meleset, EMU menyimpan 457200 dan tetap presisi
Satuan lain yang penting di sini adalah poin. Poin tipografi adalah 1/72 inci, jadi ada 12700 EMU per poin (914400 / 72). Poin adalah cara Excel memikirkan tinggi baris, ukuran font, dan margin di balik layar, jadi mengekspos geometri gambar dalam poin berguna ketika Anda ingin gambar sejajar dengan metrik teks, bukan dengan penggaris cetak. HotXLS mengenkode keempat relasi ini sebagai konstanta satuan di library:
const
XlsxEmuPerInch = 914400; // 1 inch
XlsxEmuPerCm = 360000; // 1 centimetre
XlsxEmuPerPoint = 12700; // 1 point (1/72 inch)
XlsxEmuPerPixel = 9525; // 1 pixel at 96 DPI (914400 / 96)
Baris terakhir itulah inti bug ukuran perangko. Piksel baru punya ukuran fisik setelah Anda menetapkan DPI, dan 9525 EMU adalah ukuran satu piksel, khusus pada 96 DPI. DPI rendering bawaan Excel adalah 96, jadi gambar 100 piksel akan jatuh pada 100 × 9525 = 952500 EMU ≈ 2.54 cm pada setelan default, tetapi tidak ada apa pun di file yang menjamin konsumen memakai 96. Tulis dalam satuan nyata dan ambiguitas itu hilang: 4 cm tetap 4 cm, baik layar 96 maupun 220 DPI
Permukaan geometri TXLSXImage
Gambar yang disematkan di HotXLS adalah TXLSXImage. Penyimpanan kanoniknya terdiri dari dua field integer, WidthEMU dan HeightEMU, yang ditambatkan ke Row dan Col berbasis satu, yaitu sel kiri atas tempat gambar melekat. Properti satuan nyata adalah tampilan terhitung di atas field EMU itu, bukan state terpisah, sehingga membaca WidthCM membagi EMU dengan 360000, dan menuliskannya mengalikan lalu membulatkan kembali. Jadi setiap dimensi yang Anda set hanyalah cara lain menulis nilai EMU yang sama di bawahnya:
WidthInch/HeightInch: EMU ÷ 914400WidthCM/HeightCM: EMU ÷ 360000WidthPt/HeightPt: EMU ÷ 12700WidthEMU/HeightEMU: sumber kebenaran bilangan bulat
Anda menambahkan gambar dengan AddImage(ARow, ACol, AData, AFormat), dengan meneruskan byte hasil enkode mentah dan TXLSXImageFormat (xlsxImagePng, xlsxImageJpeg, xlsxImageGif, atau xlsxImageBmp); fungsi ini mengembalikan indeks berbasis nol ke koleksi Images lembar kerja. Ada juga AddImageFromFile(ARow, ACol, AFileName), yang menyimpulkan format dari ekstensi file. Perhatikan basis indeksnya: AddImage mengembalikan berbasis nol dan Images[] juga berbasis nol, yang sengaja berbeda dari grid Cells[Row, Col] yang berbasis satu, jadi jangan mengira keduanya sama
var
Sheet: TXLSXWorksheet;
Img: TXLSXImage;
Idx: Integer;
begin
Sheet := Workbook.Sheets.Add('Images');
// Anchor a PNG at row 3, column 2; AddImage returns a 0-based index.
Idx := Sheet.AddImage(3, 2, LogoBytes, xlsxImagePng);
Img := Sheet.Images[Idx];
Img.WidthCM := 4.0; // 4 cm wide -> 1440000 EMU
Img.HeightCM := 3.0; // 3 cm tall -> 1080000 EMU
// Same geometry, read back in other units.
// Img.WidthPt is now 113.39 pt, Img.WidthInch is 1.5748 in.
end;
Gambar yang baru dibuat secara default berukuran 100×100 piksel, yaitu 952500 EMU persegi, kira-kira kotak 2.54 cm pada 96 DPI. Default itu ada supaya gambar tetap terlihat meskipun Anda lupa memberi ukuran, tetapi untuk tata letak yang serius Anda sebaiknya menetapkan ukuran fisik eksplisit, bukan mengandalkan default turunan piksel
Skala, dan flag rasio aspek
Ketika Anda ingin mengubah ukuran relatif terhadap dimensi saat ini, bukan ke target absolut, misalnya mengecilkan gambar bagan menjadi 60 persen dari ukuran saat diimpor, gunakan Scale:
procedure Scale(APercent: Double; AKeepAspect: Boolean = True);
APercent adalah persentase, dengan 100 berarti tidak berubah, 150 menambah setengah ukuran, dan 50 membagi dua. Dengan AKeepAspect pada nilai bawaan True, lebar dan tinggi sama-sama dikalikan faktor yang sama, sehingga proporsi tetap terjaga dan gambar 4×3 cm menjadi 6×4.5 cm setelah Scale(150). Jika Anda memberi False, hanya lebar yang diskalakan, sedangkan tinggi dibiarkan persis seperti semula. Asimetri ini disengaja: ketika Anda ingin meregangkan satu sumbu secara independen, alat yang tepat adalah setter eksplisit WidthCM/HeightCM, dan cabang non-aspek di Scale ada untuk kasus yang lebih sempit, yaitu menyesuaikan lebar saja. Mudah sekali membaca Scale(150, False) sebagai "meregangkan keduanya bebas" lalu terkejut, jadi pakailah setter ketika yang Anda maksud memang dua dimensi yang berdiri sendiri
Img.WidthCM := 4.0;
Img.HeightCM := 3.0;
Img.Scale(150); // aspect locked: now 6.0 x 4.5 cm
Img.Scale(100); // no-op, returns immediately
Img.Scale(50, False); // width only: 3.0 cm wide, height unchanged at 4.5 cm
Satu perilaku kecil yang perlu diketahui: Scale(100) melakukan short-circuit dan kembali tanpa menyentuh salah satu field, jadi aman dipanggil tanpa syarat di dalam loop saat persentasenya mungkin 100. Dan karena geometri disimpan sebagai EMU bilangan bulat, setiap setter melakukan pembulatan. Jadi round-trip melalui sentimeter pecahan dapat bergeser sebesar sebagian kecil EMU, jauh di bawah apa pun yang terlihat, tetapi layak diingat jika Anda pernah menegaskan kesetaraan tepat dalam tes. Untuk kontrol piksel sempurna, set WidthEMU dan HeightEMU secara langsung dan lewati konversi satuan sepenuhnya
Membaca kembali geometri
Koleksi gambar bisa diquery, dan itu penting saat Anda memuat workbook yang sudah ada lalu perlu memeriksa atau menyesuaikan apa yang sudah terpasang di sana, bukan apa yang baru saja Anda tambahkan. Images.Count menghitung setiap gambar di lembar, Images[i] mengindeksnya berbasis nol, dan FindAt(ARow, ACol) mengembalikan gambar yang ditambatkan pada sel tertentu, atau nil jika tidak ada. Ada juga IndexOfCell untuk indeks, bukan objeknya, serta DeleteAt / DeleteInRange untuk penghapusan
var
i: Integer;
Img: TXLSXImage;
begin
for i := 0 to Sheet.Images.Count - 1 do
begin
Img := Sheet.Images[i];
Writeln(Format('[%d] R%dC%d %.2f x %.2f cm (%d x %d EMU)',
[i, Img.Row, Img.Col, Img.WidthCM, Img.HeightCM,
Img.WidthEMU, Img.HeightEMU]));
end;
Img := Sheet.Images.FindAt(3, 2); // nil-check before use
if Img <> nil then
Img.Scale(80);
end;
Karena properti satuan nyata adalah tampilan langsung, gambar yang diimpor dari alat lain pada ukuran EMU tertentu akan langsung melaporkan geometrinya dalam sentimeter, tanpa langkah konversi dari pihak Anda. Ini cocok secara alami dengan model drawing yang lebih luas; jika Anda menempatkan bagan dan shape selain gambar raster, panduan pendamping tentang bagan, gambar, dan objek drawing Excel di Delphi menjelaskan model anchor yang dipakai bersama oleh objek-objek itu
Margin penyiapan halaman metrik
Ketegangan yang sama antara EMU dan satuan nyata muncul satu tingkat lebih luar, di halaman. OOXML dan Excel menyimpan margin cetak dalam inci, yang canggung jika templat laporan Anda ditentukan dalam milimeter seperti kebanyakan dunia di luar AS. v2.91.0 menambahkan pembungkus sentimeter di atas margin inci: MarginLeftCM, MarginRightCM, MarginTopCM, MarginBottomCM, MarginHeaderCM, dan MarginFooterCM. Masing-masing adalah lapisan kenyamanan tipis di atas properti inci yang sesuai, dengan konversi pada rasio tepat 1 inci = 2.54 cm
Sheet.MarginLeftCM := 2.0; // 2 cm == 0.7874 inch
Sheet.MarginRightCM := 2.0;
Sheet.MarginTopCM := 2.5;
Sheet.MarginBottomCM := 2.5;
Sheet.MarginHeaderCM := 1.0;
Sheet.MarginFooterCM := 1.0;
Properti inci (MarginLeft dan sejenisnya) tetap menjadi penyimpanan kanonik, jadi Anda bisa mencampur keduanya, misalnya mengatur margin atas dalam sentimeter lalu membacanya kembali dalam inci, atau sebaliknya, dan file yang ditulis ke disk tetap identik. Konversinya hanyalah perkalian biasa dengan 2.54, tanpa pembulatan ke kisi kasar, jadi 2 cm tetap 2 cm dengan presisi double penuh. Ini adalah filosofi kemudahan metrik yang sama seperti pada geometri gambar: format berbicara dalam sistem imperial di balik layar, dan library membiarkan Anda menulis dalam satuan apa pun yang dipakai spesifikasi Anda. Untuk menata laporan di sekelilingnya, seperti judul, blok metadata, dan total, lihat sel gabungan dan tata letak templat laporan di HotXLS, yang memakai margin ini bersama rentang gabungan dan area cetak
Catatan tentang apa yang dijamin dan tidak dijamin oleh geometri
Properti geometri mengendalikan ukuran yang dinyatakan di file, yaitu ukuran yang akan dirender oleh konsumen yang sesuai spesifikasi. Properti ini tidak melakukan resampling pada byte gambar; PNG 50×50 piksel yang disetel ke 8 cm akan diperbesar dan tampak berblok, persis seperti di Excel. Penentuan ukuran adalah operasi tata letak, bukan pemrosesan gambar, jadi beri gambar resolusi sumber yang cukup untuk ukuran fisik yang Anda inginkan. Library ini juga tidak melakukan re-encode format: byte yang Anda berikan ke AddImage disimpan dan diteruskan apa adanya, bersama TXLSXImageFormat yang Anda deklarasikan. Jika Anda memberi byte JPEG tetapi menandainya sebagai xlsxImagePng, Anda akan menghasilkan file yang tidak bisa dibuka Excel, jadi biarkan AddImageFromFile menyimpulkan format dari ekstensi ketika Anda bisa
Semua ini tidak rumit setelah Anda memahami satu gagasan di bawahnya: di OOXML, ukuran fisik adalah besaran yang nyata dan piksel hanyalah bayangan turunan yang bergantung pada DPI. Tuliskan gambar dan margin dalam sentimeter, inci, atau poin, biarkan HotXLS memetakannya ke EMU yang presisi, dan invoice serta laporan Anda akan tercetak dengan ukuran yang sama di setiap mesin yang membukanya
API geometri gambar, skala, dan margin metrik yang dijelaskan di sini tersedia bersama komponen spreadsheet Delphi HotXLS, yang membaca dan menulis XLS dan XLSX dari Delphi dan C++Builder tanpa perlu instalasi Excel