From b57a549fabad3dd07d18e4b385466fd4edd99987 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 6 Jun 2024 16:55:27 -0700 Subject: [PATCH] More RTL Support for Xlsx/Html Comments Following up from PR #4006. There is an additional RTL property available. It controls the placement of bidirectional neutral characters (mainly punctuation), as opposed to strong (alphabetic characters) or weak (numeric characters), especially at the beginning or end of a line. The new Comment property textboxDirection will be used for that purpose. In a discussion in issue #4004 following the implementation of the PR, the comment was mixed RTL and LTR, and this led to some formatting problems. The user was able to overcome these with the timely insertion of Unicode directional control characters, but it would be preferable to have it happen automatically, which this change will permit. However, the use of these control characters cannot be entirely done away with. In the new test case, if one of the all-English lines ended with, say, a colon, it would not display correctly; LRM (left-to-right mark) after the colon would be needed. Likewise, one or two of the comment lines with mixed RTL and LTR (discussed in the issue) is not formatted correctly, and might require LRO/PDF or equivalent. --- src/PhpSpreadsheet/Comment.php | 27 ++++- src/PhpSpreadsheet/Reader/Html.php | 10 ++ src/PhpSpreadsheet/Reader/Xlsx.php | 14 ++- src/PhpSpreadsheet/Writer/Html.php | 9 +- src/PhpSpreadsheet/Writer/Xlsx/Comments.php | 6 +- .../Writer/Html/CommentAlignmentTest.php | 110 ++++++++++++++++++ .../Writer/Xlsx/CommentAlignmentTest.php | 75 +++++++++++- 7 files changed, 240 insertions(+), 11 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Writer/Html/CommentAlignmentTest.php diff --git a/src/PhpSpreadsheet/Comment.php b/src/PhpSpreadsheet/Comment.php index 1c07b5154..2e18270c1 100644 --- a/src/PhpSpreadsheet/Comment.php +++ b/src/PhpSpreadsheet/Comment.php @@ -63,6 +63,14 @@ class Comment implements IComparable, Stringable */ private Drawing $backgroundImage; + public const TEXTBOX_DIRECTION_RTL = 'rtl'; + public const TEXTBOX_DIRECTION_LTR = 'ltr'; + // MS uses 'auto' in xml but 'context' in UI + public const TEXTBOX_DIRECTION_AUTO = 'auto'; + public const TEXTBOX_DIRECTION_CONTEXT = 'auto'; + + private string $textboxDirection = ''; + /** * Create a new Comment. */ @@ -232,9 +240,6 @@ class Comment implements IComparable, Stringable return $this->fillColor; } - /** - * Set Alignment. - */ public function setAlignment(string $alignment): self { $this->alignment = $alignment; @@ -242,14 +247,23 @@ class Comment implements IComparable, Stringable return $this; } - /** - * Get Alignment. - */ public function getAlignment(): string { return $this->alignment; } + public function setTextboxDirection(string $textboxDirection): self + { + $this->textboxDirection = $textboxDirection; + + return $this; + } + + public function getTextboxDirection(): string + { + return $this->textboxDirection; + } + /** * Get hash code. */ @@ -265,6 +279,7 @@ class Comment implements IComparable, Stringable . ($this->visible ? 1 : 0) . $this->fillColor->getHashCode() . $this->alignment + . $this->textboxDirection . ($this->hasBackgroundImage() ? $this->backgroundImage->getHashCode() : '') . __CLASS__ ); diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php index d365fbf23..cc4a4859a 100644 --- a/src/PhpSpreadsheet/Reader/Html.php +++ b/src/PhpSpreadsheet/Reader/Html.php @@ -9,6 +9,7 @@ use DOMNode; use DOMText; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\Comment; use PhpOffice\PhpSpreadsheet\Document\Properties; use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException; use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension; @@ -332,6 +333,15 @@ class Html extends BaseReader $sheet->getComment($column . $row) ->getText() ->createTextRun($child->textContent); + if (isset($attributeArray['dir']) && $attributeArray['dir'] === 'rtl') { + $sheet->getComment($column . $row)->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL); + } + if (isset($attributeArray['style'])) { + $alignStyle = $attributeArray['style']; + if (preg_match('/\\btext-align:\\s*(left|right|center|justify)\\b/', $alignStyle, $matches) === 1) { + $sheet->getComment($column . $row)->setAlignment($matches[1]); + } + } } else { $this->processDomElement($child, $sheet, $row, $column, $cellContent); } diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 9bfb35434..aa2a78254 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\Hyperlink; +use PhpOffice\PhpSpreadsheet\Comment; use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\AutoFilter; @@ -1137,6 +1138,14 @@ class Xlsx extends BaseReader $fillImageTitle = ''; $clientData = $shape->xpath('.//x:ClientData'); + $textboxDirection = ''; + $textboxPath = $shape->xpath('.//v:textbox'); + $textbox = (string) ($textboxPath[0]['style'] ?? ''); + if (preg_match('/rtl/i', $textbox) === 1) { + $textboxDirection = Comment::TEXTBOX_DIRECTION_RTL; + } elseif (preg_match('/ltr/i', $textbox) === 1) { + $textboxDirection = Comment::TEXTBOX_DIRECTION_LTR; + } if (is_array($clientData) && !empty($clientData)) { $clientData = $clientData[0]; @@ -1152,7 +1161,7 @@ class Xlsx extends BaseReader } $temp = $clientData->xpath('.//x:TextHAlign'); if (!empty($temp)) { - $textHAlign = $temp[0]; + $textHAlign = strtolower($temp[0]); } } } @@ -1161,6 +1170,9 @@ class Xlsx extends BaseReader if (is_numeric($rowx) && is_numeric($colx) && $textHAlign !== null) { $docSheet->getComment([1 + (int) $colx, 1 + (int) $rowx], false)->setAlignment((string) $textHAlign); } + if (is_numeric($rowx) && is_numeric($colx) && $textboxDirection !== '') { + $docSheet->getComment([1 + (int) $colx, 1 + (int) $rowx], false)->setTextboxDirection($textboxDirection); + } $fillImageRelNode = $shape->xpath('.//v:fill/@o:relid'); if (is_array($fillImageRelNode) && !empty($fillImageRelNode)) { diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index e344e3398..457af6612 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Chart\Chart; +use PhpOffice\PhpSpreadsheet\Comment; use PhpOffice\PhpSpreadsheet\Document\Properties; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\Run; @@ -1762,9 +1763,15 @@ class Html extends BaseWriter $result = ''; if (!$this->isPdf && isset($worksheet->getComments()[$coordinate])) { $sanitizedString = $this->generateRowCellDataValueRich($worksheet->getComment($coordinate)->getText()); + $dir = ($worksheet->getComment($coordinate)->getTextboxDirection() === Comment::TEXTBOX_DIRECTION_RTL) ? ' dir="rtl"' : ''; + $align = strtolower($worksheet->getComment($coordinate)->getAlignment()); + $alignment = Alignment::HORIZONTAL_ALIGNMENT_FOR_HTML[$align] ?? ''; + if ($alignment !== '') { + $alignment = " style=\"text-align:$alignment\""; + } if ($sanitizedString !== '') { $result .= ''; - $result .= '