Skip to content

Commit 13d66e3

Browse files
fix: replace wordwrap with multibyte-safe implementation
The native PHP wordwrap() function does not handle multibyte characters correctly, causing text with accents, CJK characters, or emojis to be wrapped at incorrect positions. This commit introduces mbWordwrap(), a custom implementation that uses mb_strlen() and mb_substr() to properly handle UTF-8 and multibyte text wrapping in signature images. Signed-off-by: Vitor Mattos <[email protected]>
1 parent cbd5751 commit 13d66e3

1 file changed

Lines changed: 86 additions & 1 deletion

File tree

lib/Service/SignatureTextService.php

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ public function signerNameImage(
230230
$draw->setTextAlignment($align);
231231

232232
$maxCharsPerLine = $this->splitAndGetLongestHalfLength($text);
233-
$wrappedText = wordwrap($text, $maxCharsPerLine, "\n", true);
233+
$wrappedText = $this->mbWordwrap($text, $maxCharsPerLine, "\n", true);
234234

235235
$textMetrics = $image->queryFontMetrics($draw, $wrappedText);
236236
$lineCount = substr_count($wrappedText, "\n") + 1;
@@ -300,6 +300,91 @@ private function splitAndGetLongestHalfLength(string $text): int {
300300
return !empty($results) ? max($results) : $length;
301301
}
302302

303+
/**
304+
* Multibyte-safe version of wordwrap
305+
*
306+
* @param string $text The text to wrap
307+
* @param int $width The number of characters at which the string will be wrapped
308+
* @param string $break The line break character
309+
* @param bool $cut If true, words longer than $width will be broken
310+
* @return string The wrapped text
311+
*/
312+
private function mbWordwrap(string $text, int $width, string $break = "\n", bool $cut = false): string {
313+
if ($width <= 0) {
314+
return $text;
315+
}
316+
317+
$lines = [];
318+
$currentLine = '';
319+
$currentLength = 0;
320+
321+
$paragraphs = explode("\n", $text);
322+
323+
foreach ($paragraphs as $paragraphIndex => $paragraph) {
324+
if ($paragraph === '') {
325+
if ($currentLength > 0) {
326+
$lines[] = $currentLine;
327+
$currentLine = '';
328+
$currentLength = 0;
329+
}
330+
$lines[] = '';
331+
continue;
332+
}
333+
334+
$words = explode(' ', $paragraph);
335+
336+
foreach ($words as $word) {
337+
$wordLength = mb_strlen($word);
338+
339+
if ($cut && $wordLength > $width) {
340+
if ($currentLength > 0) {
341+
$lines[] = $currentLine;
342+
$currentLine = '';
343+
$currentLength = 0;
344+
}
345+
346+
while ($wordLength > $width) {
347+
$lines[] = mb_substr($word, 0, $width);
348+
$word = mb_substr($word, $width);
349+
$wordLength = mb_strlen($word);
350+
}
351+
352+
if ($wordLength > 0) {
353+
$currentLine = $word;
354+
$currentLength = $wordLength;
355+
}
356+
continue;
357+
}
358+
359+
$spaceLength = ($currentLength > 0) ? 1 : 0;
360+
if ($currentLength + $spaceLength + $wordLength > $width && $currentLength > 0) {
361+
$lines[] = $currentLine;
362+
$currentLine = $word;
363+
$currentLength = $wordLength;
364+
} else {
365+
if ($currentLength > 0) {
366+
$currentLine .= ' ';
367+
$currentLength++;
368+
}
369+
$currentLine .= $word;
370+
$currentLength += $wordLength;
371+
}
372+
}
373+
374+
if ($currentLength > 0 && $paragraphIndex < count($paragraphs) - 1) {
375+
$lines[] = $currentLine;
376+
$currentLine = '';
377+
$currentLength = 0;
378+
}
379+
}
380+
381+
if ($currentLength > 0) {
382+
$lines[] = $currentLine;
383+
}
384+
385+
return implode($break, $lines);
386+
}
387+
303388
public function getDefaultTemplate(): string {
304389
$collectMetadata = $this->appConfig->getValueBool(Application::APP_ID, 'collect_metadata', false);
305390
if ($collectMetadata) {

0 commit comments

Comments
 (0)