Skip to content
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

Font Themes #3486

Merged
merged 3 commits into from
Apr 1, 2023
Merged
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
14 changes: 14 additions & 0 deletions docs/topics/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,20 @@ $spreadsheet->getDefaultStyle()->getFont()->setName('Arial');
$spreadsheet->getDefaultStyle()->getFont()->setSize(8);
```

Excel also offers "theme fonts", with separate font names for major (header) and minor (body) text. PhpSpreadsheet will use the Excel 2007 default (Cambria) for major (default is Calibri Light in Excel 2013+); PhpSpreadsheet default for minor is Calibri, which is used by Excel 2007+. To align the default font name with the minor font name:

```php
$spreadsheet->getTheme()
->setThemeFontName('custom')
->setMinorFontValues('Arial', 'Arial', 'Arial', []);
$spreadsheet->getDefaultStyle()->getFont()->setScheme('minor');
```

All cells bound to the theme fonts (via the `Font::setScheme` method) can be easily changed to a different font in Excel. To do this in PhpSpreadsheet, an additional method call is needed:
```php
$spreadsheet->resetThemeFonts();
```

### Styling cell borders

In PhpSpreadsheet it is easy to apply various borders on a rectangular
Expand Down
30 changes: 30 additions & 0 deletions src/PhpSpreadsheet/Reader/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,36 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
$theme = new Theme($themeName, $colourSchemeName, $themeColours);
$this->styleReader->setTheme($theme);

$fontScheme = self::getAttributes($xmlTheme->themeElements->fontScheme);
$fontSchemeName = (string) $fontScheme['name'];
$excel->getTheme()->setThemeFontName($fontSchemeName);
$majorFonts = [];
$minorFonts = [];
$fontScheme = $xmlTheme->themeElements->fontScheme->children($drawingNS);
$majorLatin = self::getAttributes($fontScheme->majorFont->latin)['typeface'] ?? '';
$majorEastAsian = self::getAttributes($fontScheme->majorFont->ea)['typeface'] ?? '';
$majorComplexScript = self::getAttributes($fontScheme->majorFont->cs)['typeface'] ?? '';
$minorLatin = self::getAttributes($fontScheme->minorFont->latin)['typeface'] ?? '';
$minorEastAsian = self::getAttributes($fontScheme->minorFont->ea)['typeface'] ?? '';
$minorComplexScript = self::getAttributes($fontScheme->minorFont->cs)['typeface'] ?? '';

foreach ($fontScheme->majorFont->font as $xmlFont) {
$fontAttributes = self::getAttributes($xmlFont);
$script = (string) ($fontAttributes['script'] ?? '');
if (!empty($script)) {
$majorFonts[$script] = (string) ($fontAttributes['typeface'] ?? '');
}
}
foreach ($fontScheme->minorFont->font as $xmlFont) {
$fontAttributes = self::getAttributes($xmlFont);
$script = (string) ($fontAttributes['script'] ?? '');
if (!empty($script)) {
$minorFonts[$script] = (string) ($fontAttributes['typeface'] ?? '');
}
}
$excel->getTheme()->setMajorFontValues($majorLatin, $majorEastAsian, $majorComplexScript, $majorFonts);
$excel->getTheme()->setMinorFontValues($minorLatin, $minorEastAsian, $minorComplexScript, $minorFonts);

break;
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/PhpSpreadsheet/Reader/Xlsx/Styles.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ public function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml):
}
}
}
if (isset($fontStyleXml->scheme)) {
$attr = $this->getStyleAttributes($fontStyleXml->scheme);
$fontStyle->setScheme((string) $attr['val']);
}
}

private function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void
Expand Down
22 changes: 22 additions & 0 deletions src/PhpSpreadsheet/Spreadsheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -1663,4 +1663,26 @@ public function jsonSerialize(): mixed
{
throw new Exception('Spreadsheet objects cannot be json encoded');
}

public function resetThemeFonts(): void
{
$majorFontLatin = $this->theme->getMajorFontLatin();
$minorFontLatin = $this->theme->getMinorFontLatin();
foreach ($this->cellXfCollection as $cellStyleXf) {
$scheme = $cellStyleXf->getFont()->getScheme();
if ($scheme === 'major') {
$cellStyleXf->getFont()->setName($majorFontLatin)->setScheme($scheme);
} elseif ($scheme === 'minor') {
$cellStyleXf->getFont()->setName($minorFontLatin)->setScheme($scheme);
}
}
foreach ($this->cellStyleXfCollection as $cellStyleXf) {
$scheme = $cellStyleXf->getFont()->getScheme();
if ($scheme === 'major') {
$cellStyleXf->getFont()->setName($majorFontLatin)->setScheme($scheme);
} elseif ($scheme === 'minor') {
$cellStyleXf->getFont()->setName($minorFontLatin)->setScheme($scheme);
}
}
}
}
39 changes: 34 additions & 5 deletions src/PhpSpreadsheet/Style/Font.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ class Font extends Supervisor
*/
public $colorIndex;

/** @var string */
protected $scheme = '';

/**
* Create a new Font.
*
Expand Down Expand Up @@ -234,6 +237,9 @@ public function applyFromArray(array $styleArray)
if (isset($styleArray['chartColor'])) {
$this->chartColor = $styleArray['chartColor'];
}
if (isset($styleArray['scheme'])) {
$this->setScheme($styleArray['scheme']);
}
}

return $this;
Expand Down Expand Up @@ -281,13 +287,11 @@ public function getComplexScript(): string
}

/**
* Set Name.
* Set Name and turn off Scheme.
*
* @param string $fontname
*
* @return $this
*/
public function setName($fontname)
public function setName($fontname): self
{
if ($fontname == '') {
$fontname = 'Calibri';
Expand All @@ -299,7 +303,7 @@ public function setName($fontname)
$this->name = $fontname;
}

return $this;
return $this->setScheme('');
}

public function setLatin(string $fontname): self
Expand Down Expand Up @@ -784,6 +788,7 @@ public function getHashCode()
$this->underline .
($this->strikethrough ? 't' : 'f') .
$this->color->getHashCode() .
$this->scheme .
implode(
'*',
[
Expand Down Expand Up @@ -812,6 +817,7 @@ protected function exportArray1(): array
$this->exportArray2($exportedArray, 'italic', $this->getItalic());
$this->exportArray2($exportedArray, 'latin', $this->getLatin());
$this->exportArray2($exportedArray, 'name', $this->getName());
$this->exportArray2($exportedArray, 'scheme', $this->getScheme());
$this->exportArray2($exportedArray, 'size', $this->getSize());
$this->exportArray2($exportedArray, 'strikethrough', $this->getStrikethrough());
$this->exportArray2($exportedArray, 'strikeType', $this->getStrikeType());
Expand All @@ -822,4 +828,27 @@ protected function exportArray1(): array

return $exportedArray;
}

public function getScheme(): string
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getScheme();
}

return $this->scheme;
}

public function setScheme(string $scheme): self
{
if ($scheme === '' || $scheme === 'major' || $scheme === 'minor') {
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['scheme' => $scheme]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->scheme = $scheme;
}
}

return $this;
}
}
193 changes: 193 additions & 0 deletions src/PhpSpreadsheet/Theme.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ class Theme
/** @var string */
private $themeColorName = 'Office';

/** @var string */
private $themeFontName = 'Office';

public const COLOR_SCHEME_2013_PLUS_NAME = 'Office 2013+';
public const COLOR_SCHEME_2013_PLUS = [
'dk1' => '000000',
Expand Down Expand Up @@ -42,6 +45,104 @@ class Theme
/** @var string[] */
private $themeColors = self::COLOR_SCHEME_2007_2010;

/** @var string */
private $majorFontLatin = 'Cambria';

/** @var string */
private $majorFontEastAsian = '';

/** @var string */
private $majorFontComplexScript = '';

/** @var string */
private $minorFontLatin = 'Calibri';

/** @var string */
private $minorFontEastAsian = '';

/** @var string */
private $minorFontComplexScript = '';

/**
* Map of Major (header) fonts to write.
*
* @var string[]
*/
private $majorFontSubstitutions = self::FONTS_TIMES_SUBSTITUTIONS;

/**
* Map of Minor (body) fonts to write.
*
* @var string[]
*/
private $minorFontSubstitutions = self::FONTS_ARIAL_SUBSTITUTIONS;

public const FONTS_TIMES_SUBSTITUTIONS = [
'Jpan' => 'MS Pゴシック',
'Hang' => '맑은 고딕',
'Hans' => '宋体',
'Hant' => '新細明體',
'Arab' => 'Times New Roman',
'Hebr' => 'Times New Roman',
'Thai' => 'Tahoma',
'Ethi' => 'Nyala',
'Beng' => 'Vrinda',
'Gujr' => 'Shruti',
'Khmr' => 'MoolBoran',
'Knda' => 'Tunga',
'Guru' => 'Raavi',
'Cans' => 'Euphemia',
'Cher' => 'Plantagenet Cherokee',
'Yiii' => 'Microsoft Yi Baiti',
'Tibt' => 'Microsoft Himalaya',
'Thaa' => 'MV Boli',
'Deva' => 'Mangal',
'Telu' => 'Gautami',
'Taml' => 'Latha',
'Syrc' => 'Estrangelo Edessa',
'Orya' => 'Kalinga',
'Mlym' => 'Kartika',
'Laoo' => 'DokChampa',
'Sinh' => 'Iskoola Pota',
'Mong' => 'Mongolian Baiti',
'Viet' => 'Times New Roman',
'Uigh' => 'Microsoft Uighur',
'Geor' => 'Sylfaen',
];

public const FONTS_ARIAL_SUBSTITUTIONS = [
'Jpan' => 'MS Pゴシック',
'Hang' => '맑은 고딕',
'Hans' => '宋体',
'Hant' => '新細明體',
'Arab' => 'Arial',
'Hebr' => 'Arial',
'Thai' => 'Tahoma',
'Ethi' => 'Nyala',
'Beng' => 'Vrinda',
'Gujr' => 'Shruti',
'Khmr' => 'DaunPenh',
'Knda' => 'Tunga',
'Guru' => 'Raavi',
'Cans' => 'Euphemia',
'Cher' => 'Plantagenet Cherokee',
'Yiii' => 'Microsoft Yi Baiti',
'Tibt' => 'Microsoft Himalaya',
'Thaa' => 'MV Boli',
'Deva' => 'Mangal',
'Telu' => 'Gautami',
'Taml' => 'Latha',
'Syrc' => 'Estrangelo Edessa',
'Orya' => 'Kalinga',
'Mlym' => 'Kartika',
'Laoo' => 'DokChampa',
'Sinh' => 'Iskoola Pota',
'Mong' => 'Mongolian Baiti',
'Viet' => 'Arial',
'Uigh' => 'Microsoft Uighur',
'Geor' => 'Sylfaen',
];

public function getThemeColors(): array
{
return $this->themeColors;
Expand Down Expand Up @@ -73,4 +174,96 @@ public function setThemeColorName(string $name, ?array $themeColors = null): sel

return $this;
}

public function getMajorFontLatin(): string
{
return $this->majorFontLatin;
}

public function getMajorFontEastAsian(): string
{
return $this->majorFontEastAsian;
}

public function getMajorFontComplexScript(): string
{
return $this->majorFontComplexScript;
}

public function getMajorFontSubstitutions(): array
{
return $this->majorFontSubstitutions;
}

/** @param null|array $substitutions */
public function setMajorFontValues(?string $latin, ?string $eastAsian, ?string $complexScript, $substitutions): self
{
if (!empty($latin)) {
$this->majorFontLatin = $latin;
}
if ($eastAsian !== null) {
$this->majorFontEastAsian = $eastAsian;
}
if ($complexScript !== null) {
$this->majorFontComplexScript = $complexScript;
}
if ($substitutions !== null) {
$this->majorFontSubstitutions = $substitutions;
}

return $this;
}

public function getMinorFontLatin(): string
{
return $this->minorFontLatin;
}

public function getMinorFontEastAsian(): string
{
return $this->minorFontEastAsian;
}

public function getMinorFontComplexScript(): string
{
return $this->minorFontComplexScript;
}

public function getMinorFontSubstitutions(): array
{
return $this->minorFontSubstitutions;
}

/** @param null|array $substitutions */
public function setMinorFontValues(?string $latin, ?string $eastAsian, ?string $complexScript, $substitutions): self
{
if (!empty($latin)) {
$this->minorFontLatin = $latin;
}
if ($eastAsian !== null) {
$this->minorFontEastAsian = $eastAsian;
}
if ($complexScript !== null) {
$this->minorFontComplexScript = $complexScript;
}
if ($substitutions !== null) {
$this->minorFontSubstitutions = $substitutions;
}

return $this;
}

public function getThemeFontName(): string
{
return $this->themeFontName;
}

public function setThemeFontName(?string $name): self
{
if (!empty($name)) {
$this->themeFontName = $name;
}

return $this;
}
}
Loading