HotPDF flader PDF-siderotation ud med THotPDF.FlattenLoadedPageRotation: metoden pakker hver roteret sides content ind i en cm-transformation med urets retning, omskriver hver sidebox, siden faktisk har, drejer annoteringsgeometri, appearance-matricer, eksplicitte destinations og tagget strukturgeometri med samme vinkel og sætter derefter /Rotate til 0. Siden ser identisk ud i en viewer, men dens koordinatsystem er nu oprejst. Det betyder noget i det øjeblik, et downstream-værktøj, en print-RIP eller din egen stemple-kode ignorerer /Rotate og placerer ting i rå user space
Den typiske udløser er en scanner eller en mobil capture-app, der skriver landskabssider som portræt-medier med /Rotate 90. Hver viewer viser dem korrekt, så ingen bemærker det, før nogen stempler et sidetal i "nederste højre hjørne", og det lander på tværs hen ad venstre kant, eller et imposition-trin, der kun læser /MediaBox, lægger en portrætplads ud til en landskabsside. Udjævning lyder som et én-linjers matrixjob. I praksis rører det fem sideboxes, tre slags annoteringsgeometri, dokumentets link-targets og strukturtræet, og hver af dem har sin egen regel i ISO 32000-1
Hvilken vej drejer /Rotate en PDF-side?
/Rotate drejer siden med urets retning til visning og udskrivning, i multipler af 90 grader (ISO 32000-1 §7.7.3.3, tabel 30). Ved 90 grader bliver mediets venstre kant toppen, og topkanten bliver højresiden, så i et y-ned device space er mappingen X = (y - Bottom) * Scale og Y = (x - Left) * Scale. Ved 270 grader bliver højrekanten toppen. /Rotate er også én af kun fire arvelige sideattributter, sammen med /Resources, /MediaBox og /CropBox (§7.7.3.4), så et side-dictionary uden egen /Rotate kan stadig drejes af en /Pages-forfader. THotPDF.GetLoadedPageRotation går /Parent-kæden igennem og normaliserer resultatet til 0–359, hvilket er den værdi, du vil have, ikke rå-nøglen på siden
Retningen er let at få forkert på en måde, der overlever test, og tidligere HotPDF-builds gjorde præcis det. Den gamle side-til-device-matrix byttede y-komponenterne ved 90 og 270, hvilket producerer en reflektion hen over diagonalen i stedet for en rotation: matricens orientering vender i forhold til det uroterede tilfælde. Begge vinkler "ser roterede ud" stadig, bitmapmen har den byttede bredde og højde, og en round-trip fra side til view og tilbage returnerer startpunktet, så dimensionstjek og round-trip-test består alle. Det eneste pålidelige tjek er, hvor en hjørnemarkør ender, sammenlignet pixel for pixel med en reference-renderer. Fordi viewer-modellen, SIMD-render-backend'en og highlight-mappingen havde kopieret samme matrix, blev alle rettet sammen, og udjævningskoden bruger nu samme konvention med urets retning som rendereren
Sådan omskriver FlattenLoadedPageRotation en side
FlattenLoadedPageRotation(PageRange, Info) behandler hver side i PageRange, hvis effektive rotation er 90, 180 eller 270, og returnerer antallet af sider, den udjævnede. En tom PageRange betyder alle sider; ellers bruger strengen den sædvanlige én-baserede '1-3,7'-syntaks, og et sidetal ude af interval rejser en exception i stedet for at blive sprunget over. De oprindelige content streams genkodes aldrig. Metoden sætter en ny stream med q 0 -1 1 0 -Bottom Width+Left cm (ved 90 grader) foran sidens /Contents, tilføjer en stream med Q til sidst og skriver til sidst en eksplicit /Rotate 0 ind i side-dictionaryen, så en arvet værdi på en /Pages-knude ikke kan dreje siden en gang til
var
Pdf: THotPDF;
Info: THPDFRotationFlattenInfo;
Flattened: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('scanned-batch.pdf') > 0 then
begin
// '' = alle sider; sider ved 0 grader scannes, men røres ikke
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;
Recorden THPDFRotationFlattenInfo er værd at logge frem for at smide væk. ScannedPageCount er størrelsen af intervallet, FlattenedPageCount svarer til returværdien, og de tre Transformed...-tællere fortæller dig, om dokumentet havde links, bogmærker eller tagget geometri, der pegede på de drejede sider. En batch, hvor hver fil rapporterer nul destinations, er fin; en tagget PDF/UA-fil, der rapporterer nul strukturgeometri, når du forventede figur-bounding boxes, er et signal om at inspicere den i hånden
Hvilke sideboxes omskriver udjævningen, og i hvilken rækkefølge?
Udjævningen omskriver kun de boxes, siden allerede har, og den læser hver box, inden den skriver nogen af dem. Rækkefølgen betyder noget på grund af standardkæden: GetLoadedPageBox(PageIndex, pbCropBox, ...) returnerer /MediaBox, når siden ikke har nogen /CropBox, og /BleedBox, /TrimBox og /ArtBox falder tilbage til CropBox (§14.11.2). En tidligere version læste, transformerede og skrev én box ad gangen. Den omskrev MediaBox først, læste så "CropBox", fik den allerede drejede MediaBox tilbage, drejede den en gang til og skrev en CropBox, siden aldrig havde haft, hvilket beskar en landskabsside ned til en firkant. Arvereglerne deler sig på samme måde: MediaBox og CropBox slås op langs /Parent-kæden, mens Bleed, Trim og ArtBox kun tæller, hvis de ligger på side-dictionaryen selv, så en løs /TrimBox på en /Pages-knude behandles som fraværende og kopieres aldrig over på siden
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 selv uden en /TrimBox-nøgle: værdien falder tilbage til CropBox, derefter MediaBox
if Pdf.GetLoadedPageBox(PageIndex, pbTrimBox, L, B, R, T) then
Writeln(Format('TrimBox [%g %g %g %g]', [L, B, R, T]));
// Forudindstillet Letter; GetLoadedPageVisibleBox lader outputs være urørte ved fejl
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;
Kør den helper før og efter udjævningen, og tallene forklarer sig selv. For en 90-graders side med MediaBox [0 0 612 792] bliver den udjævnede MediaBox [0 0 792 612]; hver omskrevet box mappes gennem samme drejning med urets retning, relativt til den oprindelige MediaBox-origo, så den nye MediaBox altid starter ved origo, og de andre boxes beholder deres position indeni. GetLoadedPageVisibleBox returnerer det, viewere viser og printere printer, CropBoxen beskåret til MediaBoxen og normaliseret, så Left er mindre end Right, og HotPDFs renderer, SVG-eksport, viewer og print-sti bruger alle samme box. Når du har brug for den sidestørrelse, et menneske ser, så kald GetLoadedPageVisibleBox frem for at læse /MediaBox
Hvorfor knækker annoteringer, når du kun drejer /Rect?
Annoteringer knækker, fordi en appearance stream ikke tegnes direkte ind i /Rect. Under §12.5.5 transformerer vieweren først formens /BBox med dens /Matrix og skalerer og translaterer derefter resultatets bounding box ind i /Rect. Drej kun /Rect, og et stempel på 200 × 40 bliver presset ind i en plads på 40 × 200, ulæseligt og på tværs. FlattenLoadedPageRotation right-multiplicerer derfor sidens drejning med urets retning på hver appearance-/Matrix (ved 90 grader [0 -1 1 0 0 0] i row-vector-konventionen), på tværs af /N-, /R- og /D-appearances og hver tilstand indeni. Én appearance stream kan deles mellem flere annoteringer eller tilstande, så hver stream drejes præcis én gang pr. kald. Det ene tilfælde uden et rent svar er en stream, der deles på tværs af sider med forskellige rotationer; den følger den første side, der når den
To regler mere holder formfelter og sticky notes på plads. En widgets /MK /R-indslag (§12.5.6.19) er en vinkel mod urets retning, så sidens vinkel med urets retning trækkes fra den, modulo 360; spring det over, og næste appearance-regenerering tegner feltteksten i den forkerte retning. Annoteringer med NoRotate-flagget (bitposition 5, værdi 16, §12.5.3) forbliver oprejst på en roteret side og pivoterer omkring øverste venstre hjørne af deres /Rect, så udjævningen beholder deres bredde, højde og oprejste udseende og kun flytter det hjørne til, hvor drejningen lægger det. Ud over annoteringer drejer metoden også /QuadPoints, /Vertices, /L og /InkList, omskriver eksplicitte destinations, der navngiver siden (/XYZ-punkter, /FitR-rektangler og /FitH / /FitV byttet ved 90 og 270 grader, §12.3.2.2) og transformerer tagget geometri som attribut-/BBox-indslag for strukturelementer, hvis /Pg er siden
Hvad dækker udjævningen ikke?
Udjævning er en geometrisk omskrivning af én sides egne objekter, og flere situationer falder uden for den i stilhed frem for med larm
- Sider, hvis effektive rotation allerede er 0, eller hvis MediaBox mangler eller har bredde eller højde nul, springes over uden en fejl; sammenlign returværdien med antallet af sider, du forventede ændret
- Form XObjects, der refereres fra sideressourcerne, beholder deres egen
/BBoxi form space, fordi den ydrecmallerede drejer dem; strukturtræ-scanningen følger kun/Kog/A, så den går aldrig ind i sidens ressourcer eller annoteringer en anden gang - Destinations findes ved at skanne hvert indirekte objekt én gang pr. udjævnet side, så et stort dokument med hundreder af roterede sider betaler for den gennemgang ved hver af dem
- HotPDFs side-renderer tegner ikke annoteringer, så et visuelt tjek af drejede stempler kræver
FlattenLoadedAnnotationsførst
// Bager appearances ind i content, så rendereren kan vise dem,
// og render siden 1 før og efter fjernelsen af dens /Rotate
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));
// Sammenlign hjørnemarkør-pixels her, ikke kun dimensionerne
finally
After.Free;
end;
finally
Before.Free;
end;
For dybere baggrund fortsætter annoteringssiden af historien i at syntetisere annotation appearances, før de udjævnes, rendereren bag før-og-efter-sammenligningen er dækket i at render en indlæst PDF-side til en bitmap, og redaktion og N-up-samling på indlæste PDF'er viser samme content-stream-append-teknik, som rotationspræfikset og -suffikset bygger på. HotPDF, inklusive FlattenLoadedPageRotation og sidebox-læserne, er tilgængelig til Delphi og C++Builder på HotPDF Delphi PDF component-siden