Skip to content

Commit 30369ca

Browse files
authored
Merge pull request #4501 from oleibman/issue2760
Allow Xlsx Reader/Writer to Process Font Charset
2 parents 84b7ab9 + f0ca95f commit 30369ca

File tree

6 files changed

+129
-6
lines changed

6 files changed

+129
-6
lines changed

docs/topics/recipes.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,21 @@ All cells bound to the theme fonts (via the `Font::setScheme` method) can be eas
10991099
$spreadsheet->resetThemeFonts();
11001100
```
11011101

1102+
### Charset for Arabic and Persian Fonts
1103+
1104+
It is unknown why this should be needed. However, some Excel
1105+
users have reported better results if the internal declaration for an
1106+
Arabic/Persian font includes a `charset` declaration.
1107+
This seems like a bug in Excel, but, starting with release 4.4,
1108+
this can be accomplished at the spreadsheet level, via:
1109+
```php
1110+
$spreadsheet->addFontCharset('C Nazanin');
1111+
```
1112+
As many charsets as desired can be added in this manner.
1113+
There is a second optional parameter specifying the charset id
1114+
to this method, but, since this seems to be needed only for
1115+
Arabic/Persian, that is its default value.
1116+
11021117
### Styling cell borders
11031118

11041119
In PhpSpreadsheet it is easy to apply various borders on a rectangular

src/PhpSpreadsheet/Reader/Xlsx.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,11 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
655655

656656
// add style to cellXf collection
657657
$objStyle = new Style();
658-
$this->styleReader->readStyle($objStyle, $style);
658+
$this->styleReader
659+
->readStyle($objStyle, $style);
660+
foreach ($this->styleReader->getFontCharsets() as $fontName => $charset) {
661+
$excel->addFontCharset($fontName, $charset);
662+
}
659663
if ($addingFirstCellXf) {
660664
$excel->removeCellXfByIndex(0); // remove the default style
661665
$addingFirstCellXf = false;

src/PhpSpreadsheet/Reader/Xlsx/Styles.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ class Styles extends BaseParserClass
3636

3737
private string $namespace = '';
3838

39+
/** @var array<string, int> */
40+
private array $fontCharsets = [];
41+
42+
/** @return array<string, int> */
43+
public function getFontCharsets(): array
44+
{
45+
return $this->fontCharsets;
46+
}
47+
3948
public function setNamespace(string $namespace): void
4049
{
4150
$this->namespace = $namespace;
@@ -85,6 +94,13 @@ public function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml):
8594
if (isset($attr['val'])) {
8695
$fontStyle->setName((string) $attr['val']);
8796
}
97+
if (isset($fontStyleXml->charset)) {
98+
$charsetAttr = $this->getStyleAttributes($fontStyleXml->charset);
99+
if (isset($charsetAttr['val'])) {
100+
$charsetVal = (int) $charsetAttr['val'];
101+
$this->fontCharsets[$fontStyle->getName()] = $charsetVal;
102+
}
103+
}
88104
}
89105
if (isset($fontStyleXml->sz)) {
90106
$attr = $this->getStyleAttributes($fontStyleXml->sz);

src/PhpSpreadsheet/Spreadsheet.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PhpOffice\PhpSpreadsheet\Document\Properties;
99
use PhpOffice\PhpSpreadsheet\Document\Security;
1010
use PhpOffice\PhpSpreadsheet\Shared\Date;
11+
use PhpOffice\PhpSpreadsheet\Shared\Font as SharedFont;
1112
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
1213
use PhpOffice\PhpSpreadsheet\Style\Style;
1314
use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
@@ -177,6 +178,36 @@ class Spreadsheet implements JsonSerializable
177178

178179
private ?IValueBinder $valueBinder = null;
179180

181+
/** @var array<string, int> */
182+
private array $fontCharsets = [
183+
'B Nazanin' => SharedFont::CHARSET_ANSI_ARABIC,
184+
];
185+
186+
/**
187+
* @param int $charset uses any value from Shared\Font,
188+
* but defaults to ARABIC because that is the only known
189+
* charset for which this declaration might be needed
190+
*/
191+
public function addFontCharset(string $fontName, int $charset = SharedFont::CHARSET_ANSI_ARABIC): void
192+
{
193+
$this->fontCharsets[$fontName] = $charset;
194+
}
195+
196+
public function getFontCharset(string $fontName): int
197+
{
198+
return $this->fontCharsets[$fontName] ?? -1;
199+
}
200+
201+
/**
202+
* Return all fontCharsets.
203+
*
204+
* @return array<string, int>
205+
*/
206+
public function getFontCharsets(): array
207+
{
208+
return $this->fontCharsets;
209+
}
210+
180211
public function getTheme(): Theme
181212
{
182213
return $this->theme;

src/PhpSpreadsheet/Writer/Xlsx/Style.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function writeStyles(Spreadsheet $spreadsheet): string
5959
for ($i = 0; $i < $this->getParentWriter()->getFontHashTable()->count(); ++$i) {
6060
$thisfont = $this->getParentWriter()->getFontHashTable()->getByIndex($i);
6161
if ($thisfont !== null) {
62-
$this->writeFont($objWriter, $thisfont);
62+
$this->writeFont($objWriter, $thisfont, $spreadsheet);
6363
}
6464
}
6565

@@ -145,7 +145,7 @@ public function writeStyles(Spreadsheet $spreadsheet): string
145145
/** @var ?Conditional */
146146
$thisstyle = $this->getParentWriter()->getStylesConditionalHashTable()->getByIndex($i);
147147
if ($thisstyle !== null) {
148-
$this->writeCellStyleDxf($objWriter, $thisstyle->getStyle());
148+
$this->writeCellStyleDxf($objWriter, $thisstyle->getStyle(), $spreadsheet);
149149
}
150150
}
151151

@@ -284,7 +284,7 @@ private function startFont(XMLWriter $objWriter, bool &$fontStarted): void
284284
/**
285285
* Write Font.
286286
*/
287-
private function writeFont(XMLWriter $objWriter, Font $font): void
287+
private function writeFont(XMLWriter $objWriter, Font $font, Spreadsheet $spreadsheet): void
288288
{
289289
$fontStarted = false;
290290
// font
@@ -365,6 +365,12 @@ private function writeFont(XMLWriter $objWriter, Font $font): void
365365
$objWriter->startElement('name');
366366
$objWriter->writeAttribute('val', $font->getName());
367367
$objWriter->endElement();
368+
$charset = $spreadsheet->getFontCharset($font->getName());
369+
if ($charset >= 0 && $charset <= 255) {
370+
$objWriter->startElement('charset');
371+
$objWriter->writeAttribute('val', "$charset");
372+
$objWriter->endElement();
373+
}
368374
}
369375

370376
if (!empty($font->getScheme())) {
@@ -504,13 +510,13 @@ private function writeCellStyleXf(XMLWriter $objWriter, \PhpOffice\PhpSpreadshee
504510
/**
505511
* Write Cell Style Dxf.
506512
*/
507-
private function writeCellStyleDxf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $style): void
513+
private function writeCellStyleDxf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $style, Spreadsheet $spreadsheet): void
508514
{
509515
// dxf
510516
$objWriter->startElement('dxf');
511517

512518
// font
513-
$this->writeFont($objWriter, $style->getFont());
519+
$this->writeFont($objWriter, $style->getFont(), $spreadsheet);
514520

515521
// numFmt
516522
$this->writeNumFmt($objWriter, $style->getNumberFormat());
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
9+
10+
class FontCharsetTest extends AbstractFunctional
11+
{
12+
private ?Spreadsheet $spreadsheet = null;
13+
14+
private ?Spreadsheet $reloadedSpreadsheet = null;
15+
16+
protected function tearDown(): void
17+
{
18+
if ($this->spreadsheet !== null) {
19+
$this->spreadsheet->disconnectWorksheets();
20+
$this->spreadsheet = null;
21+
}
22+
if ($this->reloadedSpreadsheet !== null) {
23+
$this->reloadedSpreadsheet->disconnectWorksheets();
24+
$this->reloadedSpreadsheet = null;
25+
}
26+
}
27+
28+
public function testFontCharset(): void
29+
{
30+
$spreadsheet = $this->spreadsheet = new Spreadsheet();
31+
$sheet = $this->spreadsheet->getActiveSheet();
32+
$sheet->getStyle('A1')->getFont()->setName('Nazanin');
33+
$spreadsheet->addFontCharset('Nazanin');
34+
35+
$spreadsheet2 = $this->reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
36+
$sheet2 = $spreadsheet2->getActiveSheet();
37+
$sheet2->getStyle('B1')->getFont()->setName('B Nazanin');
38+
$sheet2->getStyle('C1')->getFont()->setName('C Nazanin');
39+
$sheet2->getStyle('D1')->getFont()->setName('D Nazanin');
40+
$spreadsheet2->addFontCharset('C Nazanin');
41+
// Do not add D Nazanin for this test.
42+
self::assertSame(
43+
[
44+
'B Nazanin' => 178, // default entry
45+
'Nazanin' => 178, // should have been set by Xlsx Reader
46+
'C Nazanin' => 178, // explicitly set in this test
47+
],
48+
$spreadsheet2->getFontCharsets()
49+
);
50+
}
51+
}

0 commit comments

Comments
 (0)