💡 דוגמה מלאה עובדת זמינה ב‑GitHub:
python-linux-container-pdf-signing

מבוא

הסקריפט פועל מקומית. אתה ממכלת אותו על python:3.11-slim, והוא נכשל ב‑import groupdocs.signature. אתה מתקן זאת, והוא נכשל שוב בחתימה הראשונה. אף אחת מהשגיאות לא מציינת מה חסר בפועל.

חתימה במכולה עם Python היא זרימת עבודה של GroupDocs.Signature שדורשת שני שכבות פרוביזיה במקום אחת: ספריות זמן הריצה של .NET שעליהן נבנית הקשירה, והגופנים שכל חתימת טקסט חייבת להשתמש בהם. במדריך זה אנו בונים את שני החלקים, ולאחר מכן את הסקריפט שמזהה משפחת גופנים בזמן ריצה במקום לקודד אחת קבועה, כך שהקוד זהה פועל במכולה ובמחשב שבו נכתב.

למה שני השכבות חשובות

GroupDocs.Signature עבור Python היא קשירה ל‑.NET, ולכן חייבים להיות מותקנים libicu וספרייה תואמת ל‑OpenSSL 1.1 לפני שכל ייבוא מצליח. זו השכבה הראשונה, והיא מתועדת היטב ב‑Running in Docker.

הסיבה ששתי השכבות מתערבבות היא ששתי השגיאות מתרחשות בזמן קרוב לייבוא ושאף אחת מהן לא מציינת את הסיבה. חוסר libssl1.1 נותן שגיאת טוען על אובייקט משותף; חוסר גופן נותן שגיאת חתימה עטופה ב‑exception של פרוקסי. אף אחת לא אומרת “תמונת הבסיס שלך קטנה מדי”, שזה בעצם המשמעות של שתיהן.

השכבה השנייה היא גופנים, והיא מפתיעה אנשים. python:3.11-slim אינו מכיל קבצי גופנים כלל. GroupDocs.Signature אינו מחליף משפחה חסרה – ציון משפחה שלא מותקנת גורם לשגיאה, ואף דבר לא נכתב – וניקוי הגופן אינו פתרון, מכיוון שהספרייה מבקשת את ברירת המחדל שלה ונכשלת באותו האופן. במכונה ללא גופנים, חתימת טקסט פשוט בלתי אפשרית.

דרישות מוקדמות

Python 3.11 (הגלגלים מוגבלים מתחת ל‑CPython 3.14) ו‑groupdocs-signature-net==26.1. Docker אם ברצונך לראות את שתי הכישלונות במכוון, מה שלוקח כ‑עשר דקות.

התקנה

pip install groupdocs-signature-net==26.1

שלב 1 - בניית שכבת .NET

libssl1.1 אינו קיים ב‑bookworm, ולכן הוא מגיע מצילום Debian קבוע:

ENV SNAPSHOT_DATE=20220328T000000Z
RUN echo "deb [trusted=yes] http://snapshot.debian.org/archive/debian/${SNAPSHOT_DATE} bullseye main" \
        > /etc/apt/sources.list.d/debian-archive.list \
    && apt-get -o Acquire::Check-Valid-Until=false update \
    && apt-get install -y --no-install-recommends \
        libicu67 \
        libssl1.1 \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

נקודות מפתח:

  • שכבה זו מאפשרת רק את הייבוא; היא אינה מתייחסת לגופנים.
  • קיבוע תאריך הצילום משאיר את הבנייה ברת-חזרה כאשר הארכיון מתעדכן.

שלב 2 - בניית שכבת הפונטים

ארבעה חבילות, נשמרות כשכבה נפרדת כדי שניתן יהיה להסירן ולשחזר את הכשל:

RUN apt-get update && apt-get install -y --no-install-recommends \
        fontconfig \
        fonts-dejavu-core \
        fonts-liberation \
        fonts-noto-cjk \
    && fc-cache -f \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

fontconfig הוא הפותר ומספק fc-list. fonts-dejavu-core הוא המינימום עבור Latin, Greek ו‑Cyrillic. fonts-liberation מכסה מסמכים שמפנים ל‑Arial או Times New Roman בשם. fonts-noto-cjk מכסה סינית, יפנית וקוריאנית.

שלב 3 - שאל את הספרייה איזו משפחת גופנים היא יכולה להשתמש

סריקה של /usr/share/fonts לפי שם קובץ נראית שווה ערך אך איננה: fonts-noto-cjk מתקין NotoSansCJK-Regular.ttc, שהשם המשפחתי שלו הוא Noto Sans CJK JP. הפתרון הנייד הוא לבצע “פרוב” – חתימה אמיתית לקובץ זמני – ולהמיר את הכשל לערך:

with signature.Signature(source_path) as sign:
    options = TextSignOptions()
    options.text = "probe"
    options.left = 10
    options.top = 10
    options.width = 60
    options.height = 20
    font = SignatureFont()
    font.family_name = family_name
    font.size = 10.0
    options.font = font
    sign.sign(scratch, [options])
return None

הסתכלו היטב על font.size = 10.0. הקשירה ממפה את הגודל ל‑float של .NET ודוחה int עם ההודעה numeric argument expected, got 'int'. מכיוון שזה קורה בתוך הפרוב, כל משפחה מועמדת נכשלת והפלט נראה בדיוק כמו תמונה ללא גופנים. הוספתי שלוש חבילות גופנים לתמונה שכבר הכילה את כולן לפני שזיהיתי את ה‑literal.

הפתרון הוא לולאה:

