Techninis straipsnis

PDF puslapio pasukimo suvienodinimas be rėmelių gedimų

HotPDF PDF puslapio pasukimą suvienodina su THotPDF.FlattenLoadedPageRotation: metodas kiekvieno pasukto puslapio turinį apgaubia pagal laikrodžio rodyklę einančiu cm transformavimu, perrašo kiekvieną puslapio rėmelį, kurį puslapis iš tiesų turi, ta pačia kampu pasuka anotacijų geometriją, išvaizdų matricas, atvirus tikslus ir žymėtosios struktūros geometriją, o tada nustato /Rotate į 0. Puslapis peržiūryklėje atrodo identiškai, bet jo koordinačių sistema dabar stati. Tai ima rūpėti akimirką, kai kitas įrankis, spausdinimo RIP arba jūsų paties antspaudavimo kodas ignoruoja /Rotate ir deda daiktus į žalią vartotojo erdvę

Įprastas trigeris yra skaitytuvas arba mobili fiksavimo programėlė, kurie horizontalius puslapius rašo kaip vertikalią media su /Rotate 90. Kiekviena peržiūryklė juos rodo teisingai, tad niekas nepastebi, kol kas nors uždeda puslapio numerį „apačioje dešinėje“ ir jis nusileidžia į šoną palei kairįjį kraštą, arba kol išklotinės žingsnis, skaitantis vien /MediaBox, vertikaliam puslapiui paruošia vertikalų langelį. Suvienodinimas skamba kaip matricos darbas viena eilute. Praktikoje jis liečia penkis puslapio rėmelius, tris rūšis anotacijų geometrijos, dokumento nuorodų tikslus ir struktūros medį, ir kiekvienas iš jų turi savo taisyklę ISO 32000-1

Kuria kryptimi /Rotate pasuka PDF puslapį?

/Rotate puslapį rodymui ir spausdinimui pasuka pagal laikrodžio rodyklę, 90 laipsnių kartotiniais (ISO 32000-1 §7.7.3.3, 30 lentelė). Esant 90 laipsnių media kairysis kraštas tampa viršumi, o viršutinis kraštas – dešiniąja puse, tad į apačią krentančioje įrenginio erdvėje atvaizdavimas yra X = (y - Bottom) * Scale ir Y = (x - Left) * Scale. Esant 270 laipsnių dešinysis kraštas tampa viršumi. /Rotate yra ir vienas iš tik keturių paveldimų puslapio atributų, kartu su /Resources, /MediaBox ir /CropBox (§7.7.3.4), tad puslapio žodynas be savo /Rotate vis tiek gali būti pasuktas /Pages protėvio. THotPDF.GetLoadedPageRotation eina /Parent grandine ir rezultatą suvienodina į 0–359 – tai reikšmė, kurios jums reikia, o ne žali raktas pačiame puslapyje

Kryptį lengva sugadinti taip, kad gedimas išgyvena testavimą, ir ankstesnės HotPDF laidos padarė lygiai tai. Senoji puslapio-į-įrenginį matrica 90 ir 270 kampams sukeisdavo y dedamąsias, o tai duoda atspindį per įstrižainę vietoj pasukimo: matricos orientacija apsiverčia, lyginant su nesuktu atveju. Abu kampai vis tiek „atrodo pasukti“, taškinis vaizdas turi sukeistas plotį ir aukštį, o round-trip iš puslapio į vaizdą ir atgal grąžina pradinį tašką, tad matmenų patikros ir round-trip testai visi praeina. Vienintelė patikima patikra yra tai, kur atsiduria kampinis ženklas, lyginant pikselis po pikselio su atskaitiniu atvaizduotoju. Kadangi peržiūryklės modelis, SIMD atvaizdavimo posistemė ir žymėjimo atvaizdavimas buvo nukopijavę tą pačią matricą, visi jie ištaisyti kartu, o suvienodinimo kodas dabar naudoja tą pačią pagal laikrodžio rodyklę einančią konvenciją kaip atvaizduotojas

