💡 Ví dụ hoạt động đầy đủ có sẵn trên GitHub:
sign-pdf-in-linux-container-fonts-dotnet

Cách Cũ Đầy Khó Khăn

Dịch vụ ký hóa đơn. Nó chạy trên một laptop có ba trăm phông chữ được cài đặt, đã qua kiểm duyệt, và được đóng gói thành container vào thứ Sáu. Vào thứ Hai, công việc đầu tiên trong cụm trả về mã lỗi khác 0 với Sign document error: Font Arial was not found, và ai đó dành buổi sáng để đọc các stack trace trước khi ai nghĩ hỏi hình ảnh mcr.microsoft.com/dotnet/runtime:8.0 thực sự chứa những phông chữ nào.

Câu trả lời là không có. Không có tệp phông chữ nào, được đo trên hình ảnh mà mẫu của bài viết này chạy.

Việc biết các runtime khác so sánh thế nào là hữu ích, vì lỗi xuất hiện khác nhau trên mỗi runtime. eclipse-temurin:17-jre bao gồm 8 tệp DejaVu và node:18-bookworm bao gồm 6, cả hai đều cho AWT, vì vậy các image JVM và Node ký văn bản Latin một cách yên tĩnh và chỉ gặp lỗi khi một chuỗi tiếng Nhật hoặc tiếng Trung xuất hiện. python:3.11-slim không có phông chữ nào, giống như image .NET runtime, vì vậy nó thất bại ngay ở chữ ký đầu tiên. Không có ai nhận được CJK miễn phí trên bất kỳ image nào.

Việc cung cấp phông chữ cho container là bước cho phép ký văn bản hoạt động trong một image Linux với GroupDocs.Signature cho .NET. Điều này quan trọng vì thư viện không tự động thay thế một họ phông chữ thiếu: việc đặt tên một phông chữ chưa được cài đặt sẽ gây lỗi và không ghi tài liệu nào. Bài viết này đặt image không có phông chữ cạnh image đã được sửa, cho thấy những gì đã thay đổi, và đề cập đến quá trình giải quyết tại thời gian chạy giúp cùng một đoạn mã hoạt động trên máy của nhà phát triển.

Có Một Cách Tốt Hơn

Hai điều phải đúng. Image cần ít nhất một phông chữ, và mã cần ngừng giả định phông chữ nào.

Đầu tiên là một lớp Dockerfile. Thứ hai là một bước giải quyết: thay vì hard-coding Arial, hỏi thư viện xem trong số các họ phông chữ ứng cử nào thực sự có thể sử dụng, và giữ lại cái đầu tiên hoạt động. Kết quả chạy không thay đổi trong một container slim, trên Windows, và trong CI, vì nó không bao giờ khẳng định bất kỳ điều gì về môi trường mà nó chưa kiểm tra.

Một điều không hoạt động, và đáng nói thẳng vì đó là điều đầu tiên mọi người thử: để phông chữ không được đặt. Khi không có SignatureFont, GroupDocs.Signature sẽ yêu cầu phông chữ mặc định của nó, Times New Roman, mà image không có phông chữ cũng không có. Lệnh gọi sẽ thất bại giống hệt.

Cách Mới: Hai Ảnh, Một Sự Khác Biệt

Bước 1 - Xem image có gì

Trước khi ký bất kỳ thứ gì, liệt kê các tệp phông chữ. Số lượng này biến một ngoại lệ mơ hồ thành một chẩn đoán, vì không có phông chữ và tên họ sai cần các cách khắc phục khác nhau:

string[] roots =
{
    "/usr/share/fonts",
    "/usr/local/share/fonts",
    Path.Combine(home, ".fonts"),
    Path.Combine(home, ".local/share/fonts"),
    Environment.GetFolderPath(Environment.SpecialFolder.Fonts),
    "/System/Library/Fonts",
    "/Library/Fonts",
};

Lưu ý những gì thiếu: System.Drawing. System.Drawing.Common chỉ hỗ trợ Windows từ .NET 7 trở đi và sẽ ném lỗi trên Linux, vì vậy mã liên quan đến phông chữ được xây dựng trên nó sẽ thất bại trong container vì một lý do không liên quan.

Bước 2 - Thêm lớp phông chữ

Bốn gói, một RUN, và lỗi sẽ biến mất:

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 là trình giải quyết và cung cấp cho bạn fc-list để gỡ lỗi. fonts-dejavu-core là bộ tối thiểu cho Latin, Greek và Cyrillic. fonts-liberation cung cấp các phông chữ thay thế tương thích về metric cho Arial, Times New Roman và Courier New, đó là những phông chữ mà tài liệu được tạo trên Windows thực sự tham chiếu. fonts-noto-cjk bao phủ tiếng Trung, Nhật và Hàn.

Bước 3 - Giải quyết một họ thay vì đặt tên trực tiếp

Cách di động để chọn phông chữ là thử tạo một chữ ký tạm thời cho mỗi ứng cử và giữ lại cái đầu tiên không gây lỗi:

foreach (string candidate in candidates)
{
    if (TryFamily(sourcePath, candidate).Ok)
    {
        return candidate;
    }
}

return null;

