Skip to content

Commit b57a549

Browse files
committed
More RTL Support for Xlsx/Html Comments
Following up from PR PHPOffice#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 PHPOffice#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.
1 parent 318a82e commit b57a549

File tree

7 files changed

+240
-11
lines changed

7 files changed

+240
-11
lines changed

src/PhpSpreadsheet/Comment.php

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ class Comment implements IComparable, Stringable
6363
*/
6464
private Drawing $backgroundImage;
6565

66+
public const TEXTBOX_DIRECTION_RTL = 'rtl';
67+
public const TEXTBOX_DIRECTION_LTR = 'ltr';
68+
// MS uses 'auto' in xml but 'context' in UI
69+
public const TEXTBOX_DIRECTION_AUTO = 'auto';
70+
public const TEXTBOX_DIRECTION_CONTEXT = 'auto';
71+
72+
private string $textboxDirection = '';
73+
6674
/**
6775
* Create a new Comment.
6876
*/
@@ -232,24 +240,30 @@ public function getFillColor(): Color
232240
return $this->fillColor;
233241
}
234242

235-
/**
236-
* Set Alignment.
237-
*/
238243
public function setAlignment(string $alignment): self
239244
{
240245
$this->alignment = $alignment;
241246

242247
return $this;
243248
}
244249

245-
/**
246-
* Get Alignment.
247-
*/
248250
public function getAlignment(): string
249251
{
250252
return $this->alignment;
251253
}
252254

255+
public function setTextboxDirection(string $textboxDirection): self
256+
{
257+
$this->textboxDirection = $textboxDirection;
258+
259+
return $this;
260+
}
261+
262+
public function getTextboxDirection(): string
263+
{
264+
return $this->textboxDirection;
265+
}
266+
253267
/**
254268
* Get hash code.
255269
*/
@@ -265,6 +279,7 @@ public function getHashCode(): string
265279
. ($this->visible ? 1 : 0)
266280
. $this->fillColor->getHashCode()
267281
. $this->alignment
282+
. $this->textboxDirection
268283
. ($this->hasBackgroundImage() ? $this->backgroundImage->getHashCode() : '')
269284
. __CLASS__
270285
);

src/PhpSpreadsheet/Reader/Html.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use DOMText;
1010
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
1111
use PhpOffice\PhpSpreadsheet\Cell\DataType;
12+
use PhpOffice\PhpSpreadsheet\Comment;
1213
use PhpOffice\PhpSpreadsheet\Document\Properties;
1314
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
1415
use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension;
@@ -332,6 +333,15 @@ private function processDomElementSpanEtc(Worksheet $sheet, int &$row, string &$
332333
$sheet->getComment($column . $row)
333334
->getText()
334335
->createTextRun($child->textContent);
336+
if (isset($attributeArray['dir']) && $attributeArray['dir'] === 'rtl') {
337+
$sheet->getComment($column . $row)->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL);
338+
}
339+
if (isset($attributeArray['style'])) {
340+
$alignStyle = $attributeArray['style'];
341+
if (preg_match('/\\btext-align:\\s*(left|right|center|justify)\\b/', $alignStyle, $matches) === 1) {
342+
$sheet->getComment($column . $row)->setAlignment($matches[1]);
343+
}
344+
}
335345
} else {
336346
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
337347
}

src/PhpSpreadsheet/Reader/Xlsx.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
66
use PhpOffice\PhpSpreadsheet\Cell\DataType;
77
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
8+
use PhpOffice\PhpSpreadsheet\Comment;
89
use PhpOffice\PhpSpreadsheet\DefinedName;
910
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
1011
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\AutoFilter;
@@ -1137,6 +1138,14 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
11371138
$fillImageTitle = '';
11381139

