Tekninen artikkeli

Tasoita PDF-sivun kierto rikkomatta laatikoita Delphissä

HotPDF tasottaa PDF-sivun kierron metodilla THotPDF.FlattenLoadedPageRotation: metodi kietoo jokaisen kierretyn sivun sisällön myötäpäiväiseen cm-muunnokseen, kirjoittaa uudelleen jokaisen sivulaatikon, jonka sivu oikeasti omistaa, kiertää huomautusgeometrian, ulkoasumatriisit, eksplisiittiset kohteet ja tagitetun rakennegeometrian samalla kulmalla ja asettaa lopuksi /Rotaten arvoon 0. Sivu näyttää katselimissa identtiseltä, mutta sen koordinaatisto on nyt pystyssä. Se alkaa merkitä sillä hetkellä, kun myöhempi työkalu, tulostin-RIP tai oma leimauskoodisi ohittaa /Rotaten ja sijoittaa asioita raakaan käyttäjätilaan

Tyypillinen laukaiseja on skanneri tai mobiilikaappaussovellus, joka kirjoittaa vaakasivut pystymedianä, johon on merkitty /Rotate 90. Jokainen katselin näyttää ne oikein, joten kukaan ei huomaa mitään, ennen kuin joku leimaa sivunumeron "oikeaan alakulmaan" ja se laskeutuu kyljelleen vasemmalla reunaa pitkin, tai kun asetteluaskel, joka lukee vain /MediaBoxin, asettelee pystyslotin vaakasivulle. Tasottaminen kuulostaa yhden rivin matriisityöltä. Käytännössä se koskettaa viittä sivulaatikkoa, kolmea lajia huomautusgeometriaa, asiakirjan linkkikohteita ja rakennepuuta, ja jokaisella niistä on oma sääntönsä standardissa ISO 32000-1

Kumpaan suuntaan /Rotate kiertää PDF-sivun?

/Rotate kiertää sivun myötäpäivään näyttöä ja tulostusta varten 90 asteen kertomissa (ISO 32000-1 §7.7.3.3, taulukko 30). 90 asteessa median vasemmasta reunasta tulee yläreuna ja yläreunasta oikea sivu, joten alaspäin kasvavassa y-laitetilassa kuvaus on X = (y - Bottom) * Scale ja Y = (x - Left) * Scale. 270 asteessa oikeasta reunasta tulee yläreuna. /Rotate on lisäksi yksi vain neljästä periytyvästä sivuattribuutista yhdessä /Resourcesin, /MediaBoxin ja /CropBoxin kanssa (§7.7.3.4), joten sivusanakirja ilman omaa /Rotatea voi yhä kiertyä /Pages-esivanhemman voimalla. THotPDF.GetLoadedPageRotation kävelee /Parent-ketjun läpi ja normalisoi tuloksen välille 0–359, ja tuo on arvo, jota haluat, ei sivun raaka avain

Suunnan saa helposti väärin tavalla, joka selviää testauksesta hengissä, ja aiemmat HotPDF-buildit tekivät juuri niin. Vanha sivusta laitteeseen -matriisi vaihtoi y-komponentit keskenään kulmilla 90 ja 270, mikä tuottaa heijastuksen diagonaalin yli kierron sijaan: matriisin orientaatio kääntyy verrattuna kierrättämättömään tapaukseen. Molemmat kulmat "näyttävät kierretyiltä" silti, bittikartta on vaihdetun levyinen ja korkea, ja kiekura sivusta näkymään ja takaisin palauttaa lähtöpisteen, joten dimensioiden tarkistukset ja kiekuratestit menevät kaikki läpi. Ainoa luotettava tarkistus on se, mihin kulmamerkki päätyy, verrattuna pikseli pikseliltä referenssirenderoijaan. Koska katselinmalli, SIMD-renderbackend ja korostusmappaus olivat kopioineet saman matriisin, kaikki ne korjattiin yhdessä, ja tasottamiskoodi käyttää nyt samaa myötäpäiväkonventiota kuin renderoija

