PhpNative engine: non-ASCII characters in the signature stamp are rendered as mojibake #8155
Description
Describe the bug
With the PhpNative signature engine, non-ASCII characters in the visible signature stamp are
rendered as mojibake. The stored signature_text_template is correct — the corruption happens at
render time.
A French template produced this on the signed PDF:
| Template (stored, correct) | Rendered in the PDF |
|---|---|
Signé avec LibreSign |
Sign^ avec LibreSign |
Émetteur : Phenix Tech |
^ metteur : Phenix Tech |
To reproduce
- Set
signature_enginetoPhpNative. - Set a
signature_text_templatecontaining accented characters, e.g.
Signé avec LibreSign\n{{SignerCommonName}}\nÉmetteur : {{IssuerCommonName}}. - Sign a document and open the result — the accented characters are mangled.
occ config:app:get libresign signature_text_template still returns the correct UTF-8, confirming
the problem is in the rendering path and not in storage.
Expected behavior
The visible stamp should render the template as stored, at least for the Latin-1 range.
Root cause
Two things combine in lib/Handler/SignEngine/PhpNativeHandler.php:
1. No encoding conversion. escapePdfText() (line 398) only escapes PDF delimiters:
private function escapePdfText(string $value): string {
$value = str_replace('\\', '\\\\', $value);
$value = str_replace('(', '\\(', $value);
$value = str_replace(')', '\\)', $value);
return $value;
}The raw UTF-8 bytes are then written straight into a PDF literal string via
sprintf("(%s) Tj\n", $escaped). é (0xC3 0xA9) therefore reaches the content stream as two
separate byte codes.
2. A single-byte base font with no declared encoding (line 336):
resources: [
'Font' => [
'F1' => [
'Type' => '/Font',
'Subtype' => '/Type1',
'BaseFont' => '/Helvetica',
],
],
],There is no /Encoding entry, so the viewer falls back to Helvetica's built-in StandardEncoding.
Each UTF-8 byte is mapped independently, which is exactly the observed output.
Possible directions
- Minimal: convert the string to WinAnsi and declare
/Encoding /WinAnsiEncodingin the font
dictionary. Covers Latin-1, so most Western European templates, and is a small change. Characters
outside Latin-1 would still need a fallback rather than silent corruption. - Complete: embed a TrueType subset with a proper
/Encoding(or a Type0/Identity-H font) to
support the full Unicode range, at the cost of a larger PDF and a font to ship.
Either way, it would be worth failing loudly — or transliterating explicitly — rather than emitting
bytes that render as mojibake, since the result lands on a signed document that cannot be corrected
afterwards without invalidating the signature.
Why this matters more than it looks
This is engine-specific: JSignPdf renders the same template correctly through iText. The two
engines are therefore not interchangeable in both directions.
It compounds with #8145: JSignPdf currently cannot timestamp against any TSA that rejects SHA-1,
so instances that need RFC 3161 timestamping are pushed towards PhpNative — and there they must
restrict their signature stamp to ASCII. Right now there is no engine that does both correct
timestamping and correct non-ASCII rendering.
Workaround
Restrict signature_text_template to ASCII. In French this is doable with some care — Signature,
Certificat, Date carry no accents — but it constrains the wording, and nothing warns the
administrator that the template will be corrupted.
Environment
- LibreSign 13.3.0
- Nextcloud 33.0.8 (All-in-One)
- Signature engine:
PhpNative(jeidison/signer-php) - Certificate engine: OpenSSL, self-signed internal root CA
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Projects
- StatusShow more project fields1. to do
Activity
vitormattos commentedon Aug 31, 2026
Your analysis is correct for LibreSign 13.3.0. In the current
stable35andmainbranches, this logic was moved fromPhpNativeHandlertoSignatureStampAppearanceBuilder, and the same encoding problem is still present there.So the fix for this issue should be implemented in
SignatureStampAppearanceBuilder. Please also consider the current use ofstrlen()andsubstr(), because they work with bytes and may split UTF-8 text incorrectly.signer-phpdoes not look like the source of the corruption, but an E2E test there with non-ASCII text would be useful.We also maintain
LibreSign/xobject-template, so I think we should create a follow-up issue there to ensure proper Unicode/font handling. It does not need to block this fix.If this issue is important for your use case and you are interested in helping fund the work, let us know. We can discuss the scope and possible sponsorship for the implementation.