11391140
$clientData = $shape->xpath('.//x:ClientData');
1141+
$textboxDirection = '';
1142+
$textboxPath = $shape->xpath('.//v:textbox');
1143+
$textbox = (string) ($textboxPath[0]['style'] ?? '');
1144+
if (preg_match('/rtl/i', $textbox) === 1) {
1145+
$textboxDirection = Comment::TEXTBOX_DIRECTION_RTL;
1146+
} elseif (preg_match('/ltr/i', $textbox) === 1) {
1147+
$textboxDirection = Comment::TEXTBOX_DIRECTION_LTR;
1148+
}
11401149
if (is_array($clientData) && !empty($clientData)) {
11411150
$clientData = $clientData[0];
11421151

@@ -1152,7 +1161,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
11521161
}
11531162
$temp = $clientData->xpath('.//x:TextHAlign');
11541163
if (!empty($temp)) {
1155-
$textHAlign = $temp[0];
1164+
$textHAlign = strtolower($temp[0]);
11561165
}
11571166
}
11581167
}
@@ -1161,6 +1170,9 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
11611170
if (is_numeric($rowx) && is_numeric($colx) && $textHAlign !== null) {
11621171
$docSheet->getComment([1 + (int) $colx, 1 + (int) $rowx], false)->setAlignment((string) $textHAlign);
11631172
}
1173+
if (is_numeric($rowx) && is_numeric($colx) && $textboxDirection !== '') {
1174+
$docSheet->getComment([1 + (int) $colx, 1 + (int) $rowx], false)->setTextboxDirection($textboxDirection);
1175+
}
11641176

