تخطَّ إلى المحتوى

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"
}
}

عند الاستقبال:

Terminal window
# تقرير HTML يُعاد إلى العميل
pdfl run profiles/offset.pdfl client.pdf --output html --output-file report.html

كمجلد مراقَب: يضع المشغّل الملف فيظهر التقرير بجواره.

Terminal window
pdfl watch inbox/ --script profiles/offset.pdfl \
--output-dir reports/ --report html

12.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"
}
}
Terminal window
PDFL_DATA_DIR=./databases pdfl run profiles/insert.pdfl insert_v3.pdf

12.4 الموافقة: المقارنة بالنسخة المعتمدة

Section titled “12.4 الموافقة: المقارنة بالنسخة المعتمدة”

المشكلة: اعتمد العميل النسخة الأولى. ثم تصل الثانية بقول «غيّرنا كلمة واحدة». وتصديق ذلك مكلف.

Terminal window
# 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 تغيّر المحتوى

وللتحقق من الشكل أيضًا لا من النص وحده:

profiles/fidelity.pdfl
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"
}
}
}

ومجلد للاطلاع، لمن عليه اعتماد إعادة الطبع:

Terminal window
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 يرى الأصل والملف الجديد وكليهما معًا وفوقهما الفروق ملوّنة، وهو يفتح على الصفحات المختلفة — ففي كتالوج تغيّرت فيه صفحتان من تسعين، هاتان الصفحتان هما المقصود. وتحريك الفأرة يمرّر الملف الجديد فوق القديم، وتُكبّر العجلة الألواح الثلاثة معًا ويحرّكها السحب، فيُحسم خيط رفيع عند ثمانية أضعاف دون مغادرة الصفحة.


المشكلة: كل ملف يدخل المستودع يجب أن يجتاز تدقيق ما قبل الطبع، دون أن يشغّله أحد يدويًا.

.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.sarif

12.6 تهيئة ملف ناشر للمطبعة

Section titled “12.6 تهيئة ملف ناشر للمطبعة”
profiles/prepare.pdfl
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()
Terminal window
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 توزيع ملف تعريفي على الفريق”

المشكلة: خمسة أجهزة يجب أن تستعمل الملف التعريفي نفسه والبيانات نفسها تمامًا، دون أن يعبث بها أحد.

Terminal window
# على الجهاز الذي يصون الملف التعريفي
pdfl pack profiles/ --name print-profile --version 1.2.0
# على أجهزة الإنتاج
pdfl add print-profile.pdflpkg
# يثبّت في ./pdfl_profiles/[email protected]/ مع التحقق من كل بصمة
pdfl run pdfl_profiles/[email protected]/offset.pdfl file.pdf

وإن عُدِّلت الحزمة في الطريق رفض add التثبيت.


12.8 استقصاء ملف فيه مشكلة

Section titled “12.8 استقصاء ملف فيه مشكلة”

خطوات عملية حين لا تعرف من أين أتت المشكلة:

Terminal window
# 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
Terminal window
# مرة واحدة، حين تكون الحالات هي التي تريدها
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 هو اللحظة التي تقرّر فيها أن السلوك الجديد صحيح — ولا لحظة غيرها.



← سطر الأوامر · الفهرس · التالي: سجل التغييرات →

DigitalOceanشكرًا لـ DigitalOcean على استضافة هذا الموقع.