Kaip HotPDF Delphi suvienodina puslapio pasukimą: vertikalus puslapis, saugomas su /Rotate 90, rodomas pagal laikrodžio rodyklę kaip 792 x 612 horizontalus vaizdas, įrenginio atvaizdavimas X = (y - Bottom) * Scale, Y = (x - Left) * Scale perkelia kiekvieną kampą, o matricos y dedamųjų sukeitimas duoda atspindį, kurį pagavo tik kampinio ženklo lyginimas
Peržiūryklės puslapį rodymui pasuka pagal laikrodžio rodyklę, kol baitai lieka vertikalūs – GetLoadedPageRotation pirmiausiai eina /Parent grandine, nes /Rotate yra vienas iš keturių paveldimų puslapio atributų

Kaip FlattenLoadedPageRotation perrašo puslapį

FlattenLoadedPageRotation(PageRange, Info) apdoroja kiekvieną PageRange puslapį, kurio faktinis pasukimas yra 90, 180 arba 270, ir grąžina suvienodintų puslapių skaičių. Tuščias PageRange reiškia visus puslapius; kitu atveju eilutė naudoja įprastą nuo vieno skaičiuojamą '1-3,7' sintaksę, o už ribų esantis puslapio numeris kelia išimtį, o ne praleidžiamas. Originalūs turinio srautai niekada neperkoduojami. Metodas prieš puslapio /Contents prikiša naują srautą su q 0 -1 1 0 -Bottom Width+Left cm (90 laipsnių atveju), gale prideda srautą su Q, o galiausiai į puslapio žodyną įrašo atvirą /Rotate 0, kad /Pages mazge paveldėta reikšmė negalėtų puslapio pasukti antrą kartą

var
  Pdf: THotPDF;
  Info: THPDFRotationFlattenInfo;
  Flattened: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('scanned-batch.pdf') > 0 then
    begin
      // '' = visi puslapiai; 0 laipsnių puslapiai nuskaitomi, bet paliekami ramybėje
      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 įrašo verta įrašinėti į žurnalą, o ne išmesti. ScannedPageCount yra diapazono dydis, FlattenedPageCount lygus grąžinamai reikšmei, o trys Transformed... skaitikliai pasako, ar dokumente buvo nuorodų, gairių arba žymėtosios geometrijos, rodančios į pasuktus puslapius. Paketas, kuriame kiekvienas failas praneša nulį tikslų, yra tvarkoje; žymėtas PDF/UA failas, pranešantis nulinę struktūros geometriją, kai tikėjatės paveikslų rėmelių, yra signalas apžiūrėti jį rankomis

Kurius puslapio rėmelius suvienodinimas perrašo ir kokia tvarka?

Suvienodinimas perrašo tik rėmelius, kuriuos puslapis jau turi, ir skaito kiekvieną rėmelį dar prieš parašydamas bet kurį iš jų. Tvarka svarbi dėl numatytosios grandinės: GetLoadedPageBox(PageIndex, pbCropBox, ...) grąžina /MediaBox, kai puslapis neturi /CropBox, o /BleedBox, /TrimBox ir /ArtBox pagal nutylėjimą remiasi CropBox (§14.11.2). Ankstesnė versija vienu metu skaitė, transformuodavo ir rašydavo po vieną rėmelį. Ji pirmiausiai perrašydavo MediaBox, tada skaitydavo „CropBox“, gaudavo jau pasuktą MediaBox, pasukdavo jį antrą kartą ir parašydavo CropBox, kurio puslapis niekada neturėjo – tai horizontalų puslapį nukirpdavo iki kvadrato. Paveldimumo taisyklės skyla taip pat: MediaBox ir CropBox ieškoma /Parent grandine, o Bleed, Trim ir ArtBox skaičiuojami tik tada, kai sėdi pačiame puslapio žodyne, tad atsitiktinė /TrimBox ant /Pages mazgo laikoma nebūtima ir niekada nekopijuojama ant puslapio

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]));
  // Teisinga net be /TrimBox rakto: reikšmė grįžta į CropBox, paskui į MediaBox
  if Pdf.GetLoadedPageBox(PageIndex, pbTrimBox, L, B, R, T) then
    Writeln(Format('TrimBox  [%g %g %g %g]', [L, B, R, T]));
  // Iš anksto nustatytas Letter; GetLoadedPageVisibleBox nesėkmės atveju palieka išvestis nepaliestas
  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;