11651177
$fillImageRelNode = $shape->xpath('.//v:fill/@o:relid');
11661178
if (is_array($fillImageRelNode) && !empty($fillImageRelNode)) {

src/PhpSpreadsheet/Writer/Html.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PhpOffice\PhpSpreadsheet\Cell\Cell;
77
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
88
use PhpOffice\PhpSpreadsheet\Chart\Chart;
9+
use PhpOffice\PhpSpreadsheet\Comment;
910
use PhpOffice\PhpSpreadsheet\Document\Properties;
1011
use PhpOffice\PhpSpreadsheet\RichText\RichText;
1112
use PhpOffice\PhpSpreadsheet\RichText\Run;
@@ -1762,9 +1763,15 @@ private function writeComment(Worksheet $worksheet, string $coordinate): string
17621763
$result = '';
17631764
if (!$this->isPdf && isset($worksheet->getComments()[$coordinate])) {
17641765
$sanitizedString = $this->generateRowCellDataValueRich($worksheet->getComment($coordinate)->getText());
1766+
$dir = ($worksheet->getComment($coordinate)->getTextboxDirection() === Comment::TEXTBOX_DIRECTION_RTL) ? ' dir="rtl"' : '';
1767+
$align = strtolower($worksheet->getComment($coordinate)->getAlignment());
1768+
$alignment = Alignment::HORIZONTAL_ALIGNMENT_FOR_HTML[$align] ?? '';
1769+
if ($alignment !== '') {
1770+
$alignment = " style=\"text-align:$alignment\"";
1771+
}
17651772
if ($sanitizedString !== '') {
17661773
$result .= '<a class="comment-indicator"></a>';
1767-
$result .= '<div class="comment">' . $sanitizedString . '</div>';
1774+
$result .= "<div class=\"comment\"$dir$alignment>" . $sanitizedString . '</div>';
17681775
$result .= PHP_EOL;
17691776
}
17701777
}

src/PhpSpreadsheet/Writer/Xlsx/Comments.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,14 @@ private function writeVMLComment(XMLWriter $objWriter, string $cellReference, Co
209209
$objWriter->endElement();
210210

211211
// v:textbox
212+
$textBoxArray = [Comment::TEXTBOX_DIRECTION_RTL => 'rtl', Comment::TEXTBOX_DIRECTION_LTR => 'ltr'];
213+
$textboxRtl = $textBoxArray[strtolower($comment->getTextBoxDirection())] ?? 'auto';
212214
$objWriter->startElement('v:textbox');
213-
$objWriter->writeAttribute('style', 'mso-direction-alt:auto');
215+
$objWriter->writeAttribute('style', "mso-direction-alt:$textboxRtl");
214216

215217
// div
216218
$objWriter->startElement('div');
217-
$objWriter->writeAttribute('style', 'text-align:left');
219+
$objWriter->writeAttribute('style', ($textboxRtl === 'rtl' ? 'text-align:right;direction:rtl' : 'text-align:left'));
218220
$objWriter->endElement();
219221

220222
$objWriter->endElement();
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Html;
6+
7+
use PhpOffice\PhpSpreadsheet\Comment;
8+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9+
use PhpOffice\PhpSpreadsheet\Style\Alignment;
10+
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
11+
12+
class CommentAlignmentTest extends AbstractFunctional
13+
{
14+
public function testIssue4004(): void
15+
{
16+
$type = 'Html';
17+
$spreadsheet = new Spreadsheet();
18+
$sheet = $spreadsheet->getActiveSheet();
19+
$sheet->getCell('A3')->setValue('A3');
20+
$sheet->getCell('A4')->setValue('A4');
21+
$sheet->getComment('A3')->getText()->createText('Comment');
22+
$sheet->getComment('A4')->getText()->createText('שלום');
23+
$sheet->getComment('A4')->setAlignment(Alignment::HORIZONTAL_RIGHT);
24+
25+
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $type);
26+
$spreadsheet->disconnectWorksheets();
27+
28+
self::assertCount(1, $reloadedSpreadsheet->getAllSheets());
29+
30+
$rsheet = $reloadedSpreadsheet->getActiveSheet();
31+
$comment1 = $rsheet->getComment('A3');
32+
self::assertSame('Comment', $comment1->getText()->getPlainText());
33+
self::assertSame('general', $comment1->getAlignment());
34+
$comment2 = $rsheet->getComment('A4');
35+
self::assertSame('שלום', $comment2->getText()->getPlainText());
36+
self::assertSame('right', $comment2->getAlignment());
37+
38+
$reloadedSpreadsheet->disconnectWorksheets();
39+
}
40+
41+
public function testIssue4004td(): void
42+
{
43+
$type = 'Html';
44+
$spreadsheet = new Spreadsheet();
45+
$sheet = $spreadsheet->getActiveSheet();
46+
$sheet->setRightToLeft(true);
47+
$sheet->getCell('A1')->setValue('ברקוד');
48+
$comment = $sheet->getComment('A1');
49+
$comment->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL);
50+
$comment->setAlignment(Alignment::HORIZONTAL_RIGHT);
51+
$text = <<<EOF
52+
Report : ProductsExcel
53+
סטטוס הגדרות בזמן הרצת הדו"ח
54+
55+
2024-06-04 21:07:04
56+
תאריך התחלה :
57+
תאריך סיום :
58+
59+
60+
הצגת ברקוד מקוצר = 0
61+
הצגת חנויות אינטרנט בתוצאות = 1
62+
63+
הצגת מחיר ליחידת מידה = 0
64+
הצגת כל רשומות המחיר לתאריך = 0
65+
רשומות עם מחיר בכל הרשתות = 0
66+
% נפיצות מינימלית = 0
67+
נפיצות מינימלית= 0
68+
% נפיצות מקסימלית = 0
69+
נפיצות מקסימלית= 0
70+
פער אחוזי = 0
71+
72+
התעלמות מכלל המבצעים = 0
73+
התעלמות ממבצעי אשראי = 0
74+
התעלמות ממבצעי מועדון = 0
75+
התעלמות ממבצעים המותנים בסכום מעל 100 ₪. = 0
76+
התעלמות ממבצעים המותנים בקניה של 3 מוצרים ומעלה. = 0
77+
78+
ניתוח מבצעים
79+
============
80+
הצגת כל המבצעים = 0
81+
מבצעי מועדון = 0
82+
מבצעי אשראי = 0
83+
מבצעי ארנק = 0
84+
מחיר מוזל = 0
85+
X יחידות ב-Y ₪ = 0
86+
השני ב = 0
87+
X+Y מתנה = 0
88+
אחוז הנחה הפעלה = 0
89+
אחוז הנחה מספר = 0
90+
מוצרים חסרים - הגבלת חודשים - כמות= 0
91+
EOF;
92+
$comment->getText()->createTextRun($text);
93+
$comment->setWidth('300pt');
94+
$comment->setHeight('550pt');
95+
96+
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $type);
97+
$spreadsheet->disconnectWorksheets();
98+
99+
self::assertCount(1, $reloadedSpreadsheet->getAllSheets());
100+
101+
$rsheet = $reloadedSpreadsheet->getActiveSheet();
102+
$comment1 = $rsheet->getComment('A1');
103+
self::assertSame($text, $comment1->getText()->getPlainText());
104+
$comment->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL);
105+
self::assertSame('right', $comment1->getAlignment());
106+
self::assertSame('rtl', $comment1->getTextboxDirection());
107+
108+
$reloadedSpreadsheet->disconnectWorksheets();
109+
}
110+
}

tests/PhpSpreadsheetTests/Writer/Xlsx/CommentAlignmentTest.php

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
66

