diff --git a/lib/Service/SignatureStampPreview/SignatureStampAppearanceBuilder.php b/lib/Service/SignatureStampPreview/SignatureStampAppearanceBuilder.php index b4ba4a0640..c0a9033e73 100644 --- a/lib/Service/SignatureStampPreview/SignatureStampAppearanceBuilder.php +++ b/lib/Service/SignatureStampPreview/SignatureStampAppearanceBuilder.php @@ -65,7 +65,7 @@ public function buildXObject( $nameY = $nameStartY; $estimatedCharWidth = $nameFontSize * 0.52; foreach ($nameLines as $nameLine) { - $lineWidth = strlen($nameLine) * $estimatedCharWidth; + $lineWidth = mb_strlen($nameLine) * $estimatedCharWidth; $nameX = max($leftPadding, ($leftHalfW - $lineWidth) / 2.0); $escaped = $this->escapePdfText($nameLine); $stream .= "BT\n"; @@ -105,6 +105,7 @@ public function buildXObject( 'Type' => '/Font', 'Subtype' => '/Type1', 'BaseFont' => '/Helvetica', + 'Encoding' => '/WinAnsiEncoding', ], ], ], @@ -122,7 +123,7 @@ public function wrapTextForPdf(string $line, float $availableWidth, float $fontS $estimatedCharWidth = max(1.0, $fontSize * 0.52); $maxChars = max(1, (int)floor($availableWidth / $estimatedCharWidth)); - if (strlen($trimmed) <= $maxChars) { + if (mb_strlen($trimmed) <= $maxChars) { return [$trimmed]; } @@ -134,7 +135,7 @@ public function wrapTextForPdf(string $line, float $availableWidth, float $fontS } $candidate = $current === '' ? $word : $current . ' ' . $word; - if (strlen($candidate) <= $maxChars) { + if (mb_strlen($candidate) <= $maxChars) { $current = $candidate; continue; } @@ -144,9 +145,9 @@ public function wrapTextForPdf(string $line, float $availableWidth, float $fontS $current = ''; } - while (strlen($word) > $maxChars) { - $result[] = substr($word, 0, $maxChars); - $word = substr($word, $maxChars); + while (mb_strlen($word) > $maxChars) { + $result[] = mb_substr($word, 0, $maxChars); + $word = mb_substr($word, $maxChars); } $current = $word; @@ -160,10 +161,17 @@ public function wrapTextForPdf(string $line, float $availableWidth, float $fontS } public function escapePdfText(string $value): string { - $value = str_replace('\\', '\\\\', $value); - $value = str_replace('(', '\\(', $value); - $value = str_replace(')', '\\)', $value); + $encoded = mb_convert_encoding($value, 'Windows-1252', 'UTF-8'); + if (mb_convert_encoding($encoded, 'UTF-8', 'Windows-1252') !== $value) { + throw new \InvalidArgumentException( + 'signature stamp contains characters that cannot be represented by WinAnsiEncoding', + ); + } + + $encoded = str_replace('\\', '\\\\', $encoded); + $encoded = str_replace('(', '\\(', $encoded); + $encoded = str_replace(')', '\\)', $encoded); - return $value; + return $encoded; } } diff --git a/tests/php/Unit/Service/SignatureStampPreview/SignatureStampAppearanceBuilderTest.php b/tests/php/Unit/Service/SignatureStampPreview/SignatureStampAppearanceBuilderTest.php new file mode 100644 index 0000000000..e4775421f6 --- /dev/null +++ b/tests/php/Unit/Service/SignatureStampPreview/SignatureStampAppearanceBuilderTest.php @@ -0,0 +1,54 @@ +createMock(SignatureTextService::class); + $signatureTextService->method('parse')->willReturn([ + 'parsed' => 'Signé par Renée', + 'templateFontSize' => 10.0, + ]); + $builder = new SignatureStampAppearanceBuilder($signatureTextService); + + $xObject = $builder->buildXObject( + 100, + 50, + SignerElementsService::RENDER_MODE_DESCRIPTION_ONLY, + ); + + $this->assertSame('/WinAnsiEncoding', $xObject->resources['Font']['F1']['Encoding']); + $this->assertStringContainsString("(Sign\xE9 par Ren\xE9e) Tj", $xObject->stream); + $this->assertStringNotContainsString('Signé par Renée', $xObject->stream); + } + + public function testWrapTextForPdfDoesNotSplitMultibyteCharacters(): void { + $signatureTextService = $this->createMock(SignatureTextService::class); + $builder = new SignatureStampAppearanceBuilder($signatureTextService); + + $this->assertSame( + ['éé', 'éé'], + $builder->wrapTextForPdf('éééé', 15.0, 10.0), + ); + } + + public function testEscapePdfTextRejectsCharactersOutsideWinAnsi(): void { + $signatureTextService = $this->createMock(SignatureTextService::class); + $builder = new SignatureStampAppearanceBuilder($signatureTextService); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('signature stamp contains characters that cannot be represented by WinAnsiEncoding'); + + $builder->escapePdfText('Signed by 😀'); + } +}