Miten HotPDF tasottaa sivunkierron Delphissä: pystysivu, joka on talletettu arvolla /Rotate 90, näkyy myötäpäivään 792 kertaa 612 vaakakatseluna, laitekuvaus X = (y - Bottom) * Scale, Y = (x - Left) * Scale siirtää jokaisen kulman, ja matriisin y-komponenttien vaihtaminen tuottaa heijastuksen, jonka vain kulmamerkkien vertailu nappaa
Katselimet kiertävät sivua myötäpäivään näyttöä varten, vaikka tavut pysyvät pystyssä — GetLoadedPageRotation kävelee ensin /Parent-ketjun, koska /Rotate on yksi neljästä periytyvästä sivuattribuutista

Miten FlattenLoadedPageRotation kirjoittaa sivun uudelleen

FlattenLoadedPageRotation(PageRange, Info) käsittelee jokaisen PageRangein sivun, jonka efektiivinen kierto on 90, 180 tai 270, ja palauttaa tasottamiensa sivujen määrän. Tyhjä PageRange tarkoittaa kaikkia sivuja; muuten merkkijono noudattaa tavallista yhdestä alkavaa '1-3,7'-syntaksia, ja alueen ulkopuolinen sivunumero nostaa poikkeuksen ohittamisen sijaan. Alkuperäisiä sisältöstreameja ei koskaan koodata uudelleen. Metodi liittää uuden streamin, joka sisältää rivin q 0 -1 1 0 -Bottom Width+Left cm (90 astetta), sivun /Contentsin alkuun, lisää loppuun streamin, joka sisältää Q:n, ja kirjoittaa lopuksi eksplisiittisen /Rotate 0:n sivusanakirjaan, jotta /Pages-solmulta periytyvä arvo ei voi kiertää sivua toista kertaa

var
  Pdf: THotPDF;
  Info: THPDFRotationFlattenInfo;
  Flattened: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('scanned-batch.pdf') > 0 then
    begin
      // '' = jokainen sivu; 0 asteen sivut skannataan mutta jätetään rauhaan
      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;

THPDFRotationFlattenInfo-tietue on syytä kirjata lokiin sen sijaan, että heittäisi sen menemään. ScannedPageCount on alueen koko, FlattenedPageCount on sama kuin paluuarvo, ja kolme Transformed...-laskuria kertovat, oliko asiakirjassa linkkejä, kirjanmerkkejä tai tagitettua geometriaa, jotka osoittivat kierrettyihin sivuihin. Erä, jossa jokainen tiedosto raportoi nollaa kohdetta, on kunnossa; tagitettu PDF/UA-tiedosto, joka raportoi nollaa rakennegeometriaa, kun odotit kuvioiden rajauslaatikoita, on merkki siitä, että se kannattaa tarkistaa käsin

Mitkä sivulaatikot tasottaminen kirjoittaa uudelleen, ja missä järjestyksessä?

Tasottaminen kirjoittaa uudelleen vain laatikot, jotka sivulla jo on, ja se lukee jokaisen laatikon ennen kuin kirjoittaa yhtäkään. Järjestyksellä on väliä oletusketjun vuoksi: GetLoadedPageBox(PageIndex, pbCropBox, ...) palauttaa /MediaBoxin, kun sivulla ei ole /CropBoxia, ja /BleedBox, /TrimBox ja /ArtBox olettuvat CropBoxiin (§14.11.2). Aiempi versio luki, muunsi ja kirjoitti yhden laatikon kerrallaan. Se kirjoitti MediaBoxin ensin, luki sitten "CropBoxin", sai takaisin jo kierretyyn MediaBoxin, kiersi sen toista kertaa ja kirjoitti CropBoxin, jota sivulla ei koskaan ollut, mikä rajasi vaakasivun neliöksi. Periytymissäännöt jakautuvat samalla tavalla: MediaBox ja CropBox haetaan /Parent-ketjua pitkin, kun taas Bleed, Trim ja ArtBox lasketaan vain, jos ne istuvat sivusanakirjassa itsessään, joten harhainen /TrimBox /Pages-solmulla käsitellään puuttuvana, eikä sitä koskaan kopioida sivulle

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]));
  // Tosi ilmankin /TrimBox-avainta: arvo putoaa CropBoxiin ja sitten MediaBoxiin
  if Pdf.GetLoadedPageBox(PageIndex, pbTrimBox, L, B, R, T) then
    Writeln(Format('TrimBox  [%g %g %g %g]', [L, B, R, T]));
  // Esiasetettu Letter; GetLoadedPageVisibleBox jättää tulosteet koskemattomiksi epäonnistuessaan
  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;

