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_engine to PhpNative.
- Set a
signature_text_template containing 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 /WinAnsiEncoding in 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
Describe the bug
With the
PhpNativesignature engine, non-ASCII characters in the visible signature stamp arerendered as mojibake. The stored
signature_text_templateis correct — the corruption happens atrender time.
A French template produced this on the signed PDF:
Signé avec LibreSignSign^ avec LibreSignÉmetteur : Phenix Tech^ metteur : Phenix TechTo reproduce
signature_enginetoPhpNative.signature_text_templatecontaining accented characters, e.g.Signé avec LibreSign\n{{SignerCommonName}}\nÉmetteur : {{IssuerCommonName}}.occ config:app:get libresign signature_text_templatestill returns the correct UTF-8, confirmingthe 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: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 twoseparate 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
/Encodingentry, 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
/Encoding /WinAnsiEncodingin the fontdictionary. 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.
/Encoding(or a Type0/Identity-H font) tosupport 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:
JSignPdfrenders the same template correctly through iText. The twoengines are therefore not interchangeable in both directions.
It compounds with #8145:
JSignPdfcurrently cannot timestamp against any TSA that rejects SHA-1,so instances that need RFC 3161 timestamping are pushed towards
PhpNative— and there they mustrestrict 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_templateto ASCII. In French this is doable with some care —Signature,Certificat,Datecarry no accents — but it constrains the wording, and nothing warns theadministrator that the template will be corrupted.
Environment
PhpNative(jeidison/signer-php)