الضبط الكامل (Full justification) هو التخطيط الذي يجعل عمودًا من النص يصطف على الحافتين اليسرى واليمنى، وهو المظهر الذي تتوقعه من كتاب مطبوع أو تقرير رسمي. من السهل وصفه ومن السهل بشكل مفاجئ فهمه بشكل خاطئ، لأن الإجابة على سؤال "أين تذهب المساحة الإضافية" ليست هي نفسها بالنسبة للغة الإنجليزية كما هي بالنسبة لليابانية، ولأن الطريقة الساذجة لقياس كل سطر تحول الصفحة السريعة إلى صفحة بطيئة. يمنحك HotPDF ضبطًا مدركًا للبرنامج النصي من خلال استدعاء واحد لتخطيط المربع (box-layout)، ويكمن تحت هذا الاستدعاء إصلاح أداء كتابي يستحق الفهم في حد ذاته
تستعرض هذه المقالة كلاً منهما. أولاً، القاعدة المطبعية التي تقرر كيفية توزيع التراخي (slack) للبرامج النصية ذات الفجوات بين الكلمات مقابل البرامج النصية التي لا تحتوي عليها. ثانيًا، تغيير القياس الذي خفض تكلفة الضبط لكل صفحة بحوالي ثمانين مرة دون فرق واضح في الإخراج. كلاهما مهم إذا قمت بإنشاء مستندات بحجم كبير وتريد أن تُقرأ مثل التنضيد الحقيقي (typesetting) بدلاً من مخرجات أحادية المسافة ممتدة لتناسب
ما يتطلبه الضبط الكامل بالفعل
السطر النصي المرسوم بعرضه الطبيعي لا يصل أبدًا تقريبًا إلى الحافة اليمنى لعموده. هناك دائمًا باقٍ، التراخي، بين حيث ينتهي الـ glyph الأخير ومكان حدود العمود. المحاذاة لليسار تترك هذا التراخي على اليمين. المحاذاة لليمين تنقله إلى اليسار. التوسيط يقسمه. الضبط الكامل يزيله عن طريق توسيع السطر نفسه حتى تلتقي الحافتان بالمربع، والطريقة الصادقة الوحيدة للقيام بذلك هي دفع الـ glyphs بعيدًا عن بعضها من الداخل
القاعدة التي تفصل الضبط الجيد عن السيئ هي أين تضع التراخي. البرنامج النصي الذي يكتب كلمات بمسافات بينها، مثل اللغة الإنجليزية وبقية العائلة اللاتينية، يحتوي على طبقات طبيعية في كل مسافة بين الكلمات. توسيع هذه المسافات غير مرئي للعين لأن القراء يقبلون بالفعل أن فجوات الكلمات تختلف. البرنامج النصي الذي يكتب بدون فجوات الكلمات، مثل مقاطع هان الصينية، أو الكانا اليابانية، أو الهانغول الكورية، لا يحتوي على مثل هذه الطبقات. هناك يجب توزيع التراخي بالتساوي بين الـ glyphs المتجاورة، وهو المبدأ الذي يسميه المنضدون اليابانيون kintou-waritsuke، أو التباعد المتساوي. وضع تمديد فجوة الكلمة على الطراز اللاتيني على سطر CJK، أو حشر كل التراخي في المكان الواحد الذي يصادف أن يحتوي سطر CJK فيه على مسافة، ينتج الأنهار والفجوات التي تميز مخرجات الهواة
كيف يقرر HotPDF أين تذهب المساحة
يتخذ HotPDF هذا القرار لكل فجوة، وليس لكل سطر. عندما يضبط سطرًا، فإنه يمشي في كل زوج متجاور من الـ glyphs ويسأل عما إذا كانت هناك حدود قابلة للتمدد تقع بينهما. تكون الحدود قابلة للتمدد عندما يكون أي من الجانبين مسافة أو علامة جدولة (tab)، وهي الحالة اللاتينية، أو عندما يكون كلا الجانبين أحرف CJK قابلة للكسر، وهي حالة التباعد المتساوي. ويحسب تلك الحدود، ويقسم تراخي السطر بالتساوي بينها، ويضيف هذا النصيب إلى كل فجوة مؤهلة
النتيجة تسقط بشكل طبيعي. يحتوي السطر الإنجليزي على حدود قابلة للتمدد فقط في مسافات كلماته، لذا فإن كل التراخي يهبط هناك وتنتشر الكلمات متباعدة بينما تحافظ الأحرف داخل كل كلمة على تباعدها الطبيعي. يحتوي سطر هان أو كانا على حدود قابلة للتمدد بين كل زوج تقريبًا من الـ glyphs، لذا فإن التراخي يتوزع بالتساوي عبر السطر بأكمله، وهو بالضبط التباعد المتساوي بين الـ glyphs الذي تتطلبه هذه البرامج النصية. السطر الذي يتكون من كلمة لاتينية واحدة طويلة بلا مسافة داخلية ليس له حدود قابلة للتمدد على الإطلاق، لذلك يتركه HotPDF بعرضه الطبيعي بدلاً من تمزيق الكلمة حرفًا تلو الآخر. يتعامل المنطق نفسه مع تسلسلات اللاتينية و CJK المختلطة في سطر واحد دون تخصيص حالات (special-casing)، لأن القرار محلي لكل حدود
يتم استبعاد حد واحد عمدًا في كل مكان. الموضع بعد الـ glyph النهائي للسطر لا يُعامل أبدًا على أنه فجوة، لأن التمديد هناك سيعيد ببساطة إدخال باقٍ أيمن، وهو عكس الضبط
لماذا يُترك السطر الأخير وشأنه
السطر الأخير من الفقرة مميز، وفهمه بشكل خاطئ هو أكثر أخطاء الضبط شيوعًا. عادة ما يكون السطر الأخير للفقرة قصيرًا، وغالبًا ما يكون بضع كلمات فقط، وتمديده إلى عرض العمود بالكامل يسحب تلك كلمات عبر الصفحة إلى صف متناثر ومكسور. الطباعة الصحيحة تترك السطر الأخير بعرضه الطبيعي، متحاذيًا إلى اليسار
يكتشف HotPDF السطر اللاحق حسب الموضع. بينما يلتف النص في أسطر، فإنه يعرف متى يصل السطر الذي انفصل عنه للتو إلى نهاية السلسلة المزودة. يتم إرسال هذا السطر النهائي بمحاذاة يسار بسيطة ويحافظ على عرضه الطبيعي. كل سطر يسبقه يكون مضبوطًا على كلا الحافتين. فواصل الأسطر الصعبة التي تكتبها في النص تُحترم كما كُتبت، لذا فإن السطر القصير المقصود لا يُمط أبدًا. يرى القارئ كتلة مستطيلة نظيفة من النص الذي ينتهي سطره الأخير بشكل طبيعي، وهو ما تتوقعه العين
تكلفة القياس التي جعلت الضبط بطيئًا
لضبط سطر يجب أن تعرف عرضه الدقيق، ويجب أن تعرف تقدم (advance) كل glyph حتى تتمكن من وضع المساحة الإضافية بدقة. حصل التنفيذ الأول على هذه الأرقام بالطريقة الواضحة. لقد قاس السطر بأكمله من خلال استعلام عرض Unicode كامل، ثم قاس البادئة بعد البادئة لاستعادة تقدم كل glyph عن طريق أخذ الفروق. بالنسبة لسطر من N من الـ glyphs، هذا يعني N+1 استدعاء في محرك القياس، وكل استدعاء عبارة عن رحلة ذهاب وإياب GDI كاملة، تطلب من نظام التشغيل تشكيل وقياس النص وإعادة الإجابة
لكل سطر، يبدو هذا رخيصًا. عبر صفحة كاملة، ليس كذلك. خذ صفحة A4 كثيفة من نص النص، حوالي خمسة وأربعين سطرًا يتكون كل منها من حوالي ثمانين حرفًا. عند رحلات N+1 ذهابًا وإيابًا لكل سطر، أي حوالي 81 رحلة ذهاب وإياب لكل سطر وحوالي 3645 للصفحة، يُقضى معظمها تقريبًا في إعادة قياس النص الذي كان المحرك قد نظر إليه بالفعل قبل لحظات. في مهمة مجمعة تنتج آلاف الصفحات، يهيمن هذا العبء على وقت التخطيط، وكل رحلة ذهاب وإياب تعبر الحدود بين عمليتك والنظام الفرعي للرسومات
استدعاء واحد بدلاً من N زائد واحد
الإصلاح هو نوع التغيير الذي يبدو صغيرًا ويؤتي ثماره بشكل كبير. يمكن لـ GDI بالفعل الإبلاغ عن العرض الإجمالي لسلسلة وموضع كل glyph في استعلام واحد. يعرض HotPDF ذلك من خلال GetWideCharAdvances، الذي يملأ مصفوفة بالتقدم الطبيعي لكل glyph، بما في ذلك التقرن (kerning)، ويعيد العرض الإجمالي، في استدعاء واحد بدلاً من N+1. يطلب روتين الضبط، _HPDFEmitJustifiedWideLine داخليًا، جميع التقدمات مرة واحدة، ويحسب التراخي، ويوزعه عبر الحدود القابلة للتمدد، ويرسل السطر
بالنسبة لنفس صفحة A4 ينخفض قياس السطر الواحد من حوالي 81 رحلة ذهاب وإياب إلى واحدة، لذا تنخفض الصفحة من حوالي 3645 رحلة إلى حوالي 45 رحلة، وهو انخفاض يقارب ثمانين ضعفًا. الإخراج متطابق بايت لـ بايت، لأنه لم يتغير شيء بشأن القياس سوى عدد مرات طلبه. نفس محرك GDI، ونفس مقاييس الخط، ونفس التقرن يغذي نفس الأرقام. انخفض عدد رحلات الذهاب والإياب فقط. عندما يكون القياس صحيحًا بالفعل، فإن التحسين الصحيح هو التوقف عن طلبه بشكل متكرر، وليس تقريبه
كيف يصل السطر إلى الصفحة
بمجرد تقسيم التراخي، يرسل HotPDF السطر باستخدام ExtTextOut ومصفوفة تقدم لكل glyph، وهي مصفوفة Dx. كل إدخال هو المسافة من أصل glyph واحد إلى التالي، وهو التقدم الطبيعي لذلك الـ glyph بالإضافة إلى نصيبه من التراخي عندما يتبعه حد قابل للتمدد. يخطط هذا مباشرة على نموذج تصوير PDF. يُكتب النص المتمركز باستخدام عامل TJ، وهو مصفوفة تتداخل فيها تسلسلات الـ glyph مع تعديلات أفقية صريحة، وتصبح قيم Dx هي هذه التعديلات بالضبط. هذا هو السبب في أن المساحة الإضافية تهبط بين الـ glyphs في مواضع النقاط الفرعية الدقيقة بدلاً من تزييفها بأحرف حشو، وهذا هو السبب في أن سطر HotPDF المضبوط يُقاس بشكل صحيح إذا قامت أداة لاحقة بقراءته مرة أخرى
أنت لا تستدعي ExtTextOut بنفسك للفقرات المضبوطة. نقطة الدخول هي WideTextOutBox، والتي تلف سلسلة Unicode في مربع وتطبق المحاذاة التي تطلبها. وهي تقسم النص إلى أسطر تناسب عرض المربع، وتضع كل سطر أسفل ارتفاع المربع، وتعيد عدد الأحرف التي تمكنت من احتوائها قبل نفاد المساحة الرأسية. يتم اختيار المحاذاة بواسطة تعداد الضبط
type
THPDFJustificationType = (jtLeft, jtCenter, jtRight, jtJustify);
الثلاثة الأولى تشرح نفسها بنفسها: محاذاة لليسار، وتوسيط، ولليمين. الرابع، jtJustify، هو الضبط الكامل لكلا الحافتين الموصوف هنا، وهي القيمة التي يقرأها WideTextOutBox للتبديل إلى التباعد المدرك للبرنامج النصي
ضبط فقرة في الممارسة العملية
ينشئ مثال كامل مستندًا، ويضبط خطًا، ويسكب فقرة في مربع مع ضبط كامل. نفس الرمز يضبط النص اللاتيني ونص CJK دون تغيير في العلامة، لأن الوعي بالبرنامج النصي يعيش أسفل واجهة برمجة التطبيقات
uses
HPDFDoc;
procedure JustifyParagraph;
var
Pdf: THotPDF;
Body: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'Justified.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', 11);
Body :=
'Full justification spreads the slack on each filled line so both ' +
'edges meet the column, while the last line keeps its natural width. ' +
'For scripts with word gaps the space lands between words; for ' +
'scripts without them it spreads evenly between glyphs.';
// X, Y, LineSpacing, BoxWidth, BoxHeight, Text, Align
Pdf.CurrentPage.WideTextOutBox(72, 72, 4, 380, 240, Body, jtJustify);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
لرسم نفس الكتلة محاذاة لليسار، أو متوسطة، أو محاذاة لليمين، قم بتغيير الوسيطة النهائية فقط إلى jtLeft، أو jtCenter، أو jtRight. الالتفاف، ووضع السطر، وقيمة الإرجاع تظل كما هي. يأتي العرض المقاس الذي يوجه جميع المسارات الأربعة من GetWideTextWidth، استعلام العرض المدرك لـ Unicode والذي يقيس WideString بشكل صحيح حيث أن القياس القديم بالبايت كان سيخطئ في حجم أي شيء بعد اللاتينية-1، وهو ما يجعل المربع يلف نصوص CJK والأزواج البديلة في المكان الصحيح لتبدأ به
الضبط هو طبقة واحدة من مكدس تشكيل نص أكبر. عندما يحتوي السطر على برامج نصية تعيد ترتيب الـ glyphs الخاصة بها أو تربطها، فإن قرارات التباعد هنا تقف فوق العمل الموصوف في مقالنا حول تشكيل النص ذي البرنامج النصي المعقد، وعندما يحمل الخط متغيرات مطبعية تريد اختيارها، انظر كيفية قيادة بدائل نمطية OpenType GSUB. يتم شحن كل هذا في مكون HotPDF لـ Delphi و C++Builder، جنبًا إلى جنب مع واجهات برمجة تطبيقات النص والتخطيط والمستندات الأوسع نطاقًا المغطاة في جميع أنحاء هذه المدونة