Aja tuo apufunktio ennen tasottamista ja sen jälkeen, ja luvut selittävät itse itsensä. 90-asteiselle sivulle, jonka MediaBox on [0 0 612 792], tasotettu MediaBox muuttuu muodoksi [0 0 792 612]; jokainen uudelleenkirjoitettu laatikko kuvataan saman myötäpäiväkierroksen läpi alkuperäisen MediaBoxin origoon nähden, joten uusi MediaBox alkaa aina origosta ja muut laatikot säilyttävät asemansa sen sisällä. GetLoadedPageVisibleBox palauttaa sen, mitä katselimet näyttävät ja tulostimet tulostavat, CropBoxin rajattuna MediaBoxiin ja normalisoituna niin, että Left on pienempi kuin Right, ja HotPDF:n renderoija, SVG-vienti, katselin ja tulostuspolku käyttävät kaikkia sitä samaa laatikkoa. Kun tarvitset sen sivukoon, jonka ihminen näkee, kutsu funktiota GetLoadedPageVisibleBox /MediaBoxin lukemisen sijaan

Miksi HotPDF lukee jokaisen sivulaatikon ennen yhdenkään kirjoittamista FlattenLoadedPageRotationin aikana: BleedBox, TrimBox ja ArtBox olettuvat CropBoxiin, joka itsessään putoaa MediaBoxiin, joten laatikoiden kiertäminen yksi kerrallaan sai CropBoxin lukemaan jo uudelleenkirjoitetun MediaBoxin, ja toinen kierto kirjoitti laatikon, jota sivulla ei koskaan ollut, rajaten vaakasivun neliöksi
Oletusketju tarkoittaa, että yhden laatikon tuloste on toisen laatikon syöte — lue kaikki ensin, muunna alkuperäisen MediaBoxin origoon nähden, kirjoita sitten

Miksi huomautukset rikkoutuvat, jos kiertää vain /Rectin?

Huomautukset rikkoutuvat, koska ulkoasustreamia ei piirretä suoraan /Rectiin. Kohdan §12.5.5 mukaan katselin muuntaa ensin formia /BBoxin sen /Matrixilla ja skaalaa ja siirtää sitten kyseisen tuloksen rajauslaatikon /Rectiin. Kierrä vain /Rect, ja 200 × 40:n leima puristuu 40 × 200:n slottiin, lukukelvottomana ja kyljellään. FlattenLoadedPageRotation kertoo siksi sivun myötäpäiväkierroksen oikealta jokaisen ulkoasun /Matrixin perään (90 astetta: [0 -1 1 0 0 0] rivivektorikonventiossa) kaikissa /N-, /R- ja /D-ulkoasuissa ja jokaisessa niiden sisällä olevassa tilassa. Yhden ulkoasustreamin voi jakaa useampi huomautus tai tila, joten jokainen stream kierrätetään täsmälleen kerran per kutsu. Ainoa tapaus ilman siistiä vastausta on stream, joka on jaettu sivujen välillä eri kieroilla; se seuraa ensimmäistä sivua, joka siihen ulottuu

