HotPDF zplošťuje rotaci PDF stránek přes THotPDF.FlattenLoadedPageRotation: metoda zabalí obsah každé otočené stránky do transformace cm po směru hodinových ručiček, přepíše každou page box, kterou stránka doopravdy má, otočí geometrii anotací, appearance matice, explicitní destinace a geometrii tagované struktury o tentýž úhel a nakonec nastaví /Rotate na 0. Stránka v prohlížeči vypadá totožně, ale její souřadnicový systém je teď vzpřímený. Na tom záleží v okamžiku, kdy nástroj dále v řetězci, tiskový RIP nebo váš vlastní kód s razítky /Rotate ignoruje a umisťuje věci do surového user space
Typickým spouštěčem je skener nebo mobilní capture aplikace, která píše landscape stránky jako portrait média s /Rotate 90. Každý prohlížeč je ukazuje správně, takže si to nikdo nevšimne, dokud někdo nedá číslo stránky „dole vpravo“ a to neolže podél levé hrany na bok, nebo dokud krok imposition čtoucí jen /MediaBox nepřipraví portrait slot pro landscape stránku. Zploštění zní jako maticová úloha na jeden řádek. V praxi se dotkne pěti page boxů, tří druhů geometrie anotací, odkazových cílů dokumentu a strukturového stromu a každý z nich má v ISO 32000-1 vlastní pravidlo
Kterým směrem /Rotate stránku PDF otočí?
/Rotate otáčí stránku pro zobrazení i tisk po směru hodinových ručiček, v násobcích 90 stupňů (ISO 32000-1 §7.7.3.3, tabulka 30). Při 90 stupních se levá hrana media stane horní a horní hrana pravou stranou, takže v y-down device space je mapování X = (y - Bottom) * Scale a Y = (x - Left) * Scale. Při 270 stupních se pravá hrana stane horní. /Rotate je také jeden z pouhých čtyř dědičných atributů stránky, spolu s /Resources, /MediaBox a /CropBox (§7.7.3.4), takže slovník stránky bez vlastního /Rotate může otočit i předek /Pages. THotPDF.GetLoadedPageRotation projde řetěz /Parent a výsledek normalizuje do 0–359, což je hodnota, kterou chcete, ne surový klíč na stránce
Směr se dá pokazit takovým způsobem, který testování přežije, a starší buildy HotPDF to přesně udělaly. Stará matice page-to-device vyměnila y složky pro 90 a 270, což vyrobí zrcadlení přes diagonálu místo rotace: orientace matice se překlopí oproti neotočenému případu. Oba úhly pořád „vypadají otočeně“, bitmapa má vyměněnou šířku a výšku a round trip ze stránky do pohledu a zpátky se vrátí na startovní bod, takže kontroly rozměrů i round-trip testy všechno projdou. Jediná spolehlivá kontrola je, kde skončí marker rohu, porovnaný po pixelech s referenčním rendererem. Protože model prohlížeče, SIMD render backend i mapování highlightů okopírovaly tutéž matici, opravily se všechny najednou a zplošťovací kód teď používá tutéž konvenci po směru hodinových ručiček jako renderer
Jak FlattenLoadedPageRotation přepisuje stránku
FlattenLoadedPageRotation(PageRange, Info) zpracuje každou stránku v PageRange, jejíž efektivní rotace je 90, 180 či 270, a vrátí počet zploštěných stránek. Prázdné PageRange znamená všechny stránky; jinak řetězec používá obvyklou syntaxi '1-3,7' s číslováním od jedničky a číslo stránky mimo rozsah vyhodí výjimku místo přeskočení. Původní content streamy se nikdy znovu nekódují. Metoda předřadí nový stream obsahující q 0 -1 1 0 -Bottom Width+Left cm (pro 90 stupňů) do /Contents stránky, připojí stream s Q a nakonec zapíše explicitní /Rotate 0 do slovníku stránky, aby dědičná hodnota na uzlu /Pages neotočila stránku podruhé
var
Pdf: THotPDF;
Info: THPDFRotationFlattenInfo;
Flattened: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('scanned-batch.pdf') > 0 then
begin
// '' = všechny stránky; stránky na 0 stupňů se proskenují, ale nechají se
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;
Record THPDFRotationFlattenInfo stojí za zalogování, ne za vyhození. ScannedPageCount je velikost rozsahu, FlattenedPageCount se rovná návratové hodnotě a tři počítadla Transformed... řeknou, jestli dokument měl odkazy, záložky nebo tagovanou geometrii mířící na otočené stránky. Dávka, kde každý soubor hlásí nula destinací, je v pořádku; tagované PDF/UA, které hlásí nulovou strukturovou geometrii, když jste čekali bounding boxy obrázků, je signál k ruční prohlídce
Které page boxy zploštění přepisuje a v jakém pořadí?
Zploštění přepisuje jen boxy, které stránka už má, a přečte každý box, než kterýkoli zapíše. Pořadí záleží kvůli řetězu defaultů: GetLoadedPageBox(PageIndex, pbCropBox, ...) vrátí /MediaBox, když stránka nemá /CropBox, a /BleedBox, /TrimBox a /ArtBox se defaultují na CropBox (§14.11.2). Starší verze četla, transformovala a zapisovala jeden box po druhém. Nejprve přepsala MediaBox, pak četla „CropBox“, dostala zpět už otočený MediaBox, otočila ho podruhé a zapsala CropBox, který stránka nikdy neměla, čímž se landscape stránka ořezala na čtverec. Dědičná pravidla se dělí stejně: MediaBox a CropBox se hledají podél řetězu /Parent, zatímco Bleed, Trim a ArtBox se počítají, jen když sedí přímo na slovníku stránky, takže zbouchnutý /TrimBox na uzlu /Pages se bere jako absentující a na stránku se nikdy nekopíruje
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 i bez klíče /TrimBox: hodnota sestoupí na CropBox, pak MediaBox
if Pdf.GetLoadedPageBox(PageIndex, pbTrimBox, L, B, R, T) then
Writeln(Format('TrimBox [%g %g %g %g]', [L, B, R, T]));
// Preset Letter; GetLoadedPageVisibleBox při selhání ponechá výstupy nedotčené
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;
Pusťte tenhle helper před zploštěním a po něm a čísla vysvětlí sama sebe. U stránky s 90 stupni a MediaBox [0 0 612 792] se zploštěný MediaBox stane [0 0 792 612]; každý přepsaný box se mapuje stejnou otočkou po směru hodinových ručiček, relativně k počátku původního MediaBoxu, takže nový MediaBox vždy začíná na počátku a ostatní boxy drží svou pozici uvnitř něj. GetLoadedPageVisibleBox vrátí, co zobrazují prohlížeče a tisknou tiskárny — CropBox oříznutý na MediaBox a normalizovaný tak, že Left je menší než Right — a renderer, SVG export, viewer i tisková cesta HotPDF používají tentýž box. Když potřebujete velikost stránky, jakou vidí člověk, volejte GetLoadedPageVisibleBox místo čtení /MediaBox
Proč se anotace rozpadnou, když otočíte jen /Rect?
Anotace se rozbijí, protože appearance stream se nekreslí přímo do /Rect. Podle §12.5.5 prohlížeč nejdřív transformuje /BBox formy přes její /Matrix, pak bounding box výsledku zvětší a přemístí do /Rect. Otočíte-li jen /Rect, razítko 200 × 40 se vymáčkne do slotu 40 × 200 — nečitelné a na boku. FlattenLoadedPageRotation proto vynásobí otočku stránky po směru hodinových ručiček zprava do každé appearance /Matrix (pro 90 stupňů [0 -1 1 0 0 0] v konvenci řádkových vektorů), napříč appearances /N, /R a /D a každým stavem uvnitř nich. Jeden appearance stream se může sdílet mezi více anotací či stavů, takže každý stream se otočí přesně jednou za volání. Jediný případ bez čistého řešení je stream sdílený napříč stránkami s různými rotacemi; řídí se první stránkou, která na něj dosáhne
Dvě další pravidla drží formulářová pole a rychlé poznámky na místě. Položka /MK /R widgetu (§12.5.6.19) je úhel proti směru hodinových ručiček, takže se z ní úhel stránky po směru hodinových ručiček odečte, modulo 360; přeskočíte-li to, příští regenerace appearance nakreslí text pole špatným směrem. Anotace s flagem NoRotate (bit pozice 5, hodnota 16, §12.5.3) zůstávají na otočené stránce vzpřímené a otáčejí se kolem horního levého rohu svého /Rect, takže zploštění drží jejich šířku, výšku a vzpřímený vzhled a jen přemístí tenhle roh tam, kde ho otočka usadí. Za anotacemi metoda taky otočí /QuadPoints, /Vertices, /L a /InkList, přepíše explicitní destinace pojmenovávající stránku (body /XYZ, obdélníky /FitR a /FitH / /FitV vyměněné při 90 a 270 stupních, §12.3.2.2) a transformuje tagovanou geometrii, jako jsou položky atributu /BBox prvků struktury, jejichž /Pg je daná stránka
Co zploštění nepokrývá?
Zploštění je geometrický přepis vlastních objektů jedné stránky a několik situací zůstává mimo něj potichu, ne nahlas
- Stránky, jejichž efektivní rotace už je 0, nebo jejichž MediaBox chybí nebo má nulovou šířku či výšku, se přeskočí bez chyby; porovnejte návratovou hodnotu s počtem stránek, které jste čekali změnit
- Form XObjects odkazované ze zdrojů stránky si drží vlastní
/BBoxve form space, protože vnějšícmje už otočí; scan strukturového stromu následuje jen/Ka/A, takže se nikdy znovu nevydá do zdrojů stránky nebo anotací - Destinace se hledají proskénováním každého nepřímého objektu jednou za zploštěnou stránku, takže velký dokument se stovkami otočených stránek tu procházku platí u každé z nich
- Page renderer HotPDF nekreslí anotace, takže vizuální kontrola otočených razítek potřebuje nejdřív
FlattenLoadedAnnotations
// Zapečte appearances do obsahu, aby je renderer uměl ukázat,
// pak vykreslete stránku 1 před a po odebrání /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));
// Tady porovnejte pixely markeru rohu, ne jen rozměry
finally
After.Free;
end;
finally
Before.Free;
end;
Pro hlubší kontext: anotová strana příběhu pokračuje v syntetizování appearance anotací před zploštěním, renderer za srovnáním před a po popisuje vykreslení načtené PDF stránky do bitmapy a redakce a N-up skládání na načtených PDF ukazuje tutéž techniku připojování content streamů, na níž stojí rotační prefix i suffix. HotPDF, včetně FlattenLoadedPageRotation a čtenářů page boxů, je k dispozici pro Delphi a C++Builder na stránce HotPDF Delphi PDF component