PDF Library for Delphi рендира HTML в PDF страница с истинско двумерно оформление: display: flex и display: grid се измерват и позиционират, вместо да се деградират до подредени един под друг блокове, а бележките под линия се запазват в долната част на кутията, носеща тяхната препратка, с номериране, което остава непрекъснато през колони и страници. Входните точки са познатите — DrawHTMLTextBox за единична кутия и DrawHTMLStory за многоколонен поток
Това има значение, защото HTML е начинът, по който пристига по-голямата част от съдържанието за отчети сега. Шаблоните се създават от хора, които пишат CSS, таблата за управление се проектират като карти, а рендерер, който тихо срива flex ред в четири подредени един под друг блока, произвежда документ, който изобщо не прилича на дизайна. Докато тази способност не съществуваше, единственият двумерен контейнер, измерван от двигателя, беше таблицата, така че всяко оформление с карти трябваше да бъде пресъздадено като таблица на ръка
Какво се промени в модела на оформление?
Предишният главен цикъл поддържаше единична кутия за ред и напредваше надолу по страницата. Този модел обработва вграждано (inline) съдържание и подредени блокове идеално и не може да изрази контейнер, чиито деца се оразмеряват едно спрямо друго. Таблиците бяха единственото изключение, със собствено двупроходно измерване
Flex и grid всеки добавя ограничен проход на измерване над децата на контейнер, и важната дума е ограничен. Flex контейнер измерва до 256 директни деца в масив с фиксиран размер. Grid използва матрица на заетост от най-много 64 на 64 клетки за детерминирано автоматично позициониране. Тези тавани съществуват, за да не може враждебен или генериран стилов лист да задвижи неограничена рекурсия или квадратична памет за позициониране, което е реална загриженост, когато HTML идва от шаблон, редактиран от клиент
Как flex елементите получават своите размери
В посоката на реда контейнерът сумира базата на всеки елемент заедно с неговите тегла за растеж и свиване, след което разпределя оставащото пространство, положително или отрицателно, според тези тегла. С flex-wrap, всеки ред се решава независимо, така че ред, който се разчупва на два реда, разпределя свободното пространство поотделно за всеки ред, а не за целия контейнер. В посоката на колоната същото разпределение по главната ос се изпълнява спрямо явна височина или височината на съдържанието
justify-content, align-items, gap и обратните посоки работят върху геометрия, която вече е измерена. Те местят кутии; никога не задействат повторно измерване на съдържанието на елемент. Това разделение е това, което пази сложно табло от измерване на децата си по няколко пъти
uses
PDFlibrary;
var
Lib: TPDFlib;
Html, Remainder: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.NewDocument;
Lib.SetPageSize('A4');
Lib.NewPage;
Html :=
'<div style="display:flex; gap:12px;">' +
' <div style="flex:2 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Revenue</b><br/>EUR 4,182,300</div>' +
' <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Margin</b><br/>18.4%</div>' +
' <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Backlog</b><br/>92 days</div>' +
'</div>';
Remainder := Lib.DrawHTMLTextBox(40, 40, 515, 120, Html);
if Remainder <> '' then
Log('content did not fit - carry the remainder to the next box');
Lib.SaveToFile('dashboard.pdf');
finally
Lib.Free;
end;
end;
Върнатата стойност е низът за продължение, който е начинът, по който всяка входна точка за изчертаване на HTML отчита какво не се е побрало. Подайте го на следващата кутия или следващата страница и потокът продължава оттам, където е спрял
Позициониране в grid и какво може да бъде една пътека (track)
Пътеките на grid приемат фиксирани дължини, проценти, единицата fr, прости изрази repeat() и minmax(). Автоматичното позициониране запълва матрицата на заетост детерминирано, така че един и същ HTML винаги произвежда едно и също подреждане. Изричните координати са позволени да се застъпват, което е съзнателно: дизайн, който наслагва значка върху карта, изразява намерение, не грешка. Когато е дадена изрично само една ос, позиционирането търси само по другата ос
Елементи, обхващащи няколко реда, допринасят с измерената си височина обратно към редовете, които покриват, осреднена между тях, което пази висок обхващащ елемент от сплескване на един-единствен ред, докато оставя съседите му ниски:
Html :=
'<div style="display:grid; grid-template-columns:repeat(3, 1fr); ' +
' gap:10px;">' +
' <div style="grid-row:span 2; background:#eef;">Site plan</div>' +
' <div>Inspector</div>' +
' <div>Date</div>' +
' <div style="grid-column:2 / span 2;">Findings summary</div>' +
'</div>';
Remainder := Lib.DrawHTMLTextBox(40, 180, 515, 260, Html);
Децата на flex и grid се рендират чрез същия HTML рендерер като всичко останало, което е свойството, правещо функцията използваема, а не отделен свят. Шрифтове, CSS каскадата, връзки, изображения, таблици и по-нататъшни вложени flex или grid контейнери всички се държат вътре в flex елемент точно както на най-високо ниво, а външният план за оформление записва крайните команди за текст и правоъгълници, така че повторно изчертаване използва повторно съществуващия кеш за измерване
Защо бележките под линия са проблем на разбивката на страници?
Бележка под линия не е съдържание, което протича след параграфа, съдържащ нейната препратка; тя е съдържание, което трябва да се появи в долната част на същата кутия като нейната препратка. Това обръща обичайния ред на измерване, защото пространството, налично за основния текст, сега зависи от съдържание, което все още не е оформено
Затова рендерерът измерва бележката, когато срещне препратката, и изважда площта на бележката от бюджета за височина на основния текст на текущата ограничена кутия. Ако препратката, основният текст дотук и бележката не могат всички да се поберат, маркерът на бележката под линия и всичко след него се преместват заедно в низа за продължение. Това правило е това, което предотвратява двата класически провала: бележка, застъпваща основния текст, и бележка, останала сама на страница, чиято препратка е на предишната
В ограничена кутия площта на бележката е закачена в долната част с разделителна линия над нея. При неограничено измерване, където няма височина на кутия, към която да се закачи, площта на бележката следва веднага след основния текст. Номерирането се пренася в разширено поле в стека за продължение, така че DrawHTMLTextBox и DrawHTMLStory пазят последователността работеща през колони и страници, а низ за продължение, произведен преди това поле да е съществувало, все още продължава правилно
// Бележки под линия вътре в многоколонна история пазят една текуща последователност
Html := LoadTemplate('chapter.html'); // използва маркери float:footnote
Remainder := Lib.DrawHTMLStory(40, 40, 515, 700,
2, // колони
16, // жлеб (gutter) в пунктове
20, // максимален брой страници за тази история
Html);
if Remainder <> '' then
Log('story exceeded its page budget');
Практически насоки за автори на шаблони
Проектирайте в рамките на документираните тавани. Flex контейнер с повече от 256 директни деца почти винаги е таблица с данни, облечена в flex костюм, а таблският път така или иначе я измерва по-добре. Grid, по-голям от 64 на 64, е електронна таблица, и същият съвет важи. За многоколонен основен текст, поведението на колони и сричкопренасяне, описано в сричкопренасяне и балансирани текстови колони, управлява как потокът изглежда вътре във всяка колона
Измервайте, преди да изчертаете, когато оформление трябва да се побере. GetHTMLTextHeight отчита каква височина би изисквала дадена широчина, което е евтиният начин да изберете между едно оформление и друго, преди да ангажирате мастило. И третирайте непразен низ за продължение като нормален, а не изключителен: това е механизмът, чрез който дълго съдържание се разбива на страници, не сигнал за грешка
Когато HTML идва от двигател за отчети, а не от ръчно написани шаблони, маршрутът, задвижван от набор от данни, в двигателя за отчети, задвижван от набор от данни, се съчетава добре с това, генерирайки разметката, която flex и grid след това подреждат. А когато същото съдържание трябва да напусне PDF отново, семантичният път за експорт в експортиране на PDF в Markdown и DOCX затваря кръга
Оформлението на HTML, генерирането на отчети и семантичният експорт са част от една библиотека за Delphi, C++Builder и Free Pascal; пълният списък с функции е на страницата на PDF Library for Delphi