Phát hiện dựa trên tên tệp là cách tắt ngắn hấp dẫn nhưng sai lầm. Gói fonts-noto-cjk của Debian cài đặt NotoSansCJK-Regular.ttc, mà tên họ của nó là Noto Sans CJK JP. So khớp tên tệp sẽ bỏ qua các phông chữ đã có và đưa ra các họ sẽ không được giải quyết khi truyền vào SignatureFont.

Bước 4 - Ký những gì đã được giải quyết, xác minh những gì bạn đã ký

Một họ Latin đã được giải quyết là bắt buộc; một họ CJK đã được giải quyết là tùy chọn và nếu không có thì bỏ qua, không gây crash:

var options = new List<SignOptions>
{
    BuildTextOptions(LatinText, latinFamily, top: 50),
};

if (cjkFamily is not null)
{
    options.Add(BuildTextOptions(CjkText, cjkFamily, top: 120));
}

SignResult result = signature.Sign(outputPath, options);

Sau đó đọc lại tệp, vì CJK không có phông chữ CJK có thể hiển thị dưới dạng các hộp trống mà không gây ra bất kỳ lỗi nào:

var options = new TextSearchOptions { AllPages = true };
List<TextSignature> found = signature.Search<TextSignature>(options);

So Sánh: Trước và Sau

Dockerfile.nofonts Dockerfile
Các tệp phông chữ trong image 0 DejaVu, Liberation, Noto CJK
Chữ ký văn bản Latin thất bại, thoát 3 được ghi và khôi phục khi đọc lại
Chữ ký văn bản CJK thất bại được ghi và khôi phục
Lỗi xuất hiện Font <name> was not found không có
Sự khác biệt về mã không có - cùng binary không có - cùng binary

Hàng cuối cùng là điểm mấu chốt. Không có gì trong ứng dụng thay đổi giữa hai lần chạy. Kho lưu trữ mẫu cung cấp cả hai tệp nên việc so sánh yêu cầu hai lệnh docker build thay vì dựa vào suy đoán. Hãy giữ lại phiên bản không có phông chữ trong kho lưu trữ sau này: đó là cách nhanh nhất để tái tạo lỗi khi ai đó thay đổi image cơ sở sau sáu tháng và các chữ ký âm thầm ngừng xuất hiện.

Tại sao không cài đặt mọi phông chữ?

Bởi vì kích thước image là một ràng buộc thực tế và bốn gói ở trên đã bao phủ hầu hết các script mà các tài liệu sử dụng. fonts-dejavu-core một mình đã đủ cho việc ký Latin, Greek và Cyrillic; Liberation quan trọng khi tài liệu tham chiếu các họ Windows theo tên; Noto CJK là gói thực sự lớn và chỉ tốn tài nguyên nếu bạn ký văn bản Đông Á. Cài đặt những gì tài liệu của bạn cần, sau đó xác minh bằng cách đọc lại.

Ví Dụ Thực Tế: Worker Ký Hợp Đồng Hàng Loạt

Một worker trong hàng đợi ký vài nghìn PDF mỗi đêm. Với việc giải quyết khi khởi động, nó ghi một dòng tên các họ sẽ sử dụng, và nếu không có gì được giải quyết nó sẽ thoát trước khi chạm vào hàng đợi thay vì thất bại từng tin nhắn. Kiểm tra khởi động này là yếu tố biến vấn đề phông chữ từ một loạt các công việc thất bại thành một container từ chối khởi động với một lý do ngắn gọn.

Chi phí dò tìm đủ nhỏ để bỏ qua khi khởi động và quá lớn để lặp lại cho mỗi tài liệu. Mỗi lần dò là một chữ ký thực tế được ghi vào tệp tạm, vì vậy danh sách Latin tốn tới bốn lần và danh sách CJK tốn tới tám lần, tất cả trên một PDF một trang. Giải quyết một lần, lưu vào cache hai tên họ, và đường đi cho mỗi tài liệu vẫn như trước: xây dựng các tùy chọn, gọi Sign, đọc số lượng kết quả.

Tôi đã mất một buổi chiều vì phiên bản đoán này. Nó quét thư mục phông chữ, tìm thấy NotoSansCJK-Regular.ttc, báo cáo CJK có sẵn, rồi sau đó thất bại trên mọi tên họ tôi suy ra từ tên tệp đó. Dò tìm bằng một chữ ký thực tế vừa đơn giản hơn vừa đúng.

Còn Gì Khác Gây Rắc Rối Trong Container?

Một điều nữa, không liên quan đến phông chữ: InvariantGlobalization=true. Đây là lời khuyên tiêu chuẩn để loại bỏ ICU khỏi một image .NET, và với GroupDocs.Signature nó khiến lệnh new Signature(...) đầu tiên ném ra CultureNotFoundException: ... en-US is an invalid culture identifier, vì SignatureSettings tạo một CultureInfo("en-US"). Giữ globalization bật và để ICU ở trong image. Trang system requirements là nơi kiểm tra hỗ trợ nền tảng trước khi quyết định image cơ sở.

Kết Luận

Một dịch vụ ký hoạt động tốt trên máy cục bộ nhưng thất bại trong Docker hầu như luôn do thiếu phông chữ, và cách khắc phục là thêm một lớp bốn gói cùng với mã giải quyết một họ thay vì giả định. Xây dựng cả hai image từ mẫu, chạy chúng cạnh nhau, và đọc các dòng [fonts]: toàn bộ lập luận nằm trong một so sánh duy nhất.

Tài Nguyên Bổ Sung