diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php index b57d6c1309..579f3892da 100644 --- a/src/PhpSpreadsheet/Reader/Html.php +++ b/src/PhpSpreadsheet/Reader/Html.php @@ -18,6 +18,7 @@ use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Color; use PhpOffice\PhpSpreadsheet\Style\Fill; @@ -1014,6 +1015,17 @@ private function applyInlineStyle(Worksheet &$sheet, int $row, string $column, a break; + case 'direction': + if ($styleValue === 'rtl') { + $cellStyle->getAlignment() + ->setReadOrder(Alignment::READORDER_RTL); + } elseif ($styleValue === 'ltr') { + $cellStyle->getAlignment() + ->setReadOrder(Alignment::READORDER_LTR); + } + + break; + case 'font-weight': if ($styleValue === 'bold' || $styleValue >= 500) { $cellStyle->getFont()->setBold(true); @@ -1083,8 +1095,11 @@ private function applyInlineStyle(Worksheet &$sheet, int $row, string $column, a break; case 'text-indent': + $indentDimension = new CssDimension($styleValueString); + $indent = $indentDimension + ->toUnit(CssDimension::UOM_PIXELS); $cellStyle->getAlignment()->setIndent( - (int) str_replace(['px'], '', $styleValueString) + (int) ($indent / Alignment::INDENT_UNITS_TO_PIXELS) ); break; diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index 97c481daaa..3d4cb5e5cb 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -1254,6 +1254,8 @@ protected function readXf(): void break; } + $readOrder = (0xC0 & ord($recordData[8])) >> 6; + $objStyle->getAlignment()->setReadOrder($readOrder); // offset: 9; size: 1; Flags used for attribute groups diff --git a/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php b/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php index 657decfffe..b4afcdec49 100644 --- a/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php +++ b/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php @@ -54,6 +54,14 @@ public function parseStyle(SimpleXMLElement $styleAttributes): array case 'Indent': $style['alignment']['indent'] = $styleAttributeValue; + break; + case 'ReadingOrder': + if ($styleAttributeValue === 'RightToLeft') { + $style['alignment']['readOrder'] = AlignmentStyles::READORDER_RTL; + } elseif ($styleAttributeValue === 'LeftToRight') { + $style['alignment']['readOrder'] = AlignmentStyles::READORDER_LTR; + } + break; } } diff --git a/src/PhpSpreadsheet/Style/Alignment.php b/src/PhpSpreadsheet/Style/Alignment.php index d5c42c273a..6ff44e3ef6 100644 --- a/src/PhpSpreadsheet/Style/Alignment.php +++ b/src/PhpSpreadsheet/Style/Alignment.php @@ -92,6 +92,8 @@ class Alignment extends Supervisor const TEXTROTATION_STACK_EXCEL = 255; const TEXTROTATION_STACK_PHPSPREADSHEET = -165; // 90 - 255 + public const INDENT_UNITS_TO_PIXELS = 9; + /** * Horizontal alignment. */ diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index 27849bf374..ecf79d8060 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -1115,7 +1115,12 @@ private function createCSSStyleAlignment(Alignment $alignment): array if ($textAlign) { $css['text-align'] = $textAlign; if (in_array($textAlign, ['left', 'right'])) { - $css['padding-' . $textAlign] = (string) ((int) $alignment->getIndent() * 9) . 'px'; + $css['padding-' . $textAlign] = (string) ($alignment->getIndent() * Alignment::INDENT_UNITS_TO_PIXELS) . 'px'; + } + } else { + $indent = $alignment->getIndent(); + if ($indent !== 0) { + $css['text-indent'] = (string) ($alignment->getIndent() * Alignment::INDENT_UNITS_TO_PIXELS) . 'px'; } } $rotation = $alignment->getTextRotation(); @@ -1126,6 +1131,12 @@ private function createCSSStyleAlignment(Alignment $alignment): array $css['transform'] = "rotate({$rotation}deg)"; } } + $direction = $alignment->getReadOrder(); + if ($direction === Alignment::READORDER_LTR) { + $css['direction'] = 'ltr'; + } elseif ($direction === Alignment::READORDER_RTL) { + $css['direction'] = 'rtl'; + } return $css; } @@ -1516,7 +1527,6 @@ private function generateRowCellDataValue(Worksheet $worksheet, Cell $cell, stri /** @param string|string[] $cssClass */ private function generateRowCellData(Worksheet $worksheet, null|Cell|string $cell, array|string &$cssClass): string { - $cellData = ' '; if ($cell instanceof Cell) { $cellData = ''; // Don't know what this does, and no test cases. @@ -1565,13 +1575,21 @@ private function generateRowCellData(Worksheet $worksheet, null|Cell|string $cel } } } else { + $cellData = "$cell"; // Use default borders for empty cell if (is_string($cssClass)) { $cssClass .= ' style0'; } } + /* + * Browsers may remove an entirely empty row. + * An interesting option is to leave an empty cell empty using css. + * td:empty::after{content: "\00a0";} + * This works well in modern browsers. + * Alas, none of our Pdf writers can handle it. + */ - return $cellData; + return (trim($cellData) === '') ? ' ' : $cellData; } private function generateRowIncludeCharts(Worksheet $worksheet, string $coordinate): string diff --git a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php index fdb5804984..5c3cf5277d 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php +++ b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php @@ -151,6 +151,7 @@ private function writeCellProperties(CellStyle $style): void $vAlign = $style->getAlignment()->getVertical(); $wrap = $style->getAlignment()->getWrapText(); $indent = $style->getAlignment()->getIndent(); + $readOrder = $style->getAlignment()->getReadOrder(); $this->writer->startElement('style:table-cell-properties'); if (!empty($vAlign) || $wrap) { @@ -172,7 +173,7 @@ private function writeCellProperties(CellStyle $style): void $this->writer->endElement(); - if ($hAlign !== '' || !empty($indent)) { + if ($hAlign !== '' || !empty($indent) || $readOrder === Alignment::READORDER_RTL || $readOrder === Alignment::READORDER_LTR) { $this->writer ->startElement('style:paragraph-properties'); if ($hAlign !== '') { @@ -182,6 +183,11 @@ private function writeCellProperties(CellStyle $style): void $indentString = sprintf('%.4f', $indent * self::INDENT_TO_INCHES) . 'in'; $this->writer->writeAttribute('fo:margin-left', $indentString); } + if ($readOrder === Alignment::READORDER_RTL) { + $this->writer->writeAttribute('style:writing-mode', 'rl-tb'); + } elseif ($readOrder === Alignment::READORDER_LTR) { + $this->writer->writeAttribute('style:writing-mode', 'lr-tb'); + } $this->writer->endElement(); } } diff --git a/src/PhpSpreadsheet/Writer/Xls/Xf.php b/src/PhpSpreadsheet/Writer/Xls/Xf.php index 2ab5a4ebca..bda33ed364 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Xf.php +++ b/src/PhpSpreadsheet/Writer/Xls/Xf.php @@ -220,8 +220,9 @@ public function writeXf(): string $header = pack('vv', $record, $length); //BIFF8 options: identation, shrinkToFit and text direction - $biff8_options = $this->style->getAlignment()->getIndent(); + $biff8_options = $this->style->getAlignment()->getIndent() & 15; $biff8_options |= (int) $this->style->getAlignment()->getShrinkToFit() << 4; + $biff8_options |= $this->style->getAlignment()->getReadOrder() << 6; $data = pack('vvvC', $ifnt, $ifmt, $style, $align); $data .= pack('CCC', self::mapTextRotation((int) $this->style->getAlignment()->getTextRotation()), $biff8_options, $used_attrib); diff --git a/tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php b/tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php index a61f901131..6a8f2a6b93 100644 --- a/tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php @@ -199,7 +199,7 @@ public function testCanApplyAlignment(): void