Kaksi sääntöä lisää pitävät lomakekentät ja muistilaput paikoillaan. Widgetin /MK /R-merkintä (§12.5.6.19) on vastapäiväkulma, joten sivun myötäpäiväkulma vähennetään siitä modulo 360; jos tuon ohittaa, seuraava ulkoasun uudelleengenerointi piirtää kenttätekstin väärään suuntaan. NoRotate-lipun (bittipaikka 5, arvo 16, §12.5.3) kantavat huomautukset pysyvät pystyssä kierretyllä sivulla ja kääntyvät /Rectinsä vasemman yläkulman ympäri, joten tasottaminen säilyttää niiden leveyden, korkeuden ja pystyasun ja siirtää vain kyseisen kulman sinne, mihin kierto sen laittaa. Huomautusten lisäksi metodi kiertää myös /QuadPointsit, /Verticesit, /Lin ja /InkListin, kirjoittaa uudelleen sivun nimeävät eksplisiittiset kohteet (/XYZ-pisteet, /FitR-suorakulmiot sekä /FitH / /FitV vaihdettuina kulmilla 90 ja 270, §12.3.2.2) ja muuntaa tagitettua geometriaa, kuten attribuutin /BBox-merkintöjä rakennealkioille, joiden /Pg on kyseinen sivu

Miksi huomautukset rikkoutuvat, kun HotPDF-sivu tasotetaan kiertämällä vain /Rect: 200 kertaa 40:n leima skaalataan 40 kertaa 200:n slottiin ja muuttuu lukukelvottomaksi, joten FlattenLoadedPageRotation kertoo myötäpäiväkierroksen oikealta jokaisen ulkoasun /Matrixin perään kaikissa /N, /R ja /D, säätää vastapäiväisen /MK /R:n ja kääntää NoRotate-huomautukset niiden vasemman yläkulman ympäri
Katselin sovittaa ulkoasun muunnetun BBoxin /Rectiin, joten streamin itsensä on käännyttävä — yksi kierros jaettua ulkoasua kohti, täsmälleen kerran per kutsu

Mitä tasottaminen ei kata?

Tasottaminen on yhden sivun omien objektien geometrinen uudelleenkirjoitus, ja useat tilanteet jäävät sen ulkopuolelle hiljaa, eivät äänekkäästi

  • Sivut, joiden efektiivinen kierto on jo 0 tai joiden MediaBox puuttuu tai on nollan levyinen tai korkea, ohitetaan ilman virhettä; vertaa paluuarvoa siihen sivumäärään, jonka odotit muuttuvan
  • Sivuresursseista viitatut form-XObjectit pitävät oman /BBoxinsa formin tilassa, koska uloin cm kiertää niitä jo; rakennepuun skannaus seuraa vain /Kta ja /Ata, joten se ei koskaan kävele sivun resursseihin tai huomautuksiin toista kertaa
  • Kohteet löydetään skannaamalla jokainen epäsuora objekti kerran tasotettua sivua kohti, joten suuri asiakirja, jossa on satoja kierrettyjä sivuja, maksaa sen kävelyn jokaisesta
  • HotPDF:n sivurenderoija ei piirrä huomautuksia, joten kierrettyjen leimojen visuaalinen tarkistus tarvitsee ensin funktion FlattenLoadedAnnotations
// Paista ulkoasut sisältöön, jotta renderoija voi näyttää ne,
// ja renderöi sivu 1 ennen ja jälkeen sen /Rotaten poistamisen
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));
    // Vertaa kulmamerkin pikseleitä tässä, ei vain dimensioita
  finally
    After.Free;
  end;
finally
  Before.Free;
end;

Syvempää taustaa varten tämän tarinan huomautuspuoli jatkuu kirjoituksessa huomautusten ulkoasujen syntetisointi ennen niiden tasottamista, vertailun taustalla oleva renderoija on käsitelty kirjoituksessa ladatun PDF-sivun renderöinti bittikartaksi, ja mustaus ja N-up-ompeleminen ladatuille PDF:ille näyttää saman sisältöstreamiin liittämisen tekniikan, johon kiertoetuliite ja -loppuliite nojaavat. HotPDF, mukaan lukien FlattenLoadedPageRotation ja sivulaatikoiden lukijat, on saatavilla Delphille ja C++Builderille HotPDF Delphi PDF component -sivulla