بیشتر کدهای دلفی که با PDF کار میکنند، این فرمت را به عنوان ظرفی برای دو چیز در نظر میگیرند: رشتههایی از متن و چند بیتمپ قرار داده شده. این دیدگاه تا حدی درست است، اما تواناترین بخش فرمت را بلااستفاده میگذارد. یک صفحه PDF یک بوم ۲ بعدی مستقل از رزولوشن است که بر روی همان مدل تصویرسازی PostScript ساخته شده است. این بوم میتواند خطوط، منحنیها، مناطق پرشده، گرادیانها و الگوهای تکراری را رسم کند، که همه به صورت بردارهایی هستند که در هر زومی واضح باقی میمانند و با وضوح کامل دستگاه چاپ میشوند. اگر در حال رسم یک لوگو، نمودار، واترمارک یا حاشیه یک گواهینامه هستید، مسیر برداری تقریباً همیشه شیء ابتدایی مناسب است، و این مسیر نسبت به تصویر رستری شده که بسیاری از برنامهها به جای آن استفاده میکنند، کوچکتر و واضحتر است
این مقاله مدل برداری را همانطور که ISO 32000-1 تعریف میکند مرور میکند و فراخوانیهای مربوطه در PDF Library for Delphi را نشان میدهد. هدف این است که مشخصات ملموس شود، زیرا API به دقت با آن تطبیق دارد، و درک یکی به شما دیگری را میآموزد
صفحه یک ماشین مسیر است
استاندارد ISO 32000-1 §8.5 گرافیک را در دو مرحله توصیف میکند که هرگز همپوشانی ندارند. ابتدا شما یک مسیر میسازید، که هندسه خالصی است و هیچ نتیجه بصری ندارد. سپس این مسیر را در یک عملیات واحد رنگآمیزی میکنید که خطوط پیرامون آن را میکشد (stroke)، داخل آن را پر میکند (fill)، یا هر دو را انجام میدهد. هیچ چیزی در طول ساخت و ساز روی صفحه ظاهر نمیشود. مسیر یک دنباله انتزاعی از نقاط و بخشهایی است که در حالت گرافیکی نگهداری میشود تا زمانی که یک عملگر رنگآمیزی آن را مصرف کند، که در این نقطه مسیر رندر شده و کنار گذاشته میشود
یک مسیر از یک یا چند زیرمسیر ساخته شده است. یک زیرمسیر در یک نقطه شروع میشود و با اضافه کردن بخشها رشد میکند: خطوط مستقیم، منحنیهای بزیه مکعبی، و در برخی پلتفرمها مستطیلهای کامل که به عنوان زیرمسیرهای بسته جداگانه اضافه میشوند. در PDF Library for Delphi شما یک مسیر را با StartPath باز میکنید، که نقطه شروع را تعیین میکند، سپس آن را با AddLineToPath و AddCurveToPath گسترش میدهید. هر فراخوانی یک نقطه فعلی ضمنی را به جلو میبرد، بنابراین بخش بعدی از جایی که بخش قبلی پایان یافت ادامه مییابد. ClosePath یک بخش مستقیم نهایی را به نقطه شروع زیرمسیر رسم میکند، که برای stroke زدن مهم است زیرا به جای دو کلاهک انتهایی شل، یک اتصال خط واقعی در رأس بسته تولید میکند
// یک چهارضلعی بسته، ابتدا stroke و سپس fill شده
PDF.SetLineColor(0, 0, 0);
PDF.SetFillColor(0.6, 0.8, 1.0);
PDF.SetLineWidth(1.5);
PDF.StartPath(150, 100); // مسیر را از اولین vertex باز کن
PDF.AddLineToPath(220, 140);
PDF.AddLineToPath(180, 210);
PDF.AddLineToPath(110, 170);
PDF.ClosePath; // قطعه مستقیم را تا (150, 100) برگردان
PDF.DrawPath(2); // 2 = fill و stroke؛ مسیر مصرف میشود
منحنیها از AddCurveToPath استفاده میکنند، که دو نقطه کنترل بزیه و یک نقطه پایانی میگیرد: AddCurveToPath(CtAX, CtAY, CtBX, CtBY, EndX, EndY). منحنی از نقطه فعلی به (EndX, EndY) کشیده میشود و در طول مسیر به سمت دو نقطه کنترل کشیده میشود. کمانهای دایرهای از طریق AddArcToPath(CenterX, CenterY, TotalAngle) در دسترس هستند، که در آن شعاع از فاصله بین نقطه فعلی و مرکز گرفته میشود، و موتور، کمان را به صورت زنجیرهای از بخشهای بزیه ساطع میکند. مستطیلها یک میانبر دارند، AddBoxToPath(Left, Top, Width, Height)، که یک مستطیل کامل و بسته را به عنوان زیرمسیر خودش بدون نیاز به StartPath قبلی اضافه میکند
دو قانون پر کردن (fill rules) و دلیل تفاوت آنها
هنگامی که شما مسیری را پر میکنید که از خودش عبور میکند یا حاوی یک حلقه داخلی است، رندرکننده برای تصمیمگیری اینکه کدام مناطق داخل شکل و کدام سوراخ هستند، نیاز به یک قانون دارد. استاندارد ISO 32000-1 §8.5.3.3 دو قانون را تعریف میکند، و آنها میتوانند هندسه مشابه را به طور متفاوتی رنگ کنند. قانون nonzero winding (پیچش غیر صفر)، تقاطعهای علامتدارِ یک پرتو که از نقطه تست تا بینهایت تابیده میشود را میشمارد، برای هر بخشی که از چپ به راست عبور میکند یک واحد اضافه میکند و برای هر بخشی که از مسیر مخالف عبور میکند یک واحد کم میکند؛ زمانی که مجموع صفر نباشد، نقطه در داخل است. قانون even-odd (زوج-فرد) جهت را نادیده میگیرد و به سادگی تقاطعها را میشمارد و در صورت فرد بودن شمارش، نقطه را داخل در نظر میگیرد
مورد کلاسیکی که در آن این دو با هم متفاوت هستند، شکلی با یک سوراخ، یک دونات یا واشر است. یک مرز خارجی و یک مرز داخلی را داخل آن رسم کنید. بر اساس قانون even-odd، حلقه داخلی همیشه یک سوراخ ایجاد میکند، زیرا هر نقطهای بین دو مرز یک بار عبور میکند و هر نقطهای در داخل حلقه داخلی دو بار عبور میکند. طبق قانون nonzero winding، سوراخ تنها زمانی ظاهر میشود که حلقه داخلی در جهت مخالف حلقه بیرونی بپیچد؛ اگر آنها را در یک جهت بپیچانید، پیچشها به جای خنثی شدن تقویت میشوند و منطقه داخلی به طور یکپارچه پر میشود. یک ستاره پنج پر که به صورت یک خط پیرامونی خود متقاطع رسم میشود شکاف مشابهی را نشان میدهد: even-odd پنج ضلعی مرکزی را خالی میگذارد در حالی که nonzero winding آن را پر میکند
در PDF Library for Delphi شما قانون را با فراخوانیِ نقاشی که انجام میدهید انتخاب میکنید، نه با یک پرچم (flag). متد DrawPath با قانون nonzero winding پر میکند؛ DrawPathEvenOdd با قانون even-odd پر میکند. هر دو یک حالت صحیح مشابه دریافت میکنند: 0 فقط دور خط پیرامونی را میکشد (stroke)، 1 فقط داخل را پر میکند (fill)، و 2 هم پر میکند و هم خط میکشد. قانون even-odd دقیقاً به این دلیل ابزار سادهتری برای سوراخهای پانچ شده است که نیازی به مدیریت جهت زیرمسیرها ندارد
// همان دو box، دو fill rule، دو نتیجه متفاوت
// Nonzero winding: هر دو box در یک جهت میپیچند، پس box داخلی
// حفره ایجاد نمیکند و کل box بیرونی کاملاً fill میشود
PDF.SetFillColor(0.2, 0.4, 0.8);
PDF.AddBoxToPath(100, 100, 200, 120); // outer
PDF.AddBoxToPath(140, 130, 120, 60); // inner
PDF.DrawPath(1); // 1 = fill, nonzero winding
// Even-odd: box داخلی تعداد زوجی قطع میشود
// پس یک حفره مستطیلی تمیز در box بیرونی ایجاد میکند
PDF.SetFillColor(0.2, 0.4, 0.8);
PDF.AddBoxToPath(100, 300, 200, 120); // outer
PDF.AddBoxToPath(140, 330, 120, 60); // inner cut-out
PDF.DrawPathEvenOdd(1); // 1 = fill, even-odd
گرادیانهای محوری رنگ را در طول یک خط تغییر میدهند
رنگ پرکننده یکپارچه (flat fill color) یک مقدار است که در سراسر منطقه قرار میگیرد. یک گرادیان رنگ را به طور پیوسته تغییر میدهد، و سادهترین نوع آن گرادیان محوری یا خطی است. استاندارد ISO 32000-1 §8.7.4.5 آن را به عنوان یک سایهزنی محوری Type 2 (Type 2 axial shading) مشخص میکند: شما دو نقطه ارائه میدهید که یک محور را تعریف میکنند، یک رنگ شروع در نقطه اول و یک رنگ پایان در نقطه دوم، و رندرکننده رنگ را در طول آن محور درونیابی (interpolate) میکند. هر نقطهای در منطقه پر شده رنگ تصویر عمودی خود را روی محور میگیرد، بنابراین گرادیان به صورت نوارهایی با زاویه راست نسبت به خط بین دو نقطه اجرا میشود
در PDF Library for Delphi، گرادیان یک منبع سند نامگذاری شده است که شما یک بار آن را ایجاد میکنید و سپس به عنوان رنگ فعال انتخاب میکنید. NewRGBAxialShader آن را ثبت میکند. امضای این متد به این شکل است: NewRGBAxialShader(ShaderName, StartX, StartY, StartRed, StartGreen, StartBlue, EndX, EndY, EndRed, EndGreen, EndBlue, Extend): دو نقطه انتهایی محور، سهتاییهای RGB در هر انتها به عنوان مقادیری در محدوده 0 تا 1، و یک پرچم Extend. در صورت تنظیم Extend روی 1 رنگهای انتهایی به صورت رنگ یکپارچه فراتر از نقاط پایانی محور ادامه مییابند، که این معمولاً همان چیزی است که میخواهید تا گوشههای منطقهای در خارج از محور رنگنشده باقی نماند؛ 0 آنها را دست نخورده باقی میگذارد. هنگامی که شیدر (shader) وجود دارد، شما آن را با SetFillShader برای مناطق پرشده، SetLineShader برای خطوط پیرامونی یا SetTextShader برای متن پیوند میزنید. این اتصال برای فراخوانیهای رسم بعدی فعال میماند، بنابراین مسیری که در مرحله بعد رنگآمیزی میکنید، به جای یک رنگ یکپارچه، گرادیان را دریافت میکند
// یک gradient عمودی را یکبار تعریف کن: آبی پایین تا سفید بالا
PDF.NewRGBAxialShader('panelGrad',
0, 100, 0.10, 0.25, 0.55, // نقطه شروع و RGB شروع
0, 260, 1.00, 1.00, 1.00, // نقطه پایان و RGB پایان
1); // 1 = انتهاها را بهصورت رنگ solid امتداد بده
// gradient را بهعنوان fill انتخاب کن، سپس مستطیلی با آن رسم کن
PDF.SetFillShader('panelGrad');
PDF.AddBoxToPath(80, 100, 300, 160);
PDF.DrawPath(1); // 1 = fill، اکنون با shader پر شده است
محور در اینجا عمودی است، از y=100 تا y=260 در یک x ثابت، بنابراین نوارهای رنگ به صورت افقی قرار میگیرند و مستطیل از رنگ آبی در پایه به سفید در بالای خود محو میشود. از آنجا که شیدر بر اساس نام مشخص میشود، یک تعریف میتواند به هر تعداد شکل در صفحه رنگ دهد، و بازگشت به یک رنگ یکپارچه فقط با یک فراخوانی دیگرِ SetFillColor قبل از مسیر بعدی انجام میشود
الگوهای کاشیکاری یک سلول را تکرار میکنند
در جایی که یک گرادیان یک رنگ واحد را به صورت هموار تغییر میدهد، یک الگوی کاشیکاری قطعه کوچکی از کار هنری را در یک منطقه تکرار میکند. استاندارد ISO 32000-1 §8.7.3.1 الگوی کاشیکاری را به عنوان یک سلول الگو (یک قطعه محتوای مستقل) تعریف میکند که رندرکننده آن را روی یک شبکه ثابت تکثیر میکند تا منطقهای را که رنگآمیزی میشود کاشیکاری کند. اینگونه است که شما هاشورزنی برای یک شکل پر شده مهندسی، یک موتیف نام تجاری تکرار شونده در پشت یک هدر، یا یک پسزمینه بافتدار که بدون توجه به بزرگی ناحیه، کاملاً واضح و برداری میماند و حجم آن تقریباً صفر است را میسازید، زیرا سلول یک بار ذخیره میشود و همه جا ارجاع داده میشود
در PDF Library for Delphi، سلول الگو از محتوای ضبط شده صفحه ساخته میشود. شما یک صفحه یا ناحیه را با CapturePage ضبط میکنید، این فایل ضبط شده را با NewTilingPatternFromCapturedPage(PatternName, CaptureID) به یک الگوی نامگذاری شده تبدیل میکنید، و سپس آن الگو را با SetFillTilingPattern(PatternName) به عنوان پُرکننده (fill) فعلی انتخاب میکنید. از آن نقطه به بعد، هر مسیری که پُر میکنید با سلول تکرارشونده رنگ میشود، نه با یک رنگ ساده؛ این دقیقاً مانند پر کردن سایهزن کار میکند اما با یک سلول کاشیکاری شده به عنوان منبع رنگ. این توالی کمی پیچیدهتر از یک فراخوانی واحد است، بنابراین اگر مرحله ضبط ناآشناست، الگو را به عنوان یک عملیات دو مرحلهای در نظر بگیرید: ابتدا سلول ضبط شده را تولید کنید، سپس قبل از کشیدن ناحیهای که میخواهید کاشیکاری شود، آن را به عنوان یک پرکننده از طریق نام متصل کنید
قرار دادن اشکال اولیه در کنار هم
قطعات به طور مستقیم ترکیب میشوند. یک لکه بزیه پرشده مسیری از منحنیهاست که با DrawPath رنگآمیزی میشود. همان طرح پس از اضافه کردن یک حلقه داخلی و نقاشی با DrawPathEvenOdd سوراخی را نشان میدهد که پرکردنِ nonzero آن را بسته بود. یک مستطیل پر از گرادیان یک باکس است که به یک شیدر متصل شده است. مثال زیر هر سه مورد را به ترتیب رسم میکند تا تفاوت بین دو قانون پر کردن (fill rules) در یک صفحه قابل مشاهده باشد، سپس یک پنل گرادیان را در زیر آنها قرار میدهد
// 1. یک شکل Bezier پرشده (nonzero winding)
PDF.SetFillColor(0.85, 0.30, 0.25);
PDF.StartPath(120, 480);
PDF.AddCurveToPath(160, 560, 240, 560, 280, 480); // top lobe
PDF.AddCurveToPath(240, 420, 160, 420, 120, 480); // bottom lobe
PDF.ClosePath;
PDF.DrawPath(1); // 1 = fill
// 2. همان outline، بهعلاوه یک loop داخلی که بهصورت even-odd برای نمایش حفره پر شده است
PDF.SetFillColor(0.85, 0.30, 0.25);
PDF.StartPath(120, 300);
PDF.AddCurveToPath(160, 380, 240, 380, 280, 300);
PDF.AddCurveToPath(240, 240, 160, 240, 120, 300);
PDF.ClosePath;
PDF.MovePath(180, 300); // new subpath: the hole
PDF.AddArcToPath(200, 300, 360); // a full circle
PDF.ClosePath;
PDF.DrawPathEvenOdd(1); // حفره بریده شده است
// 3. مستطیلی پرشده با gradient محوری
PDF.NewRGBAxialShader('footerGrad',
60, 100, 0.95, 0.55, 0.10,
60, 200, 0.20, 0.10, 0.40,
1);
PDF.SetFillShader('footerGrad');
PDF.AddBoxToPath(60, 100, 340, 100);
PDF.DrawPath(1);
دو نکته ارزش به خاطر سپردن دارند. فراخوانیِ نقاشی، قانون پر کردن (fill rule) را تعیین میکند، بنابراین انتخاب بین DrawPath و DrawPathEvenOdd انتخابی بین nonzero winding و even-odd است، و برای اشکال دارای سوراخ، قانون even-odd شما را از استدلال در مورد جهت زیرمسیر نجات میدهد. و وضعیت گرافیکی (graphics state) در لحظهای که شما رنگ میزنید نمونهبرداری میشود: رنگها، عرض خط، و پیوند شیدر خود را قبل از فراخوانی نقاشی تنظیم کنید، زیرا این وضعیتی است که موتور میخواند. ابتدا بسازید، وضعیت گرافیکی را پیکربندی کنید، و در نهایت رنگ کنید، و مدل برداری هر بار به طور قابل پیشبینی رفتار میکند
از اینجا، گامهای طبیعی بعدی شامل خواندن بردارها و متن خارج از یک سند موجود است، که در مقاله ما در مورد استخراج متن، تصویر و فونت پوشش داده شده است، و رندر کردنِ همان مدل نقاشی در زمینه دستگاهِ (device context) ویندوز برای پیشنمایش و چاپ روی صفحه است که در راهنمای قدم به قدم چاپ و پیشنمایش توضیح داده شده است. فراخوانیهای مسیر، شیدر و الگو که در اینجا شرح داده شدهاند، به عنوان بخشی از کتابخانه PDF دلفی در کنار APIهای متن، تصویر، فرم و امضا که در جای دیگرِ این وبلاگ پوشش داده شدهاند، عرضه میشوند