ضع رواية يابانية على الصفحة، وأول ما ستلاحظه هو أن النص يهبط داخل العمود لا عبر السطر، وأن الأعمدة تتقدم من الحافة اليمنى للورقة نحو اليسار. من تربى على هذا النمط يجد النص الأفقي شديد الرسمية قليلًا. المشكلة الهندسية هي أن PDF، مثل معظم أنظمة النص الرقمية، بُني حول baseline أفقي ينمو من اليسار إلى اليمين، ومجرى المحتوى لا يفهم عبارة "اكتب هذه الفقرة إلى الأسفل بدلًا من ذلك". لذلك عندما تحتاج تطبيقات Delphi إلى إنتاج شهادة أو قصيدة أو لافتة أو مستند قانوني بالتنسيق التقليدي لقارئ تايواني أو ياباني أو كوري، فعليك تركيب التخطيط يدويًا: حرف تحت حرف، وعمود إلى يسار العمود السابق.
يمنحك HotPDF مفتاحًا يتولى عنك الحساب على مستوى كل حرف. فالخط الذي تضبطه يحمل العلامة IsVertical، وبمجرد تفعيلها، يجعل استدعاء TextOut واحد سلسلة كاملة تتراص داخل عمود رأسي بدلًا من أن تمتد على baseline. موضع العمود، والترتيب من اليمين إلى اليسار، واستبدال glyph مهم بصمت، كلها موضوعات تشرحها بقية الصفحة.

