12. وصفات عملية
حالات كاملة تُنقل كما هي. كل واحدة تحلّ مشكلة حقيقية من الميدان.
12.1 مطبعة: تدقيق ما قبل الطبع لمجلة أوفست
Section titled “12.1 مطبعة: تدقيق ما قبل الطبع لمجلة أوفست”المشكلة: يسلّم العميل ملفه، وقبل حرق الألواح يجب التحقق من الأحبار والخطوط والصور والفيض. والخطأ الذي يُكتشف لاحقًا يضيّع الطبعة كلها.
profiles/offset.pdfl:
profile "offset-magazine" {
const TAC_LIMIT = 300% // حدّ الحبر على الورق المطلي const BLEED = 3mm // مقتضى التركيب const MIN_DPI = 300
check "Ink coverage" tags: ["prepress"] { // الـ TAC الدقيق يقرأ الألوان المعلَنة في الملف؛ أما التقدير // بالتصيير فيقلّل من الأسود الغني ويفوّت التجاوزات doc.pages.each { |page| tac = prepress::calculate_exact_tac(page.number) assert tac <= TAC_LIMIT, "page #{page.number}: #{tac}% ink (limit #{TAC_LIMIT}%)" } }
check "Colors" tags: ["prepress"] { assert prepress::detect_color_mode() != "RGB", "document is in RGB — convert to CMYK"
spots = prepress::detect_spot_colors() assert spots.length == 0, "unquoted special ink: #{spots.join(", ")}"
assert !prepress::detect_rich_black(), "rich black detected — use 0/0/0/100 for text" }
check "Fonts" tags: ["fonts"] { loose = prepress::detect_text_substitution() assert loose.length == 0, "fonts not embedded (text will change at the RIP): #{loose.join(", ")}" assert prepress::validate_font_size(6), "there is text below 6 pt — illegible once printed" }
check "Strokes" tags: ["prepress"] { assert !prepress::detect_hairlines(0.25), "strokes below 0.25 pt disappear in print" assert !prepress::detect_hairlines_exact(), "there is a stroke with 0 width — set a real thickness" }
check "Images" tags: ["images"] { doc.images.each { |img| assert img.dpi >= MIN_DPI, "image on page #{img.page_number}: #{round(img.dpi)} DPI (minimum #{MIN_DPI})" assert img.color_space != "DeviceRGB", "RGB image on page #{img.page_number}" } }
check "Geometry" tags: ["prepress"] { assert prepress::validate_trim_box(), "no TrimBox — imposition cannot know where to trim" assert prepress::validate_bleed_box(), "no BleedBox — no bleed is defined" assert prepress::check_page_geometry(BLEED), "bleed smaller than 3 mm on some page" }}عند الاستقبال:
# تقرير HTML يُعاد إلى العميلpdfl run profiles/offset.pdfl client.pdf --output html --output-file report.htmlكمجلد مراقَب: يضع المشغّل الملف فيظهر التقرير بجواره.
pdfl watch inbox/ --script profiles/offset.pdfl \ --output-dir reports/ --report html12.2 دار نشر قانونية: تدقيق عقد قبل النشر
Section titled “12.2 دار نشر قانونية: تدقيق عقد قبل النشر”المشكلة: يجب أن تحمل العقود والوثائق البنود الإلزامية، وألا يبقى فيها نص مسودّة، وألا تكشف بيانات شخصية، وأن يظل نصها قابلًا للبحث.
profiles/legal.pdfl:
profile "standard-contract" {
check "Mandatory clauses" tags: ["legal"] { // مسرد تديره الإدارة القانونية missing = data::validate_against_reference("terms/clauses.txt") assert missing.length == 0, "missing clauses: #{missing.join("; ")}" }
check "No drafts" tags: ["legal"] { assert text::forbid_text("DRAFT"), "document marked as draft" assert text::forbid_text("lorem ipsum"), "placeholder text present" assert text::forbid_match("X{3,}"), "unfilled fields (XXX)" }
check "Privacy" tags: ["compliance"] { // أرقام الضريبة لا تُحتسب إلا بخانة تحقق صحيحة، // فأرقام الأمثلة لا تثير إنذارًا كاذبًا found = text::detect_personal_data() assert found.length == 0, "personal data in the document: #{found.join("; ")}" }
check "Numbering and initials" tags: ["legal"] { doc.pages.each { |page| footer = region(0, 0, page.width, 60, "footer") content = text::extract_from_region(page.number, footer).trim() assert content != "", "page #{page.number} has no numbering/initials in the footer" } }
check "Searchable text" tags: ["accessibility"] { assert !text::detect_rasterized_text(), "there are scanned pages — text cannot be searched or read by screen readers" }}12.3 مختبر أدوية: نشرة برمز الدفعة
Section titled “12.3 مختبر أدوية: نشرة برمز الدفعة”المشكلة: يجب أن تحمل النشرة النصوص التي تطلبها الجهة التنظيمية، وأن يشير الباركود إلى المنتج الصحيح. وتبديل الرموز بين المنتجات أغلى أخطاء هذا القطاع.
profiles/insert.pdfl:
profile "regulated-insert" {
check "Mandatory texts" tags: ["regulatory"] { missing = data::validate_against_reference("databases/regulatory_texts.txt") assert missing.length == 0, "mandatory texts missing: #{missing.join("; ")}" }
check "Legibility" tags: ["regulatory"] { assert prepress::validate_font_size(6), "there is text below 6 pt" }
check "Barcode" tags: ["codes", "critical"] { assert codes::detect_barcodes(), "insert has no barcode"
code = codes::decode_barcode(1) assert codes::validate_barcode_checksum(1), "invalid check digit: #{code}"
// هذا الفحص يلتقط أغلى خطأ: // رمز منتج مع نص منتج آخر assert codes::compare_barcode_with_text(), "the code number does not appear in the insert text" }
check "Approved product" tags: ["data", "critical"] { code = codes::decode_barcode(1) product = data::query_gtin(code) assert product, "GTIN #{code} is not in the product database"
name = product.get(2) assert text::require_text(name), "the name '#{name}' does not appear on the insert" print("product verified:", name) }
check "Code position" tags: ["layout"] { area = region(400, 20, 180, 90, "barcode area") assert codes::validate_barcode_position(area), "code outside the reserved area — risk of being trimmed off" }}PDFL_DATA_DIR=./databases pdfl run profiles/insert.pdfl insert_v3.pdf12.4 الموافقة: المقارنة بالنسخة المعتمدة
Section titled “12.4 الموافقة: المقارنة بالنسخة المعتمدة”المشكلة: اعتمد العميل النسخة الأولى. ثم تصل الثانية بقول «غيّرنا كلمة واحدة». وتصديق ذلك مكلف.
# HTML يُظهر ما تغيّر فعلًاpdfl compare approved/catalogue_v1.pdf received/catalogue_v2.pdf \ --normalize --ignore-dates \ --output html --output-file differences.html
echo "exit: $?" # 0 متطابقان · 1 بيانات وصفية فقط · 2 تغيّر المحتوىوللتحقق من الشكل أيضًا لا من النص وحده:
profile "visual-fidelity" {
const APPROVED = "approved/catalogue_v1.pdf"
check "Pages visually identical" tags: ["approval"] { doc.pages.each { |page| ssim = visual::measure_ssim(page.number, APPROVED) assert ssim > 0.99, "page #{page.number} changed visually (SSIM #{ssim}, #{visual::pixel_diff(page.number, APPROVED)}% of pixels)" } }
check "No image replaced" tags: ["approval"] { doc.pages.each { |page| assert !visual::detect_image_replacement(page.number, APPROVED), "page #{page.number}: image swapped compared to the approved version" } }}ومجلد للاطلاع، لمن عليه اعتماد إعادة الطبع:
pdfl pixelcompare approved/catalogue_v1.pdf received/catalogue_v2.pdf \ --max-diff 0.05 --viewer proof/ --output-file pixels.json
zip -r proof.zip proof/ # ملف index.html وثلاث صور PNG لكل صفحة، لا غيرلا يحتاج المجلد إلى خادم ولا إلى شبكة: فمن يفتح index.html يرى الأصل والملف
الجديد وكليهما معًا وفوقهما الفروق ملوّنة، وهو يفتح على الصفحات المختلفة — ففي
كتالوج تغيّرت فيه صفحتان من تسعين، هاتان الصفحتان هما المقصود. وتحريك الفأرة
يمرّر الملف الجديد فوق القديم، وتُكبّر العجلة الألواح الثلاثة معًا ويحرّكها
السحب، فيُحسم خيط رفيع عند ثمانية أضعاف دون مغادرة الصفحة.
12.5 CI/CD: التدقيق بالجملة
Section titled “12.5 CI/CD: التدقيق بالجملة”المشكلة: كل ملف يدخل المستودع يجب أن يجتاز تدقيق ما قبل الطبع، دون أن يشغّله أحد يدويًا.
.github/workflows/preflight.yml:
name: PDF preflight
on: [push, pull_request]
jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Install pdfl env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # رمز Actions التلقائي، دون إعداد run: | gh release download --repo kotarokikuchi/pdflang \ --pattern 'pdfl_*_amd64.deb' sudo dpkg -i pdfl_*_amd64.deb
- name: Check the scripts themselves run: | for f in profiles/*.pdfl; do pdfl lint "$f" pdfl fmt "$f" --check done
- name: Preflight every PDF run: | pdfl watch files/ --script profiles/offset.pdfl \ --output-dir reports/ --once
- name: Publish the reports if: always() uses: actions/upload-artifact@v4 with: name: reports path: reports/
# الأثر يحتاج من يذهب ليفتحه، أما التعليق على طلب السحب فلا. - name: الملاحظات على طلب السحب run: | pdfl run profiles/offset.pdfl files/cover.pdf \ --output sarif --output-file pdfl.sarif continue-on-error: true # الرمز 2 يعني ملفًا مرفوضًا، والرفع يجب أن يتم - uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: pdfl.sarif12.6 تهيئة ملف ناشر للمطبعة
Section titled “12.6 تهيئة ملف ناشر للمطبعة”check "Preconditions" { require doc.page_count > 0 assert !struct::check_encryption(), "file is encrypted — ask for the open version"}
// أُطر الإنتاج التي لم يضبطها الناشرfix::set_trim_box(8.5, 8.5, 586.5, 833.5)fix::set_bleed_box(0, 0, 595, 842)
// التنظيفfix::remove_annotations() // تعليقات المراجعةfix::remove_attachments() // مرفقات لا تزيد الملف إلا ثقلًاfix::flatten_layers() // يمنع تشغيل طبقة عن طريق الخطأfix::remove_unused_resources()pdfl fix publisher.pdf profiles/prepare.pdfl --output print.pdf --dry-run # للتحققpdfl fix publisher.pdf profiles/prepare.pdfl --output print.pdf # للتطبيقpdfl run profiles/offset.pdfl print.pdf # للتدقيق12.7 توزيع ملف تعريفي على الفريق
Section titled “12.7 توزيع ملف تعريفي على الفريق”المشكلة: خمسة أجهزة يجب أن تستعمل الملف التعريفي نفسه والبيانات نفسها تمامًا، دون أن يعبث بها أحد.
# على الجهاز الذي يصون الملف التعريفيpdfl pack profiles/ --name print-profile --version 1.2.0
# على أجهزة الإنتاجpdfl add print-profile.pdflpkg# يثبّت في ./pdfl_profiles/[email protected]/ مع التحقق من كل بصمة
وإن عُدِّلت الحزمة في الطريق رفض add التثبيت.
12.8 استقصاء ملف فيه مشكلة
Section titled “12.8 استقصاء ملف فيه مشكلة”خطوات عملية حين لا تعرف من أين أتت المشكلة:
# 1. صورة عامة في ثوانٍpdfl inspect suspect.pdf
# 2. نص برمجي للاستقصاء، بـ print() فقطcat > investigate.pdfl <<'EOF'check "X-ray" { print("exact TAC:", prepress::calculate_exact_tac(), "%") print("estimated TAC:", prepress::calculate_tac(), "%") print("spots:", prepress::detect_spot_colors().join(", ")) print("rich black?", prepress::detect_rich_black()) print("overprint ok?", prepress::validate_overprint_settings()) print("loose fonts:", prepress::detect_text_substitution().join(", "))
doc.images.each { |img| print("image page", img.page_number, ":", img.width, "x", img.height, "@", round(img.dpi), "DPI", img.color_space) }}EOF
pdfl run investigate.pdfl suspect.pdf > /dev/null# print() يكتب على مخرج الأخطاء، فيمكن رمي التقرير# والاكتفاء بنتائج الاستقصاء12.9 اختبار ملف تعريفي قبل أن يكلّف أحدهم طبعة كاملة
Section titled “12.9 اختبار ملف تعريفي قبل أن يكلّف أحدهم طبعة كاملة”المشكلة: الملف التعريفي شيفرة، وأحدهم يعدّله. يتزحزح حدٌّ، ويتغيّر اسم فحص، ولا ينتبه أحد إلى أن ملفًا كان يجب أن يُرفض قد ذهب إلى اللوح.
احتفظ بالملفات التي علّمتك القاعدة، وثبّت ما يقوله الملف التعريفي عنها:
profiles/print-shop/ offset.pdfl tests/ approved.pdf # نجح، وعليه أن يظل ناجحًا approved.expected.json ink_324.pdf # الملف الذي كلّف إعادة طبع في مارس ink_324.expected.json fonts_not_embedded.pdf fonts_not_embedded.expected.json# مرة واحدة، حين تكون الحالات هي التي تريدهاpdfl test profiles/print-shop/offset.pdfl --update
# وبعدها — في التكامل المستمر، وقبل كل تعديل على الملف التعريفيpdfl test profiles/print-shop/offset.pdfl --jobs 0والملف المرفوض حالةٌ لا تقلّ جودة عن الملف المقبول: فالمسجَّل هو التقرير كاملًا، ولذلك يُخفق الاختبار بالقوة نفسها إن كفّ الملف التعريفي عن الاعتراض على 324% من الحبر، أو إن بدأ يعترض على ملف سليم.
- name: هل ما زالت الملفات التعريفية تجد ما كانت تجده run: pdfl test profiles/print-shop/offset.pdfl --jobs 0اقرأ الإخفاق قبل أن تعيد التسجيل. فـ--update هو اللحظة التي تقرّر فيها أن
السلوك الجديد صحيح — ولا لحظة غيرها.