تسطّح HotPDF دوران صفحات PDF عبر THotPDF.FlattenLoadedPageRotation: تغلف الطريقة محتوى كل صفحة مدورة بتحويل cm باتجاه عقارب الساعة، وتعيد كتابة كل صندوق صفحة تملكه الصفحة فعلاً، وتدير هندسة التعليقات ومصفوفات المظهر والوجهات الصريحة وهندسة البنية الموسومة بالزاوية نفسها، ثم تضبط /Rotate على 0. الصفحة تبدو مطابقة في عارض، لكن نظام إحداثياتها صار معتدلاً. وهذا يهم لحظة أن يتجاهل أداة لاحقة، أو RIP طباعة، أو كود الختم خاصتك، الـ /Rotate ويضع الأشياء في فضاء المستخدم الخام
المحفز النموذجي ماسح ضوئي أو تطبيق التقاط جوال يكتب صفحات عرضية بوصفها وسائط طولية مع /Rotate 90. كل عارض يعرضها صحيحة، فلا ينتبه أحد حتى يختم أحدهم رقم صفحة عند «أسفل اليمين» فيهبط جانبياً على طول الحافة اليسرى، أو ترتب خطوة تصفيف تقرأ /MediaBox وحدها خانة طولية لصفحة عرضية. التسطيح يبدو عمل مصفوفة من سطر واحد. فعلياً يمس خمسة صناديق صفحة، وثلاثة أنواع من هندسة التعليقات، وأهداف روابط المستند، وشجرة البنية، ولكل منها قاعدتها الخاصة في ISO 32000-1
إلى أي جهة تدور /Rotate صفحة PDF؟
تدور /Rotate الصفحة باتجاه عقارب الساعة للعرض والطباعة، بمضاعفات 90 درجة (ISO 32000-1 §7.7.3.3، الجدول 30). وعند 90 درجة تصير الحافة اليسرى للوسائط أعلى والحافة العليا تصبح يميناً، ففي فضاء جهاز y-down يكون التعيين X = (y - Bottom) * Scale و Y = (x - Left) * Scale. وعند 270 درجة تصير الحافة اليمنى أعلى. و /Rotate هي أيضاً واحدة من أربع خصائص صفحة قابلة للوراثة فقط، مع /Resources و /MediaBox و /CropBox (§7.7.3.4)، فقاموس صفحة بلا /Rotate خاصة يمكن أن تدوره سلف /Pages. وتجول THotPDF.GetLoadedPageRotation سلسلة /Parent وتطبع النتيجة في 0–359، وهي القيمة التي تريدها، لا المفتاح الخام على الصفحة
الجهة سهلة الخطأ فيها على نحو ينجو من الاختبار، وبناءات HotPDF الأقدم فعلت ذلك بالضبط. مصفوفة الصفحة إلى الجهاز القديمة كانت تبدل مركبتي y لـ 90 و 270، وهذا ينتج انعكاساً عبر القطر لا دوراناً: توجه المصفوفة ينقلب مقارنة بالحالة غير المدورة. والزاويتان ما زالتا «تبدوان مدورتين»، والصورة النقطية ذات العرض والارتفاع المبدلين، ورحلة من الصفحة إلى العرض والعودة تعيد نقطة البداية، ففحوص الأبعاد واختبارات الذهاب والإياب كلها تنجح. والفحص الموثوق الوحيد هو أين تنتهي علامة زاوية، مقارنة بكسلاً ببكسل ضد عارض مرجعي. ولأن نموذج العارض وواجهة العرض SIMD وتعيين التظليل كانوا قد نسخوا المصفوفة نفسها، صُححوا جميعاً معاً، ويستخدم كود التسطيح الآن الاتفاقية نفسها مع عقارب الساعة كالعارض
كيف تعيد FlattenLoadedPageRotation كتابة صفحة
تعالج FlattenLoadedPageRotation(PageRange, Info) كل صفحة في PageRange دورانها الفعلي 90 أو 180 أو 270، وتعيد عدد الصفحات التي سطّحتها. و PageRange الفارغة تعني كل الصفحات؛ وإلا استخدمت السلسلة صياغة '1-3,7' المعتيكة بالترقيم من الواحد، ورقم صفحة خارج النطاق يرفع استثناء بدل أن يُتخطى. تدفقات المحتوى الأصلية لا تُعاد ترميزها أبداً. تسجل الطريقة في مقدم /Contents للصفحة تدفقاً جديداً يحوي q 0 -1 1 0 -Bottom Width+Left cm (لـ 90 درجة)، وتلحق تدفقاً يحوي 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 يستحق التسجيل لا الإهمال. 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]));
// True حتى بلا مفتاح /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;
شغّل تلك المساعدة قبل التسطيح وبعده والأرقام تشرح نفسها. لصفحة 90 درجة بـ MediaBox [0 0 612 792] يصبح MediaBox المسطح [0 0 792 612]؛ كل صندوق معاد كتابته يُعين عبر الدورة مع عقارب الساعة نفسها، نسبةً إلى أصل MediaBox الأصلي، فيبدأ MediaBox الجديد دائماً من الأصل وتبقى الصناديق الأخرى موضعها داخله. تعيد GetLoadedPageVisibleBox ما يعرضه العارضون ويطبعه الطابعات، أي CropBox مقصوصة على MediaBox ومطبوعة بحيث يقل Left عن Right، ويستخدم ذلك الصندوق نفسه عارض HotPDF وتصدير SVG ومسار العرض والطباعة كلها. وحين تحتاج حجم الصفحة كما يراه إنسان نادِ GetLoadedPageVisibleBox لا قراءة /MediaBox
لماذا تنكسر التعليقات إن دوّرت /Rect وحدها؟
تنكسر التعليقات لأن تدفق مظهر لا يُرسم مباشرة في /Rect. وفق §12.5.5 يحول العارض أولاً /BBox للنموذج عبر /Matrix الخاصة به، ثم يقيس ويصدر مربع إحاطة تلك النتيجة إلى /Rect. دوّر /Rect وحدها وختم من 200 × 40 يُعصر في خانة 40 × 200، غير مقروء وعلى جنبه. لذا تضرب FlattenLoadedPageRotation من اليمين دورة الصفحة مع عقارب الساعة في كل /Matrix مظهر (لـ 90 درجة، [0 -1 1 0 0 0] في اصطلاح متجه الصف)، عبر مظاهر /N و /R و /D وكل حالة داخلها. وتدفق مظهر واحد قد يتشاركه عدة تعليقات أو حالات، فيدار كل تدفق مرة واحدة بالضبط لكل استدعاء. والحالة الوحيدة بلا جواب نظيف تدفق مشترك عبر صفحات بدورانات مختلفة؛ يتبع أول صفحة تصل إليه
قاعدتان أخريان تثبتان حقول النماذج والملاحظات اللاصقة في مواضعهما. مدخل /MK /R للودجة (§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 محمّلة إلى صورة نقطية، و الحجب وخياطة N-up على PDF محمّلة تُظهر تقنية إلحاق تدفق المحتوى نفسها التي يعتمد عليها سابقاً ولاحقاً الدوران. و HotPDF، بما فيها FlattenLoadedPageRotation وقارئات صناديق الصفحات، متاح لـ Delphi و C++Builder على صفحة HotPDF Delphi PDF component