HotPDF משטח סיבוב עמודי PDF עם ה-THotPDF.FlattenLoadedPageRotation: המתודה עוטפת את התוכן של כל עמוד סבוב בטרנספורמציית cm בכיוון השעון, כותבת מחדש כל תיבת עמוד שהעמוד באמת מחזיק, מסובבת את גיאומטריית ההערות, מטריצות המראה, היעדים המפורשים וגיאומטריית המבנה המתויג באותה זווית, ואז מגדירה את ה-/Rotate ל-0. העמוד נראה זהה במציג, אבל מערכת הקואורדינטות שלו זקופה עכשיו. זה חשוב ברגע שכלי במורד הזרם, print RIP או קוד החותמות שלכם מתעלם מה-/Rotate וממקם דברים במרחב המשתמש הגולמי
הטריגר הטיפוסי הוא סורק או אפליקציית לכידה ניידת שכותבת עמודי רוחב כמדיום אנכי עם /Rotate 90. כל מציג מציג אותם נכון, ולכן אף אחד לא מבחין עד שמישהו מטביע מספר עמוד ב"תחתית-ימין" והוא נוחת על הצד לאורך הקצה השמאלי, או שצעד imposition שקורא רק את ה-/MediaBox מסדר slot אנכי לעמוד רוחבי. שיטוח נשמע כמו עבודת מטריצה של שורה אחת. בפועל הוא נוגע בחמש תיבות עמוד, בשלושה סוגים של גיאומטריית הערות, ביעדי הקישורים של המסמך ובעץ המבנה, ולכל אחד מאלה יש כלל משלו ב-ISO 32000-1
לאיזה כיוון ה-/Rotate מסובב עמוד PDF?
ה-/Rotate מסובב את העמוד בכיוון השעון לתצוגה ולהדפסה, בכפולות של 90 מעלות (ISO 32000-1 §7.7.3.3, טבלה 30). ב-90 מעלות הקצה השמאלי של המדיום הופך לצד העליון והקצה העליון הופך לצד ימין, כך שבמרחב מכשיר y-מטה המיפוי הוא X = (y - Bottom) * Scale ו-Y = (x - Left) * Scale. ב-270 מעלות הקצה הימני הופך לצד העליון. ה-/Rotate הוא גם אחת מארבע תכונות העמוד העוברות בירושה בלבד, לצד ה-/Resources, ה-/MediaBox וה-/CropBox (§7.7.3.4), ולכן מילון עמוד בלי /Rotate משלו עדיין יכול להיות מסובב על ידי אב קדום /Pages. ה-THotPDF.GetLoadedPageRotation הולך בשרשרת ה-/Parent ומנרמל את התוצאה אל 0–359, שהוא הערך שאתם רוצים, לא המפתח הגולמי על העמוד
את הכיוון קל לפספס באופן ששורד בדיקות, ו-build-ים קודמים של HotPDF עשו בדיוק זאת. מטריצת העמוד-אל-מכשיר הישנה החליפה את רכיבי ה-y עבור 90 ו-270, מה שמניב השתקפות מעל האלכסון במקום סיבוב: הכיוון של המטריצה מתהפך יחסית למקרה הלא מסובב. שתי הזוויות עדיין "נראו מסובבות", ל-bitmap היה הרוחב והגובה המוחלפים, ומסע עמוד-אל-תצוגה-וחזרה החזיר את נקודת ההתחלה, כך שבדיקות ממדים ובדיקות round-trip כולן עברו. הבדיקה האמינה היחידה היא איפה סמן פינה נוחת, בהשוואה פיקסל-פיקסל מול רנדרר ייחוס. כי מודל המציג, ה-backend של הרינדור ה-SIMD ומיפוי ה-highlight כולם העתיקו את אותה מטריצה, כולם תוקנו יחד, וקוד השיטוח משתמש עכשיו באותה מוסכמת כיוון-שעון כמו הרנדרר
איך FlattenLoadedPageRotation כותב עמוד מחדש
ה-FlattenLoadedPageRotation(PageRange, Info) מעבד כל עמוד ב-PageRange שהסיבוב האפקטיבי שלו הוא 90, 180 או 270, ומחזיר את מספר העמודים ששיטח. PageRange ריק אומר כל העמודים; אחרת המחרוזת משתמשת בתחביר הרגיל המבוסס-אחד '1-3,7', ומספר עמוד מחוץ לטווח מעלה חריגה במקום להידלג. זרמי התוכן המקוריים לעולם לא מקודדים מחדש. המתודה מקדימה זרם חדש שמכיל q 0 -1 1 0 -Bottom Width+Left cm (עבור 90 מעלות) אל ה-/Contents של העמוד, מצרפת זרם שמכיל Q, ולבסוף כותבת /Rotate 0 מפורש אל מילון העמוד כדי שערך יורש על צומת /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;
הרשומה THPDFRotationFlattenInfo שווה רישום ל-log ולא השלכה. ה-ScannedPageCount הוא גודל הטווח, ה-FlattenedPageCount שווה לערך המוחזר, ושלושת מוני ה-Transformed... מספרים לכם אם למסמך היו קישורים, סימניות או גיאומטריה מתויגת שמצביעים על העמודים המסובבים. אצווה שבה כל קובץ מדווח אפס יעדים תקינה; קובץ PDF/UA מתויג שמדווח אפס גיאומטריית מבנה כשציפיתם לתיבות תוחם של איורים הוא אות לבחון אותו ביד
אילו תיבות עמוד השיטוח כותב מחדש, ובאיזה סדר?
השיטוח כותב מחדש רק את התיבות שהעמוד כבר מחזיק, והוא קורא כל תיבה לפני שהוא כותב אחת. הסדר חשוב בגלל שרשרת ברירות המחדל: ה-GetLoadedPageBox(PageIndex, pbCropBox, ...) מחזיר את ה-/MediaBox כשלעמוד אין /CropBox, וה-/BleedBox, ה-/TrimBox וה-/ArtBox מוגדרים כברירת מחדל אל ה-CropBox (§14.11.2). גרסה מוקדמת כן קראה, טרנספורמה וכתבה תיבה אחת בכל פעם. היא כתבה מחדש את ה-MediaBox קודם, ואז קראה את ה-"CropBox", קיבלה חזרה את ה-MediaBox שכבר הסתובב, סובבה אותו פעם שנייה וכתבה CropBox שלעמוד מעולם לא היה, מה שחתך עמוד רוחבי לריבוע. כללי הירושה מתפצלים באותה צורה: MediaBox ו-CropBox נחפשים לאורך שרשרת ה-/Parent, בזמן ש-Bleed, Trim ו-ArtBox נספרים רק אם הם יושבים על מילון העמוד עצמו, כך ש-/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 מחזיר מה שמציגים מציגים ומדפסות מדפיסות, ה-CropBox חתוך אל ה-MediaBox ומנורמל כך ש-Left קטן מ-Right, והרנדרר, ייצוא ה-SVG, המציג ונתיב ההדפסה של HotPDF כולם משתמשים באותה תיבה. כשאתם צריכים את גודל העמוד שבן אנוש רואה, קראו ל-GetLoadedPageVisibleBox ולא לקרוא את ה-/MediaBox
למה הערות נשברות כשמסובבים רק את ה-/Rect?
הערות נשברות כי זרם מראה אינו נצייר ישר אל ה-/Rect. תחת §12.5.5 המציג קודם מטרנספורם את ה-/BBox של הטופס על ידי ה-/Matrix שלו, ואז מותח וממקם את התיבה התוחמת של התוצאה הזאת אל ה-/Rect. סובבו רק את ה-/Rect וחותמת בגודל 200 × 40 תמתח אל slot של 40 × 200, בלתי קריאה ועל הצד. ה-FlattenLoadedPageRotation לכן מכפיל מימין את הסיבוב בכיוון השעון של העמוד אל כל /Matrix מראה (עבור 90 מעלות, [0 -1 1 0 0 0] במוסכמת וקטור-שורה), על פני מראות ה-/N, ה-/R וה-/D וכל מצב בתוכן. זרם מראה אחד יכול להיות משותף לכמה הערות או מצבים, ולכן כל זרם מסובב בדיוק פעם אחת לכל קריאה. המקרה היחיד בלי תשובה נקייה הוא זרם משותף על פני עמודים עם סיבובים שונים; הוא הולך אחר העמוד הראשון שמגיע אליו
שני כללים נוספים שומרים על שדות טופס ופתקים דביקים במקומם. רשומת ה-/MK /R של widget (§12.5.6.19) היא זווית נגד-כיוון-השעון, ולכן הזווית בכיוון השעון של העמוד מוחסרת ממנה, במודולו 360; דלגו על זה והיווצרות המראה הבאה תצייר את טקסט השדה בכיוון הלא נכון. הערות עם דגל ה-NoRotate (מיקום ביט 5, ערך 16, §12.5.3) נשארות זקופות על עמוד מסובב ומציקות סביב פינת השמאלי-העליון של ה-/Rect שלהן, ולכן השיטוח שומר על הרוחב, הגובה והמראה הזקופה שלהן ומזיז רק את אותה פינה אל המקום שהסיבוב שם אותה. מעבר להערות, המתודה גם מסובבת את ה-/QuadPoints, ה-/Vertices, ה-/L וה-/InkList, כותבת מחדש יעדים מפורשים שקוראים לעמוד בשמו (נקודות /XYZ, מלבני /FitR, וה-/FitH / /FitV מתחלפים ב-90 ו-270 מעלות, §12.3.2.2), ומטרנספורמת גיאומטריה מתויגת כמו רשומות /BBox של תכונות עבור אלמנטי מבנה שה-/Pg שלהם הוא העמוד
מה השיטוח לא מכסה?
השיטוח הוא כתיבה גיאומטרית מחדש של האובייקטים של עמוד אחד עצמו, וכמה מצבים נופלים מחוץ לו בשקט ולא בקול
- עמודים שהסיבוב האפקטיבי שלהם כבר 0, או שה-MediaBox שלהם חסר או בעל רוחב או גובה אפס, מדולגים בלי שגיאה; השוו את ערך ההחזרה אל מספר העמודים שציפיתם לשנות
- Form XObjects שהופנו מתוך משאבי העמוד שומרים על ה-
/BBoxשלהם במרחב הטופס, כי ה-cmהחיצוני כבר מסובב אותם; הסריקה של עץ המבנה עוקבת רק אחר ה-/Kוה-/Aכך שהיא לעולם לא הולכת אל משאבי העמוד או אל ההערות פעם שנייה - יעדים נמצאים על ידי סריקת כל אובייקט עקיף פעם אחת לכל עמוד משוטח, ולכן מסמך גדול עם מאות עמודים מסובבים משלם על ההליכה הזאת בכל אחד מהם
- רנדרר העמודים של HotPDF אינו מצייר הערות, ולכן בדיקה חזותית של חותמות מסובבות צריכה קודם
FlattenLoadedAnnotations
// אופים את המראות אל התוכן כדי שהרנדרר יוכל להציג אותן,
// ואז מרנדרים את עמוד 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;
לרקע מעמיק יותר, הצד של ההערות בסיפור הזה ממשיך ביצירת מראות הערות באופן סינתטי לפני שיטוחן, הרנדרר מאחורי השוואת הלפני-ואחרי מכוסה ברנדור עמוד PDF טעון אל bitmap, וההשחרה ותפירת N-up על PDF טעונים מציגה את אותה טכניקת צירוף זרם-תוכן שקידומת הסיבוב והסיומת נשענות עליה. HotPDF, כולל ה-FlattenLoadedPageRotation וקוראי תיבות העמוד, זמין עבור דלפי ו-C++Builder בעמוד רכיב HotPDF ל-Delphi PDF