Paleiskite tą pagalbinę procedūrą prieš suvienodinimą ir po jo, ir skaičiai pasakys patys. 90 laipsnių puslapiui su MediaBox [0 0 612 792] suvienodintas MediaBox tampa [0 0 792 612]; kiekvienas perrašomas rėmelis atvaizduojamas per tą patį pagal laikrodžio rodyklę einantį posūkį, priskiriant originalios MediaBox pradžiai, tad naujas MediaBox visuomet prasideda nuo pradžios taško, o kiti rėmeliai išlaiko savo vietą jame. GetLoadedPageVisibleBox grąžina tai, ką rodo peržiūryklės ir spausdina spausdintuvai – CropBox, apkarpytą MediaBox ir suvienodintą taip, kad Left būtų mažesnis už Right – ir HotPDF atvaizduotojas, SVG eksportas, peržiūryklė bei spausdinimo kelias naudoja tą patį rėmelį. Kai reikia žmogui matomo puslapio dydžio, kvieskite GetLoadedPageVisibleBox, o ne skaitykite /MediaBox

Kodėl HotPDF FlattenLoadedPageRotation metu skaito kiekvieną puslapio rėmelį prieš rašydamas bet kurį: BleedBox, TrimBox ir ArtBox pagal nutylėjimą remiasi CropBox, kuris pats grįžta į MediaBox, tad rėmelių posūkis po vieną priversdavo CropBox skaityti jau perrašytą MediaBox, o antras posūkis parašydavo rėmelį, kurio puslapis niekada neturėjo, ir horizontalų puslapį nukirpdavo iki kvadrato
Numatytoji grandinė reiškia, kad vieno rėmelio išvestis yra kito rėmelio įvedimas – perskaitykite viską, transformuokite pagal originalios MediaBox pradžią, tada rašykite

Kodėl anotacijos sulūžta, kai pasuki vien /Rect?

Anotacijos sulūžta, nes išvaizdos srautas nėra piešiamas tiesiai į /Rect. Pagal §12.5.5 peržiūryklė pirmiausiai formos /BBox transformuoja jos /Matrix, tada to rezultato aptinkamąjį rėmelį ištempia ir perkelia į /Rect. Pasukus vien /Rect, 200 × 40 antspaudas suspaudžiamas į 40 × 200 vietą – neskaitytinas ir gulintis ant šono. FlattenLoadedPageRotation todėl puslapio posūkį pagal laikrodžio rodyklę dešinėja sudaugina su kiekviena išvaizdos /Matrix (90 laipsnių atveju [0 -1 1 0 0 0] eilučių-viktorės konvencijoje) per /N, /R ir /D išvaizdas ir kiekvieną jų būseną. Vienas išvaizdos srautas gali būti bendras kelioms anotacijoms ar būsenoms, tad kiekvienas srautas per iškvietimą pasukamas lygiai kartą. Vienintelis atvejis be švaraus atsakymo yra srautas, bendras puslapiams su skirtingais pasukimais; jis seka pirmąjį puslapį, kuris jį pasiekia

