Techninis straipsnis

Rowspan tinkleliai ir kartojamos lentelių antraštės HotPDF

HotPDF atvaizduoja HTML lenteles per savo HTML5 puslapiuotos medijos profilį naudodamas tikrą užimtumo tinklelį rowspan ir colspan, išmatuotus eilučių aukščius vietoje simbolių skaičiaus spėjimų ir antraščių eilutes, kartojamas kiekviename tęstiniame puslapyje. Dvi situacijos priverčia jį atsisakyti antraštės kartoti, ir jas žinoti iš anksto pigiau, nei vėliau derinti pakartotą langelį

Dokumentų klasė, priverčianti tai daryti, yra ta, kurią anksčiau ar vėliau išsiunčia kiekviena ataskaitų komanda: sąskaita arba atitikties ataskaita, kur tiesos šaltinis yra HTML, lentelė eina per keturis puslapius, ir antraštė turi būti skaitoma kiekviename jų. Bet kas mažiau už tikrą lentelės išdėstymą duoda du gedimus, kuriuos skaitytojai pastebi tučtuojau: antraštę, pasirodančią tik pirmame puslapyje, ir eilutes, kurių aukščiai buvo atspėti pagal simbolių skaičių

Kodėl lentelės galimybė persikėlė į HTML atvaizduotoją?

Todėl, kad alternatyva praranda raiškųjį tekstą (rich text), o raiškusis tekstas yra priežastis, kodėl turinys apskritai yra HTML. Akivaizdus planas atrodo kaip pakartotinis panaudojimas: HotPDF jau turi išdėstymo DOM lentelės objektą su tikru tinkleliu, tad prijunkite HTML analizatorių prie jo ir gaukite dengimą (span) nemokamai. Problema ta, kuo tas lentelės objektas piešia. Jo langeliai neša tekstą ir stilių, o jo piešimo kelias išleidžia griežtai paprastą tekstą, todėl viskas, ką HTML iš tikrųjų turėjo už šrifto ir spalvos ribų — nuorodos, viršutiniai indeksai, inline dydžio pakeitimai, spalva pagal fragmentą — kol pasiekia puslapį, jau būna dingę

Kryptis, išgyvenanti susidūrimą su tikrais dokumentais, yra priešinga. Perkelti lentelės variklio galimybes — užimtumo tinklelį, tikrą matavimą, antraščių kartojimą ir stulpelių svertinimą — į HTML atvaizduotoją ir palikti raiškiojo teksto atvaizdavimą ten, kur jis jau veikia. Tai didesnis pakeitimas už tiltą, ir būtent jis išlaiko hipersaitą lentelės langelyje hipersaitu

Rowspan be union-find

Dengiantys langeliai sukuria atomines eilučių grupes, bet uždarymas per tas grupes apsieina be bendros disjoint-set struktūros, nes užimtumas visada yra ištisinis intervalas. Langelis su rowspan="3", prasidedantis eilutėje K, užima eilutes K iki K+2 ir nieko daugiau, todėl grupės informacija sutraukiasi iki atskiros eilutės pabaigos žymeklio

Algoritmas — dvi sumanymo eilutės. Padėję dengiantį langelį, kuris prasideda ties K ir baigiasi ties E, užrašykite GroupEnd[K] := Max(GroupEnd[K], E). Tada vieną kartą pereikite eilutes atvirkščiai ir pritaikykite G[R] := G[G[R]], kas kiekvieną eilutės pabaigą skleidžia atgal per besikertančius dengimus ir duoda tranzityvųjį uždarymą vienu praėjimu. Gaunate tai, kas kiekvienai eilutei yra paskutinė eilutė, privalanti likti su ja tame pačiame puslapyje — būtent to ir reikia puslapiavimo žingsniui, sprendžiančiam, kur gali kristi lūžis

Aukščio paskirstymas yra kita pusė. Kai dengiantis langelis reikalauja daugiau vertikalios erdvės, nei šiuo metu duoda jo dengiamos eilutės, perteklius atitenka paskutinei dengimo eilutei, o ne tolygiai išskirstomas per jas. Dengiančius langelius apdorokite po to, kai paprasti eilučių aukščiai išspręsti, tada prikipirkite kiekvieno dengimo galutinę eilutę. Pertekliaus tolygus paskirstymas atrodo teisingesnis ir duoda akivaizdžiai blogą išvestį: eilutės, kuriose tik trumpi vienos eilutės langeliai, išsipučia, nes koks nepriklausomas langelis tris eilutes aukščiau atsitiktinai buvo aukštas

