Tehnični članak

Izravnava zasuka strani PDF: okvirji ostanejo cela

HotPDF izravna zasuk strani PDF s THotPDF.FlattenLoadedPageRotation: metoda ovije vsebino vsake zasukane strani v urno transformacijo cm, prepiše vsak okvir strani, ki ga stran dejansko ima, zavrti geometrijo opomb, matrike videza, eksplicitne cilje in označeno geometrijo strukture za istim kotom in nato nastavi /Rotate na 0. Stran v pregledovalniku izgleda identično, njen koordinatni sistem pa je zdaj pokončen. To je pomembno v trenutku, ko nek kasnejši orod, tiskalni RIP ali vaša lastna koda za žige spregleda /Rotate in postavlja stvari v surovi uporabniški prostor

Tipični sprožilec je skener ali mobilna aplikacija za zajem, ki ležeče strani zapiše kot pokončni medij z /Rotate 90. Vsak pregledovalnik jih prikaže prav, zato nihče ne opazi ničesar, dokler nekdo ne žige številko strani pri »desnem spodnjem kotu« in ta pristane na boku ob levem robu, ali pa korak impozicije, ki bere samo /MediaBox, ne razporedi pokončne reže za ležečo stran. Izravnavanje se sliši kot matrično delo za eno vrstico. V praksi se dotakne petih okvirjev strani, treh vrst geometrije opomb, ciljev povezav dokumenta in drevesa strukture, vsak od njih pa ima v ISO 32000-1 svoje pravilo

V katero smer /Rotate zavrti stran PDF?

/Rotate zavrti stran za prikaz in tiskanje v smeri urinega kazalca, v večkratnikih 90 stopinj (ISO 32000-1 §7.7.3.3, tabela 30). Pri 90 stopinjah levi rob medija postane zgornji, zgornji rob pa desna stran, zato je v y-navzdol napravnem prostoru preslikava X = (y - Bottom) * Scale in Y = (x - Left) * Scale. Pri 270 stopinjah desni rob postane zgornji. /Rotate je tudi eden od le štirih podedljivih atributov strani, skupaj z /Resources, /MediaBox in /CropBox (§7.7.3.4), zato lahko slovar strani brez lastnega /Rotate vseeno zavrti prednik /Pages. THotPDF.GetLoadedPageRotation prehodi verigo /Parent in rezultat normalizira v 0–359, kar je vrednost, ki jo želite, ne surovi ključ na strani

Smer je lahko narobe dojeti na način, ki preživi testiranje, in zgodnejše verzije HotPDF so storile točno to. Stara matrika od strani do naprave je zamenjala y-komponente za 90 in 270, kar da odsev čez diagonalo namesto zasuka: usmerjenost matrike se obrne glede na nezasukani primer. Oba kota še vedno »izgledata zasukana«, bitna slika ima zamenjani širini in višini, povratna pot od strani do pogleda in nazaj vrne izhodiščno točko, zato preizkusi dimenzij in povratnih poti vsi uspejo. Edini zanesljiv preizkus je, kjer pristne oznaka kota, primerjana piksel za pikselom z referenčnim izrisovalnikom. Ker so si model pregledovalnika, SIMD izrisovalna zadnja plast in preslikava poudarkov kopirali isto matriko, so bili vsi popravljeni skupaj, koda izravnavanja pa zdaj uporablja isto urno konvencijo kot izrisovalnik

Kako HotPDF izravna zasuk strani v Delphiju: pokončna stran, shranjena z /Rotate 90, se prikaže v smeri urinega kazalca kot ležeči pogled 792 krat 612, napravna preslikava X = (y - Bottom) * Scale, Y = (x - Left) * Scale premakne vsak kot, zamenjava y-komponent matrike pa da odsev, ki ga ujame samo primerjava oznak kotov
Pregledovalniki zavrtijo stran za prikaz v smeri urinega kazalca, medtem ko bajti ostanejo pokončni — GetLoadedPageRotation najprej prehodi verigo /Parent, ker je /Rotate eden od štirih podedljivih atributov strani

Kako FlattenLoadedPageRotation prepiše stran

FlattenLoadedPageRotation(PageRange, Info) obdela vsako stran v PageRange, katere dejanski zasuk je 90, 180 ali 270, in vrne število strani, ki jih je izravnila. Prazen PageRange pomeni vse strani; sicer niz uporablja običajno eno-osnovno skladnjo '1-3,7', številka strani izven obsega pa sproži izjemo, namesto da bi bila preskočena. Izvirni tokovi vsebine se nikoli ne znova kodirajo. Metoda pred /Contents strani pripne nov tok z q 0 -1 1 0 -Bottom Width+Left cm (za 90 stopinj), na konec pripne tok z Q in na koncu zapiše eksplicitni /Rotate 0 v slovar strani, tako da podedovana vrednost na vozlišču /Pages ne more zavrtiti strani še enkrat