Dar dvi taisyklės laiko formos laukus ir lipnius lapelius savo vietose. Valdiklio /MK /R įrašas (§12.5.6.19) yra kampas prieš laikrodžio rodyklę, tad puslapio kampas pagal laikrodžio rodyklę iš jo atimamas, modulis 360; praleiskite tai, ir kita išvaizdos atkūrimo iteracija nupieš lauko tekstą netinkama kryptimi. Anotacijos su NoRotate vėliavėle (bito pozicija 5, reikšmė 16, §12.5.3) pasuktame puslapyje lieka statios ir sukasi aplink savo /Rect viršutinį kairįjį kampą, tad suvienodinimas išlaiko jų plotį, aukštį ir statų pavidalą ir tik tą kampą perkelia ten, kur jį padeda posūkis. Už anotacijų ribų metodas taip pat pasuka /QuadPoints, /Vertices, /L ir /InkList, perrašo atvirus tikslus, įvardijančius puslapį (/XYZ taškus, /FitR stačiakampius, o /FitH / /FitV sukeistus ties 90 ir 270 laipsnių, §12.3.2.2), ir transformuoja žymėtąją geometriją, pavyzdžiui, atributų /BBox įrašus struktūros elementams, kurių /Pg yra tas puslapis

Kodėl anotacijos sulūžta, kai HotPDF puslapis suvienodinamas pasukant vien /Rect: 200 x 40 antspaudas ištempiamas į 40 x 200 vietą ir tampa neskaitytinu, tad FlattenLoadedPageRotation puslapio posūkį pagal laikrodžio rodyklę dešinėja sudaugina su kiekviena išvaizdos /Matrix per /N, /R ir /D, pakoreuoja prieš laikrodžio rodyklę einantį /MK /R ir NoRotate anotacijas suka aplink jų viršutinį kairįjį kampą
Peržiūryklė išvaizdos transformuotą BBox įtalpina į /Rect, tad pats srautas turi pasukti – vienas praėjimas kiekvienai bendrai išvaizdai, lygiai kartą per iškvietimą

Ko suvienodinimas nedengia?

Suvienodinimas yra geometrinis vieno puslapio savų objektų perrašymas, ir kelios situacijos iš jo iškrenta tyliai, o ne garsiai

  • Puslapiai, kurių faktinis pasukimas jau 0, arba kurių MediaBox dingęs arba turi nulinį plotį arba aukštį, praleidžiami be klaidos; grąžinamą reikšmę palyginkite su puslapių, kurių tikėtasi pakeisti, skaičiumi
  • Formos XObjects, nurodyti iš puslapio išteklių, savo /BBox formos erdvėje palieka sau, nes išorinis cm juos jau pasuka; struktūros medžio skenavimas seka tik /K ir /A, tad antrą kartą niekada neužbėga į puslapio išteklius ar anotacijas
  • Tikslai randami nuskaitant kiekvieną netiesioginį objektą po kartą kiekvienam suvienodintam puslapiui, tad didelis dokumentas su šimtais pasuktų puslapių už tą apėjimą sumoka po kartą kiekvienam
  • HotPDF puslapių atvaizduotojas anotacijų nededo, tad pasuktų antspaudų vizualinei patikrai pirmiausiai prireikia FlattenLoadedAnnotations
// Išvaizdas įkepkite į turinį, kad atvaizduotojas jas parodytų,
// tada atvaizduokite 1 puslapį prieš ir po jo /Rotate pašalinimo
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));
    // Čia palyginkite kampinio ženklo pikselius, o ne vien matmenis
  finally
    After.Free;
  end;
finally
  Before.Free;
end;

Dėl gilesnio konteksto šios istorijos anotacijų pusė tęsiasi anotacijų išvaizdų sintezėje prieš suvienodinimą, atvaizduotojas už prieš-po lyginimo aprašytas pakrauto PDF puslapio atvaizdavime į taškinį vaizdą, o juodinimas ir N-up suklijavimas pakrautuose PDF rodo tą pačią turinio srauto priklijavimo techniką, kuria remiasi pasukimo priešdėlis ir galūnė. HotPDF, įskaitant FlattenLoadedPageRotation ir puslapių rėmelių skaitytuvus, prieinamas Delphi ir C++Builder HotPDF Delphi PDF komponento puslapyje