From 718946f7ac76a514440a4a56342ef0cb6562c120 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Wed, 3 Sep 2025 18:44:08 -0500 Subject: [PATCH 01/23] Update RTF.php - Support for ListTable --- src/PhpWord/Writer/RTF.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/PhpWord/Writer/RTF.php b/src/PhpWord/Writer/RTF.php index 390311aa26..b694ee072d 100644 --- a/src/PhpWord/Writer/RTF.php +++ b/src/PhpWord/Writer/RTF.php @@ -101,6 +101,16 @@ public function getColorTable() return $this->getWriterPart('Header')->getColorTable(); } + /** + * Get list table. + * + * @return array + */ + public function getListTable() + { + return $this->getWriterPart('Header')->getListTable(); + } + /** * Get last paragraph style. * From 190a58a45d98f1cc409f6b6c605ece31e9bae7ba Mon Sep 17 00:00:00 2001 From: rasamassen Date: Wed, 3 Sep 2025 18:52:37 -0500 Subject: [PATCH 02/23] Update Header.php - Initial Commit for List support Adds $listTable, getListTable(), writeListTable(), and registerListItem(). Also extends registerHeaderItem() to check if item is instanceof Numbering. Most functions empty for now. Work forthcoming. #NOTE: Changes registerFont() to registerHeader() and registerFontItem() to registerHeaderItem(). This will break pull request #2818, but is simply fixed. --- src/PhpWord/Writer/RTF/Part/Header.php | 59 +++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 97644fe4ac..ac7f3270e2 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -23,6 +23,7 @@ use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Table; +use PhpOffice\PhpWord\Style\Numbering; /** * RTF header part writer. @@ -53,6 +54,13 @@ class Header extends AbstractPart */ private $colorTable = []; + /** + * List table. + * + * @var array + */ + private $listTable = []; + /** * Get font table. * @@ -73,6 +81,16 @@ public function getColorTable() return $this->colorTable; } + /** + * Get list table. + * + * @return array + */ + public function getListTable() + { + return $this->listTable; + } + /** * Write part. * @@ -80,7 +98,7 @@ public function getColorTable() */ public function write() { - $this->registerFont(); + $this->registerHeader(); $content = ''; @@ -88,6 +106,7 @@ public function write() $content .= $this->writeDefaults(); $content .= $this->writeFontTable(); $content .= $this->writeColorTable(); + $content .= $this->writeListTable(); $content .= $this->writeGenerator(); $content .= PHP_EOL; @@ -166,6 +185,18 @@ private function writeColorTable() return $content; } + /** + * Write list table. + * + * @return string + */ + private function writeListTable() + { + $content = ''; + + return $content; + } + /** * Write. * @@ -182,9 +213,9 @@ private function writeGenerator() } /** - * Register all fonts and colors in both named and inline styles to appropriate header table. + * Register all fonts, colors, and lists in both named and inline styles to appropriate header table. */ - private function registerFont(): void + private function registerHeader(): void { $phpWord = $this->getParentWriter()->getPhpWord(); $this->fontTable[] = Settings::getDefaultFontName(); @@ -192,7 +223,7 @@ private function registerFont(): void // Search named styles $styles = Style::getStyles(); foreach ($styles as $style) { - $this->registerFontItems($style); + $this->registerHeaderItems($style); } // Search inline styles @@ -203,7 +234,7 @@ private function registerFont(): void foreach ($elements as $element) { if (method_exists($element, 'getFontStyle')) { $style = $element->getFontStyle(); - $this->registerFontItems($style); + $this->registerHeaderItems($style); } } } @@ -225,11 +256,11 @@ private function registerBorderColor($style): void } /** - * Register fonts and colors. + * Register fonts, colors, and lists. * * @param Style\AbstractStyle $style */ - private function registerFontItems($style): void + private function registerHeaderItems($style): void { $defaultFont = Settings::getDefaultFontName(); $defaultColor = Settings::DEFAULT_FONT_COLOR; @@ -247,6 +278,9 @@ private function registerFontItems($style): void $this->registerTableItem($this->colorTable, $style->getBorderLeftColor(), $defaultColor); $this->registerTableItem($this->colorTable, $style->getBorderBottomColor(), $defaultColor); } + if ($style instanceof Numbering) { + $this->registerList($this->listTable, $style); + } } /** @@ -262,4 +296,15 @@ private function registerTableItem(&$table, $value, $default = null): void $table[] = $value; } } + + /** + * Register lists and fonts within lists. + * + * @param array &$table + * @param Style\Numbering $style + */ + private function registerList(&$table, $style): void + { + $table[] = []; + } } From b1f2b1d93d0adb6117c51659405de3897a221878 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Thu, 4 Sep 2025 09:30:40 -0500 Subject: [PATCH 03/23] Update Header.php - Write listtable and listoverridetable --- src/PhpWord/Writer/RTF/Part/Header.php | 202 ++++++++++++++++++++++++- 1 file changed, 201 insertions(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index ac7f3270e2..d470f09c09 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -194,6 +194,164 @@ private function writeListTable() { $content = ''; + $listType = [ + 'singleLevel' => '\listsimple1', + 'multilevel' => '\listsimple0', + 'hybridMultilevel' => '\listhybrid', + ]; + + // see page 31-34 of RTF 1.9.1 spec - unsure which code to use for commented out items + $numberType = [ + \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL => '0', + \PhpOffice\PhpWord\SimpleType\NumberFormat::UPPER_ROMAN => '1', + \PhpOffice\PhpWord\SimpleType\NumberFormat::LOWER_ROMAN => '2', + \PhpOffice\PhpWord\SimpleType\NumberFormat::UPPER_LETTER => '3', + \PhpOffice\PhpWord\SimpleType\NumberFormat::LOWER_LETTER => '4', + \PhpOffice\PhpWord\SimpleType\NumberFormat::ORDINAL => '5', + \PhpOffice\PhpWord\SimpleType\NumberFormat::CARDINAL_TEXT => '6', + \PhpOffice\PhpWord\SimpleType\NumberFormat::ORDINAL_TEXT => '7', + /* \PhpOffice\PhpWord\SimpleType\NumberFormat::HEX => 'hex', + \PhpOffice\PhpWord\SimpleType\NumberFormat::CHICAGO => 'chicago', + \PhpOffice\PhpWord\SimpleType\NumberFormat::IDEOGRAPH_DIGITAL => 'ideographDigital', + \PhpOffice\PhpWord\SimpleType\NumberFormat::JAPANESE_COUNTING => 'japaneseCounting', */ + \PhpOffice\PhpWord\SimpleType\NumberFormat::AIUEO => '12', + \PhpOffice\PhpWord\SimpleType\NumberFormat::IROHA => '13', + /* \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_FULL_WIDTH => 'decimalFullWidth', + \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_HALF_WIDTH => 'decimalHalfWidth', + \PhpOffice\PhpWord\SimpleType\NumberFormat::JAPANESE_LEGAL => 'japaneseLegal', + \PhpOffice\PhpWord\SimpleType\NumberFormat::JAPANESE_DIGITAL_TEN_THOUSAND => 'japaneseDigitalTenThousand', + \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_ENCLOSED_CIRCLE => 'decimalEnclosedCircle', + \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_FULL_WIDTH2 => 'decimalFullWidth2', */ + \PhpOffice\PhpWord\SimpleType\NumberFormat::AIUEO_FULL_WIDTH => '20', + \PhpOffice\PhpWord\SimpleType\NumberFormat::IROHA_FULL_WIDTH => '21', + /* \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_ZERO => 'decimalZero', + \PhpOffice\PhpWord\SimpleType\NumberFormat::BULLET => 'bullet', + \PhpOffice\PhpWord\SimpleType\NumberFormat::GANADA => 'ganada', + \PhpOffice\PhpWord\SimpleType\NumberFormat::CHOSUNG => 'chosung', + \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_ENCLOSED_FULL_STOP => 'decimalEnclosedFullstop', + \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_ENCLOSED_PAREN => 'decimalEnclosedParen', + \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_ENCLOSED_CIRCLE_CHINESE => 'decimalEnclosedCircleChinese', + \PhpOffice\PhpWord\SimpleType\NumberFormat::IDEOGRAPHENCLOSEDCIRCLE => 'ideographEnclosedCircle', + \PhpOffice\PhpWord\SimpleType\NumberFormat::IDEOGRAPH_TRADITIONAL => 'ideographTraditional', + \PhpOffice\PhpWord\SimpleType\NumberFormat::IDEOGRAPH_ZODIAC => 'ideographZodiac', + \PhpOffice\PhpWord\SimpleType\NumberFormat::IDEOGRAPH_ZODIAC_TRADITIONAL => 'ideographZodiacTraditional', + \PhpOffice\PhpWord\SimpleType\NumberFormat::TAIWANESE_COUNTING => 'taiwaneseCounting', + \PhpOffice\PhpWord\SimpleType\NumberFormat::IDEOGRAPH_LEGAL_TRADITIONAL => 'ideographLegalTraditional', + \PhpOffice\PhpWord\SimpleType\NumberFormat::TAIWANESE_COUNTING_THOUSAND => 'taiwaneseCountingThousand', + \PhpOffice\PhpWord\SimpleType\NumberFormat::TAIWANESE_DIGITAL => 'taiwaneseDigital', + \PhpOffice\PhpWord\SimpleType\NumberFormat::CHINESE_COUNTING => 'chineseCounting', + \PhpOffice\PhpWord\SimpleType\NumberFormat::CHINESE_LEGAL_SIMPLIFIED => 'chineseLegalSimplified', + \PhpOffice\PhpWord\SimpleType\NumberFormat::CHINESE_COUNTING_THOUSAND => 'chineseCountingThousand', + \PhpOffice\PhpWord\SimpleType\NumberFormat::KOREAN_DIGITAL => 'koreanDigital', + \PhpOffice\PhpWord\SimpleType\NumberFormat::KOREAN_COUNTING => 'koreanCounting', + \PhpOffice\PhpWord\SimpleType\NumberFormat::KOREAN_LEGAL => 'koreanLegal', + \PhpOffice\PhpWord\SimpleType\NumberFormat::KOREAN_DIGITAL2 => 'koreanDigital2', */ + \PhpOffice\PhpWord\SimpleType\NumberFormat::VIETNAMESE_COUNTING => '56', + \PhpOffice\PhpWord\SimpleType\NumberFormat::RUSSIAN_LOWER => '58', + \PhpOffice\PhpWord\SimpleType\NumberFormat::RUSSIAN_UPPER => '59', + \PhpOffice\PhpWord\SimpleType\NumberFormat::NONE => '255', + /* \PhpOffice\PhpWord\SimpleType\NumberFormat::NUMBER_IN_DASH => 'numberInDash', + \PhpOffice\PhpWord\SimpleType\NumberFormat::HEBREW1 => 'hebrew1', + \PhpOffice\PhpWord\SimpleType\NumberFormat::HEBREW2 => 'hebrew2', + \PhpOffice\PhpWord\SimpleType\NumberFormat::ARABIC_ALPHA => 'arabicAlpha', */ + \PhpOffice\PhpWord\SimpleType\NumberFormat::ARABIC_ABJAD => '48', + \PhpOffice\PhpWord\SimpleType\NumberFormat::HINDI_VOWELS => '49', + \PhpOffice\PhpWord\SimpleType\NumberFormat::HINDI_CONSONANTS => '50', + \PhpOffice\PhpWord\SimpleType\NumberFormat::HINDI_NUMBERS => '51', + \PhpOffice\PhpWord\SimpleType\NumberFormat::HINDI_COUNTING => '52', + \PhpOffice\PhpWord\SimpleType\NumberFormat::THAI_LETTERS => '53', + \PhpOffice\PhpWord\SimpleType\NumberFormat::THAI_NUMBERS => '54', + \PhpOffice\PhpWord\SimpleType\NumberFormat::THAI_COUNTING => '55', + ]; + + // listtable + $content .= '{'; + $content .= '\*\listtable' . PHP_EOL; + + foreach ($this->listTable as $list) { + // $list = RTF tag + // [0] = \listtemplateid && \listid + // [1] = \listsimple OR \listhybrid + // [2] = array for \listlevel + $content .= '{'; + $content .= '\list\listtemplateid' . $list[0]; + if (isset($listType[$list[1]])) { + $content .= $listType[$list[1]]; + } + $content .= PHP_EOL; + foreach ($list[2] as $listItem) { + // $listItem = RTF tag (may require manipulation) + // [0] = \listlevel + // [1] = \levelstartat + // [2] = \levellnfc + // [3] = \leveljc + // [4] = \leveltext && \levelnumbers + // [5] = \tx + // [6] = \li && \lin + // [7] = \fi + $content .= '{'; + $content .= '\listlevel'; + if (isset($numberType[$listItem[2]])) { + $content .= '\levelnfc' . $numberType[$listItem[2]]; + $content .= '\levelnfcn' . $numberType[$listItem[2]]; + } + $content .= '\leveljc' . $listItem[3]; + $content .= '\leveljcn' . $listItem[3]; + $content .= '\levelstartat' . $listItem[1]; + + // Level Text and Numbers + $level = $this->lowerDigitsByOne(str_replace('%', '\\\'0', $listItem[4])); + $levelNumbers = preg_replace('/\d/', 'X', str_replace('%', '', $listItem[4])); + $positions = []; + $offset = 0; + while (($pos = strpos($levelNumbers, 'X', $offset)) !== false) { + $positions[] = $pos; + $offset = $pos + 1; + } + $strLength = sprintf("%02d", strlen($levelNumbers)); + + $content .= '{'; + $content .= '\leveltext \\\'' . $strLength . $level; + $content .= ' ;}'; + $content .= '{'; + $content .= '\levelnumbers '; + foreach ($positions as $position) { + $position++; + $content .= '\\\'0' . $position; + } + $content .= ';}'; + + // Tabs, Hanging, and First Line + $content .= '\levelfollow' . '0'; + $content .= '\jclisttab'; + $content .= '\tx' . $listItem[5]; + $hanging = $listItem[6] + $listItem[7]; + $left = 0 - $listItem[7]; + $content .= '\fi' . $left; + $content .= '\li' . $hanging; + $content .= '\lin' . $hanging; + $content .= '}'; + $content .= PHP_EOL; + } + $content .= '\listid' . $list[0] . '}'; + $content .= PHP_EOL; + } + $content .= '}'; + $content .= PHP_EOL . PHP_EOL; + + // listoverridetable + $content .= '{'; + $content .= '\*\listoverridetable' . PHP_EOL; + foreach ($this->listTable as $list) { + $content .= '{'; + $content .= '\listoverride\listid' . $list[0]; + $content .= '\listoverridecount0\ls' . $list[0]; + $content .= '}'; + $content .= PHP_EOL; + } + $content .= '}'; + $content .= PHP_EOL . PHP_EOL; + return $content; } @@ -305,6 +463,48 @@ private function registerTableItem(&$table, $value, $default = null): void */ private function registerList(&$table, $style): void { - $table[] = []; + $listItems = []; + + $levels = $style->getLevels(); + foreach ($levels as $level) { + echo 'Level:' . $level->getLevel() . ' Start:' . $level->getStart() . ' Format:' . $level->getFormat() . ' Restart:' . $level->getRestart() . ' PStyle:' . $level->getPStyle() . ' Suffix:' . $level->getSuffix() . ' Text:' . $level->getText() . ' Alignment:' . $level->getAlignment() . ' Left:' . $level->getLeft() . ' Hanging:' . $level->getHanging() . ' TabPos:' . $level->getTabPos() . ' Font:' . $level->getFont() . ' Hint:' . $level->getHint() . '
'; + $this->registerTableItem($this->fontTable, $level->getFont(), $defaultFont); + + /** + * $listItem = RTF tag (may require manipulation for correct output) + * [$level->getLevel()] = \listlevel + * [$level->getStart()] = \levelstartat + * [$level->getFormat()] = \levellnfc + * [$level->getAlignment()] = \leveljc + * [$level->getText()] = \leveltext && \levelnumbers + * [$level->getTabPos()] = \tx + * [$level->getLeft()] = \li && \lin + * [$level->getHanging()] = \fi */ + $listItem = [$level->getLevel(), $level->getStart(), $level->getFormat(), $level->getAlignment(), $level->getText(), $level->getTabPos(), $level->getLeft(), $level->getHanging()]; + array_push($listItems, $listItem); + } + + /** + * $list = RTF tag in listtable + * [$style->getNumId()] = \listtemplateid && \listid + * [$style->getType()] = \listsimple OR \listhybrid + * [$listItems] = array for \listlevel */ + $list = [$style->getNumId(), $style->getType(), $listItems]; + $table[] = $list; + } + + /** + * NumberingLevel->getText() returns levels a step higher than expected in RTF \leveltext, (1-9) instead of (0-8). + * Thus all the digits need to be reduced by 1. + * + * @param string $string + */ + + private function lowerDigitsByOne($string) { + return preg_replace_callback('/\d/', function($matches) { + $digit = (int)$matches[0]; + // Ensure the digit does not go below 0 + return ($digit > 0) ? ($digit - 1) : $digit; + }, $string); } } From 000efe4ff46ddd747bceafb400181371a7be6c9a Mon Sep 17 00:00:00 2001 From: rasamassen Date: Thu, 4 Sep 2025 09:34:00 -0500 Subject: [PATCH 04/23] Update ListItem.php - RTF Writer needs to access the NumberingStyle For some reason in RTF, defining the tab, hanging, and firstLine in the listtable (which is part of the spec) does not transfer down to the list (at least not in Word365), so it must be defined in each listitem individually. --- src/PhpWord/Style/ListItem.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index e34aeb7c7c..11e58bbdca 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -115,6 +115,21 @@ public function getNumStyle() return $this->numStyle; } + /** + * Get numbering style. + * + * @return ?NumberingStyle + */ + public function getNumberingStyle() + { + $numStyleObject = Style::getStyle($this->numStyle); + if ($numStyleObject instanceof Numbering) { + return $numStyleObject; + } + + return null; + } + /** * Set numbering style name. * From 73cb2ba424f91686a66fabd8b1e4920ebef06b4d Mon Sep 17 00:00:00 2001 From: rasamassen Date: Thu, 4 Sep 2025 09:35:37 -0500 Subject: [PATCH 05/23] Update ListItem.php - Make writer work for basic lists --- src/PhpWord/Writer/RTF/Element/ListItem.php | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/PhpWord/Writer/RTF/Element/ListItem.php b/src/PhpWord/Writer/RTF/Element/ListItem.php index 8ce7d0d063..d682d6facd 100644 --- a/src/PhpWord/Writer/RTF/Element/ListItem.php +++ b/src/PhpWord/Writer/RTF/Element/ListItem.php @@ -25,4 +25,40 @@ */ class ListItem extends Text { + /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + $element = $this->element; + if (!$element instanceof \PhpOffice\PhpWord\Element\ListItem) { + return; + } + + $this->getStyles(); + + $depth = (int) $element->getDepth(); + $style = $element->getStyle(); + $numStyle = $style->getNumberingStyle(); + $levels = $numStyle->getLevels(); + $text = $element->getTextObject(); + + // Bullet List + $content = ''; + $content .= $this->writeOpening(); + $content .= '\ilvl' . $element->getDepth(); + $content .= '\ls' . $style->getNumId(); + $content .= '\tx' . $levels[$depth]->getTabPos(); + $hanging = $levels[$depth]->getLeft() + $levels[$depth]->getHanging(); + $left = 0 - $levels[$depth]->getHanging(); + $content .= '\fi' . $left; + $content .= '\li' . $hanging; + $content .= '\lin' . $hanging; + $content .= $this->writeFontStyle(); // Doesn't work. Don't know why. Probalby something to do with \PphOffice\PhpWord\Element\ListItem storing styles in a textObject type \PphOffice\PhpWord\Element\Text rather than within the Element itself + $content .= PHP_EOL; + /* $content .= '{\listtext\f2 \\\'b7\tab }'; // Not sure if needed for listItemRun + $content .= PHP_EOL; */ + $content .= '{'; + $content .= $this->writeText($element->getText()); + $content .= '}'; + $content .= PHP_EOL; + $content .= $this->writeClosing(); + + return $content; } From 7e5f31ad582660de2515de4a0a8d9fc0585f8e24 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Thu, 4 Sep 2025 09:44:22 -0500 Subject: [PATCH 06/23] Update ListItem.php - Accidentally rmoved function --- src/PhpWord/Writer/RTF/Element/ListItem.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/PhpWord/Writer/RTF/Element/ListItem.php b/src/PhpWord/Writer/RTF/Element/ListItem.php index d682d6facd..3db105fa14 100644 --- a/src/PhpWord/Writer/RTF/Element/ListItem.php +++ b/src/PhpWord/Writer/RTF/Element/ListItem.php @@ -25,6 +25,11 @@ */ class ListItem extends Text { + /** + * Write list item element. + */ + public function write() + { /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ $element = $this->element; if (!$element instanceof \PhpOffice\PhpWord\Element\ListItem) { @@ -61,4 +66,5 @@ class ListItem extends Text $content .= $this->writeClosing(); return $content; + } } From ffd4dd2f3cf5e19da695bae1679049b35b058569 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Thu, 4 Sep 2025 09:53:35 -0500 Subject: [PATCH 07/23] Update Header.php - Minor fixes --- src/PhpWord/Writer/RTF/Part/Header.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index d470f09c09..0df17da6f2 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -306,9 +306,9 @@ private function writeListTable() $offset = 0; while (($pos = strpos($levelNumbers, 'X', $offset)) !== false) { $positions[] = $pos; - $offset = $pos + 1; + $offset = $pos + 1; } - $strLength = sprintf("%02d", strlen($levelNumbers)); + $strLength = sprintf('%02d', strlen($levelNumbers)); $content .= '{'; $content .= '\leveltext \\\'' . $strLength . $level; @@ -316,7 +316,7 @@ private function writeListTable() $content .= '{'; $content .= '\levelnumbers '; foreach ($positions as $position) { - $position++; + ++$position; $content .= '\\\'0' . $position; } $content .= ';}'; @@ -343,7 +343,7 @@ private function writeListTable() $content .= '{'; $content .= '\*\listoverridetable' . PHP_EOL; foreach ($this->listTable as $list) { - $content .= '{'; + $content .= '{'; $content .= '\listoverride\listid' . $list[0]; $content .= '\listoverridecount0\ls' . $list[0]; $content .= '}'; @@ -437,7 +437,7 @@ private function registerHeaderItems($style): void $this->registerTableItem($this->colorTable, $style->getBorderBottomColor(), $defaultColor); } if ($style instanceof Numbering) { - $this->registerList($this->listTable, $style); + $this->registerList($this->listTable, $style, $defaultFont); } } @@ -459,9 +459,10 @@ private function registerTableItem(&$table, $value, $default = null): void * Register lists and fonts within lists. * * @param array &$table - * @param Style\Numbering $style + * @param Numbering $style + * @param string $default */ - private function registerList(&$table, $style): void + private function registerList(&$table, $style, $defaultFont): void { $listItems = []; @@ -492,7 +493,7 @@ private function registerList(&$table, $style): void $list = [$style->getNumId(), $style->getType(), $listItems]; $table[] = $list; } - + /** * NumberingLevel->getText() returns levels a step higher than expected in RTF \leveltext, (1-9) instead of (0-8). * Thus all the digits need to be reduced by 1. @@ -500,7 +501,8 @@ private function registerList(&$table, $style): void * @param string $string */ - private function lowerDigitsByOne($string) { + private function lowerDigitsByOne($string) + { return preg_replace_callback('/\d/', function($matches) { $digit = (int)$matches[0]; // Ensure the digit does not go below 0 From 07ff04706cc0fd28ff1a7097e0d417c255abd0fd Mon Sep 17 00:00:00 2001 From: rasamassen Date: Thu, 4 Sep 2025 09:54:51 -0500 Subject: [PATCH 08/23] Update ListItem.php - format fix --- src/PhpWord/Writer/RTF/Element/ListItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/RTF/Element/ListItem.php b/src/PhpWord/Writer/RTF/Element/ListItem.php index 3db105fa14..81e57454c4 100644 --- a/src/PhpWord/Writer/RTF/Element/ListItem.php +++ b/src/PhpWord/Writer/RTF/Element/ListItem.php @@ -37,7 +37,7 @@ public function write() } $this->getStyles(); - + $depth = (int) $element->getDepth(); $style = $element->getStyle(); $numStyle = $style->getNumberingStyle(); From c311204c4c0cd364570c165a4c4e19af4d22ba30 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Thu, 4 Sep 2025 09:56:31 -0500 Subject: [PATCH 09/23] Update Header.php - Formatting fixes --- src/PhpWord/Writer/RTF/Part/Header.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 0df17da6f2..10c6d6445e 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -460,7 +460,7 @@ private function registerTableItem(&$table, $value, $default = null): void * * @param array &$table * @param Numbering $style - * @param string $default + * @param string $defaultFont */ private function registerList(&$table, $style, $defaultFont): void { @@ -503,8 +503,8 @@ private function registerList(&$table, $style, $defaultFont): void private function lowerDigitsByOne($string) { - return preg_replace_callback('/\d/', function($matches) { - $digit = (int)$matches[0]; + return preg_replace_callback('/\d/', function ($matches) { + $digit = (int) $matches[0]; // Ensure the digit does not go below 0 return ($digit > 0) ? ($digit - 1) : $digit; }, $string); From f73a09d1c5a5f8ba1f2b0caa92344a6610abb199 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Thu, 4 Sep 2025 09:59:04 -0500 Subject: [PATCH 10/23] Update Header.php - Fix formatting --- src/PhpWord/Writer/RTF/Part/Header.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 10c6d6445e..17e9778c81 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -500,11 +500,11 @@ private function registerList(&$table, $style, $defaultFont): void * * @param string $string */ - private function lowerDigitsByOne($string) { return preg_replace_callback('/\d/', function ($matches) { $digit = (int) $matches[0]; + // Ensure the digit does not go below 0 return ($digit > 0) ? ($digit - 1) : $digit; }, $string); From 42e94f242f92e42611a42a8fbf27162e13257549 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Thu, 4 Sep 2025 10:01:28 -0500 Subject: [PATCH 11/23] Update Header.php - Fix formatting --- src/PhpWord/Writer/RTF/Part/Header.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 17e9778c81..dca1313a12 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -22,8 +22,8 @@ use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; -use PhpOffice\PhpWord\Style\Table; use PhpOffice\PhpWord\Style\Numbering; +use PhpOffice\PhpWord\Style\Table; /** * RTF header part writer. From 1db3452d7c64f438dd1bb12f3340741e9cb2c8a2 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Fri, 5 Sep 2025 12:00:43 -0500 Subject: [PATCH 12/23] Update ListItem.php - ensure $style instanceof ListItem --- src/PhpWord/Writer/RTF/Element/ListItem.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Element/ListItem.php b/src/PhpWord/Writer/RTF/Element/ListItem.php index 81e57454c4..32e6f1c886 100644 --- a/src/PhpWord/Writer/RTF/Element/ListItem.php +++ b/src/PhpWord/Writer/RTF/Element/ListItem.php @@ -47,14 +47,18 @@ public function write() // Bullet List $content = ''; $content .= $this->writeOpening(); - $content .= '\ilvl' . $element->getDepth(); - $content .= '\ls' . $style->getNumId(); - $content .= '\tx' . $levels[$depth]->getTabPos(); - $hanging = $levels[$depth]->getLeft() + $levels[$depth]->getHanging(); - $left = 0 - $levels[$depth]->getHanging(); - $content .= '\fi' . $left; - $content .= '\li' . $hanging; - $content .= '\lin' . $hanging; + if ($style instanceof \PhpOffice\PhpWord\Style\ListItem) { + $numStyle = $style->getNumberingStyle(); + $levels = $numStyle->getLevels(); + $content .= '\ilvl' . $element->getDepth(); + $content .= '\ls' . $style->getNumId(); + $content .= '\tx' . $levels[$depth]->getTabPos(); + $hanging = $levels[$depth]->getLeft() + $levels[$depth]->getHanging(); + $left = 0 - $levels[$depth]->getHanging(); + $content .= '\fi' . $left; + $content .= '\li' . $hanging; + $content .= '\lin' . $hanging; + } $content .= $this->writeFontStyle(); // Doesn't work. Don't know why. Probalby something to do with \PphOffice\PhpWord\Element\ListItem storing styles in a textObject type \PphOffice\PhpWord\Element\Text rather than within the Element itself $content .= PHP_EOL; /* $content .= '{\listtext\f2 \\\'b7\tab }'; // Not sure if needed for listItemRun From 4530e1be4ddc05d98866d6f888b2465e3a25438f Mon Sep 17 00:00:00 2001 From: rasamassen Date: Fri, 5 Sep 2025 12:01:16 -0500 Subject: [PATCH 13/23] Update ListItem.php - ensure $style instanceof ListItem additional --- src/PhpWord/Writer/RTF/Element/ListItem.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Element/ListItem.php b/src/PhpWord/Writer/RTF/Element/ListItem.php index 32e6f1c886..0c1a4caf23 100644 --- a/src/PhpWord/Writer/RTF/Element/ListItem.php +++ b/src/PhpWord/Writer/RTF/Element/ListItem.php @@ -40,8 +40,6 @@ public function write() $depth = (int) $element->getDepth(); $style = $element->getStyle(); - $numStyle = $style->getNumberingStyle(); - $levels = $numStyle->getLevels(); $text = $element->getTextObject(); // Bullet List From 5f590b3f4fe6c8ef7536f03e606d603d38d60be5 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Fri, 5 Sep 2025 12:06:50 -0500 Subject: [PATCH 14/23] Update Header.php - Make Bullet Lists work properly --- src/PhpWord/Writer/RTF/Part/Header.php | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index dca1313a12..b98c4f7536 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -300,15 +300,22 @@ private function writeListTable() $content .= '\levelstartat' . $listItem[1]; // Level Text and Numbers - $level = $this->lowerDigitsByOne(str_replace('%', '\\\'0', $listItem[4])); - $levelNumbers = preg_replace('/\d/', 'X', str_replace('%', '', $listItem[4])); $positions = []; - $offset = 0; - while (($pos = strpos($levelNumbers, 'X', $offset)) !== false) { - $positions[] = $pos; - $offset = $pos + 1; + $level = ''; + $strLength = ''; + if (strpos($listItem[4], '%') !== false) { + $level = $this->lowerDigitsByOne(str_replace('%', '\\\'0', $listItem[4])); + $levelNumbers = preg_replace('/\d/', 'X', str_replace('%', '', $listItem[4])); + $offset = 0; + while (($pos = strpos($levelNumbers, 'X', $offset)) !== false) { + $positions[] = $pos; + $offset = $pos + 1; + } + $strLength = sprintf("%02d", strlen($levelNumbers)); + } else { + $level = '\\\'' . strtoupper(dechex(ord(iconv('UTF-8', 'UCS-2', $listItem[4])))); + $strLength = '01'; } - $strLength = sprintf('%02d', strlen($levelNumbers)); $content .= '{'; $content .= '\leveltext \\\'' . $strLength . $level; @@ -500,7 +507,7 @@ private function registerList(&$table, $style, $defaultFont): void * * @param string $string */ - private function lowerDigitsByOne($string) + private function lowerDigitsByOne($string): string { return preg_replace_callback('/\d/', function ($matches) { $digit = (int) $matches[0]; From 4ff9eedfa29672ca14720f54dfffd4c179103947 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Fri, 5 Sep 2025 15:19:28 -0500 Subject: [PATCH 15/23] Update ListItem.php - Address CS Fixer Issue --- src/PhpWord/Style/ListItem.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index 11e58bbdca..e7699704fb 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -19,6 +19,7 @@ namespace PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Style\Numbering as NumberingStyle; /** * List item style. From 19bc0878c6f8feec5db336f74da2773ff6209a69 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Fri, 5 Sep 2025 15:23:13 -0500 Subject: [PATCH 16/23] Update ListItem.php - Fix Static Analysis error --- src/PhpWord/Writer/RTF/Element/ListItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/RTF/Element/ListItem.php b/src/PhpWord/Writer/RTF/Element/ListItem.php index 0c1a4caf23..5fd7c24a75 100644 --- a/src/PhpWord/Writer/RTF/Element/ListItem.php +++ b/src/PhpWord/Writer/RTF/Element/ListItem.php @@ -33,7 +33,7 @@ public function write() /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ $element = $this->element; if (!$element instanceof \PhpOffice\PhpWord\Element\ListItem) { - return; + return ''; } $this->getStyles(); From 4a800d1dd121b0abb9dc2c477e3bf8ef52cb939c Mon Sep 17 00:00:00 2001 From: rasamassen Date: Fri, 12 Sep 2025 22:08:31 -0500 Subject: [PATCH 17/23] Update Header.php - ListTable Fixes, Improvements, and Additions --- src/PhpWord/Writer/RTF/Part/Header.php | 143 +++++++++++++------------ 1 file changed, 75 insertions(+), 68 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index b98c4f7536..710e0374c4 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -224,9 +224,9 @@ private function writeListTable() \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_FULL_WIDTH2 => 'decimalFullWidth2', */ \PhpOffice\PhpWord\SimpleType\NumberFormat::AIUEO_FULL_WIDTH => '20', \PhpOffice\PhpWord\SimpleType\NumberFormat::IROHA_FULL_WIDTH => '21', - /* \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_ZERO => 'decimalZero', - \PhpOffice\PhpWord\SimpleType\NumberFormat::BULLET => 'bullet', - \PhpOffice\PhpWord\SimpleType\NumberFormat::GANADA => 'ganada', + /* \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_ZERO => 'decimalZero', */ + \PhpOffice\PhpWord\SimpleType\NumberFormat::BULLET => '23', + /* \PhpOffice\PhpWord\SimpleType\NumberFormat::GANADA => 'ganada', \PhpOffice\PhpWord\SimpleType\NumberFormat::CHOSUNG => 'chosung', \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_ENCLOSED_FULL_STOP => 'decimalEnclosedFullstop', \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_ENCLOSED_PAREN => 'decimalEnclosedParen', @@ -264,56 +264,53 @@ private function writeListTable() \PhpOffice\PhpWord\SimpleType\NumberFormat::THAI_COUNTING => '55', ]; + $listAlignment = [ + 'left' => '0', + 'center' => '1', + 'right' => '2', + ]; + // listtable $content .= '{'; $content .= '\*\listtable' . PHP_EOL; foreach ($this->listTable as $list) { - // $list = RTF tag - // [0] = \listtemplateid && \listid - // [1] = \listsimple OR \listhybrid - // [2] = array for \listlevel $content .= '{'; - $content .= '\list\listtemplateid' . $list[0]; - if (isset($listType[$list[1]])) { - $content .= $listType[$list[1]]; + $content .= '\list\listtemplateid' . $list['numId']; + if (isset($listType[$list['type']])) { + $content .= $listType[$list['type']]; } $content .= PHP_EOL; - foreach ($list[2] as $listItem) { - // $listItem = RTF tag (may require manipulation) - // [0] = \listlevel - // [1] = \levelstartat - // [2] = \levellnfc - // [3] = \leveljc - // [4] = \leveltext && \levelnumbers - // [5] = \tx - // [6] = \li && \lin - // [7] = \fi + foreach ($list['listItems'] as $listItem) { + $content .= '{'; $content .= '\listlevel'; - if (isset($numberType[$listItem[2]])) { - $content .= '\levelnfc' . $numberType[$listItem[2]]; - $content .= '\levelnfcn' . $numberType[$listItem[2]]; + if (isset($numberType[$listItem['format']])) { + $content .= '\levelnfc' . $numberType[$listItem['format']]; + $content .= '\levelnfcn' . $numberType[$listItem['format']]; } - $content .= '\leveljc' . $listItem[3]; - $content .= '\leveljcn' . $listItem[3]; - $content .= '\levelstartat' . $listItem[1]; + if (isset($listAlignment[$listItem['alignment']])) { + $content .= '\leveljc' . $listAlignment[$listItem['alignment']]; + $content .= '\leveljcn' . $listAlignment[$listItem['alignment']]; + } + $content .= '\levelstartat' . $listItem['start']; + if (isset($listItem['restart'])) { $content .= '\levelnorestart' . $listItem['restart']; } // Level Text and Numbers $positions = []; $level = ''; $strLength = ''; - if (strpos($listItem[4], '%') !== false) { - $level = $this->lowerDigitsByOne(str_replace('%', '\\\'0', $listItem[4])); - $levelNumbers = preg_replace('/\d/', 'X', str_replace('%', '', $listItem[4])); + if (strpos($listItem['text'], '%') !== false) { + $level = $this->lowerDigitsByOne(str_replace('%', '\\\'0', $listItem['text'])); + $levelNumbers = (string) preg_replace('/\d/', 'X', str_replace('%', '', $listItem['text'])); $offset = 0; while (($pos = strpos($levelNumbers, 'X', $offset)) !== false) { $positions[] = $pos; $offset = $pos + 1; } - $strLength = sprintf("%02d", strlen($levelNumbers)); + $strLength = (string) sprintf("%02d", strlen($levelNumbers)); } else { - $level = '\\\'' . strtoupper(dechex(ord(iconv('UTF-8', 'UCS-2', $listItem[4])))); + $level = '\\\'' . (string) strtoupper(dechex(ord(iconv('UTF-8', 'UCS-2', $listItem['text'])))); $strLength = '01'; } @@ -323,36 +320,46 @@ private function writeListTable() $content .= '{'; $content .= '\levelnumbers '; foreach ($positions as $position) { - ++$position; + $position++; $content .= '\\\'0' . $position; } $content .= ';}'; + print_r($listItem); + echo '
'; + + // Font settings for Level Numbers + if (isset($listItem['font']) && !empty($listItem['font'])) { + $fontKey = array_search($listItem['font'], $this->fontTable); + if ($fontKey !== FALSE) { + $content .= '\f' . $fontKey; + + } + } + // Tabs, Hanging, and First Line $content .= '\levelfollow' . '0'; $content .= '\jclisttab'; - $content .= '\tx' . $listItem[5]; - $hanging = $listItem[6] + $listItem[7]; - $left = 0 - $listItem[7]; - $content .= '\fi' . $left; - $content .= '\li' . $hanging; - $content .= '\lin' . $hanging; + $content .= '\tx' . $listItem['tabPos']; + $hanging = 0 - $listItem['hanging']; + $content .= '\fi' . $hanging; + $content .= '\li' . $listItem['left']; + $content .= '\lin' . $listItem['left']; $content .= '}'; $content .= PHP_EOL; } - $content .= '\listid' . $list[0] . '}'; + $content .= '\listid' . $list['numId'] . '}'; $content .= PHP_EOL; } $content .= '}'; $content .= PHP_EOL . PHP_EOL; - // listoverridetable $content .= '{'; $content .= '\*\listoverridetable' . PHP_EOL; foreach ($this->listTable as $list) { - $content .= '{'; - $content .= '\listoverride\listid' . $list[0]; - $content .= '\listoverridecount0\ls' . $list[0]; + $content .= '{'; + $content .= '\listoverride\listid' . $list['numId']; + $content .= '\listoverridecount0\ls' . $list['numId']; $content .= '}'; $content .= PHP_EOL; } @@ -466,38 +473,39 @@ private function registerTableItem(&$table, $value, $default = null): void * Register lists and fonts within lists. * * @param array &$table - * @param Numbering $style - * @param string $defaultFont + * @param Style\Numbering $style */ - private function registerList(&$table, $style, $defaultFont): void + private function registerList(&$table, $style): void { $listItems = []; $levels = $style->getLevels(); foreach ($levels as $level) { - echo 'Level:' . $level->getLevel() . ' Start:' . $level->getStart() . ' Format:' . $level->getFormat() . ' Restart:' . $level->getRestart() . ' PStyle:' . $level->getPStyle() . ' Suffix:' . $level->getSuffix() . ' Text:' . $level->getText() . ' Alignment:' . $level->getAlignment() . ' Left:' . $level->getLeft() . ' Hanging:' . $level->getHanging() . ' TabPos:' . $level->getTabPos() . ' Font:' . $level->getFont() . ' Hint:' . $level->getHint() . '
'; $this->registerTableItem($this->fontTable, $level->getFont(), $defaultFont); - /** - * $listItem = RTF tag (may require manipulation for correct output) - * [$level->getLevel()] = \listlevel - * [$level->getStart()] = \levelstartat - * [$level->getFormat()] = \levellnfc - * [$level->getAlignment()] = \leveljc - * [$level->getText()] = \leveltext && \levelnumbers - * [$level->getTabPos()] = \tx - * [$level->getLeft()] = \li && \lin - * [$level->getHanging()] = \fi */ - $listItem = [$level->getLevel(), $level->getStart(), $level->getFormat(), $level->getAlignment(), $level->getText(), $level->getTabPos(), $level->getLeft(), $level->getHanging()]; + $listItem = [ + 'level' => $level->getLevel(), + 'start' => $level->getStart(), + 'restart' => $level->getRestart(), + 'format' => $level->getFormat(), + 'pStyle' => $level->getPStyle(), + 'suffix' => $level->getSuffix(), + 'text' => $level->getText(), + 'alignment' => $level->getAlignment(), + 'left' => $level->getLeft(), + 'hanging' => $level->getHanging(), + 'tabPos' => $level->getTabPos(), + 'font' => $level->getFont(), + 'hint' => $level->getHint(), + ]; array_push($listItems, $listItem); } - /** - * $list = RTF tag in listtable - * [$style->getNumId()] = \listtemplateid && \listid - * [$style->getType()] = \listsimple OR \listhybrid - * [$listItems] = array for \listlevel */ - $list = [$style->getNumId(), $style->getType(), $listItems]; + $list = [ + 'numId' => $style->getNumId(), + 'type' => $style->getType(), + 'listItems' => $listItems, + ]; $table[] = $list; } @@ -507,13 +515,12 @@ private function registerList(&$table, $style, $defaultFont): void * * @param string $string */ + private function lowerDigitsByOne($string): string { - return preg_replace_callback('/\d/', function ($matches) { + return preg_replace_callback('/\d/', function($matches) { $digit = (int) $matches[0]; - - // Ensure the digit does not go below 0 - return ($digit > 0) ? ($digit - 1) : $digit; + return ($digit > 0) ? (string) ($digit - 1) : (string) $digit; }, $string); } } From 647929288ab93232f3b7b6efec096ca5745f0ccd Mon Sep 17 00:00:00 2001 From: rasamassen Date: Fri, 12 Sep 2025 22:19:25 -0500 Subject: [PATCH 18/23] Update 1.5.0.md - Changelog for Pull 2821 --- docs/changes/1.x/1.5.0.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changes/1.x/1.5.0.md b/docs/changes/1.x/1.5.0.md index b96865bada..03da4ad70a 100644 --- a/docs/changes/1.x/1.5.0.md +++ b/docs/changes/1.x/1.5.0.md @@ -7,6 +7,7 @@ ### Bug fixes - Set writeAttribute return type by [@radarhere](https://github.com/radarhere) fixing [#2204](https://github.com/PHPOffice/PHPWord/issues/2204) in [#2776](https://github.com/PHPOffice/PHPWord/pull/2776) +- Writer RTF: Support listItem, including listTable by [@rasamassen](https://github.com/rasamassen) in [#2821](https://github.com/PHPOffice/PHPWord/pull/2821), fixing [#1106](https://github.com/PHPOffice/PHPWord/issues/1106) ### Miscellaneous @@ -16,4 +17,4 @@ ### BC Breaks -### Notes \ No newline at end of file +### Notes From 9f16198caf9e0312a37e383bc1c5336e4273ea9b Mon Sep 17 00:00:00 2001 From: rasamassen Date: Fri, 12 Sep 2025 22:20:53 -0500 Subject: [PATCH 19/23] Update Header.php - Minor Fixes --- src/PhpWord/Writer/RTF/Part/Header.php | 46 ++++++++++++++++---------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 710e0374c4..b0ec125277 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -200,7 +200,6 @@ private function writeListTable() 'hybridMultilevel' => '\listhybrid', ]; - // see page 31-34 of RTF 1.9.1 spec - unsure which code to use for commented out items $numberType = [ \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL => '0', \PhpOffice\PhpWord\SimpleType\NumberFormat::UPPER_ROMAN => '1', @@ -224,7 +223,7 @@ private function writeListTable() \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_FULL_WIDTH2 => 'decimalFullWidth2', */ \PhpOffice\PhpWord\SimpleType\NumberFormat::AIUEO_FULL_WIDTH => '20', \PhpOffice\PhpWord\SimpleType\NumberFormat::IROHA_FULL_WIDTH => '21', - /* \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_ZERO => 'decimalZero', */ + // \PhpOffice\PhpWord\SimpleType\NumberFormat::DECIMAL_ZERO => 'decimalZero', \PhpOffice\PhpWord\SimpleType\NumberFormat::BULLET => '23', /* \PhpOffice\PhpWord\SimpleType\NumberFormat::GANADA => 'ganada', \PhpOffice\PhpWord\SimpleType\NumberFormat::CHOSUNG => 'chosung', @@ -270,7 +269,6 @@ private function writeListTable() 'right' => '2', ]; - // listtable $content .= '{'; $content .= '\*\listtable' . PHP_EOL; @@ -282,7 +280,6 @@ private function writeListTable() } $content .= PHP_EOL; foreach ($list['listItems'] as $listItem) { - $content .= '{'; $content .= '\listlevel'; if (isset($numberType[$listItem['format']])) { @@ -294,7 +291,9 @@ private function writeListTable() $content .= '\leveljcn' . $listAlignment[$listItem['alignment']]; } $content .= '\levelstartat' . $listItem['start']; - if (isset($listItem['restart'])) { $content .= '\levelnorestart' . $listItem['restart']; } + if (isset($listItem['restart'])) { + $content .= '\levelnorestart' . $listItem['restart']; + } // Level Text and Numbers $positions = []; @@ -306,9 +305,9 @@ private function writeListTable() $offset = 0; while (($pos = strpos($levelNumbers, 'X', $offset)) !== false) { $positions[] = $pos; - $offset = $pos + 1; + $offset = $pos + 1; } - $strLength = (string) sprintf("%02d", strlen($levelNumbers)); + $strLength = (string) sprintf('%02d', strlen($levelNumbers)); } else { $level = '\\\'' . (string) strtoupper(dechex(ord(iconv('UTF-8', 'UCS-2', $listItem['text'])))); $strLength = '01'; @@ -320,20 +319,16 @@ private function writeListTable() $content .= '{'; $content .= '\levelnumbers '; foreach ($positions as $position) { - $position++; + ++$position; $content .= '\\\'0' . $position; } $content .= ';}'; - print_r($listItem); - echo '
'; - // Font settings for Level Numbers if (isset($listItem['font']) && !empty($listItem['font'])) { $fontKey = array_search($listItem['font'], $this->fontTable); - if ($fontKey !== FALSE) { + if ($fontKey !== false) { $content .= '\f' . $fontKey; - } } @@ -357,7 +352,7 @@ private function writeListTable() $content .= '{'; $content .= '\*\listoverridetable' . PHP_EOL; foreach ($this->listTable as $list) { - $content .= '{'; + $content .= '{'; $content .= '\listoverride\listid' . $list['numId']; $content .= '\listoverridecount0\ls' . $list['numId']; $content .= '}'; @@ -469,13 +464,28 @@ private function registerTableItem(&$table, $value, $default = null): void } } + /** + * Register individual font and color. + * + * @param array &$table + * @param string $value + * @param string $default + */ + private function registerTableItem(&$table, $value, $default = null): void + { + if (in_array($value, $table) === false && $value !== null && $value != $default) { + $table[] = $value; + } + } + /** * Register lists and fonts within lists. * * @param array &$table - * @param Style\Numbering $style + * @param Numbering $style + * @param string $defaultFont */ - private function registerList(&$table, $style): void + private function registerList(&$table, $style, $defaultFont = null): void { $listItems = []; @@ -515,11 +525,11 @@ private function registerList(&$table, $style): void * * @param string $string */ - private function lowerDigitsByOne($string): string { - return preg_replace_callback('/\d/', function($matches) { + return preg_replace_callback('/\d/', function ($matches) { $digit = (int) $matches[0]; + return ($digit > 0) ? (string) ($digit - 1) : (string) $digit; }, $string); } From d727be7d7689606c66dc30103f3cf35fa5fdf385 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Fri, 12 Sep 2025 22:27:28 -0500 Subject: [PATCH 20/23] Update Header.php - Fix --- src/PhpWord/Writer/RTF/Part/Header.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index b0ec125277..67c1ea42a0 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -464,20 +464,6 @@ private function registerTableItem(&$table, $value, $default = null): void } } - /** - * Register individual font and color. - * - * @param array &$table - * @param string $value - * @param string $default - */ - private function registerTableItem(&$table, $value, $default = null): void - { - if (in_array($value, $table) === false && $value !== null && $value != $default) { - $table[] = $value; - } - } - /** * Register lists and fonts within lists. * From 0f4bb94d6bb6c7aa998f1d20b6b55d1c6e558ceb Mon Sep 17 00:00:00 2001 From: rasamassen Date: Fri, 12 Sep 2025 22:48:07 -0500 Subject: [PATCH 21/23] Update Header.php - Fixing Static --- src/PhpWord/Writer/RTF/Part/Header.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 67c1ea42a0..4e96deb3e4 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -299,9 +299,10 @@ private function writeListTable() $positions = []; $level = ''; $strLength = ''; - if (strpos($listItem['text'], '%') !== false) { - $level = $this->lowerDigitsByOne(str_replace('%', '\\\'0', $listItem['text'])); - $levelNumbers = (string) preg_replace('/\d/', 'X', str_replace('%', '', $listItem['text'])); + $listText = (string) $listItem['text']; + if (strpos($listText, '%') !== false) { + $level = $this->lowerDigitsByOne(str_replace('%', '\\\'0', $listText)); + $levelNumbers = preg_replace('/\d/', 'X', str_replace('%', '', $listText)); $offset = 0; while (($pos = strpos($levelNumbers, 'X', $offset)) !== false) { $positions[] = $pos; @@ -309,7 +310,7 @@ private function writeListTable() } $strLength = (string) sprintf('%02d', strlen($levelNumbers)); } else { - $level = '\\\'' . (string) strtoupper(dechex(ord(iconv('UTF-8', 'UCS-2', $listItem['text'])))); + $level = '\\\'' . (string) strtoupper(dechex(ord(iconv('UTF-8', 'UCS-2', $listText)))); $strLength = '01'; } From f68a5347c5f0508d94ef78ae53886fe7087a9a41 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Fri, 12 Sep 2025 22:49:48 -0500 Subject: [PATCH 22/23] Update ListItem.php - CS Fix --- src/PhpWord/Style/ListItem.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index e7699704fb..fca6b1a924 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -124,7 +124,7 @@ public function getNumStyle() public function getNumberingStyle() { $numStyleObject = Style::getStyle($this->numStyle); - if ($numStyleObject instanceof Numbering) { + if ($numStyleObject instanceof NumberingStyle) { return $numStyleObject; } @@ -142,7 +142,7 @@ public function setNumStyle($value) { $this->numStyle = $value; $numStyleObject = Style::getStyle($this->numStyle); - if ($numStyleObject instanceof Numbering) { + if ($numStyleObject instanceof NumberingStyle) { $this->numId = $numStyleObject->getIndex(); $numStyleObject->setNumId($this->numId); } From a453cbd2ce7ed4a486f3c59d7ad13f1d521dc0c2 Mon Sep 17 00:00:00 2001 From: rasamassen Date: Fri, 12 Sep 2025 22:54:58 -0500 Subject: [PATCH 23/23] Update Header.php - Static Fix --- src/PhpWord/Writer/RTF/Part/Header.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 4e96deb3e4..8e3ad034d3 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -310,7 +310,7 @@ private function writeListTable() } $strLength = (string) sprintf('%02d', strlen($levelNumbers)); } else { - $level = '\\\'' . (string) strtoupper(dechex(ord(iconv('UTF-8', 'UCS-2', $listText)))); + $level = '\\\'' . (string) strtoupper(dechex(ord((string) iconv('UTF-8', 'UCS-2', $listText)))); $strLength = '01'; }