💡 Contoh kerja penuh tersedia di GitHub:
nodejs-docker-signing-with-fonts
Pendahuluan
Resolusi font adalah bagian dari penandatanganan kontainer yang menentukan apakah layanan Node Anda menghasilkan dokumen atau pengecualian. GroupDocs.Signature tidak menggantikan keluarga font yang hilang: jika nama satu font tidak ada dalam image, pemanggilan akan menghasilkan pengecualian, tidak menulis apa‑apa. Mengosongkan font bukan solusi alternatif, karena pustaka kemudian meminta default miliknya sendiri dan gagal dengan cara yang sama.
Ada tiga cara untuk menentukan keluarga font yang akan dipakai, dan hanya satu di antaranya yang bertahan di dalam kontainer. Artikel ini membandingkan ketiganya, lalu membahas penyediaan dan perilaku binding yang membentuk kode di sekitarnya, karena Node.js via Java memiliki lebih banyak hal tersebut dibandingkan platform lain tempat pustaka ini dijual.
Mengapa Ini Lebih Penting di Node.js
Paket ini adalah jembatan: node-java memuat JVM dalam proses. Jadi image penandatanganan Node memerlukan JDK, toolchain node-gyp untuk membangun jembatan, dan LD_LIBRARY_PATH yang mengarah ke libjvm.so, semua itu sebelum font menjadi relevan. node:18-bookworm kemudian menyertakan 6 berkas font DejaVu untuk AWT – cukup untuk Latin, tidak ada untuk CJK.
Kombinasi itu menghasilkan kegagalan yang tampak seperti bug aplikasi. Jalur JVM yang hilang, font yang hilang, dan ketidaksesuaian marshalling semuanya muncul sebagai Error running instance method, karena itulah yang dilaporkan node-java untuk apa pun yang dilempar di sisi Java.
Prasyarat
Node 18 – jembatan dibangun melawan NAN, yang tidak dapat dikompilasi melawan V8 di Node 20 atau 22 ('AccessorSignature' is not a member of 'v8'). JDK 8 hingga 17: pada JDK 25 lapisan imaging gagal dengan Cannot open an image. The image size can not be 0!.
Instalasi
npm install @groupdocs/groupdocs.signature
Di dalam image, instalasi tersebut memerlukan build-essential dan python3 yang tersedia, plus openjdk-17-jdk-headless serta jalur pemuat:
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
ENV PATH="${JAVA_HOME}/bin:${PATH}"
# node-java dlopens libjvm.so at run time; it is not on the default loader path.
ENV LD_LIBRARY_PATH="${JAVA_HOME}/lib/server:${LD_LIBRARY_PATH}"
Metode 1 – Hard‑code nama keluarga
Versi yang biasanya ditulis pertama kali: pilih Arial, kirimkan, lanjutkan. Itu bekerja di mesin pengembang dan gagal pada run pertama kontainer, karena image Debian tidak memasang Arial – mereka memasang Liberation Sans, yang kompatibel secara metrik dengan nama keluarga yang berbeda.
Tidak ada kode yang layak ditampilkan di sini, itulah intinya. Seluruh isi metode ini hanyalah literal string yang kebetulan benar di satu lingkungan.
Metode 2 – Deteksi font dari sistem berkas
Perbaikan alami: pindai direktori font, lihat apa yang ada, pilih sesuatu. Setengahnya memang berguna – inventaris memberi tahu Anda apakah image memiliki 0 font atau 6:
const roots = [
'/usr/share/fonts',
'/usr/local/share/fonts',
path.join(home, '.fonts'),
path.join(home, '.local', 'share', 'fonts'),
'/System/Library/Fonts',
'/Library/Fonts',
];
Setengah lainnya tidak berfungsi. Berkas font jarang membawa string keluarga yang harus diberikan pemanggil: fonts-noto-cjk pada Debian memasang NotoSansCJK-Regular.ttc, yang keluarganya adalah Noto Sans CJK JP. Menurunkan keluarga dari nama berkas itu memberi Anda NotoSansCJK-Regular, yang tidak mengarah ke apa‑apa. Deteksi nama berkas sekaligus melewatkan font yang ada dan melaporkan keluarga dengan yakin yang akan gagal.
Simpan inventaris sebagai diagnostik. Jangan gunakan untuk memilih. Hitungan menjawab apakah image pernah diprovisikan sama sekali, yang merupakan pertanyaan berbeda dan sama bergunanya.
Metode 3 – Tanyakan ke pustaka
Coba buat tanda tangan sementara untuk setiap keluarga kandidat dan simpan yang pertama tidak melempar pengecualian. Ini memerlukan satu penulisan PDF per kandidat dan satu‑satunya metode yang jawabannya otoritatif, karena itu panggilan yang sama akan dilakukan oleh tanda tangan sebenarnya.
for (const candidate of candidates) {
if (tryFamily(sourcePath, candidate) === null) {
return candidate;
}
}
return null;
Di Node, probe membutuhkan satu potongan tambahan. node-java mengubah setiap pengecualian Java menjadi Error running instance method, sehingga pesan sebenarnya harus dipulihkan dari jejak stack yang dibungkus:
const stack = err.stack || '';
const match = stack.match(/com\.groupdocs\.signature\.exception\.[^\n]*/);
return match ? match[0].trim() : (err.message || String(err));
Tanpa dua baris itu, kontainer tanpa font dan jalur JVM yang rusak menghasilkan log yang identik. Saya menghabiskan lebih lama daripada yang ingin saya akui membandingkan dua kontainer yang mencetak error yang sama karena alasan yang sepenuhnya berbeda sebelum menambahkan regex.
Biaya Probe
Keberatan terhadap probing adalah bahwa ia menulis berkas, dan memang begitu: satu PDF kecil per kandidat, dihapus segera. Daftar Latin dalam contoh memiliki empat entri dan daftar CJK memiliki delapan, jadi pada cold start paling banyak ditulis dua belas dokumen satu halaman ke direktori sementara sebelum layanan siap.
Itu adalah biaya startup, bukan biaya per‑permintaan, dan memberi baris log yang menyebutkan kedua keluarga yang ter‑resolusi. Dibandingkan dengan kontainer yang mulai bersih lalu gagal pada dokumen pelanggan pertama dengan error bridge, dua belas berkas sementara bukan trade‑off yang sulit.
Membandingkan Metode: Kapan Menggunakan Masing‑Masing
| Metode | Terbaik Untuk | Keunggulan Utama | Batasan |
|---|---|---|---|
| Hard‑coded family | lingkungan terkontrol tunggal | trivial, tanpa biaya startup | gagal pada image apa pun yang tidak memiliki keluarga itu persis |
| Deteksi nama berkas | mendiagnosa apa yang ada dalam image | cepat, tanpa panggilan penandatanganan | nama berkas bukan nama keluarga, sehingga pilihan yang diambil darinya gagal |
| Probing pustaka | apa saja yang dikontainerkan atau portabel | otoritatif, bekerja di laptop dan image sama | satu penulisan PDF per kandidat, jadi lakukan pada startup dan cache |
Dua Keanehan Binding yang Perlu Diketahui
Setelah sebuah keluarga ter‑resolusi, panggilan penandatanganan itu sendiri memiliki bentuk khusus Node. API Java menerima daftar opsi, tetapi array JavaScript tidak dimarshal ke java.util.List, sehingga memberikan satu menghasilkan Could not find method "sign(java.lang.String, [Ljava.lang.Object;)". Solusinya adalah menumpuk overload satu‑opsi dan melewati file sementara:
new signatureLib.Signature(sourcePath)
.sign(firstOutput, buildTextOptions(LATIN_TEXT, latinFamily, 50));
if (stageTwo) {
new signatureLib.Signature(firstOutput)
.sign(outputPath, buildTextOptions(CJK_TEXT, cjkFamily, 120));
}
Keanehan kedua adalah pembacaan kembali. TextVerifyOptions tidak dapat melakukan round‑trip melalui binding ini: verify menimbulkan error bridge generik yang sama, sehingga contoh mengembalikan sentinel dan mencetak unavailable alih‑alih berpura‑pura tanda tangan gagal. Paket npm berversi 24.12.0, dipublikasikan pada Desember 2024, dan menyertakan engine 23.6.1 sementara .NET berada pada 26.6 dan Java pada 26.5. Penandatanganan tidak terpengaruh; hanya jalur verifikasi yang hilang.
Haruskah Saya Masih Menggunakan Binding Node.js di Produksi?
Untuk penandatanganan hanya Latin, ya: ia menandatangani dengan benar, dan font yang hilang akan memicu pengecualian alih‑alih menurun secara diam‑diam, sehingga mode kegagalan menjadi jelas. Untuk pekerjaan dengan skrip campuran, pertimbangkan kehilangan pembacaan kembali, karena tidak ada proses yang kemudian dapat mengonfirmasi glyph CJK yang tertanam daripada muncul sebagai kotak. Verifier kecil di .NET atau Java dalam pipeline yang sama menutup celah itu.
Praktik Terbaik dan Tips
- Provisikan secara berurutan: JDK dan toolchain, jalur pemuat, font, kemudian aplikasi. Setiap lapisan gagal dengan cara yang berbeda dan mencampurnya membuat diagnosis menjadi lambat.
- Resolusi keluarga satu kali pada startup dan log bersama hitungan font.
- Tetapkan Node 18 dan JDK antara 8 dan 17, serta perlakukan keduanya sebagai infrastruktur tetap bukan upgrade rutin.
- Simpan Dockerfile tanpa font di repositori, sehingga kegagalan tetap satu build jauhnya.
Kesimpulan
Tiga cara memilih font, satu yang bertahan di produksi. Probe pustaka, cache jawabannya, dan biarkan inventaris berfungsi sebagai diagnostik bukan keputusan. Kemudian gunakan binding apa adanya: tanda tangani satu opsi pada satu waktu, ekstrak pengecualian Java dari jejak stack, dan laporkan verifikasi yang hilang secara jujur alih‑alih menyembunyikannya. Repositori contoh membangun kedua image sehingga setiap klaim di sini dapat diverifikasi dengan dua perintah.