HotPDF HTML lentelės tinklelis, kuriame vienas langelis su rowspan 3, prasidedantis 2 eilutėje, užima 2–4 eilutes kaip vieną atomą stačiakampį, šalia eilutės grupės pabaigos reikšmių G(R), sugeneruotų vienu atvirkščiu praėjimu ir rodančių, kad 2, 3 ir 4 eilutės sujungtos su tuo pačiu puslapiu
Dengiantis užimtumas visada yra ištisinis intervalas, todėl atskiros eilutės pabaigos žymekliai ir vienas atvirkščias praėjimas pakeičia union-find ir pasako puslapiavimui tiksliai, kur gali kristi lūžis
var
  Pdf: THotPDF;
  Importer: THPDFHTMLImporter;
  Stats: THPDFHTMLImportStatistics;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'audit-report.pdf';
    Pdf.BeginDoc;
    Importer := THPDFHTMLImporter.Create(Pdf);
    try
      Importer.Margin := 48;
      Importer.BaseFontName := 'Arial';
      Importer.BaseFontSize := 10;
      Importer.MaxDOMNodes := 200000;
      Importer.MaxLayoutOperations := 2000000;
      if Importer.RenderHTML5(SourceHtml, PrintStyleSheet) then
      begin
        Stats := Importer.Statistics;
        Writeln('tables ', Stats.TableCount,
                '  page breaks ', Stats.PageBreakCount);
      end;
    finally
      Importer.Free;
    end;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

RenderHTML5 kaip antrą argumentą priima pasirinktinį autoriaus stiliaus lapą — tai vieta, kur privalo gyventi spaudos taisyklės. Ekrano stiliaus lapą laikykite nuošaly. Profilis turi versijas, ir HTML5ProfileMilestones praneša, kurių galimybių grupes įgyvendina dabartinė statyba, himParserCascade, himPagedLayout, himTablesForms ir himBoundedResources, todėl programa gali degraduoti sąmoningai, vietoje to, kad gamyboje atrastų spragą

Matavimas turi sutapti su piešimu, tiksliai

Eilutės aukštis teisingas tik tada, kai kodas, matuojantis laužomas eilutes, jas laužia ta pačia taisykle kaip kodas, jas piešiantis. Skamba akivaizdžiai ir yra vienintelis dažniausias lentelių, kurių rėmeliai nesutampa su turiniu, šaltinis. HotPDF matuoja godžiu eilučių skaitikliu, ir tas skaitiklis turi atitikti raiškiojo teksto išvesties kelio laužymo semantiką trimis konkretiais atžvilgiais: jis lūžta tik ties tarpais, niekada nesuskaldo žodžio, o žodis, platesnis už stulpelį, gauna savą eilutę

Antras reikalavimas — šriftas. Matavimas turi bėgti su paties langelio šriftu, nustatytu per SetFont su tikru vardu, stilių ir dydžiu prieš kviečiant pločio funkciją, o ne su kokiu šriftu atsitiko būti aktyviam. Pusjuodis to paties dydžio tekstas įprastai platesnis daugiau nei dešimt procentų už įprastinį, ir to užtenka, kad trijų eilučių langelis taptų keturių eilučių. Lentelė, kurioje antraštės langeliai pusjuodžiai, o duomenų — ne, išmatuota vienu šriftu, bus neteisinga būtent tose eilutėse, į kurias skaitytojai žiūri pirmiausiai

Tai padarius teisingai, keičiasi tai, ką galima teigti teste. Tikslaus matavimo stebimas efektas yra eilučių tarpatramis, o ne glifų skaičiai: vienos eilutės eilutė yra maždaug 20 taškų aukščio, o to paties turinio simbolių skaičiaus įvertinimas spėja dvi eilutes ir maždaug 35. Teikite reikalavimus vertikaliajam atstumui tarp eilučių. Ir atminkite, kad PDF naudotojo erdvėje Y didėja aukštyn, todėl antraštė, sėdinti virš duomenų eilutės, reiškia, kad antraštės Y reikšmė yra didesnioji — priešingybė tai, ką parašytų ekrano koordinačių instinktas

Kada HotPDF atsisako kartoti antraštę?

