Пускате лого 600×400 пиксела в header-а на генерирана фактура, изглежда правилно на 96-DPI монитора ви, а седмица по-късно клиент на high-DPI лаптоп съобщава, че се отпечатва с размера на пощенска марка. Пикселите не са се променили. Променило се е предположението, че броят на пикселите означава физически размер, а в OOXML това не е така. Spreadsheet image-ът носи размерите си в EMU и докато не мислите в EMU или в реалните единици, които се напасват към него, оформлението ви зависи от DPI-то, което рендър машината случайно приема
HotXLS е нативен VCL spreadsheet компонент за Delphi и C++Builder, който чете и записва XLS и XLSX без Excel или COM dependency. От v2.91.0 нататък XLSX image object-ът вече не ви кара да правите сметките за единиците на ръка: наред със суровите EMU той излага ширина и височина в сантиметри, инчове и points, плюс метод за мащабиране по процент с опционално заключване на aspect ratio-то. Тази статия е за това какво всъщност е EMU, защо DrawingML го е избрал и как да използвате новата геометрична повърхност, за да поставяте картинки по физически размер, вместо по броя пиксели, на който не можете да разчитате.Scale Тази статия е за това какво всъщност е EMU, защо DrawingML го е избрал и как да използвате новата геометрична повърхност, за да поставяте картинки по физически размер, вместо по броя пиксели, на който не можете да разчитате
Какво е EMU и защо DrawingML използва такъв
EMU означава English Metric Unit и е основната единица за дължина на DrawingML, слоя за рисуване, споделен от цялото семейство Office Open XML (ECMA-376, Part 1, §20). Един EMU е дефиниран така, че да има точно 914400 EMU на инч и 360000 EMU на сантиметър. Тези две константи са цялата причина тази единица да съществува. 914400 се дели на 2, 3, 4, 5, 6, 8, 9, 10, 12 и още много; той се разлага като 26 × 32 × 52 × 127. Понеже 1 inch = 2.54 cm точно, избирайки единица, делима и на 360000, и на чиста част от 914400, форматът може да изразява inches, centimetres и points като цели числа без закръгляне на границата на единицата. Там, където floating-point "1.27 cm" би дрейфнал, EMU пази 457200 и остава точен
Другата единица, която е важна тук, е point-ът. Typographic point е 1/72 inch, така че има 12700 EMU на point (914400 / 72). Points са начинът, по който Excel мисли за row heights, font sizes и margins под капака, което е причината да е полезно да излагате image geometry в points, когато искате картинка да се подравни с text metrics, а не с печатна линия. HotXLS кодира всички четири връзки като unit constants в библиотеката:
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)
Тази последна линия е ядрото на пощенската-марка bug-ът. Един pixel има физически размер едва след като фиксирате DPI, а 9525 EMU е размерът на pixel при 96 DPI конкретно. Excel по подразбиране рендерира на 96 DPI, така че изображение от 100 пиксела каца на 100 × 9525 = 952500 EMU или около 2.54 cm при default setup - но нищо във файла не гарантира, че потребителят използва 96. Ако авторирате в реални единици, тази неяснота изчезва: 4 cm са 4 cm, независимо дали екранът е 96 или 220 DPI
Геометричната повърхност на TXLSXImage
Вградена картинка в HotXLS е TXLSXImage. Каноничното ѝ хранилище са две integer полета, WidthEMU и HeightEMU, закрепени към едно-базирана Row и Col (горната лява клетка, от която виси картинката). Свойствата в реални единици са изчисляеми изгледи върху тези EMU полета, не отделно състояние - четенето на WidthCM дели EMU на 360000, а записът умножава и закръгля обратно. Така всеки размер, който задавате, е просто друг начин да се напише същата подлежаща EMU стойност:
WidthInch/HeightInch— EMU ÷ 914400WidthCM/HeightCM— EMU ÷ 360000WidthPt/HeightPt— EMU ÷ 12700WidthEMU/HeightEMU— integer източникът на истината
Добавяте изображение с AddImage(ARow, ACol, AData, AFormat), като подавате суровите encoded bytes и TXLSXImageFormat (xlsxImagePng, xlsxImageJpeg, xlsxImageGif, или xlsxImageBmp); той връща zero-based index в worksheet Images collection-а. Има и AddImageFromFile(ARow, ACol, AFileName), който извежда формата от file extension-а. Обърнете внимание на базата на индекса: AddImage връща zero-based и Images[] е zero-based, което е умишлен контраст с one-based Cells[Row, Col] grid-а, така че не приемайте, че двете съвпадат
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;
Току-що създадено изображение по подразбиране е 100×100 пиксела, тоест 952500 EMU квадрат, приблизително кутия 2.54 cm при 96 DPI. Този default съществува, за да е изображението видимо дори ако забравите да го оразмерите, но за всяко реално оформление трябва да зададете изричен физически размер, вместо да разчитате на pixel-derived default-а
Мащабиране и флагът за aspect ratio
Когато искате да преоразмерите спрямо текущите размери вместо към абсолютна цел - да речем, да свиете chart image до 60% от това, с което е импортнато - използвайте Scale:
procedure Scale(APercent: Double; AKeepAspect: Boolean = True);
APercent е процент, при който 100 означава без промяна, 150 увеличава с половина, 50 го дели на две. С AKeepAspect в неговия default True, ширината и височината се умножават по един и същ фактор, така че proportions-ите се запазват и 4×3 cm изображение става 6×4.5 cm след Scale(150). Подайте False и само ширината се мащабира - височината остава точно каквато е била. Тази асиметрия е умишлена: когато искате да разтегнете една ос независимо, правилният инструмент са изричните WidthCM/ HeightCM setters, а не-aspect branch-ът на Scale е за по-тесния случай да коригирате само ширината. Лесно е да прочетете Scale(150, False) като "stretch both freely" и да се изненадате, така че посягайте към setters, когато наистина искате две независими измерения
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
Една малка behaviour подробност, която трябва да знаете: Scale(100) short-circuit-ва и се връща без да пипа което и да е поле, така че е безопасно да го викате безусловно в цикъл, в който процентът може да е 100. И понеже геометрията се съхранява като integer EMU, всеки setter закръгля. Round-tripping през fractional сантиметри може следователно да дрейфне с част от EMU - далеч под това, което е видимо, но си струва да го знаете, ако някога проверявате exact equality в test. За pixel-perfect control задайте WidthEMU и HeightEMU директно и прескочете unit conversion-а изцяло
Четене на геометрията обратно
Image collection-ът е queryable, което има значение, когато зареждате съществуващ workbook и трябва да инспектирате или коригирате това, което вече е там, вместо това, което току-що сте добавили. Images.Count изброява всяка картинка на sheet-а, Images[i] я индексира zero-based, и FindAt(ARow, ACol) връща картинката, закрепена към конкретна клетка - или nil ако няма такава. Има и IndexOfCell за индекса вместо за обекта, и DeleteAt/ DeleteInRange за премахване
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;
Понеже свойствата в реални единици са live изгледи, картинка, импортната на някакъв EMU размер от друг инструмент, веднага отчита геометрията си в сантиметри - без conversion стъпка от ваша страна. Това се съчетава естествено с по-широкия drawing model; ако поставяте chart-и и shapes наред с raster images, придружаващото ръководство за HotXLS charts, images, and Excel drawings in Delphi покрива anchor модела, който тези обекти споделят
Метрики за margins на page setup
Същото напрежение между EMU и реалните единици се появява едно ниво навън, на страницата. OOXML и Excel съхраняват print margins в инчове, което е неудобно, ако report template-ите ви са зададени в милиметри, както е при повечето от света извън US. v2.91.0 добавя сантиметрови wrappers върху inch margin-ите: MarginLeftCM, MarginRightCM, MarginTopCM, MarginBottomCM, MarginHeaderCM, и MarginFooterCM. Всеки е тънко удобство върху съответното inch property, което конвертира при точния ratio 1 inch = 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;
Inch properties-те (MarginLeft и останалите) остават каноничното хранилище, така че можете да ги смесвате - да зададете горен margin в сантиметри и да го прочетете обратно в инчове, или обратно - и файлът, записан на диск, е идентичен и в двата случая. Конверсията е обикновено умножение по 2.54, без закръгляне към груба мрежа, така че 2 cm остава 2 cm с пълна double precision. Това е същата metric-convenience философия като image geometry-то: форматът говори imperial под капака, а библиотеката ви позволява да author-вате в която и да е единица е написана спецификацията ви. За подреждането на обкръжаващия report - titles, metadata blocks, totals - вижте merged cells and report template layout in HotXLS, който използва тези margins заедно с merged ranges и print area
Бележка за това какво геометрията гарантира и какво не
Геометричните properties управляват declared size на изображението във файла - размера, с който съвместим consumer ще го рендерира. Те не ресемплират image bytes-овете; 50×50 pixel PNG, оразмерен до 8 cm, ще се увеличи и ще изглежда blocky, точно както би било в Excel. Оразмеряването е layout операция, не image-processing операция, така че подавайте на картинката достатъчна source resolution за физическия размер, който искате. Библиотеката също не прекодира format-и: байтовете, които подавате на AddImage, се съхраняват и се записват такива, каквито са, с TXLSXImageFormat който декларирате. Подадете JPEG bytes, но ги отбележите xlsxImagePng и ще произведете файл, който Excel не може да отвори, така че оставяйте AddImageFromFile да извежда формата от extension-а, когато можете
Нищо от това не е екзотично, щом усвоите една идея под всичко: в OOXML физическият размер е реалната величина, а пикселите са производна, зависима от DPI сянка на нея. Авторирайте изображения и margins в сантиметри, инчове или points, оставете HotXLS да ги картографира към точен EMU и фактурите и отчетите ви ще се отпечатват в еднакъв размер на всяка машина, която ги отваря
Image geometry, scaling и metric-margin API-тата, описани тук, идват с HotXLS Delphi spreadsheet component, който чете и записва XLS и XLSX от Delphi и C++Builder без да се изисква Excel инсталация