HotPDF jämnar ut PDF-sidrotation med THotPDF.FlattenLoadedPageRotation: metoden sveper in varje roterad sidas innehåll i en medurs cm-transform, skriver om varje sidbox som sidan faktiskt har, vrider annoteringsgeometri, appearance-matriser, explicita destinationer och taggad strukturgeometri med samma vinkel och sätter sedan /Rotate till 0. Sidan ser identisk ut i en visare, men dess koordinatsystem står nu rakt upp. Det spelar roll i ögonblicket ett nedströmsverktyg, en print RIP eller din egen stämplingskod ignorerar /Rotate och placerar saker i rå user space
Den typiska utlösaren är en skanner eller en mobil insamlingsapp som skriver liggande sidor som stående media med /Rotate 90. Varje visare visar dem korrekt, så ingen lägger märke till det förrän någon stämplar ett sidnummer i "nedersta högra hörnet" och det landar på tvären längs vänsterkanten, eller ett impositionssteg som bara läser /MediaBox ritar upp en stående plats åt en liggande sida. Att jämna ut låter som ett enradsmatrisjobb. I praktiken rör det fem sidboxar, tre slags annoteringsgeometri, dokumentets länkmål och strukturträdet, och vart och ett av dem har sin egen regel i ISO 32000-1
Vilket håll vrider /Rotate en PDF-sida?
/Rotate vrider sidan medurs för visning och utskrift, i multipler av 90 grader (ISO 32000-1 §7.7.3.3, tabell 30). Vid 90 grader blir mediats vänsterkant toppen och toppkanten den högra sidan, så i ett y-ned-enhetrum är mappningen X = (y - Bottom) * Scale och Y = (x - Left) * Scale. Vid 270 grader blir högerkanten toppen. /Rotate är också en av bara fyra ärvbara sidattribut, jämte /Resources, /MediaBox och /CropBox (§7.7.3.4), så en sidordbok utan egen /Rotate kan ändå vridas av en /Pages-förfader. THotPDF.GetLoadedPageRotation går /Parent-kedjan och normaliserar resultatet till 0–359, vilket är värdet du vill ha, inte den råa nyckeln på sidan
Riktningen är lätt att få fel på ett sätt som överlever testning, och tidigare HotPDF-byggen gjorde precis det. Den gamla sidan-till-enhet-matrisen bytte plats på y-komponenterna för 90 och 270, vilket ger en spegling över diagonalen i stället för en rotation: matrisens orientering flippas relativt det oroterade fallet. Båda vinklarna "såg roterade ut" ändå, bitmapen hade det utbytta höjd- och breddmåttet, och en roundtrip från sida till vy och tillbaka returnerade startpunkten, så dimensionskontroller och roundtrip-tester gick alla igenom. Den enda tillförlitliga kontrollen är var en hörnmarkör hamnar, jämförd pixel för pixel mot en referensrenderare. Eftersom visarmodellen, SIMD-renderbackenden och highlight-mappningen hade kopierat samma matris rättades alla tillsammans, och flatteningskoden använder nu samma medurs konvention som renderaren
Hur FlattenLoadedPageRotation skriver om en sida
FlattenLoadedPageRotation(PageRange, Info) bearbetar varje sida i PageRange vars effektiva rotation är 90, 180 eller 270 och returnerar antalet sidor den jämnade ut. En tom PageRange betyder alla sidor; i annat fall använder strängen den vanliga enbaserade syntaxen '1-3,7', och ett sidnummer utanför intervallet kastar ett undantag i stället för att hoppas över. De ursprungliga innehållsströmmarna kodas aldrig om. Metoden lägger en ny ström som innehåller q 0 -1 1 0 -Bottom Width+Left cm (för 90 grader) före sidans /Contents, bifogar en ström med Q och skriver slutligen en explicit /Rotate 0 i sidordboken så att ett ärvt värde på en /Pages-nod inte kan vrida sidan en gång till
var
Pdf: THotPDF;
Info: THPDFRotationFlattenInfo;
Flattened: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('scanned-batch.pdf') > 0 then
begin
// '' = alla sidor; sidor på 0 grader skannas men lämnas ifred
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;
Posten THPDFRotationFlattenInfo är värd att logga i stället för att kassera. ScannedPageCount är intervallets storlek, FlattenedPageCount är lika med returvärdet, och de tre Transformed...-räknarna talar om huruvida dokumentet hade länkar, bokmärken eller taggad geometri som pekade på de vridna sidorna. En batch där varje fil rapporterar noll destinationer är fin; en taggad PDF/UA-fil som rapporterar noll strukturgeometri när du väntade figuromslutningsrutor är en signal att inspektera den för hand
Vilka sidboxar skriver flattening om, och i vilken ordning?
Flatteningen skriver bara om boxarna sidan redan har, och den läser varje box innan den skriver någon. Ordningen spelar roll på grund av standardkedjan: GetLoadedPageBox(PageIndex, pbCropBox, ...) returnerar /MediaBox när sidan saknar /CropBox, och /BleedBox, /TrimBox och /ArtBox faller tillbaka till CropBox (§14.11.2). En tidigare version läste, transformerade och skrev en box i taget. Den skrev om MediaBox först, läste sedan "CropBox", fick den redan vridna MediaBox:en tillbaka, vred den en gång till och skrev en CropBox som sidan aldrig haft, vilket beskar en liggande sida till en kvadrat. Ärvningsreglerna delas på samma sätt: MediaBox och CropBox slås upp längs /Parent-kedjan, medan Bleed, Trim och ArtBox bara räknas om de sitter på sidordboken själv, så en villfarelse som en /TrimBox på en /Pages-nod behandlas som frånvarande och kopieras aldrig över på sidan
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]));
// Sant även utan en /TrimBox-nyckel: värdet faller tillbaka till CropBox, sedan MediaBox
if Pdf.GetLoadedPageBox(PageIndex, pbTrimBox, L, B, R, T) then
Writeln(Format('TrimBox [%g %g %g %g]', [L, B, R, T]));
// Förinställt Letter; GetLoadedPageVisibleBox lämnar utgångsvärdena orörda vid misslyckande
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 hjälparen före och efter flattening så förklarar talen sig själva. För en sida på 90 grader med MediaBox [0 0 612 792] blir den utjämnade MediaBox:en [0 0 792 612]; varje omskriven box mappas genom samma medurs vridning, relativt original-MediaBox:ens origo, så den nya MediaBox:en börjar alltid i origo och de andra boxarna behåller sin plats inuti den. GetLoadedPageVisibleBox returnerar det visare visar och skrivare skriver ut, CropBox:en klippt mot MediaBox:en och normaliserad så att Left är mindre än Right, och HotPDF:s renderare, SVG-export, visare och utskriftsväg använder alla samma box. När du behöver den sidstorlek en människa ser, anropa GetLoadedPageVisibleBox i stället för att läsa /MediaBox
Varför går annoteringar sönder när du bara vrider /Rect?
Annoteringar går sönder för att en appearance-ström inte ritas rakt in i /Rect. Enligt §12.5.5 transformerar visaren först formens /BBox med dess /Matrix, skalar och flyttar sedan den resultatets omslutningsruta in i /Rect. Vrid bara /Rect och ett stämpel på 200 × 40 kläms ihop i en plats på 40 × 200, oläslig och liggande på sidan. FlattenLoadedPageRotation högermultiplicerar därför sidans medurs vridning på varje appearance-/Matrix (för 90 grader, [0 -1 1 0 0 0] i radvektorkonventionen), över /N-, /R- och /D-appearance:erna och varje tillstånd i dem. En appearance-ström kan delas av flera annoteringar eller tillstånd, så varje ström vrids exakt en gång per anrop. Det enda fallet utan ett rent svar är en ström som delas över sidor med olika rotationer; den följer den första sida som når den
Två regler till håller formulärfält och klisterlappar på plats. En widgets /MK /R-post (§12.5.6.19) är en moturs vinkel, så sidans medurs vinkel subtraheras från den, modulo 360; hoppa över det och nästa appearance-regenerering ritar fälttexten i fel riktning. Annoteringar med NoRotate-flaggan (bitposition 5, värde 16, §12.5.3) förblir upprätta på en roterad sida och pivoterar runt det övre vänstra hörnet av sin /Rect, så flatteningen behåller deras bredd, höjd och upprätta utseende och flyttar bara det hörnet till dit vridningen lägger det. Utöver annoteringar vrider metoden också /QuadPoints, /Vertices, /L och /InkList, skriver om explicita destinationer som namnger sidan (/XYZ-punkter, /FitR-rektanglar, och /FitH / /FitV utbytta vid 90 och 270 grader, §12.3.2.2), och transformerar taggad geometri som attributet /BBox-poster för strukturobjekt vars /Pg är sidan
Vad täcker flattening inte?
Flattening är en geometrisk omskrivning av en sidas egna objekt, och flera situationer faller utanför den tyst snarare än högljutt
- Sidor vars effektiva rotation redan är 0, eller vars MediaBox saknas eller har bredd eller höjd noll, hoppas över utan fel; jämför returvärdet med antalet sidor du väntade skulle ändras
- Form-XObjects som refereras från sidresurserna behåller sin egen
/BBoxi formrummet, eftersom den yttrecm:en redan vrider dem; strukturträdsskanningen följer bara/Koch/Aså den går aldrig in i sidans resurser eller annoteringar en andra gång - Destinationer hittas genom att skanna varje indirekt objekt en gång per utjämnad sida, så ett stort dokument med hundratals roterade sidor betalar den vandringen för var och en
- HotPDF:s sidrenderare ritar inte annoteringar, så en visuell kontroll av vridna stämplar kräver
FlattenLoadedAnnotationsförst
// Baka in appearances i innehållet så att renderaren kan visa dem,
// och rendera sedan sida 1 före och efter att dess /Rotate tagits bort
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));
// Jämför hörnmarkörpixlar här, inte bara dimensionerna
finally
After.Free;
end;
finally
Before.Free;
end;
För djupare bakgrund fortsätter annoteringssidan av denna historia i att syntetisera annoteringappearances före flattening, renderaren bakom jämförelsen före-och-efter täcks i att rendera en inläst PDF-sida till en bitmap, och redaktion och N-up-stitchning på inlästa PDF:er visar samma teknik att bifoga innehållsströmmar som rotationsprefixet och suffixet litar på. HotPDF, inklusive FlattenLoadedPageRotation och sidboxläsarna, finns för Delphi och C++Builder på HotPDF Delphi PDF component-sidan