المفتاح موجود في SetFont
التخطيط الرأسي ليس خاصية للصفحة أو للمستند. إنه خاصية للخط الذي ترسم به، وتفعّله عبر الوسيط الخامس في SetFont:
// SetFont(FontName, FontStyle, Size, FontCharset, IsVertical)
// The 5th argument flips the current font into vertical mode.
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, False); // horizontal
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, True); // vertical
وبما أن العلامة تابعة للخط، فإنك تنتقل بين الكتابة الأفقية والرأسية ببساطة عبر استدعاء SetFont مرة أخرى مع وسيط أخير مختلف. يمكن للصفحة أن تحمل عنوانًا أفقيًا في الأعلى ونصًا رأسيًا في الأسفل، ويُبقي HotPDF النمطين منفصلين بحسب كائن الخط لا بحسب الصفحة. هذا ما يجعل التخطيطات المختلطة ممكنة من دون أي معالجة خاصة على جانبك: كل TextOut بعد SetFont رأسي يكدّس النص، وكل TextOut بعد أفقي يسير على baseline، وآخر SetFont هو الذي يحسم الأمر.
الوسيط الرابع هو مجموعة أحرف Windows، وهي نفسها التي يأخذها الاستدعاء الأفقي. يتيح تمرير DEFAULT_CHARSET للنظام أن يحل glyphs بحسب السلسلة، وهذا مهم هنا لأن المستند الرأسي يخلط بين عدة نصوص كثيرًا. وينطبق كل شيء آخر يخص الخط أيضًا: يجب أن يكون مثبتًا على جهاز البناء، وغالبًا سترغب في FontEmbedding := True حتى يَرسم الملف glyphs CJK نفسها على قارئ لم يكن يملك Arial Unicode MS أبدًا.
استدعاء TextOut واحد هو عمود واحد
مع تفعيل خط رأسي، لا يعود استدعاء TextOut يوزع السلسلة أفقيًا من النقطة المعطاة. بل يضع أول حرف في الأعلى ثم ينزل بالباقي مباشرة إلى الأسفل، متقدمًا بمقدار line height الخاص بالخط لكل glyph. إحداثي X الذي تمرره يثبت العمود؛ وإحداثي Y يحدد أين يبدأ أعلى العمود. لذا فإن تخطيط فقرة فعلية يعني استدعاء TextOut واحد لكل عمود، ثم تحريك X إلى اليسار بين الاستدعاءات، لأن أعمدة CJK تُقرأ من اليمين إلى اليسار.
var
Pdf: THotPDF;
const
ColTop = 760; // y of the first glyph in every column (points)
ColGap = 28; // horizontal distance between columns
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'VerticalText.pdf';
Pdf.FontEmbedding := True; // embed the CJK face for portable rendering
Pdf.BeginDoc;
Pdf.CurrentPage.Size := psA4;
// A horizontal heading first, in the ordinary writing mode.
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 16, DEFAULT_CHARSET, False);
Pdf.CurrentPage.TextOut(60, 800, 0, 'Tang poem, vertical layout');
// Switch the font into vertical mode; every TextOut below now stacks.
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 18, DEFAULT_CHARSET, True);
// Columns advance right to left, so X decreases with each call.
Pdf.CurrentPage.TextOut(520, ColTop, 0, '床前明月光');
Pdf.CurrentPage.TextOut(520 - ColGap, ColTop, 0, '疑是地上霜');
Pdf.CurrentPage.TextOut(520 - ColGap * 2, ColTop, 0, '舉頭望明月');
Pdf.CurrentPage.TextOut(520 - ColGap * 3, ColTop, 0, '低頭思故鄉');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
تقرأ القصيدة كما ينبغي: العمود الأيمن أولًا، من الأعلى إلى الأسفل، ثم تقفز العين إلى العمود التالي على اليسار. لا شيء في السلسلة نفسها يرمّز هذا الترتيب. أنت ترمزه في إحداثيات X عبر إنقاصها من استدعاء إلى آخر. إذا عكست الاتجاه، خرج البيت مقلوبًا، وهي أكثر الأخطاء شيوعًا عند تحويل تخطيط أفقي إلى رأسي.
الإحداثيات: إلى أسفل الصفحة، ومن اليمين إلى اليسار
هناك محوران يعملان هنا ويدفع كل منهما في اتجاه معاكس، لذا يجب أن نكون دقيقين. يقيس HotPDF من الزاوية السفلية اليسرى للصفحة مع ازدياد Y إلى أعلى، بالنقاط. لذلك فإن العمود الرأسي يبدأ عند Y مرتفع، قريب من أعلى الورقة، ثم تنزل الأحرف من هناك بينما يطرح HotPDF ارتفاع السطر لكل حرف. أنت تضبط قيمة Y الابتدائية مرة واحدة لكل عمود، ويتولى المكوّن النزول بعد ذلك.
أما المحور الأفقي فهو الذي تديره يدويًا. كل عمود يقف عند X خاص به، والأعمدة اللاحقة تتقدم إلى قيم X أصغر لأن ترتيب القراءة من اليمين إلى اليسار. والإيقاع المعقول هو أن تختار X العمود الأيمن ثم تخصم مسافة ثابتة بين الأعمدة في كل استدعاء لاحق، كما يفعل المثال مع ColGap. المسافة بين الأعمدة متروكة لك؛ إذا كانت ضيقة جدًا تلامست الأعمدة المتجاورة، وإذا كانت واسعة جدًا بدا البلوك متفرقًا. في نصوص الجسم بحجم 12 إلى 18 نقطة تبدو المسافة الأكبر قليلًا من حجم الخط مريحة.
glyphs يجب أن تغيّر شكلها
بعض الأحرف ليست مجرد نسخ مدوّرة من نظيراتها الأفقية؛ يجب استبدالها عندما يتحول النص إلى الوضع الرأسي. والحرف الذي يعالجه HotPDF عنك هو علامة المد اليابانية ー (U+30FC)، وهي شريط المد الطويل الذي يظهر في كلمات الكاتاكانا مثل コーヒー. عندما ترسم أفقيًا تكون شرطة قصيرة على baseline. ولو رصصت glyph نفسه داخل عمود فسيمتد أفقيًا عبر العمود، وهذا خطأ: ففي اليابانية الرأسية تتحول العلامة إلى خط رأسي يصل بين الحرفين اللذين تقع بينهما. يكتشف HotPDF الرمز U+30FC في المسار الرأسي ويرسمه كشريط رأسي (U+007C) حتى يتجه المد الطويل في الاتجاه الصحيح من دون أي عمل منك.
هذا الاستبدال الواحد يغطي الحالة التي تكسر معظم التطبيقات الساذجة، لكن يجدر أن تعرف أين يتعمق الأمر أكثر. فالمطابقة الرأسية الكاملة تدير أيضًا الحروف اللاتينية وعلامات الترقيم الغربية بتدوير 90 درجة، وتحرك kana الصغيرة، وتعيد تموضع الأقواس والفواصل إلى أشكالها الرأسية، ويعيش التنفيذ الكامل لذلك في ميزات OpenType الرأسية الخاصة بالخط لا في قواعد ثابتة. يستطيع HotPDF الاستفادة من تلك الميزات عندما يحملها الخط: البدائل الرأسية، أي ميزات GSUB vert وvrt2، والتباعد الرأسي، أي عمليات GPOS vkrn وvpal، كلها اختيارات تُطبّق على المسار الرأسي فقط عندما يعرّفها الخط النشط بالفعل. بالنسبة إلى نص CJK المختلط داخل وجه واسع التغطية مثل Arial Unicode MS، يكفي التعامل المدمج مع U+30FC مع خطوة line height متساوية لإنتاج أعمدة صحيحة وقابلة للقراءة؛ أما ميزات OpenType فتهمك عندما تنتقل إلى خط مصمم للتنضيد الرأسي الدقيق وتريد منه تموضع kana الطبيعي وتباعد الحروف.
خلط النصوص والاتجاهات في صفحة واحدة
المستندات الحقيقية نادرًا ما تكون نقية. فقد تحمل صفحة يابانية رأسية تعليقًا إنجليزيًا أفقيًا، أو رقم صفحة على الحافة السفلية، أو كتلة كورية موضوعة رأسيًا بجانب اليابانية. وبما أن العلم الرأسي هو مفتاح على مستوى الخط، فإنك تركّب هذه الأجزاء عبر التناوب في استدعاءات SetFont بدلًا من إدارة حالة عامة على مستوى الصفحة. اضبط خطًا أفقيًا، واكتب العنوان الجاري ورقم الصفحة، ثم اضبط خطًا رأسيًا، وارسم الأعمدة، ثم اضبط خطًا أفقيًا من جديد للتذييل. كل منطقة تأخذ نمط آخر SetFont، لذلك فالانضباط الوحيد المطلوب هو استدعاؤه كلما غيّرت الاتجاه.
تفصيل يجب التخطيط له عند خلط النصوص: إن glyphs الصينية واليابانية والكورية قريبة من الشكل المربع وتتراص بخطوات متساوية، لكن المقاطع اللاتينية المضمنة داخل عمود رأسي لا تملك التقدم المنتظم نفسه. إذا احتجت إلى بضع كلمات لاتينية داخل نص رأسي في الغالب، فاحسم مسبقًا هل يجب تدويرها لتسير في العمود أم إبقاؤها أفقية كإدراج قصير، وضع هذا الجزء باستخدام TextOut خاص به بدلًا من تركه يركب الخطوة الرأسية المخصصة للرموز الأيديوغرافية. معاملة المقطع المختلط كمشكلة موضع مستقلة تحافظ على إيقاع الأعمدة.
من المثال إلى الإنتاج
الأجزاء صغيرة وتتآلف بشكل متوقع. فعّل العلم الرأسي في SetFont، وأعطِ استدعاء TextOut واحدًا لكل عمود من Y علوي ثابت، وأنقص X عبر الاستدعاءات حتى تُقرأ الأعمدة من اليمين إلى اليسار. ضمّن الخط حتى تصمد glyphs CJK في انتقالها إلى جهاز القارئ، ودع المكوّن يتولى علامة المد الطويل U+30FC، وميزات OpenType الرأسية حيث يدعمها الخط. ومن هناك، فإن تحويل التخطيط إلى إنتاج هو في معظمه حساب: اشتقاق مواضع X للأعمدة من عرض كتلة مقاس، وتقسيم المقاطع الطويلة إلى أعمدة تناسب ارتفاع الصفحة، وترك مساحة للعناوين الأفقية وأرقام الصفحات.
وللجانب الأوسع من النص والخط الذي يرتكز عليه هذا، بما في ذلك اصطلاحات TextOut الأفقية وتسجيل خطوط Unicode، راجع مثال Hello World متعدد اللغات ومثال TextOut. ومفتاح SetFont الرأسي واستدعاءات TextOut المعروضة هنا جزء من مكوّن HotPDF الخاص بـ Delphi وC++Builder.