Skip to content

Commit

Permalink
Xlsx Column Autosize Approximate for CJK (#3416)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
oleibman authored Mar 4, 2023
1 parent 0610e57 commit bb54c89
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 10 deletions.
17 changes: 7 additions & 10 deletions src/PhpSpreadsheet/Shared/Font.php
Original file line number Diff line number Diff line change
Expand Up @@ -453,29 +453,26 @@ public static function getTextWidthPixelsApprox($columnText, FontStyle $font, $r
$fontName = $font->getName();
$fontSize = $font->getSize();

// Calculate column width in pixels. We assume fixed glyph width. Result varies with font name and size.
// Calculate column width in pixels.
// We assume fixed glyph width, but count double for "fullwidth" characters.
// Result varies with font name and size.
switch ($fontName) {
case 'Calibri':
// value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
$columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
$columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size

break;
case 'Arial':
// value 8 was set because of experience in different exports at Arial 10 font.
$columnWidth = (int) (8 * StringHelper::countCharacters($columnText));
$columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
$columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size

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

break;
default:
// just assume Calibri
$columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
// value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
$columnWidth = (int) (8.26 * StringHelper::countCharactersDbcs($columnText));
$columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size

break;
Expand Down
12 changes: 12 additions & 0 deletions src/PhpSpreadsheet/Shared/StringHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,18 @@ public static function countCharacters(string $textValue, string $encoding = 'UT
return mb_strlen($textValue, $encoding);
}

/**
* Get character count using mb_strwidth rather than mb_strlen.
*
* @param string $encoding Encoding
*
* @return int Character count
*/
public static function countCharactersDbcs(string $textValue, string $encoding = 'UTF-8'): int
{
return mb_strwidth($textValue, $encoding);
}

/**
* Get a substring of a UTF-8 encoded string.
*
Expand Down
2 changes: 2 additions & 0 deletions tests/PhpSpreadsheetTests/Shared/FontTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ public function providerCalculateApproximateColumnWidth(): array
[9.2834, new StyleFont(), "Hello\nWorld", 0, new StyleFont(), true, 0],
[17.5671, new StyleFont(), 'PhpSpreadsheet', 0, new StyleFont(), false, 0],
[19.8523, new StyleFont(), 'PhpSpreadsheet', 0, new StyleFont(), false, 1],
'CJK characters width must be >= 43.00' => [55.2722, new StyleFont(), '如果某一列是CJK 其中的一种,这样的设置方式无效', 0, new StyleFont(), false, 0],
'non-CJK characters width must be >= 24.73' => [31.7065, new StyleFont(), 'abcdefghijklmnopqrstuvwxyz', 0, new StyleFont(), false, 0],
];
}
}

0 comments on commit bb54c89

Please sign in to comment.