Skip to content

Allow Xlsx Reader/Writer to Process Font Charset #4501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/topics/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,21 @@ All cells bound to the theme fonts (via the `Font::setScheme` method) can be eas
$spreadsheet->resetThemeFonts();
```

### Charset for Arabic and Persian Fonts

It is unknown why this should be needed. However, some Excel
users have reported better results if the internal declaration for an
Arabic/Persian font includes a `charset` declaration.
This seems like a bug in Excel, but, starting with release 4.4,
this can be accomplished at the spreadsheet level, via:
```php
$spreadsheet->addFontCharset('C Nazanin');
```
As many charsets as desired can be added in this manner.
There is a second optional parameter specifying the charset id
to this method, but, since this seems to be needed only for
Arabic/Persian, that is its default value.

### Styling cell borders

In PhpSpreadsheet it is easy to apply various borders on a rectangular
Expand Down
6 changes: 5 additions & 1 deletion src/PhpSpreadsheet/Reader/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,11 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet

// add style to cellXf collection
$objStyle = new Style();
$this->styleReader->readStyle($objStyle, $style);
$this->styleReader
->readStyle($objStyle, $style);
foreach ($this->styleReader->getFontCharsets() as $fontName => $charset) {
$excel->addFontCharset($fontName, $charset);
}
if ($addingFirstCellXf) {
$excel->removeCellXfByIndex(0); // remove the default style
$addingFirstCellXf = false;
Expand Down
16 changes: 16 additions & 0 deletions src/PhpSpreadsheet/Reader/Xlsx/Styles.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ class Styles extends BaseParserClass

private string $namespace = '';

/** @var array<string, int> */
private array $fontCharsets = [];

/** @return array<string, int> */
public function getFontCharsets(): array
{
return $this->fontCharsets;
}

public function setNamespace(string $namespace): void
{
$this->namespace = $namespace;
Expand Down Expand Up @@ -85,6 +94,13 @@ public function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml):
if (isset($attr['val'])) {
$fontStyle->setName((string) $attr['val']);
}
if (isset($fontStyleXml->charset)) {
$charsetAttr = $this->getStyleAttributes($fontStyleXml->charset);
if (isset($charsetAttr['val'])) {
$charsetVal = (int) $charsetAttr['val'];
$this->fontCharsets[$fontStyle->getName()] = $charsetVal;
}
}
}
if (isset($fontStyleXml->sz)) {
$attr = $this->getStyleAttributes($fontStyleXml->sz);
Expand Down
31 changes: 31 additions & 0 deletions src/PhpSpreadsheet/Spreadsheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PhpOffice\PhpSpreadsheet\Document\Properties;
use PhpOffice\PhpSpreadsheet\Document\Security;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\Font as SharedFont;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
Expand Down Expand Up @@ -177,6 +178,36 @@ class Spreadsheet implements JsonSerializable

private ?IValueBinder $valueBinder = null;

/** @var array<string, int> */
private array $fontCharsets = [
'B Nazanin' => SharedFont::CHARSET_ANSI_ARABIC,
];

/**
* @param int $charset uses any value from Shared\Font,
* but defaults to ARABIC because that is the only known
* charset for which this declaration might be needed
*/
public function addFontCharset(string $fontName, int $charset = SharedFont::CHARSET_ANSI_ARABIC): void
{
$this->fontCharsets[$fontName] = $charset;
}

public function getFontCharset(string $fontName): int
{
return $this->fontCharsets[$fontName] ?? -1;
}

/**
* Return all fontCharsets.
*
* @return array<string, int>
*/
public function getFontCharsets(): array
{
return $this->fontCharsets;
}

public function getTheme(): Theme
{
return $this->theme;
Expand Down
16 changes: 11 additions & 5 deletions src/PhpSpreadsheet/Writer/Xlsx/Style.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function writeStyles(Spreadsheet $spreadsheet): string
for ($i = 0; $i < $this->getParentWriter()->getFontHashTable()->count(); ++$i) {
$thisfont = $this->getParentWriter()->getFontHashTable()->getByIndex($i);
if ($thisfont !== null) {
$this->writeFont($objWriter, $thisfont);
$this->writeFont($objWriter, $thisfont, $spreadsheet);
}
}

Expand Down Expand Up @@ -145,7 +145,7 @@ public function writeStyles(Spreadsheet $spreadsheet): string
/** @var ?Conditional */
$thisstyle = $this->getParentWriter()->getStylesConditionalHashTable()->getByIndex($i);
if ($thisstyle !== null) {
$this->writeCellStyleDxf($objWriter, $thisstyle->getStyle());
$this->writeCellStyleDxf($objWriter, $thisstyle->getStyle(), $spreadsheet);
}
}

Expand Down Expand Up @@ -284,7 +284,7 @@ private function startFont(XMLWriter $objWriter, bool &$fontStarted): void
/**
* Write Font.
*/
private function writeFont(XMLWriter $objWriter, Font $font): void
private function writeFont(XMLWriter $objWriter, Font $font, Spreadsheet $spreadsheet): void
{
$fontStarted = false;
// font
Expand Down Expand Up @@ -365,6 +365,12 @@ private function writeFont(XMLWriter $objWriter, Font $font): void
$objWriter->startElement('name');
$objWriter->writeAttribute('val', $font->getName());
$objWriter->endElement();
$charset = $spreadsheet->getFontCharset($font->getName());
if ($charset >= 0 && $charset <= 255) {
$objWriter->startElement('charset');
$objWriter->writeAttribute('val', "$charset");
$objWriter->endElement();
}
}

if (!empty($font->getScheme())) {
Expand Down Expand Up @@ -504,13 +510,13 @@ private function writeCellStyleXf(XMLWriter $objWriter, \PhpOffice\PhpSpreadshee
/**
* Write Cell Style Dxf.
*/
private function writeCellStyleDxf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $style): void
private function writeCellStyleDxf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $style, Spreadsheet $spreadsheet): void
{
// dxf
$objWriter->startElement('dxf');

// font
$this->writeFont($objWriter, $style->getFont());
$this->writeFont($objWriter, $style->getFont(), $spreadsheet);

// numFmt
$this->writeNumFmt($objWriter, $style->getNumberFormat());
Expand Down
51 changes: 51 additions & 0 deletions tests/PhpSpreadsheetTests/Writer/Xlsx/FontCharsetTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;

class FontCharsetTest extends AbstractFunctional
{
private ?Spreadsheet $spreadsheet = null;

private ?Spreadsheet $reloadedSpreadsheet = null;

protected function tearDown(): void
{
if ($this->spreadsheet !== null) {
$this->spreadsheet->disconnectWorksheets();
$this->spreadsheet = null;
}
if ($this->reloadedSpreadsheet !== null) {
$this->reloadedSpreadsheet->disconnectWorksheets();
$this->reloadedSpreadsheet = null;
}
}

public function testFontCharset(): void
{
$spreadsheet = $this->spreadsheet = new Spreadsheet();
$sheet = $this->spreadsheet->getActiveSheet();
$sheet->getStyle('A1')->getFont()->setName('Nazanin');
$spreadsheet->addFontCharset('Nazanin');

$spreadsheet2 = $this->reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
$sheet2 = $spreadsheet2->getActiveSheet();
$sheet2->getStyle('B1')->getFont()->setName('B Nazanin');
$sheet2->getStyle('C1')->getFont()->setName('C Nazanin');
$sheet2->getStyle('D1')->getFont()->setName('D Nazanin');
$spreadsheet2->addFontCharset('C Nazanin');
// Do not add D Nazanin for this test.
self::assertSame(
[
'B Nazanin' => 178, // default entry
'Nazanin' => 178, // should have been set by Xlsx Reader
'C Nazanin' => 178, // explicitly set in this test
],
$spreadsheet2->getFontCharsets()
);
}
}