HotPDF може да изгради документ с разбивка на страници от декларативно дърво, вместо от координати. Съставяте THPDFDOMDocument от секции, стекове, текст, списъци и таблици, подавате го на THPDFDOMRenderer, а рендерерът измерва, разбива на страници, изчертава обзавеждането на страницата и, при поискване, извежда структурното дърво на PDF/UA, което прави резултата достъпен. Кодът за оформлението никога не изчислява координата y
Всеки, който е поддържал генератор на отчети, задвижван от координати, знае защо това има значение. Първата версия работи. После адресът на клиент нараства до три реда, таблица получава още редове, локализирано заглавие се пренася на нов ред, и всяка следваща позиция y става грешна. Поправките се натрупват като ръчни проверки за прекъсване на страница, разпръснати из бизнес логиката, а изискването за маркиран (tagged) PDF, което пристига две години по-късно, не може да бъде вградено допълнително в код, който няма представа какво е параграф
Какво притежава дървото и защо собствеността е строга
DOM моделът налага единствена собственост на всяко ниво: документът притежава своите секции, секция притежава своето тяло, заглавка и долен колонтитул, а стекове, контейнери и таблици притежават своите деца. Повторната употреба се случва чрез Clone или чрез регистрирана фабрика, никога чрез прикачване на един и същ обект към два родителя. Това правило не е формалност. Компонент, който се появява два пъти в дървото, би бил измерен два пъти с различни ограничения и освободен два пъти при разрушаване
Практическата последица за извикващия код е, че помощните функции връщат нови инстанции. Регистрирането на фабрика чрез RegisterComponent и извикването на CreateComponent ви дава именувана рецепта, която произвежда свеж компонент всеки път — така повтарящо се обзавеждане като блок за подпис или правен долен колонтитул принадлежи на дървото
uses
HPDFDoc, HPDFLayoutDOM;
var
Doc: THPDFDOMDocument;
Section: THPDFDOMSection;
Table: THPDFDOMTable;
Row: THPDFDOMTableRow;
I: Integer;
begin
Doc := THPDFDOMDocument.Create;
Doc.GenerateStructure := True; // извежда структурното дърво на PDF/UA
Doc.Language := 'en-US';
Section := Doc.AddSection;
Section.PageWidth := 595; // A4 в пункти
Section.PageHeight := 842;
Section.MarginLeft := 56;
Section.MarginTop := 56;
Section.MarginRight := 56;
Section.MarginBottom := 56;
Section.Style.FontName := 'Helvetica';
Section.Style.FontSize := 10;
Section.Body.AddHeading('Annual maintenance report', 1);
Section.Body.AddText('Every asset inspected during the reporting ' +
'period is listed below, grouped by site.');
Section.Body.AddSpacer(12);
Table := THPDFDOMTable.Create('assets');
Table.AddColumn(3); // тегла, не абсолютни широчини
Table.AddColumn(1);
Table.AddColumn(1);
Table.RepeatHeaders := True;
Row := Table.AddRow(18, True); // заглавен ред
Row[0].Text := 'Asset';
Row[1].Text := 'Last service';
Row[2].Text := 'Status';
for I := 0 to High(Assets) do
begin
Row := Table.AddRow(16);
Row[0].Text := Assets[I].Name;
Row[1].Text := Assets[I].ServiceDate;
Row[2].Text := Assets[I].Status;
end;
Section.Body.Add(Table);
end;
Как разбивката на страници избягва квадратична цена?
Наивният начин да се разбие дърво на страници е да се клонира каквото не се е побрало и да се пренесе на следващата страница. При таблица с десет хиляди реда това клонира оставащите редове веднъж за всяка страница и превръща линеен документ в квадратичен
HotPDF разделя стеснено вместо това. Рендерерът от най-високо ниво обхожда децата на тялото по индекс и никога не клонира цяла секция или тяло. Само вложени стекове и контейнери, които наистина застъпват границата на страница, получават клониран засегнатия си поддървовиден фрагмент, а двата тежки листови типа носят курсор, а не копие: продължение на текст съхранява диапазона от знаци на източника, който все още дължи, а продължение на таблица съхранява резена от редове, който все още предстои да бъде поставен. Дългите документи остават линейни, а дългите параграфи струват еднакво, независимо дали се прекъсват веднъж или пет пъти
Измерването остава честно относно страничните ефекти. Изисква се THPDFLayoutElement.Measure да е без странични ефекти на изчертаване, а действителното поставяне винаги преминава през THotPDF.PlaceLayoutElement, същата централна рутина, която повторно измерва поставения фрагмент, настройва собствеността на препълването и записва диагностика. DOM рендерерът решава само политиката за нова страница, обзавеждането на страницата, разстоянията и жизнения цикъл на продълженията
Правилата за заглавка на таблица, които предотвратяват безкраен документ
Повтарянето на заглавки на таблица през страниците звучи просто и крие два начина на провал. HotPDF изисква заглавните редове да се появяват само в първата поредица от последователни редове, и първото разделяне да побере всички заглавни редове плюс поне един ред от тялото. Без второто правило заглавка, по-висока от оставащото пространство, произвежда страница, съдържаща само заглавката, последвана от друга идентична страница, безкрайно
Страниците продължения преизчертават заглавката, а тази преизчертана копия се маркира като артефакт, а не като съдържание, което е верният отговор както за достъпност, така и за извличане на текст. Оригиналният заглавен ред остава в логическата структура на таблицата точно веднъж. Пропуснете това и екранен четец обявява заглавията на колоните отново по средата на данните, а извличач на текст вмъква дублиран заглавен ред между редовете на тялото
Съществува и защитен таван за дълбочината на продължението, защото собствен компонент е свободен да реализира Split по начин, който винаги връща еквивалентна опашка. Рендерерът проверява лимита след отделяне на опашката и преди започване на следващата страница, а текущата итерация освобождава опашката в собствения си блок finally, така че некоректно държащ се компонент на трета страна се проваля с диагностируема грешка, вместо да запълва диск
Един логически елемент, много фрагменти на страница
Автоматичното маркиране е мястото, където моделът на разбивка на страници и моделът на структурата трябва да се съгласуват. Параграф, разделен на две страници, е един логически параграф, така че трябва да остане един структурен елемент. Но идентификаторите на маркирано съдържание са за отделна страница, така че всеки видим фрагмент се нуждае от собствен MCID на страницата, на която се появява
HotPDF разрешава това, като поддържа един-единствен структурен елемент и добавя препратка към маркирано съдържание в неговия масив /K за всеки фрагмент, като двойката /Pg и /MCID идентифицира страницата и идентификатора. Слотът ParentTree за този MCID сочи обратно към същия елемент. Това е точно това, което изисква ISO 14289, и това е причината клоновете за продължение да се различават от обикновените клонове: обикновен Clone означава ново логическо съдържание и получава нова семантична идентичност, докато вътрешният клон за продължение наследява идентичността на компонента, който продължава
Повторната употреба на елементи се търси чрез индекс от семантични идентичности, сортирани по показалец на компонент и претърсвани чрез двоично сравнение, което поддържа търсенето логаритмично при големи дървета. Индексът съдържа само непритежаващи препратки; жизненият цикъл на самите структурни обекти остава с графа от обекти на PDF
Правила за структура, които рендерерът налага предварително
С включен GenerateStructure, няколко правила на PDF/UA се проверяват, докато дървото се рендира, а не след като файлът вече съществува. Заглавията започват от ниво 1 и не могат да прескачат нива. LI може да се появява само вътре в L, а Lbl и LBody само вътре в LI. TR принадлежи на таблица, а TH и TD — на ред. Фигура без алтернативен текст се отхвърля в режим PDF/UA
Ранното отхвърляне е съзнателният избор тук. Валидатор, който отчита липсващ алтернативен текст, след като документът вече е записан, ви казва, че партида от десет хиляди извлечения се нуждае от регенериране; рендерер, който отказва компонента, ви казва кой компонент, докато данните, които са го произвели, все още са в обхват. Проверката за съответствие все пак принадлежи на конвейера като отделна стъпка, а механиката на това е разгледана в валидиране на PDF/A, PDF/X и PDF/UA
var
Pdf: THotPDF;
Renderer: THPDFDOMRenderer;
Stats: THPDFDOMRenderStatistics;
begin
Pdf := THotPDF.Create(nil);
Renderer := THPDFDOMRenderer.Create;
try
Pdf.FileName := 'maintenance-report.pdf';
Pdf.BeginDoc;
Stats := Renderer.Render(Doc, Pdf);
Pdf.EndDoc;
Writeln(Format('%d page(s), %d placement(s), %d split(s)',
[Stats.PageCount, Stats.PlacementCount, Stats.SplitCount]));
Writeln(Format('structure elements=%d marked content=%d artifacts=%d',
[Stats.StructureElementCount, Stats.MarkedContentCount,
Stats.ArtifactCount]));
Writeln(Format('deepest continuation chain: %d',
[Stats.MaximumContinuationDepth]));
finally
Renderer.Free;
Doc.Free;
Pdf.Free;
end;
end;
Записът със статистики е по-полезен, отколкото изглежда на пръв поглед. Рязкото нарастване на SplitCount след промяна на шаблон обикновено означава, че компонент е започнал да се измерва по-висок от своя контейнер. Пълзящото нарастване на MaximumContinuationDepth е ранното предупреждение за компонент, чийто Split прави твърде малък напредък на страница. А сравняването на ArtifactCount с броя на страниците продължения потвърждава, че повтарящите се заглавки наистина са маркирани като артефакти
Къде се вписва DOM моделът наред с директното API
DOM моделът не заменя директното изчертаване; той седи върху същите обекти на страница. Всичко, което рендерерът поставя, може да се редува с директни извиквания върху THotPDF, което има значение, когато отчет се нуждае от един ръчно позициониран елемент, например изображение на подпис на точно определено място. Затварянето на страници остава под контрола на AddPage и EndDoc, така че режимът на незабавно изхвърляне (flush) не държи завършени страници в паметта, а резидентната памет остава управлявана от текущите продължения, ресурсите за шрифтове и обичайния обектен граф на документа
Изберете DOM модела, когато съдържанието е задвижвано от данни, а оформлението — от правила, и запазете директното изчертаване за фиксирана графика. Ако текущият ви проблем е конкретно разбивката на таблица на страници, по-тесният подход в генериране на таблици в PDF си струва да бъде прочетен първо, а поведение на ниво текст като подравняването е описано в подравняване на текст
Декларативното оформление, автоматичното маркиране и директното API за изчертаване се доставят в един и същ компонент за Delphi и C++Builder; пълният списък с функции е на страницата на HotPDF Delphi PDF компонента