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.
This commit is contained in:
oleibman
2024-06-06 16:55:27 -07:00
parent 318a82e0f9
commit b57a549fab
7 changed files with 240 additions and 11 deletions
+21 -6
View File
@@ -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__
);
+10
View File
@@ -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);
}
+13 -1
View File
@@ -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)) {
+8 -1
View File
@@ -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 .= '<a class="comment-indicator"></a>';
$result .= '<div class="comment">' . $sanitizedString . '</div>';
$result .= "<div class=\"comment\"$dir$alignment>" . $sanitizedString . '</div>';
$result .= PHP_EOL;
}
}
+4 -2
View File
@@ -209,12 +209,14 @@ class Comments extends WriterPart
$objWriter->endElement();
// v:textbox
$textBoxArray = [Comment::TEXTBOX_DIRECTION_RTL => 'rtl', Comment::TEXTBOX_DIRECTION_LTR => 'ltr'];
$textboxRtl = $textBoxArray[strtolower($comment->getTextBoxDirection())] ?? 'auto';
$objWriter->startElement('v:textbox');
$objWriter->writeAttribute('style', 'mso-direction-alt:auto');
$objWriter->writeAttribute('style', "mso-direction-alt:$textboxRtl");
// div
$objWriter->startElement('div');
$objWriter->writeAttribute('style', 'text-align:left');
$objWriter->writeAttribute('style', ($textboxRtl === 'rtl' ? 'text-align:right;direction:rtl' : 'text-align:left'));
$objWriter->endElement();
$objWriter->endElement();
@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests\Writer\Html;
use PhpOffice\PhpSpreadsheet\Comment;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
class CommentAlignmentTest extends AbstractFunctional
{
public function testIssue4004(): void
{
$type = 'Html';
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->getCell('A3')->setValue('A3');
$sheet->getCell('A4')->setValue('A4');
$sheet->getComment('A3')->getText()->createText('Comment');
$sheet->getComment('A4')->getText()->createText('שלום');
$sheet->getComment('A4')->setAlignment(Alignment::HORIZONTAL_RIGHT);
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $type);
$spreadsheet->disconnectWorksheets();
self::assertCount(1, $reloadedSpreadsheet->getAllSheets());
$rsheet = $reloadedSpreadsheet->getActiveSheet();
$comment1 = $rsheet->getComment('A3');
self::assertSame('Comment', $comment1->getText()->getPlainText());
self::assertSame('general', $comment1->getAlignment());
$comment2 = $rsheet->getComment('A4');
self::assertSame('שלום', $comment2->getText()->getPlainText());
self::assertSame('right', $comment2->getAlignment());
$reloadedSpreadsheet->disconnectWorksheets();
}
public function testIssue4004td(): void
{
$type = 'Html';
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setRightToLeft(true);
$sheet->getCell('A1')->setValue('ברקוד');
$comment = $sheet->getComment('A1');
$comment->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL);
$comment->setAlignment(Alignment::HORIZONTAL_RIGHT);
$text = <<<EOF
Report : ProductsExcel
סטטוס הגדרות בזמן הרצת הדו"ח
2024-06-04 21:07:04
תאריך התחלה :
תאריך סיום :
berber@berber.co.il
הצגת ברקוד מקוצר = 0
הצגת חנויות אינטרנט בתוצאות = 1
הצגת מחיר ליחידת מידה = 0
הצגת כל רשומות המחיר לתאריך = 0
רשומות עם מחיר בכל הרשתות = 0
% נפיצות מינימלית = 0
נפיצות מינימלית= 0
% נפיצות מקסימלית = 0
נפיצות מקסימלית= 0
פער אחוזי = 0
התעלמות מכלל המבצעים = 0
התעלמות ממבצעי אשראי = 0
התעלמות ממבצעי מועדון = 0
התעלמות ממבצעים המותנים בסכום מעל 100 . = 0
התעלמות ממבצעים המותנים בקניה של 3 מוצרים ומעלה. = 0
ניתוח מבצעים
============
הצגת כל המבצעים = 0
מבצעי מועדון = 0
מבצעי אשראי = 0
מבצעי ארנק = 0
מחיר מוזל = 0
X יחידות ב-Y = 0
השני ב = 0
X+Y מתנה = 0
אחוז הנחה הפעלה = 0
אחוז הנחה מספר = 0
מוצרים חסרים - הגבלת חודשים - כמות= 0
EOF;
$comment->getText()->createTextRun($text);
$comment->setWidth('300pt');
$comment->setHeight('550pt');
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $type);
$spreadsheet->disconnectWorksheets();
self::assertCount(1, $reloadedSpreadsheet->getAllSheets());
$rsheet = $reloadedSpreadsheet->getActiveSheet();
$comment1 = $rsheet->getComment('A1');
self::assertSame($text, $comment1->getText()->getPlainText());
$comment->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL);
self::assertSame('right', $comment1->getAlignment());
self::assertSame('rtl', $comment1->getTextboxDirection());
$reloadedSpreadsheet->disconnectWorksheets();
}
}
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Comment;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
@@ -15,6 +16,8 @@ class CommentAlignmentTest extends AbstractFunctional
$type = 'Xlsx';
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->getCell('A3')->setValue('A3');
$sheet->getCell('A4')->setValue('A4');
$sheet->getComment('A3')->getText()->createText('Comment');
$sheet->getComment('A4')->getText()->createText('שלום');
$sheet->getComment('A4')->setAlignment(Alignment::HORIZONTAL_RIGHT);
@@ -30,7 +33,77 @@ class CommentAlignmentTest extends AbstractFunctional
self::assertSame('general', $comment1->getAlignment());
$comment2 = $rsheet->getComment('A4');
self::assertSame('שלום', $comment2->getText()->getPlainText());
self::assertSame('Right', $comment2->getAlignment());
self::assertSame('right', $comment2->getAlignment());
$reloadedSpreadsheet->disconnectWorksheets();
}
public function testIssue4004td(): void
{
$type = 'Xlsx';
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setRightToLeft(true);
$sheet->getCell('A1')->setValue('ברקוד');
$comment = $sheet->getComment('A1');
$comment->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL);
$comment->setAlignment(Alignment::HORIZONTAL_RIGHT);
$text = <<<EOF
Report : ProductsExcel
סטטוס הגדרות בזמן הרצת הדו"ח
2024-06-04 21:07:04
תאריך התחלה :
תאריך סיום :
berber@berber.co.il
הצגת ברקוד מקוצר = 0
הצגת חנויות אינטרנט בתוצאות = 1
הצגת מחיר ליחידת מידה = 0
הצגת כל רשומות המחיר לתאריך = 0
רשומות עם מחיר בכל הרשתות = 0
% נפיצות מינימלית = 0
נפיצות מינימלית= 0
% נפיצות מקסימלית = 0
נפיצות מקסימלית= 0
פער אחוזי = 0
התעלמות מכלל המבצעים = 0
התעלמות ממבצעי אשראי = 0
התעלמות ממבצעי מועדון = 0
התעלמות ממבצעים המותנים בסכום מעל 100 . = 0
התעלמות ממבצעים המותנים בקניה של 3 מוצרים ומעלה. = 0
ניתוח מבצעים
============
הצגת כל המבצעים = 0
מבצעי מועדון = 0
מבצעי אשראי = 0
מבצעי ארנק = 0
מחיר מוזל = 0
X יחידות ב-Y = 0
השני ב = 0
X+Y מתנה = 0
אחוז הנחה הפעלה = 0
אחוז הנחה מספר = 0
מוצרים חסרים - הגבלת חודשים - כמות= 0
EOF;
$comment->getText()->createTextRun($text);
$comment->setWidth('300pt');
$comment->setHeight('550pt');
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $type);
$spreadsheet->disconnectWorksheets();
self::assertCount(1, $reloadedSpreadsheet->getAllSheets());
$rsheet = $reloadedSpreadsheet->getActiveSheet();
$comment1 = $rsheet->getComment('A1');
self::assertSame($text, $comment1->getText()->getPlainText());
$comment->setTextboxDirection(Comment::TEXTBOX_DIRECTION_RTL);
self::assertSame('right', $comment1->getAlignment());
self::assertSame('rtl', $comment1->getTextboxDirection());
$reloadedSpreadsheet->disconnectWorksheets();
}