for candidate in candidates:
    if try_family(source_path, candidate) is None:
        return candidate
return None

שלב 4 - חתום על מה שנפתר, אמת מה שחתמת

המשפחה ה‑Latin נדרשת, המשפחה CJK אופציונלית:

with signature.Signature(source_path) as sign:
    options = [build_text_options(LATIN_TEXT, latin_family, 50)]
    if cjk_family:
        options.append(build_text_options(CJK_TEXT, cjk_family, 120))
    result = sign.sign(output_path, options)
    return len(result.succeeded)

לאחר מכן מאמתים, מכיוון שה‑CJK מוצג כתיבות ריקות ולא זורק שגיאה:

options = TextVerifyOptions()
options.text = expected_text
options.match_type = gsd.TextMatchType.CONTAINS
options.all_pages = True
result = sign.verify(options)

CONTAINS נבחר בכוונה: במצב הערכה הספרייה מוסיפה טקסט ניסוי לדף, והתאמה מדויקת הייתה מדווחת על מסמך תקין ככושל.

מה לגבי המסמכים שאומרים של-Python יש תמיכה מוגבלת בלינוקס?

דף Running in Docker מציין חבילות Python מוכנות ללינוקס ומשאיר את Signature מחוץ לרשימה. ב‑groupdocs-signature-net==26.1 הדוגמה הזו חתם ואימת בתוך python:3.11-slim, כולל CJK, עם שתי השכבות מותקנות. יש להתייחס לרשימה כמיושנת ולא כמכשול, ולאמת עם הגרסה שלכם לפני שמתחייבים לפריסה.

יישומים בעולם האמיתי

שירות חשבוניות שמוסיף קו אישור ל‑PDFs שנוצרו צריך בדיוק את זה: שכבת .NET, גופן Latin אחד, ובדיקה בזמן אתחול. הבדיקה היא מה שהופך פריסה רעה למכולה שסורקת את עצמה מלהתחיל, במקום תור של חשבוניות שמפסידות בשקט אחת אחרי השנייה. פורטל מסמכים שמקבל שמות לקוחות בכל כתב צריך גם את חבילת CJK, בנוסף שלב האימות, מכיוון שזהו הדבר היחיד שמפריד בין תיבה ריקה לשם חתום.

היכן בדיקת הפתרון שייכת

הכניסו אותה לכל מה שרץ פעם אחת לכל תהליך: קריאה ברמת מודול, מטפל lifespan של FastAPI, AppConfig.ready של Django, או השורות הראשונות של קובץ ה‑worker הראשי. שני ערכים יוצאים ממנה – משפחת ה‑Latin וה‑CJK – ושניהם צריכים להופיע ביומן האתחול לצד ספירת הגופנים.

המיקום הזה חוסך יותר מזמן הפרוב בלבד. הוא מעביר את הכשל מטיפול בבקשה, שבו הוא בעיית לקוח אחת ו‑stack trace שאף אחד לא קורא, לאתחול, שבו הוא פריסה שלא עלתה ומישהו כבר צופה. מכולה שיוצאת עם ההודעה „no usable font family, install fonts-dejavu-core“ אינה דורשת שום ניפוי באגים.

פתרון בעיות נפוצות

import groupdocs.signature נכשל
השכבה של .NET חסרה או שמאגר הצילומים היה בלתי נגיש בזמן הבנייה. זו שכבה ראשונה ואין לה קשר לגופנים. בדקו את יומן הבנייה עבור שלב ה‑apt לפני שמגעים לקוד החתימה, מכיוון שכשל בצילום אינו מונע מהתמונה להיבנות.

כל גופן מועמד נכשל, אך fc-list מציג גופנים
בדקו ש‑font.size הוא int לפני שמוסיפים חבילות נוספות.

החתימה קיימת אך הטקסט CJK מוצג כתיבות
fonts-noto-cjk חסרה. החתימה נכתבה עם משפחה שאין לה גליפים לנקודות הקוד האלו, ולכן שלב האימות קיים: הוא נכשל בדיוק במצב זה, שבו החתימה דיווחה הצלחה.

מה שתי התמונות מדפיסות בפועל

הפעילו את שתיהן וקראו את ארבע השורות הראשונות. תמונת ללא גופנים מדווחת font files on disk: 0, שתי שורות הפתרון כ‑(none), השגיאה המכוונת של חוסר גופן, ואז יוצאת עם קוד 3 לאחר שהדפיסו את התיקון המינימלי. תמונת הפרוביזיה מדווחת ספירת גופנים שונה מאפס, DejaVu Sans ל‑Latin ו‑Noto Sans CJK JP ל‑CJK, שני חתימות יושמו, ושני הטקסטים אומתו.

זוג הפלטים הזה הוא הארטיפקט שכדאי לשמור. הדביקו אותו בהערות הפריסה שלכם והאדם הבא שישנה את תמונת הבסיס יקבל רפרנס למה שמראה מכולה בריאה, ללא צורך להבין את fontconfig בכלל.

סיכום

שתי שכבות ו‑פרוב אחד. התקינו את תלויות .NET, התקינו לפחות fontconfig ו‑DejaVu, מצאו את המשפחה על‑ידי שאילה במקום ניחוש, ואמתו את הפלט לפני שמכריזים על סיום העבודה. אין כאן הרבה קוד, והכול הוא דבר שנראה ברור במבט לאחור ונעלם ב‑traceback. מאגר הדוגמה מספק שני קבצי Dockerfile, כך שההבדל בין תמונה עובדת לתמונה שבורה הוא בנייה אחת בלבד.

משאבים נוספים