var
  Pdf: THotPDF;
  Info: THPDFRotationFlattenInfo;
  Flattened: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('scanned-batch.pdf') > 0 then
    begin
      // '' = vse strani; strani na 0 stopinj se preiskajo, a ostanejo pri miru
      Flattened := Pdf.FlattenLoadedPageRotation('', Info);
      Writeln(Format('Scanned %d, flattened %d pages', [Info.ScannedPageCount, Info.FlattenedPageCount]));
      Writeln(Format('Turned %d annotations, %d destinations, %d tagged geometry entries',
        [Info.TransformedAnnotationCount, Info.TransformedDestinationCount,
         Info.TransformedStructureGeometryCount]));
      if Flattened > 0 then
        Pdf.SaveLoadedDocument('scanned-batch-upright.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

Zapis THPDFRotationFlattenInfo je vreden beleženja, ne zavrženja. ScannedPageCount je velikost obsega, FlattenedPageCount je enak vrnjeni vrednosti, trije števci Transformed... pa vam povedo, ali je dokument imel povezave, zaznamke ali označeno geometrijo, ki kaže na zavrtene strani. Paket, kjer vsaka datoteka poroča nič ciljev, je v redu; označena datoteka PDF/UA, ki poroča nič strukturne geometrije, ko ste pričakovali ograde slike, pa je znak, da jo pregledate z roko

Katerih okvirjev strani se izravnavanje dotakne in v kakšnem vrstnem redu?

Izravnavanje prepisuje samo okvirje, ki jih stran že ima, in prebere vsak okvir, preden zapiše katerega koli. Vrstni red je pomemben zaradi privzetne verige: GetLoadedPageBox(PageIndex, pbCropBox, ...) vrne /MediaBox, kadar stran nima /CropBox, /BleedBox, /TrimBox in /ArtBox pa privzamejo CropBox (§14.11.2). Zgodnejša verzija je brala, transformirala in zapisovala en okvir naenkrat. Najprej je prepisala MediaBox, nato prebrala »CropBox«, dobila nazaj že zavrti MediaBox, zavrtela ga še enkrat in zapisala CropBox, ki ga stran nikoli ni imela, kar je ležečo stran obrezalo do kvadrata. Pravila podedovanja se delijo enako: MediaBox in CropBox se iščeta po verigi /Parent, Bleed, Trim in ArtBox pa štejejo le, če sedijo na samem slovarju strani, zato odcepljen /TrimBox na vozlišču /Pages velja za odsotnega in se nikoli ne prekopira na stran

procedure DumpPageGeometry(Pdf: THotPDF; PageIndex: Integer);
var
  L, B, R, T: Single;
begin
  Writeln('Effective /Rotate: ', Pdf.GetLoadedPageRotation(PageIndex));
  if Pdf.GetLoadedPageBox(PageIndex, pbMediaBox, L, B, R, T) then
    Writeln(Format('MediaBox [%g %g %g %g]', [L, B, R, T]));
  // True tudi brez ključa /TrimBox: vrednost pade nazaj na CropBox, nato MediaBox
  if Pdf.GetLoadedPageBox(PageIndex, pbTrimBox, L, B, R, T) then
    Writeln(Format('TrimBox  [%g %g %g %g]', [L, B, R, T]));
  // Prednastavljen Letter; GetLoadedPageVisibleBox pusti izhode nedotaknjene ob odpovedi
  L := 0; B := 0; R := 612; T := 792;
  Pdf.GetLoadedPageVisibleBox(PageIndex, L, B, R, T);
  Writeln(Format('Visible  [%g %g %g %g]', [L, B, R, T]));
end;

Pomožnik poženite pred izravnavanjem in po njem, številke pa se razložijo same. Za stran na 90 stopinj z MediaBox [0 0 612 792] postane izravnani MediaBox [0 0 792 612]; vsak prepisani okvir je preslikan skozi isti urni zasuk, relativno na izhodišče izvirnega MediaBox, zato novi MediaBox vedno začne na izhodišču in ostali okvirji obdržijo svoj položaj znotraj njega. GetLoadedPageVisibleBox vrne to, kar prikažejo pregledovalniki in natisnejo tiskalniki, CropBox obrezan na MediaBox in normaliziran tako, da je Left manjši od Right, izrisovalnik, izvoz SVG, pregledovalnik in tiskalna pot HotPDF pa vsi uporabljajo ta isti okvir. Ko potrebujete velikost strani, ki jo vidi človek, pokličite GetLoadedPageVisibleBox, namesto da bi brali /MediaBox

Zakaj HotPDF med FlattenLoadedPageRotation prebere vsak okvir strani, preden zapiše katerega koli: BleedBox, TrimBox in ArtBox privzamejo CropBox, ta pa sam pade nazaj na MediaBox, zato je zavrtanje okvirjev enega naenkrat povzročilo, da je CropBox prebral že prepisan MediaBox, drugi zasuk pa zapisal okvir, ki ga stran nikoli ni imela, in ležečo stran obrezal do kvadrata
Privzeta veriga pomeni, da je izhod enega okvirja vhod drugega — najprej preberite vse, transformirajte glede na izhodišče izvirnega MediaBox, nato zapišite

Zakaj se opome pokvarijo, če zavrtite samo /Rect?

Opome se pokvarijo, ker tok videza ni risan neposredno v /Rect. Po §12.5.5 pregledovalnik najprej preoblikuje /BBox obrazca z njegovo /Matrix, nato pa ograde tega rezultata skalira in prevedi v /Rect. Zavrtite samo /Rect in žig 200 × 40 bo stisnjen v režo 40 × 200, neberljiv in na boku. FlattenLoadedPageRotation torej z desne pomnoži urni zasuk strani na vsak /Matrix videza (za 90 stopinj [0 -1 1 0 0 0] v konvenciji vrstičnih vektorjev), čez videze /N, /R in /D ter vsako stanje znotraj njih. En tok videza si lahko deli več opomb ali stanj, zato se vsak tok zavrti točno enkrat na klic. Primer brez čistega odgovora je tok, ki si ga deli več strani z različnimi zasuki; sledi prvi strani, ki do njega pride

Še dve pravili držita polja obrazcev in lepljive listke na mestu. Vnos /MK /R gradnika (§12.5.6.19) je kot v nasprotni smeri urinega kazalca, zato se urni kot strani od njega odšteje, po modulu 360; izpustite to in naslednja ponovna izdelava videza nariše tekst polja v napačno smer. Opome z zastavico NoRotate (bitni položaj 5, vrednost 16, §12.5.3) ostanejo pokončne na zasukani strani in se vrtijo okoli zgornjega levega kota svojega /Rect, zato izravnavanje obdrži njihovo širino, višino in pokončni videz ter samo premakne ta kot tja, kamor ga spravi zasuk. Poleg opomb metoda zavrti tudi /QuadPoints, /Vertices, /L in /InkList, prepiše eksplicitne cilje, ki poimenujejo stran (točke /XYZ, pravokotnike /FitR ter /FitH in /FitV zamenjana pri 90 in 270 stopinjah, §12.3.2.2), in preoblikuje označeno geometrijo, kot so vnosi /BBox atributov za strukturne elemente, katerih /Pg je stran

Zakaj se opome pokvarijo, ko HotPDF stran izravna z zasukom samega /Rect: žig 200 krat 40 se skalira v režo 40 krat 200 in postane neberljiv, zato FlattenLoadedPageRotation z desne pomnoži urni zasuk na vsak /Matrix videza čez /N, /R in /D, prilagodi nasprotno-urni /MK /R in zavrti opome NoRotate okoli njihovega zgornjega levega kota
Pregledovalnik spravi preoblikovani BBox videza v /Rect, zato se mora tok sam zavrti — en prehod na deljeni videz, točno enkrat na klic

Kaj izravnavanje ne pokrije?

Izravnavanje je geometrijski prepis lastnih objektov ene strani, več situacij pa pade izven nje tiho, ne glasno

  • Strani, katerih dejanski zasuk je že 0 ali katerih MediaBox manjka ali ima ničelno širino ali višino, se preskočijo brez napake; primerjajte vrnjeno vrednost s številom strani, za katere ste pričakovali spremembo
  • Form XObjects, navedeni iz virov strani, obdržijo svoj /BBox v prostoru obrazca, ker ju zunanji cm že zavrti; preiskava drevesa strukture sledi samo /K in /A, zato nikoli ne vstopi v vire strani ali opome drugič
  • Cilji se najdejo s preiskavo vsakega posrednega objekta enkrat na izravnano stran, zato velik dokument s stotinami zasukanih strani plača ta sprehod za vsako od njih
  • Izrisovalnik strani HotPDF ne riše opomb, zato vizualni preizkus zavrtih žigov najprej potrebuje FlattenLoadedAnnotations
// Zapečite videze v vsebino, da jih izrisovalnik zna prikazati,
// nato izrišite stran 1 pred odstranitvijo njenega /Rotate in po njej
Pdf.FlattenLoadedAnnotations('1');
Before := Pdf.RenderLoadedPageToBitmap(0, 96);
try
  Pdf.FlattenLoadedPageRotation('1', Info);
  After := Pdf.RenderLoadedPageToBitmap(0, 96);
  try
    Assert((Before.Width = After.Width) and (Before.Height = After.Height));
    // Tukaj primerjajte piksle oznake kota, ne samo dimenzij
  finally
    After.Free;
  end;
finally
  Before.Free;
end;

Za globlje ozadje se strana opomb te zgodbe nadaljuje v izdelavi videzov opomb, preden jih izravnamo, izrisovalnik za primerjavo pred in po je pokrit v izrisu naložene strani PDF v bitno sliko, izrez in N-up spajanje na naloženih PDF pa prikazuje isto tehniko pripetja tokov vsebine, na katero se zanašata predpona in pripona zasuka. HotPDF, vključno s FlattenLoadedPageRotation in bralci okvirjev strani, je na voljo za Delphi in C++Builder na strani komponente HotPDF Delphi PDF