تبدیل HTML به PDF روی کاغذ آسان به نظر میرسد. شما یک قالب صیقلخورده میسازید، دادههای خود را در آن قرار میدهید و انتظار سندی را دارید که دقیقاً پیکسلبهپیکسل مشابه صفحه وب باشد. در واقعیت، این فرآیند اغلب به مبارزهای روزمره علیه کرشها، گلیفهای مفقود و خرابیهای بصری تبدیل میشود. در طول یک پروژه اخیر، سه مشکل مدام تکرار میشدند: iText هنگام برخورد با برخی گرافیکهای SVG کاملاً از کار میافتاد، ایموجیها در مربعهای سفید خالی ناپدید میشدند و پسزمینههای شفافِ ظریف به بلوکهای سیاه مات تبدیل میشدند. هر شکست علت مشخصی داشت و رفع هر سه مشکل مستلزم بازنگری در نحوه آمادهسازی محتوا توسط اپلیکیشن، پیش از آن بود که موتور PDF با آن مواجه شود.
وقتی SVG فرآیند را مختل میکند
iText برای راحتی کار، یک رندرکننده SVG داخلی دارد، اما این یکپارچگی یک نقطه ضعف حیاتی را پنهان میکند. وقتی یک SVG شامل مسیرهای پیچیده، استایلهای سنگین CSS یا برخی تبدیلهای مختصاتی باشد، پارسرِ تعبیهشده یک استثنای (exception) مرتب صادر نمیکند و از آن عبور نمیکند؛ بلکه منفجر میشود. اینها کرشهای کامل سیستم هستند که بدون هشدار، تردِ (thread) تولید PDF را از کار میاندازند و تنها یک فایل ناقص و یک stack trace برای شما باقی میگذارند که به اعماق پارسر برداری اشاره میکند.
راه حل قابل اعتماد این است که اصلاً از iText نخواهید که SVG را رندر کند. در عوض، آن کار را به Apache Batik که در حالت standalone اجرا میشود، بسپارید. Batik همان مسیرهای پیچیده و قوانین CSS را بدون آن شکنندگیِ مشابه مدیریت میکند، و جدا نگه داشتن آن، موتور PDF شما را در برابر ناپایداریهای مربوط به گرافیک محافظت میکند. گردش کار ساده است: قبل از شروع مونتاژ سند، SVG را از طریق Batik اجرا کنید تا یک PNG data URL تولید شود. آن تصویر رستری را به جای نشانهگذاری برداری خام، به iText پاس دهید. Batik standalone مشخصات SVG را دقیقتر از یک رندرکننده تعبیهشده که در یک کتابخانه بزرگتر بستهبندی و ثابت شده است، دنبال میکند؛ همچنین این جداسازی به این معناست که یک گرافیک ناقص نمیتواند کل فرآیند تبدیل سند را از کار بیندازد.
یک جزئیات کوچک تعیین میکند که آیا نمودار شما حرفهای به نظر میرسد یا شبیه به یک گزارش باگ. SVG برای تعریف سیستم مختصات و رفتار مقیاسبندی خود به ویژگی viewBox متکی است. اگر کد تبدیل شما viewBox را نادیده بگیرد، یک نمودار کاملاً معتبر میتواند تا حد یک نقطه غیرقابل خواندن کوچک شود یا به شکلی کج و معوج کشیده شود. این ویژگی را به طور صریح تجزیه (parse) کنید و آن ابعاد را به اندازه خروجی خود نگاشت کنید. نادیده گرفتن این مرحله باعث میشود ساعتها وقت صرف عیبیابی مشکلی در چیدمان (layout) کنید که هیچ ارتباطی به کیفیت رندر ندارد و تماماً مربوط به اعلام نشدن مختصات است.
مشکل جوهر نامرئی
مربعهای خالی در جایی که ایموجیها باید باشند، داستان سادهای را روایت میکنند: فونت فعلی این زبان را بلد نیست. فونت Helvetica و سایر فونتهای استاندارد PDF مربوط به زمانی هستند که استفاده از ایموجیها گسترده نشده بود. آنها شامل گلیفهایی برای محدودههای Unicode ایموجی نیستند، بنابراین وقتی iText با آن نقاط کد مواجه میشود، چیزی رندر نمیکند و از آن عبور میکند. نتیجه، سندی پر از جعبههای خالی است که باعث میشود گزارشهای تحلیل احساسات اجتماعی یا خروجیهای بازخورد کاربران، خراب به نظر برسند.
شما نمیتوانید برای پر کردن این شکاف به سیستمعامل کلاینت تکیه کنید. فایلهای PDF منابع فونت خود را به همراه دارند و آنچه در مرورگر شما درست به نظر میرسد، زمانی که فایل از فونتهای سیستم شما جدا شد، دیگر اهمیتی ندارد. راه حل، ساخت یک لایه مسیریابی فونت صریح است. یک فونت اختصاصی با قابلیت نمایش ایموجی مانند Symbola را ثبت کنید که نمادهای تکرنگ را پوشش میدهد و بلوکهای Unicode ایموجی را شامل میشود. یک قلب سیاه و سفید یا نماد هشدار ممکن است ظرافت یک مجموعه گلیف رنگی براق را نداشته باشد، اما معنا را منتقل میکند. یک مستطیل خالی، نشاندهنده شکست است. رندر کردن مداوم فونتهای ایموجی تمامرنگی در داخل نمایشگرهای PDF همچنان دشوار است و تلاش برای پشتیبانی از رنگ، اغلب مشکلات سازگاری بیشتری نسبت به آنچه حل میکند، ایجاد میکند.
iText از طریق شکستن خط (line breaking)، مشکل دوم و بدتری را اضافه میکند. این کتابخانه میتواند جفتهای جایگزین (surrogate pairs) ایموجی را در مرز اشتباهی تقسیم کند و یک کاراکتر واحد را به دو نیمه نامعتبر تبدیل کند. وقتی این اتفاق میافتد، جریان متن خراب شده و در نهایت با قطعات غیرقابل خواندن در جایی که یک گلیف واحد باید باشد، مواجه میشوید. برای جلوگیری از این اتفاق، یک ISplitCharacter سفارشی پیادهسازی کنید که جفتهای جایگزین را شناسایی کرده و با آنها به عنوان واحدهای اتمی برخورد کند. این کار مانع از این میشود که موتور چیدمان در میان یک ایموجی شکست خط ایجاد کند و یکپارچگی متن را حفظ میکند.
وقتی شفافیت سیاه میشود
یک SVG با پسزمینه soft rgba یا افکت fill-opacity لایهبندی شده، در مرورگر ظریف به نظر میرسد. همان نشانهگذاری را به iText بدهید و شفافیت اغلب به یک مستطیل سیاه توپر تبدیل میشود. موتور، توابع رنگ CSS و ویژگیهای opacity را به درستی مدیریت نمیکند و opacity را با جوهر با تراکم کامل جایگزین میکند.
پیشپردازش SVG پیش از رسیدن به تبدیلکننده (converter)، تنها دفاع قابل اتکا است. هر عنصری را که به ترکیب آلفا (alpha blending) وابسته است، حذف یا جایگزین کنید. مقادیر rgba() را به رنگهای توپر rgb() تبدیل کنید. اگر مجبورید مفهوم شفافیت (opacity) را حفظ کنید، مقادیر را از میانبرهای CSS خارج کرده و به ویژگیهای استاندارد opacity منتقل کنید، هرچند حذف کامل شفافیت امنترین راه است. این تغییرات ممکن است گویی گامی به عقب در طراحی وب به نظر برسند، اما PDF از مدل تصویرسازی متفاوتی استفاده میکند که قدیمیتر از شفافیت مدرن CSS است. این فرمت انتظار مقادیر رنگی مشخص را دارد و ارائه مقادیر مبهم، فاجعهبار خواهد بود.
در حین پاکسازی (sanitizing) نشانهگذاری (markup)، مجدداً بررسی کنید که هر SVG دارای اعلان فضای نام (namespace) مناسب xmlns باشد. HTMLهای تولید شده و موتورهای قالب (template engines) اغلب ویژگیهای فضای نام را در طول فشردهسازی (minification) یا سریالسازی DOM حذف میکنند. بدون آن فضای نام، تجزیهکننده (parser) SVG ممکن است عناصر را اشتباه شناسایی کند یا بدون اعلام خطا متوقف شود، که نتیجه آن یا خطای تجزیهکننده است و یا دادههای برداری ناقص که هرگز به صفحه راه نمییابند. این یک بررسی ساده است که تنها چند ثانیه زمان میبرد اما ساعتها در وقت شما صرفهجویی میکند.
یک قالب، دو جهان
بدترین راهکار بلندمدت، نگهداری قالبهای HTML مجزا برای مرورگر و PDF است. برچسبها جابهجا میشوند، حاشیهها تغییر میکنند و بهزودی گزارش خروجی دیگر با داشبورد مطابقت نخواهد داشت. یک معماری تمیزتر بر یک قالب واحد تکیه دارد و منطق رندرینگ را با یک پرچم (flag) واحد، چیزی شبیه به context.isForPdf()، شاخهبندی میکند.
وقتی آن پرچم false باشد، قالب تجربه کامل مرورگر را ارائه میدهد. این قالب SVG بومی را برای زوم بینهایت، CSS مدرن و هر دارایی رنگی که مرورگر پشتیبانی میکند، ارائه میدهد. وقتی پرچم true باشد، همان قالب، داراییهای SVG را با PNGهای از پیش رندر شده جایگزین میکند، پشته فونت ایمن برای ایموجی (emoji-safe font stack) را فعال میکند و هرگونه اثر شفافیت پشتیبانینشده را حذف میکند. متن و ساختار بدون تغییر باقی میمانند؛ تنها خط لوله داراییها (asset pipeline) و قوانین استایلدهی با رسانه هدف سازگار میشوند.
این رویکرد دو مسیره، کد را منسجم و قابل اعتماد نگه میدارد. شما محتوا را در یک جا بهروزرسانی میکنید و لایه مسیریابی (routing layer)، تفاوتهای مکانیکی بین صفحه نمایش و کاغذ را مدیریت میکند. همچنین تست کردن را سادهتر میکند. میتوانید منطق قالب را در مرورگر با ابزارهای کامل توسعهدهنده (developer tools) تأیید کنید، سپس پرچم PDF را فعال کرده و مطمئن شوید که همان دادهها، بدون از کار انداختن تبدیلکننده، سندی تمیز تولید میکنند.
حقیقت تلخ درباره تولید PDF
PDF هرگز مانند یک مرورگر رفتار نخواهد کرد. مدلهای رندرینگ اساساً متفاوت هستند و کتابخانههایی مانند iText تعادلهای آگاهانهای بین سرعت، اندازه فایل و انطباق با مشخصات (specification compliance) برقرار میکنند. موفقیت از جنگیدن با موتور و امید به بهترین نتیجه حاصل نمیشود؛ بلکه از پذیرش زودهنگام محدودیتها و طراحی خط لوله بر اساس آنها حاصل میشود.
بردارهای خود را پیش از مرحله PDF تبدیل کنید. فونتهای خود را بهطور صریح مسیریابی کنید تا هر نویسه (glyph) یک جایگزین (fallback) داشته باشد. شفافیت را حذف کرده و به رنگهای توپر برگردانید. به قالبهای خود زمینهای (context) بدهید تا بدانند برای کدام جهان در حال رندر کردن هستند. این کار را بهطور مداوم انجام دهید تا اسناد شما دیگر با رندرکننده درگیر نشوند و دقیقاً همانطور که قصد داشتید، به نظر برسند.