7+
use PhpOffice\PhpSpreadsheet\Comment;
78
use PhpOffice\PhpSpreadsheet\Spreadsheet;
89
use PhpOffice\PhpSpreadsheet\Style\Alignment;
910
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
@@ -15,6 +16,8 @@ public function testIssue4004(): void
1516
$type = 'Xlsx';
1617
$spreadsheet = new Spreadsheet();
1718
$sheet = $spreadsheet->getActiveSheet();
19+
$sheet->getCell('A3')->setValue('A3');
20+
$sheet->getCell('A4')->setValue('A4');
1821
$sheet->getComment('A3')->getText()->createText('Comment');
1922
$sheet->getComment('A4')->getText()->createText('שלום');
2023
$sheet->getComment('A4')->setAlignment(Alignment::HORIZONTAL_RIGHT);
@@ -30,7 +33,77 @@ public function testIssue4004(): void
3033
self::assertSame('general', $comment1->getAlignment());
3134
$comment2 = $rsheet->getComment('A4');
3235
self::assertSame('שלום', $comment2->getText()->getPlainText());
33-
self::assertSame('Right', $comment2->getAlignment());
36+
self::assertSame('right', $comment2->getAlignment());
37+
38+
$reloadedSpreadsheet->disconnectWorksheets();
39+
}
40+
41+
public function testIssue4004td(): void
42+
{
43+
$type = 'Xlsx';
44+
$spreadsheet = new Spreadsheet();
45+
$sheet = $spreadsheet->getActiveSheet();
46+
$sheet->setRightToLeft(true);
47+
$sheet->getCell('A1')->setValue('ברקוד');
48+
$comment = $sheet->getComment('A1');
49+
$comment->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL);
50+
$comment->setAlignment(Alignment::HORIZONTAL_RIGHT);
51+
$text = <<<EOF
52+
Report : ProductsExcel
53+
סטטוס הגדרות בזמן הרצת הדו"ח
54+
55+
2024-06-04 21:07:04
56+
תאריך התחלה :
57+
תאריך סיום :
58+
59+
60+
הצגת ברקוד מקוצר = 0
61+
הצגת חנויות אינטרנט בתוצאות = 1
62+
63+
הצגת מחיר ליחידת מידה = 0
64+
הצגת כל רשומות המחיר לתאריך = 0
65+
רשומות עם מחיר בכל הרשתות = 0
66+
% נפיצות מינימלית = 0
67+
נפיצות מינימלית= 0
68+
% נפיצות מקסימלית = 0
69+
נפיצות מקסימלית= 0
70+
פער אחוזי = 0
71+
72+
התעלמות מכלל המבצעים = 0
73+
התעלמות ממבצעי אשראי = 0
74+
התעלמות ממבצעי מועדון = 0
75+
התעלמות ממבצעים המותנים בסכום מעל 100 ₪. = 0
76+
התעלמות ממבצעים המותנים בקניה של 3 מוצרים ומעלה. = 0
77+
78+
ניתוח מבצעים
79+
============
80+
הצגת כל המבצעים = 0
81+
מבצעי מועדון = 0
82+
מבצעי אשראי = 0
83+
מבצעי ארנק = 0
84+
מחיר מוזל = 0
85+
X יחידות ב-Y ₪ = 0
86+
השני ב = 0
87+
X+Y מתנה = 0
88+
אחוז הנחה הפעלה = 0
89+
אחוז הנחה מספר = 0
90+
מוצרים חסרים - הגבלת חודשים - כמות= 0
91+
EOF;
92+
$comment->getText()->createTextRun($text);
93+
$comment->setWidth('300pt');
94+
$comment->setHeight('550pt');
95+
96+
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $type);
97+
$spreadsheet->disconnectWorksheets();
98+
99+
self::assertCount(1, $reloadedSpreadsheet->getAllSheets());
100+
101+
$rsheet = $reloadedSpreadsheet->getActiveSheet();
102+
$comment1 = $rsheet->getComment('A1');
103+
self::assertSame($text, $comment1->getText()->getPlainText());
104+
$comment->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL);
105+
self::assertSame('right', $comment1->getAlignment());
106+
self::assertSame('rtl', $comment1->getTextboxDirection());
34107

35108
$reloadedSpreadsheet->disconnectWorksheets();
36109
}

0 commit comments

Comments
 (0)