مقاله فنی

متن عمودی CJK در PDF با Delphi و HotPDF

یک رمان ژاپنی را روی صفحه قرار دهید و اولین چیزی که متوجه می‌شوید این است که متن در طول ستون به سمت پایین می‌رود، نه در طول خط، و ستون‌ها از لبه راست صفحه به سمت چپ پیش می‌روند. خواننده‌ای که با آن بزرگ شده است، متن افقی را کمی غیرطبیعی می‌یابد. مشکل مهندسی این است که PDF، مانند تقریباً تمام سیستم‌های متن دیجیتال، حول یک خط پایه افقی ساخته شده است که از چپ به راست رشد می‌کند و یک جریان محتوا هیچ مفهومی از "در عوض این پاراگراف را به سمت پایین بنویس" ندارد. بنابراین وقتی یک برنامه Delphi باید یک گواهی، یک شعر، یک تابلو یا یک سند قانونی با فرمت سنتی برای یک خواننده تایوانی، ژاپنی یا کره‌ای تولید کند، چیدمان باید با دست مونتاژ شود: یک کاراکتر زیر دیگری، یک ستون در سمت چپ ستون قبلی

HotPDF به شما یک سوئیچ می‌دهد که حسابداری هر کاراکتر را برای شما انجام می‌دهد. فونتی که تنظیم می‌کنید یک پرچم IsVertical به همراه دارد و هنگامی که روشن است، یک فراخوانی TextOut به جای اینکه آن را در امتداد یک خط پایه اجرا کند، کل رشته را در یک ستون عمودی روی هم می‌چیند. قرار دادن ستون، ترتیب راست به چپ و یک جایگزینی گلیف به طور خاموش مهم، مواردی هستند که بقیه این صفحه به آن‌ها می‌پردازد

صفحه A4 PDF تولید شده توسط HotPDF که متن‌های چینی، ژاپنی و کره‌ای انباشته شده در ستون‌های عمودی را نشان می‌دهد که از راست به چپ خوانده می‌شوند
یک صفحه تک A4 با متن‌های چینی، ژاپنی و کره‌ای در ستون‌های عمودی، تولید شده از یک رویه Delphi.

سوئیچ در 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 بعد از یک افقی در امتداد خط پایه اجرا می‌شود، و آخرین SetFont برنده است

آرگومان چهارم مجموعه کاراکترهای ویندوز است، همان آرگومانی که یک فراخوانی افقی می‌گیرد. ارسال DEFAULT_CHARSET به سیستم اجازه می‌دهد تا گلیف‌ها را بر اساس هر رشته حل کند، که در اینجا مهم است زیرا یک سند عمودی اغلب اسکریپت‌ها را با هم ترکیب می‌کند. سایر موارد مربوط به فونت همچنان اعمال می‌شود: باید روی دستگاه ساخت نصب شود و شما تقریباً همیشه FontEmbedding := True را می‌خواهید تا فایل همان گلیف‌های CJK را در خواننده‌ای که هرگز Arial Unicode MS نداشته است رندر کند

یک فراخوانی TextOut یک ستون است

با فعال بودن یک فونت عمودی، یک فراخوانی TextOut دیگر رشته خود را از نقطه داده شده به طرفین پخش نمی‌کند. اولین کاراکتر را در بالا می‌کارد و بقیه را مستقیماً به سمت پایین هدایت می‌کند و برای هر گلیف به اندازه ارتفاع خط فونت پیش می‌رود. 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 پوینت، فاصله‌ای کمی بزرگتر از اندازه فونت به راحتی خوانده می‌شود

گلیف‌هایی که باید تغییر شکل دهند

چند کاراکتر فقط کپی‌های چرخیده از خود افقی‌شان نیستند؛ هنگامی که متن عمودی می‌شود، باید جایگزین شوند. موردی که HotPDF برای شما مدیریت می‌کند علامت صدای کشیده ژاپنی (U+30FC) است، نوار مصوت بلندی که در کلمات کاتاکانا مانند コーヒー ظاهر می‌شود. در حالت افقی به صورت یک خط تیره کوتاه روی خط پایه کشیده می‌شود. اگر همان گلیف را در یک ستون قرار دهید، به صورت صاف روی ستون قرار می‌گیرد، که اشتباه است: در زبان ژاپنی عمودی، این علامت به یک خط عمودی تبدیل می‌شود که دو کاراکتر بین خود را به هم متصل می‌کند. HotPDF کاراکتر U+30FC را در مسیر عمودی تشخیص می‌دهد و آن را به عنوان یک نوار عمودی (U+007C) رندر می‌کند، بنابراین علامت مصوت بلند بدون هیچ کاری از طرف شما در جهت درست قرار می‌گیرد

