HotPDF изравнява въртенето на PDF страници с THotPDF.FlattenLoadedPageRotation: методът опакова съдържанието на всяка обърната страница в transform cm по часовниковата стрелка, пренаписва всяка page кутия, която страницата действително има, обръща геометрията на анотациите, appearance матриците, изричните дестинации и геометрията на tagged структурата със същия ъгъл, а после задава /Rotate на 0. Страницата изглежда идентична във viewer, но координатната ѝ система вече е изправена. Това има значение в момента, в който инструмент надолу по веригата, print RIP или вашият код за щампиране игнорира /Rotate и поставя неща в суров user space
Типичният спусък е скенер или мобилно capture приложение, което записва пейзажни страници като портретни media с /Rotate 90. Всеки viewer ги показва правилно, така че никой не забелязва, докато някой не щампне номер на страница в „долния десен ъгъл" и той не излезе настрани по левия ръб, или стъпка за imposition, четяща само /MediaBox, не подреди портретен слот за пейзажна страница. Изравняването звучи като матрична работа на един ред. На практика пипа пет page кутии, три вида анотационна геометрия, линк целите на документа и структурното дърво, а всяко от тях има собствено правило в ISO 32000-1
В коя посока /Rotate обръща PDF страница?
/Rotate обръща страницата по часовниковата стрелка за показване и печат, в кратно на 90 градуса (ISO 32000-1 §7.7.3.3, Table 30). При 90 градуса левият ръб на media става горе, а горният ръб става десен, така че в y-надолу device space съпоставянето е X = (y - Bottom) * Scale и Y = (x - Left) * Scale. При 270 градуса десният ръб става горе. /Rotate е и един от само четирите наследяеми page атрибута, редом с /Resources, /MediaBox и /CropBox (§7.7.3.4), така че page речник без собствен /Rotate може пак да бъде обърнат от /Pages предшественик. THotPDF.GetLoadedPageRotation изминава /Parent веригата и нормализира резултата в 0–359, което е стойността, от която се нуждаете, а не суровият ключ на страницата
Посоката лесно се сбърква по начин, който оцелява при тестване, и по-стари HotPDF build-ове точно това направиха. Старата page-to-device матрица разменяше y компонентите за 90 и 270, което дава отражение по диагонала вместо въртене: ориентацията на матрицата се обръща спрямо необърнатия случай. И двата ъгла пак „изглеждат обърнати", bitmap-ът има разменени ширина и височина, а round trip от страница към изглед и обратно връща началната точка, така че проверките за размер и round-trip тестовете минават всички. Единствена надеждна проверка е където свършва ъглов маркер, сравнен пиксел по пиксел с reference renderer. Понеже viewer моделът, SIMD render backend-ът и highlight съпоставянето бяха копирали същата матрица, всички бяха поправени заедно, а кодът за изравняване вече ползва същата конвенция по часовниковата стрелка като renderer-а
Как FlattenLoadedPageRotation пренаписва страница
FlattenLoadedPageRotation(PageRange, Info) обработва всяка страница в PageRange, чиято ефективна ротация е 90, 180 или 270, и връща броя изравнени страници. Празен PageRange значи всички страници; иначе низът ползва обичайния едно-базиран синтаксис '1-3,7', а номер на страница извън обхвата вдига изключение, вместо да бъде прескачан. Оригиналните content stream-и никога не се преенкодират. Методът залепя отпред нов stream, съдържащ q 0 -1 1 0 -Bottom Width+Left cm (за 90 градуса), към /Contents на страницата, залепя отзад stream с Q и най-после записва изричен /Rotate 0 в page речника, така че наследена стойност върху /Pages възел да не може да обърне страницата втори път
var
Pdf: THotPDF;
Info: THPDFRotationFlattenInfo;
Flattened: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('scanned-batch.pdf') > 0 then
begin
// '' = всички страници; страници на 0 градуса се сканират, но се оставят
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 си заслужава да се логва, а не да се изхвърля. ScannedPageCount е размерът на диапазона, FlattenedPageCount е равен на връщаната стойност, а трите брояча Transformed... ви казват дали документът е имал линкове, bookmarks или tagged геометрия, сочещи към обърнатите страници. Партида, в която всеки файл докладва нула дестинации, е ок; tagged PDF/UA файл, докладвал нула structure геометрия, когато сте очаквали figure bounding boxes, е сигнал да го огледате на ръка
Кои page кутии пренаписва изравняването и в какъв ред?
Изравняването пренаписва само кутиите, които страницата вече има, и чете всяка кутия, преди да е записало някоя. Редът има значение заради веригата по подразбиране: GetLoadedPageBox(PageIndex, pbCropBox, ...) връща /MediaBox, когато страницата няма /CropBox, а /BleedBox, /TrimBox и /ArtBox се подразбират от CropBox (§14.11.2). По-ранна версия четеше, трансформираше и записваше по една кутия наведнъж. Тя пренаписа MediaBox първо, после прочете „CropBox", получи обратно вече обърнатия MediaBox, обърна го втори път и записа CropBox, който страницата никога не е имала — което отреже пейзажна страница до квадрат. Правилата за наследяване се разделят по същия начин: MediaBox и CropBox се търсят по /Parent веригата, докато Bleed, Trim и ArtBox броят само ако седят върху самия page речник, така че изгубен /TrimBox върху /Pages възел се третира като отсъстващ и никога не се копира върху страницата
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]));
// Вярно дори без ключ /TrimBox: стойността се връща към CropBox, после MediaBox
if Pdf.GetLoadedPageBox(PageIndex, pbTrimBox, L, B, R, T) then
Writeln(Format('TrimBox [%g %g %g %g]', [L, B, R, T]));
// Предзададен Letter; GetLoadedPageVisibleBox оставя изходите недокоснати при неуспех
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;
Пуснете този helper преди и след изравняването и числата сами си обясняват. За страница на 90 градуса с MediaBox [0 0 612 792] изравненият MediaBox става [0 0 792 612]; всяка пренаписана кутия се мапва през същото обръщане по часовниковата стрелка, спрямо оригиналното начало на MediaBox, така че новият MediaBox винаги започва от началото, а другите кутии пазят позицията си вътре в него. GetLoadedPageVisibleBox връща това, което viewer-ите показват и принтерите печатат — CropBox, отрязан до MediaBox и нормализиран така, че Left е по-малък от Right — а renderer-ът, SVG експортът, viewer-ът и print пътят на HotPDF ползват всички тази съща кутия. Когато ви трябва размерът на страницата, който човек вижда, викайте GetLoadedPageVisibleBox, а не четете /MediaBox
Защо анотациите се чупят, ако обърнете само /Rect?
Анотациите се чупят, защото appearance stream не се чертае направо в /Rect. Под §12.5.5 viewer-ът първо трансформира /BBox на формата чрез неговия /Matrix, после мащабира и премества bounding box-а на този резултат в /Rect. Обърнете само /Rect и 200 × 40 щампа се изцедва в 40 × 200 слот, нечетема и настрани. Затова FlattenLoadedPageRotation умножава отдясно обръщането на страницата по часовниковата стрелка върху всеки appearance /Matrix (за 90 градуса [0 -1 1 0 0 0] в конвенцията row-vector), през appearance-ите /N, /R и /D и всяко състояние вътре в тях. Един appearance stream може да се споделя от няколко анотации или състояния, така че всеки stream се обръща точно веднъж на извикване. Единственият случай без чист отговор е stream, споделен през страници с различни ротации; той следва първата страница, която го стигне
Още две правила държат form полетата и стикерните бележки на място. Записът /MK /R на widget (§12.5.6.19) е ъгъл обратно на часовниковата стрелка, затова от него се изважда часовниковият ъгъл на страницата, по модул 360; пропуснете го и следващото преизграждане на appearance ще нарисува текста на полето в грешната посока. Анотации с флага NoRotate (битова позиция 5, стойност 16, §12.5.3) остават изправени на обърната страница и се въртят около горния ляв ъгъл на /Rect им, така че изравняването пази ширината, височината и изправения им вид и мести само този ъгъл там, където обръщането го слага. Освен анотациите методът обръща също /QuadPoints, /Vertices, /L и /InkList, пренаписва изрични дестинации, които назовават страницата (/XYZ точки, /FitR правоъгълници, а /FitH / /FitV разменени при 90 и 270 градуса, §12.3.2.2), и трансформира tagged геометрия като записи /BBox на атрибути за структурни елементи, чийто /Pg е страницата
Какво изравняването не покрива?
Изравняването е геометрично пренаписване на собствените обекти на една страница, а няколко ситуации излизат извън него тихо, а не на викове
- Страници с ефективна ротация вече 0, или с липсващ MediaBox или MediaBox с нулева ширина или височина, се прескачат без грешка; сравнявайте връщаната стойност с броя страници, които сте очаквали да се променят
- Form XObjects, сочени от page ресурсите, пазят собствените си
/BBoxв form space, защото външниятcmвече ги обръща; сканирането на структурното дърво следва само/Kи/A, така че никога не влиза втори път в ресурсите или анотациите на страницата - Дестинациите се откриват със сканиране на всеки indirect обект по веднъж на изравнена страница, така че голям документ със стотици обърнати страници плаща за това минаване на всяка
- Page renderer-ът на HotPDF не чертае анотации, така че визуална проверка на обърнати щампи първо се нуждае от
FlattenLoadedAnnotations
// Изпиче appearance-ите в съдържанието, за да може renderer-ът да ги покаже,
// после рендерирай страница 1 преди и след махането на /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));
// Сравнявайте пикселите на ъгловия маркер тук, не само размерите
finally
After.Free;
end;
finally
Before.Free;
end;
За по-дълбок фон разказът продължава с анотационната страна в синтезирането на annotation appearance-и преди изравняването им, renderer-ът зад сравнението преди-и-след е покрит в рендерирането на заредена PDF страница към bitmap, а redaction и N-up долепяне върху заредени PDF-и показва същата техника за долепяне към content stream, на която разчитат префиксът и суфиксът за ротация. HotPDF, включително FlattenLoadedPageRotation и четците на page кутии, е наличен за Delphi и C++Builder на страницата на HotPDF Delphi PDF компонента