From eb486262fa11434884ca28d98a4ac15b176f7c9f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 3 Jun 2025 21:36:05 -0700 Subject: [PATCH] Support Current Office Theme A while back, Microsoft introduced changes to the default theme colors and fonts. We are adding support for this new theme so that users can use it easily on new spreadsheets if they wish. (It is already supported when using the Xlsx Reader to load an appropriate file.) The old theme was described by constants COLOR_SCHEME_2013_PLUS_NAME and COLOR_SCHEME_2013_PLUS; these are deprecated in favor of COLOR_SCHEME_2013_2022_NAME and COLOR_SCHEME_2013_2022. The new theme is described by constants COLOR_SCHEME_2023_PLUS_NAME and COLOR_SCHEME_2023_PLUS. PhpSpreadsheet's default theme remains COLOR_SCHEME_2007_2010, to avoid breaking changes. A third optional parameter `$spreadsheet` is added to setThemeColorName. If specified, the default font names for the theme will be applied to the default style for the spreadsheet. You can thus use the new theme with its relatively new default `Aptos Narrow` font. IMHO, that isn't necessarily a good choice, but it is available. MS stores the new font in a different location than other system fonts, and that can lead to portability problems, e.g. if your spreadsheet uses Aptos and you export it to Html, browsers will not be able to find the font and a substitute font will be used. --- samples/Chart33a/33_Chart_create_area_2.php | 2 +- src/PhpSpreadsheet/Style/Font.php | 9 ++ src/PhpSpreadsheet/Theme.php | 47 ++++++++-- .../Writer/Xlsx/ThemeColorsTest.php | 89 ++++++++++++++++++- 4 files changed, 139 insertions(+), 8 deletions(-) diff --git a/samples/Chart33a/33_Chart_create_area_2.php b/samples/Chart33a/33_Chart_create_area_2.php index 41b342beb1..e7bdb143c3 100644 --- a/samples/Chart33a/33_Chart_create_area_2.php +++ b/samples/Chart33a/33_Chart_create_area_2.php @@ -13,7 +13,7 @@ /** @var PhpOffice\PhpSpreadsheet\Helper\Sample $helper */ $spreadsheet = new Spreadsheet(); // same as 33_Chart_create_area, but with 2013+ schemes -$spreadsheet->getTheme()->setThemeColorName(SpreadsheetTheme::COLOR_SCHEME_2013_PLUS_NAME); +$spreadsheet->getTheme()->setThemeColorName(SpreadsheetTheme::COLOR_SCHEME_2013_2022_NAME); $worksheet = $spreadsheet->getActiveSheet(); $worksheet->fromArray( [ diff --git a/src/PhpSpreadsheet/Style/Font.php b/src/PhpSpreadsheet/Style/Font.php index ade8e7ead0..6032e1e717 100644 --- a/src/PhpSpreadsheet/Style/Font.php +++ b/src/PhpSpreadsheet/Style/Font.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Style; use PhpOffice\PhpSpreadsheet\Chart\ChartColor; +use PhpOffice\PhpSpreadsheet\Theme; class Font extends Supervisor { @@ -124,6 +125,14 @@ public function __construct(bool $isSupervisor = false, bool $isConditional = fa } } + public function applyThemeFonts(Theme $theme): void + { + $this->setName($theme->getMinorFontLatin()); + $this->setLatin($theme->getMinorFontLatin()); + $this->setEastAsian($theme->getMinorFontEastAsian()); + $this->setComplexScript($theme->getMinorFontComplexScript()); + } + /** * Get the shared style component for the currently active cell in currently active sheet. * Only used for style supervisor. diff --git a/src/PhpSpreadsheet/Theme.php b/src/PhpSpreadsheet/Theme.php index 91cd84485b..97d0568bf5 100644 --- a/src/PhpSpreadsheet/Theme.php +++ b/src/PhpSpreadsheet/Theme.php @@ -9,8 +9,8 @@ class Theme private string $themeFontName = 'Office'; public const HYPERLINK_THEME = 10; - public const COLOR_SCHEME_2013_PLUS_NAME = 'Office 2013+'; - public const COLOR_SCHEME_2013_PLUS = [ + public const COLOR_SCHEME_2013_2022_NAME = 'Office 2013-2022'; + public const COLOR_SCHEME_2013_2022 = [ 'dk1' => '000000', 'lt1' => 'FFFFFF', 'dk2' => '44546A', @@ -24,6 +24,10 @@ class Theme 'hlink' => '0563C1', 'folHlink' => '954F72', ]; + /** @deprecated 4.4.0 Use COLOR_SCHEME_2013_2022_NAME */ + public const COLOR_SCHEME_2013_PLUS_NAME = 'Office 2013+'; + /** @deprecated 4.4.0 Use COLOR_SCHEME_2013_2022 */ + public const COLOR_SCHEME_2013_PLUS = self::COLOR_SCHEME_2013_2022; public const COLOR_SCHEME_2007_2010_NAME = 'Office 2007-2010'; public const COLOR_SCHEME_2007_2010 = [ @@ -41,6 +45,22 @@ class Theme 'folHlink' => '800080', ]; + public const COLOR_SCHEME_2023_PLUS_NAME = 'Office 2023+'; + public const COLOR_SCHEME_2023_PLUS = [ + 'dk1' => '000000', + 'lt1' => 'FFFFFF', + 'dk2' => '0E2841', + 'lt2' => 'E8E8E8', + 'accent1' => '156082', + 'accent2' => 'E97132', + 'accent3' => '196B24', + 'accent4' => '0F9ED5', + 'accent5' => 'A02B93', + 'accent6' => '4EA72E', + 'hlink' => '467886', + 'folHlink' => '96607D', + ]; + /** @var string[] */ private array $themeColors = self::COLOR_SCHEME_2007_2010; @@ -155,17 +175,34 @@ public function getThemeColorName(): string } /** @param null|string[] $themeColors */ - public function setThemeColorName(string $name, ?array $themeColors = null): self + public function setThemeColorName(string $name, ?array $themeColors = null, ?Spreadsheet $spreadsheet = null): self { $this->themeColorName = $name; if ($name === self::COLOR_SCHEME_2007_2010_NAME) { $themeColors = $themeColors ?? self::COLOR_SCHEME_2007_2010; - } elseif ($name === self::COLOR_SCHEME_2013_PLUS_NAME) { - $themeColors = $themeColors ?? self::COLOR_SCHEME_2013_PLUS; + $this->majorFontLatin = 'Cambria'; + $this->minorFontLatin = 'Calibri'; + } elseif ($name === self::COLOR_SCHEME_2013_PLUS_NAME) { //* @phpstan-ignore-line + // delete this block when deprecated constants removed + $themeColors = $themeColors ?? self::COLOR_SCHEME_2013_PLUS; //* @phpstan-ignore-line + $this->majorFontLatin = 'Calibri Light'; + $this->minorFontLatin = 'Calibri'; + } elseif ($name === self::COLOR_SCHEME_2013_2022_NAME) { + $themeColors = $themeColors ?? self::COLOR_SCHEME_2013_2022; + $this->majorFontLatin = 'Calibri Light'; + $this->minorFontLatin = 'Calibri'; + } elseif ($name === self::COLOR_SCHEME_2023_PLUS_NAME) { + $themeColors = $themeColors ?? self::COLOR_SCHEME_2023_PLUS; + $this->majorFontLatin = 'Aptos Display'; + $this->minorFontLatin = 'Aptos Narrow'; } if ($themeColors !== null) { $this->themeColors = $themeColors; } + if ($spreadsheet !== null) { + $spreadsheet->getDefaultStyle()->getFont() + ->applyThemeFonts($this); + } return $this; } diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/ThemeColorsTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/ThemeColorsTest.php index 1b18603c5b..d8b5e64f38 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/ThemeColorsTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/ThemeColorsTest.php @@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Theme as SpreadsheetTheme; use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional; @@ -14,11 +15,48 @@ class ThemeColorsTest extends AbstractFunctional public function testOffice2013Theme(): void { $spreadsheet = new Spreadsheet(); - $spreadsheet->getTheme()->setThemeColorName(SpreadsheetTheme::COLOR_SCHEME_2013_PLUS_NAME); + $spreadsheet->getTheme() + ->setThemeColorName( + SpreadsheetTheme::COLOR_SCHEME_2013_PLUS_NAME //* @phpstan-ignore-line + ); $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); $spreadsheet->disconnectWorksheets(); - self::assertSame('Office 2013+', $reloadedSpreadsheet->getTheme()->getThemeColorName()); + self::assertSame( + SpreadsheetTheme::COLOR_SCHEME_2013_PLUS_NAME, //* @phpstan-ignore-line + $reloadedSpreadsheet->getTheme()->getThemeColorName() + ); self::assertSame('FFC000', $reloadedSpreadsheet->getTheme()->getThemeColors()['accent4']); + self::assertSame('Calibri Light', $reloadedSpreadsheet->getTheme()->getMajorFontLatin()); + self::assertSame('Calibri', $reloadedSpreadsheet->getTheme()->getMinorFontLatin()); + $defaultFont2 = $reloadedSpreadsheet->getDefaultStyle()->getFont()->getName(); + self::assertSame('Calibri', $defaultFont2); + $font3 = $reloadedSpreadsheet->getActiveSheet() + ->getStyle('Z10')->getFont()->getName(); + self::assertSame('Calibri', $font3); + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function testOffice2013Theme2(): void + { + $spreadsheet = new Spreadsheet(); + $spreadsheet->getTheme() + ->setThemeColorName( + SpreadsheetTheme::COLOR_SCHEME_2013_2022_NAME + ); + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + self::assertSame( + SpreadsheetTheme::COLOR_SCHEME_2013_2022_NAME, + $reloadedSpreadsheet->getTheme()->getThemeColorName() + ); + self::assertSame('FFC000', $reloadedSpreadsheet->getTheme()->getThemeColors()['accent4']); + self::assertSame('Calibri Light', $reloadedSpreadsheet->getTheme()->getMajorFontLatin()); + self::assertSame('Calibri', $reloadedSpreadsheet->getTheme()->getMinorFontLatin()); + $defaultFont2 = $reloadedSpreadsheet->getDefaultStyle()->getFont()->getName(); + self::assertSame('Calibri', $defaultFont2); + $font3 = $reloadedSpreadsheet->getActiveSheet() + ->getStyle('Z10')->getFont()->getName(); + self::assertSame('Calibri', $font3); $reloadedSpreadsheet->disconnectWorksheets(); } @@ -30,6 +68,13 @@ public function testOffice2007Theme(): void $spreadsheet->disconnectWorksheets(); self::assertSame('Office 2007-2010', $reloadedSpreadsheet->getTheme()->getThemeColorName()); self::assertSame('8064A2', $reloadedSpreadsheet->getTheme()->getThemeColors()['accent4']); + self::assertSame('Cambria', $reloadedSpreadsheet->getTheme()->getMajorFontLatin()); + self::assertSame('Calibri', $reloadedSpreadsheet->getTheme()->getMinorFontLatin()); + $defaultFont2 = $reloadedSpreadsheet->getDefaultStyle()->getFont()->getName(); + self::assertSame('Calibri', $defaultFont2); + $font3 = $reloadedSpreadsheet->getActiveSheet() + ->getStyle('Z10')->getFont()->getName(); + self::assertSame('Calibri', $font3); $reloadedSpreadsheet->disconnectWorksheets(); } @@ -40,6 +85,13 @@ public function testDefaultTheme(): void $spreadsheet->disconnectWorksheets(); self::assertSame('Office', $reloadedSpreadsheet->getTheme()->getThemeColorName()); self::assertSame('8064A2', $reloadedSpreadsheet->getTheme()->getThemeColors()['accent4']); + self::assertSame('Cambria', $reloadedSpreadsheet->getTheme()->getMajorFontLatin()); + self::assertSame('Calibri', $reloadedSpreadsheet->getTheme()->getMinorFontLatin()); + $defaultFont2 = $reloadedSpreadsheet->getDefaultStyle()->getFont()->getName(); + self::assertSame('Calibri', $defaultFont2); + $font3 = $reloadedSpreadsheet->getActiveSheet() + ->getStyle('Z10')->getFont()->getName(); + self::assertSame('Calibri', $font3); $reloadedSpreadsheet->disconnectWorksheets(); } @@ -53,4 +105,37 @@ public function testGalleryTheme(): void self::assertSame('795FAF', $reloadedSpreadsheet->getTheme()->getThemeColors()['accent4']); $reloadedSpreadsheet->disconnectWorksheets(); } + + public function testOffice2023Theme(): void + { + $spreadsheet = new Spreadsheet(); + $spreadsheet->getTheme() + ->setThemeColorName( + SpreadsheetTheme::COLOR_SCHEME_2023_PLUS_NAME, + null, + $spreadsheet + ); + self::assertSame('Aptos Narrow', $spreadsheet->getDefaultStyle()->getFont()->getName(), 'default style is attached to spreadsheet'); + $style = new Style(); + self::assertSame('Calibri', $style->getFont()->getName(), 'style not attached to spreadsheet'); + $style2 = $spreadsheet->getActiveSheet()->getStyle('A7'); + self::assertSame('Aptos Narrow', $style2->getFont()->getName(), 'font is attached to spreadsheet'); + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + self::assertSame( + SpreadsheetTheme::COLOR_SCHEME_2023_PLUS_NAME, + $reloadedSpreadsheet->getTheme()->getThemeColorName() + ); + self::assertSame('0F9ED5', $reloadedSpreadsheet->getTheme()->getThemeColors()['accent4']); + self::assertSame('Aptos Display', $reloadedSpreadsheet->getTheme()->getMajorFontLatin()); + self::assertSame('Aptos Narrow', $reloadedSpreadsheet->getTheme()->getMinorFontLatin()); + $defaultFont = $reloadedSpreadsheet->getDefaultStyle()->getFont()->getName(); + self::assertSame('Aptos Narrow', $defaultFont); + $defaultFont2 = $reloadedSpreadsheet->getDefaultStyle()->getFont()->getName(); + self::assertSame('Aptos Narrow', $defaultFont2); + $font3 = $reloadedSpreadsheet->getActiveSheet() + ->getStyle('Z10')->getFont()->getName(); + self::assertSame('Aptos Narrow', $font3); + $reloadedSpreadsheet->disconnectWorksheets(); + } }