Dviem atvejais, ir abu jų tęsiantis duotų akivaizdžiai blogą išvestį. Pirmasis — antraštės blokas, turintis dengiantį langelį, besitęsiantį už antraštės į duomenų eilutes. Antraštės kartojimas tą langelio turinį nupieštų antrą kartą vietoje, kurioje jis nebeteisėtas, todėl antraštė piešiama vieną kartą, o lentelė tęsiasi be jos. Antrasis — antraštė, aukštesnė nei 90 procentų naudotino puslapio aukščio, kur kartojimas paliktų beveik jokios vietos duomenims, ir lentelė nebejudėtų į priekį

HotPDF sprendimų kelias, kartojant HTML lentelių antraštes per puslapių lūžius: antraštė, kurios rowspan kertasi į duomenų eilutes, piešiama vieną kartą, antraštė, aukštesnė nei 90 procentų naudotino puslapio aukščio, piešiama vieną kartą, o kiekviena kita antraštė kartojasi kiekviename tęstiniame puslapyje
Du atsisakymai sąmoningi: antraštės, valdančios dengiantį duomenų langelį arba užpildančios didžiąją puslapio dalį, kartojimas pieštų turinį, kur jis nebeteisėtas, arba paliktų jokios vietos duomenims

Abu atsisakymai pagal projektą sąmoningi ir tylūs, nes alternatyva blogesnė. Jei jūsų antraštės kartojasi nevalia, o jūs tikėjotės kitaip, patikrinkite markup'ą dėl rowspan, kertančio thead ribą, prieš įtardami variklį. Tas vienintelis markup'o šablonas paaiškina daugumą siurprizų

// Stulpelių svoriai ateina iš markup'o, todėl spaudos stiliaus lapas yra
// vieta jiems valdyti. Plotys traktuojamos kaip svoriai, o ne pikseliai
const
  PrintStyleSheet =
    'table { width: 100%; }' +
    'thead th { font-weight: bold; background: #eee; }' +
    'td.amount { text-align: right; }';

// Antraštės eilutė, nešanti rowspan, kertantį į duomenų dalį, slopina
// antraštės kartojimą. Laikykite span'us vienoje sekcijoje:
//   <thead><tr><th rowspan="2">Item</th>...</tr></thead>  ok
//   <tr><th rowspan="3">Item</th>...  einama į tbody, nekarts

Stulpelių plotys elgiasi kaip svoriai, o ne absoliutūs matavimai, ir būtent tokia elgsena išlaiko lentelę naudojamą, kai turinys nesutampa su autoriaus įvertinimu. 30 procentų deklaruotas stulpelis gauna maždaug 30 procentų prieinamo pločio, bet paskirstymas gerbia mažiausią plotį, kurio kiekvienas stulpelis iš tikrųjų reikalauja, todėl siauras stulpelis su ilga nesulaužoma leksema tyčiomis neužlieja lentelės dėžės

Kur tai telpa dokumento konvejeryje

Lentelių darbas sėdi platesniame puslapiuotos medijos profilyje, ir puslapiavimo taisyklės, resursų biudžetai ir CSS apdorojimas, aprašyti straipsnyje HTML5 puslapiuotos medijos importo kelias, dokumentams su lentelėmis galioja nepakitę. Jei jūsų duomenys nepradedami kaip HTML, tiesioginė konstravimo kryptis iš straipsnio lentelių statymas tiesiai į PDF aplenkia analizavimo sluoksnį ir duoda tą patį tinklelio elgesį per API. O kadangi eilutės aukštis galiausiai priklauso nuo to, kur lūžta eilutės, matavimo nagrinėjimas iš straipsnio teksto lygiavimas ir eilučių laužymas yra palydovinis straipsnis visiems, derinantiems tankią lentelėtinę išvestį

Pakartotinai pritaikoma pamoka čia apskritai ne apie lenteles. Kai nauja posistemė reikalauja galimybės, kurią sena posistemė jau turi, paklauskite, kuri iš dviejų valdo tai, sunkiausiai pernaudojamą iš naujo. Tinklelio aritmetika yra keliolika eilučių ir persikelia lengvai. Raiškiojo teksto atvaizdavimas su inline nuorodomis, viršutiniais indeksais ir stilizavimu pagal fragmentą — ne, todėl tinklelis persikėlė, o tekstas liko. HotPDF siunčia abu kelius kaip HotPDF Delphi PDF komponento dalį, todėl pasirinkimas tarp HTML įvesties ir tiesioginės konstravimo yra projekto sprendimas, o ne bibliotekos