آن جایگزینی منفرد موردی را که بیشتر پیاده‌سازی‌های ساده را خراب می‌کند پوشش می‌دهد، اما ارزش آن را دارد که بدانیم مشکل کلی در کجا عمیق‌تر می‌شود. تایپوگرافی کاملاً عمودی همچنین حروف لاتین و علائم نگارشی غربی را نود درجه می‌چرخاند، کاناهای کوچک را جابجا می‌کند و پرانتزها و ویرگول‌ها را به فرم عمودی آن‌ها تغییر مکان می‌دهد، و یک پیاده‌سازی کامل از آن به جای قوانین ثابت در ویژگی‌های عمودی OpenType یک فونت قرار دارد. HotPDF زمانی که فونت آن‌ها را به همراه دارد می‌تواند از آن ویژگی‌ها استفاده کند: جایگزین‌های عمودی (ویژگی‌های GSUB vert و vrt2) و کرنینگ عمودی (جستجوهای GPOS vkrn و vpal) اختیاری هستند، و تنها زمانی که فونت فعال واقعاً آن‌ها را تعریف کند در امتداد مسیر عمودی اعمال می‌شوند. برای متن ترکیبی CJK در یک فونت تک پوشش گسترده مانند Arial Unicode MS، مدیریت داخلی U+30FC به علاوه یک گام ارتفاع خط زوج برای تولید ستون‌های صحیح و خوانا کافی است؛ ویژگی‌های OpenType زمانی اهمیت پیدا می‌کنند که به فونتی بروید که برای تنظیم دقیق عمودی طراحی شده است و می‌خواهید موقعیت‌دهی کانای بومی و فاصله‌گذاری بین گلیف‌های آن را داشته باشید

ترکیب اسکریپت‌ها و جهت‌گیری‌ها در یک صفحه

اسناد واقعی به ندرت خالص هستند. یک صفحه ژاپنی عمودی ممکن است یک زیرنویس افقی انگلیسی، یک شماره صفحه در پایین، یا یک بلوک از زبان کره‌ای که به صورت عمودی در کنار ژاپنی تنظیم شده است را به همراه داشته باشد. از آنجا که پرچم عمودی یک سوئیچ در سطح فونت است، شما با جابجایی فراخوانی‌های SetFont به جای مدیریت هرگونه حالت در سطح صفحه، این موارد را ترکیب می‌کنید. یک فونت افقی تنظیم کنید، سرصفحه در حال اجرا و شماره صفحه را بنویسید، یک فونت عمودی تنظیم کنید، ستون‌ها را بچینید، دوباره یک فونت افقی برای پاورقی تنظیم کنید. هر منطقه حالت جدیدترین SetFont را دریافت می‌کند، بنابراین تنها نظم مورد نیاز این است که هر زمان جهت را تغییر دادید آن را فراخوانی کنید

یک نکته برای برنامه‌ریزی در هنگام ترکیب اسکریپت‌ها: اندیشه‌نگاشت‌های چینی، ژاپنی و کره‌ای نزدیک به مربع هستند و در یک گام زوج روی هم قرار می‌گیرند، اما متن‌های لاتین تعبیه‌شده در یک ستون عمودی این پیشروی یکنواخت را ندارند. اگر به چند کلمه لاتین در داخل متنی که در غیر این صورت عمودی است نیاز دارید، آگاهانه تصمیم بگیرید که آیا باید برای حرکت به سمت پایین ستون چرخانده شوند یا به عنوان یک درج افقی کوتاه به صورت عمودی تنظیم شوند، و به جای اینکه اجازه دهید روی گام عمودی در نظر گرفته شده برای اندیشه‌نگاشت‌ها سوار شود، آن بخش را با TextOut خودش قرار دهید. در نظر گرفتن متن ترکیبی به عنوان مشکل قرارگیری خاص خود، ریتم ستون را دست‌نخورده نگه می‌دارد

از نمونه تا تولید

قطعات کوچک هستند و به طور قابل پیش‌بینی ترکیب می‌شوند. پرچم عمودی را در SetFont روشن کنید، یک TextOut برای هر ستون از یک Y بالای ثابت صادر کنید، و X را در بین فراخوانی‌ها کاهش دهید تا ستون‌ها از راست به چپ خوانده شوند. فونت را جاسازی کنید تا گلیف‌های CJK در سفر به دستگاه خواننده زنده بمانند، و اجازه دهید کامپوننت علامت مصوت بلند U+30FC و ویژگی‌های عمودی OpenType را در صورت پشتیبانی فونت مدیریت کند. از آنجا، تولیدی کردن چیدمان بیشتر محاسباتی است: استخراج موقعیت‌های X ستون از یک عرض بلوک اندازه‌گیری شده، شکستن متن‌های طولانی به ستون‌هایی که با ارتفاع صفحه متناسب هستند، و رزرو فضا برای سرصفحه‌ها و شماره صفحات افقی

برای سطح گسترده‌تر متن و فونت که این بر روی آن ساخته شده است، از جمله قراردادهای افقی TextOut و ثبت فونت یونیکد، به مثال چندزبانه Hello World و نمونه TextOut مراجعه کنید. سوئیچ عمودی SetFont و فراخوانی‌های TextOut نشان داده شده در اینجا بخشی از کامپوننت HotPDF برای Delphi و C++Builder هستند