Skip to content

Commit bb54c89

Browse files
authored
Xlsx Column Autosize Approximate for CJK (#3416)
Fix #3405. Autosize is definitely not working well with CJK characters (column is not wide enough). User reports a workaround using `mb_strwidth` to calculate and set the column width. PhpSpreadsheet uses `mb_strlen` for width calculations. Change it to use mb_strwidth instead. For non-CJK strings, the results will be identical (and there are already unit tests on such strings which assert the expected results, and these tests did not need to change). For CJK strings, the results will be wider. The string I'm using to test comes from the issue. It currently results in a column width of 30.564. When I open the resulting sheet in Excel and auto-fit the column width, the width winds up as 43.00. So, as long as the computed width exceeds 43.00, the spreadsheet will show the full cell. With the new calculation, the computed width is 55.2722, satisfying our condition. This is wider than expected, but that is generally true for this type of computation. For example, for 'abcdefghijklmnopqrstuvwxyz', the computed width (before and after this change) is 31.7065, but Excel auto-fit actually uses 24.73. Disappointingly, "exact width calculation" does not solve this problem. It does seem to do a little better than "approximate" for non-CJK, but its CJK calculation is not wide enough. This might or might not indicate a bug in Php function `imagegetttfbbox`; I do not know enough about it to report a bug. Anyhow, since we're dependent on that result, there is no equivalent in this case for swapping mb_strlen out for mb_strwidth.
1 parent 0610e57 commit bb54c89

File tree

3 files changed

+21
-10
lines changed

3 files changed

+21
-10
lines changed

src/PhpSpreadsheet/Shared/Font.php

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -453,29 +453,26 @@ public static function getTextWidthPixelsApprox($columnText, FontStyle $font, $r
453453
$fontName = $font->getName();
454454
$fontSize = $font->getSize();
455455

456-
// Calculate column width in pixels. We assume fixed glyph width. Result varies with font name and size.
456+
// Calculate column width in pixels.
457+
// We assume fixed glyph width, but count double for "fullwidth" characters.
458+
// Result varies with font name and size.
457459
switch ($fontName) {
458-
case 'Calibri':
459-
// value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
460-
$columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
461-
$columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
462-
463-
break;
464460
case 'Arial':
465461
// value 8 was set because of experience in different exports at Arial 10 font.
466-
$columnWidth = (int) (8 * StringHelper::countCharacters($columnText));
462+
$columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
467463
$columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
468464

469465
break;
470466
case 'Verdana':
471467
// value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font.
472-
$columnWidth = (int) (8 * StringHelper::countCharacters($columnText));
468+
$columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
473469
$columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
474470

475471
break;
476472
default:
477473
// just assume Calibri
478-
$columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
474+
// value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
475+
$columnWidth = (int) (8.26 * StringHelper::countCharactersDbcs($columnText));
479476
$columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
480477

481478
break;

src/PhpSpreadsheet/Shared/StringHelper.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,18 @@ public static function countCharacters(string $textValue, string $encoding = 'UT
451451
return mb_strlen($textValue, $encoding);
452452
}
453453

454+
/**
455+
* Get character count using mb_strwidth rather than mb_strlen.
456+
*
457+
* @param string $encoding Encoding
458+
*
459+
* @return int Character count
460+
*/
461+
public static function countCharactersDbcs(string $textValue, string $encoding = 'UTF-8'): int
462+
{
463+
return mb_strwidth($textValue, $encoding);
464+
}
465+
454466
/**
455467
* Get a substring of a UTF-8 encoded string.
456468
*

tests/PhpSpreadsheetTests/Shared/FontTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ public function providerCalculateApproximateColumnWidth(): array
130130
[9.2834, new StyleFont(), "Hello\nWorld", 0, new StyleFont(), true, 0],
131131
[17.5671, new StyleFont(), 'PhpSpreadsheet', 0, new StyleFont(), false, 0],
132132
[19.8523, new StyleFont(), 'PhpSpreadsheet', 0, new StyleFont(), false, 1],
133+
'CJK characters width must be >= 43.00' => [55.2722, new StyleFont(), '如果某一列是CJK 其中的一种,这样的设置方式无效', 0, new StyleFont(), false, 0],
134+
'non-CJK characters width must be >= 24.73' => [31.7065, new StyleFont(), 'abcdefghijklmnopqrstuvwxyz', 0, new StyleFont(), false, 0],
133135
];
134136
}
135137
}

0 commit comments

Comments
 (0)