Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/PhpSpreadsheet/Reader/Html.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/PhpSpreadsheet/Reader/Xls.php
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/PhpSpreadsheet/Style/Alignment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
24 changes: 21 additions & 3 deletions src/PhpSpreadsheet/Writer/Html.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion src/PhpSpreadsheet/Writer/Ods/Cell/Style.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 !== '') {
Expand All @@ -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();
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/PhpSpreadsheet/Writer/Xls/Xf.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ public function testCanApplyAlignment(): void
<td valign="center">Center valign</td>
<td style="text-align: center;">Center align</td>
<td style="vertical-align: center;">Center valign</td>
<td style="text-indent: 10px;">Text indent</td>
<td style="text-indent: 9px;">Text indent</td>
<td style="word-wrap: break-word;">Wraptext</td>
</tr>
</table>';
Expand All @@ -220,7 +220,7 @@ public function testCanApplyAlignment(): void
self::assertEquals(Alignment::VERTICAL_CENTER, $style->getAlignment()->getVertical());

$style = $firstSheet->getCell('E1')->getStyle();
self::assertEquals(10, $style->getAlignment()->getIndent());
self::assertEquals(1, $style->getAlignment()->getIndent());

$style = $firstSheet->getCell('F1')->getStyle();
self::assertTrue($style->getAlignment()->getWrapText());
Expand Down Expand Up @@ -302,14 +302,14 @@ public function testTextIndentUseRowspan(): void
</tr>
<tr>
<td>2</td>
<td style="text-indent:10px">Text Indent</td>
<td style="text-indent:9px">Text Indent</td>
</tr>
</table>';
$filename = HtmlHelper::createHtml($html);
$spreadsheet = HtmlHelper::loadHtmlIntoSpreadsheet($filename, true);
$firstSheet = $spreadsheet->getSheet(0);
$style = $firstSheet->getCell('C2')->getStyle();
self::assertEquals(10, $style->getAlignment()->getIndent());
self::assertEquals(1, $style->getAlignment()->getIndent());
$spreadsheet->disconnectWorksheets();
}

Expand Down
2 changes: 1 addition & 1 deletion tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4248Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function testHtml(): void
$data = str_replace(["\r", "\n"], '', $writer->generateHtmlAll());
$expected = ' <tr class="row17">' // Cell D18
. ' <td class="column0 style0">&nbsp;</td>'
. ' <td class="column1 style28 null"></td>'
. ' <td class="column1 style28 null">&nbsp;</td>'
. ' <td class="column2 style35 s">Eligible </td>'
. ' <td class="column3 style70 s">Non</td>';
self::assertStringContainsString($expected, $data, 'Cell D18 style');
Expand Down
39 changes: 39 additions & 0 deletions tests/PhpSpreadsheetTests/Reader/Xml/ReadOrderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests\Reader\Xml;

use PhpOffice\PhpSpreadsheet\Reader\Xml as XmlReader;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;

class ReadOrderTest extends AbstractFunctional
{
public function testReadOrder(): void
{
// Issue 850 - Xls Reader/Writer didn't support Alignment ReadOrder
$infile = 'tests/data/Reader/Xml/issue.850.xml';
$reader = new XmlReader();
$robj = $reader->load($infile);

$sheet0 = $robj->setActiveSheetIndex(0);
self::assertSame(
Alignment::READORDER_RTL,
$sheet0->getStyle('A1')->getAlignment()->getReadOrder()
);
self::assertSame(
Alignment::READORDER_LTR,
$sheet0->getStyle('A2')->getAlignment()->getReadOrder()
);
self::assertSame(
Alignment::READORDER_CONTEXT,
$sheet0->getStyle('A3')->getAlignment()->getReadOrder()
);
self::assertSame(
2,
$sheet0->getStyle('A5')->getAlignment()->getIndent()
);
$robj->disconnectWorksheets();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function testColourScaleHtmlOutput(): void
['G3', 'background-color:#EC926F;">7<', 'cell G3'],
['H3', 'background-color:#E67C73;">8<', 'cell H3'],
['A4', 'background-color:#57BB8A;">1<', 'cell A4'],
['I4', 'null"><', 'empty cell I4'],
['I4', 'null">&nbsp;<', 'empty cell I4'],
['J4', 'background-color:#E67C73;">10<', 'cell J4'],
];
foreach ($expectedMatches as $expected) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public function testFormatValueWithMask(mixed $expectedResult, mixed $val, strin

$writer = new Html($spreadsheet);
$html = $writer->generateHTMLAll();
$html = str_replace('>&nbsp;<', '><', $html); // clear empty cells
$dom = new DOMDocument();
$dom->loadHTML($html);
$body = $dom->getElementsByTagName('body')->item(0);
Expand Down
121 changes: 121 additions & 0 deletions tests/PhpSpreadsheetTests/Writer/Html/ReadOrderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests\Writer\Html;

use PhpOffice\PhpSpreadsheet\Reader\Html as HtmlReader;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;
use PHPUnit\Framework\TestCase;

class ReadOrderTest extends TestCase
{
public function testInline(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', '1-' . 'منصور حسين الناصر');
$sheet->setCellValue('A2', '1-' . 'منصور حسين الناصر');
$sheet->setCellValue('A3', '1-' . 'منصور حسين الناصر');
$sheet->getStyle('A1')
->getAlignment()->setReadOrder(Alignment::READORDER_RTL);
$sheet->getStyle('A2')
->getAlignment()->setReadOrder(Alignment::READORDER_LTR);
$sheet->getStyle('A2')->getFont()->setName('Arial');
$sheet->getStyle('A3')->getFont()->setName('Times New Roman');
$sheet->setCellValue('A5', 'hello');
$sheet->getStyle('A5')->getFont()->setName('Tahoma');
$sheet->getStyle('A5')
->getAlignment()->setIndent(2);
$writer = new HtmlWriter($spreadsheet);
$writer->setUseInlineCss(true);
$html = $writer->generateHtmlAll();
self::assertStringContainsString(
'<td class="gridlines" style="vertical-align:bottom; direction:rtl; color:#000000; font-family:\'Calibri\';',
$html
);
self::assertStringContainsString(
'<td class="gridlines" style="vertical-align:bottom; direction:ltr; color:#000000; font-family:\'Arial\';',
$html
);
self::assertStringContainsString(
'<td class="gridlines" style="vertical-align:bottom; color:#000000; font-family:\'Times New Roman\';',
$html
);
self::assertStringContainsString(
'>&nbsp;</td>',
$html
);
self::assertStringContainsString(
'<td class="gridlines" style="vertical-align:bottom; text-indent:18px; color:#000000; font-family:\'Tahoma\';',
$html
);
$spreadsheet->disconnectWorksheets();

$reader = new HtmlReader();
$spreadsheet2 = $reader->loadFromString($html);
$sheet0 = $spreadsheet2->getActiveSheet();
self::assertSame(
Alignment::READORDER_RTL,
$sheet0->getStyle('A1')->getAlignment()->getReadOrder()
);
self::assertSame(
Alignment::READORDER_LTR,
$sheet0->getStyle('A2')->getAlignment()->getReadOrder()
);
self::assertSame(
Alignment::READORDER_CONTEXT,
$sheet0->getStyle('A3')->getAlignment()->getReadOrder()
);
self::assertSame(
2,
$sheet0->getStyle('A5')->getAlignment()->getIndent()
);
$spreadsheet2->disconnectWorksheets();
}

public function testNotInline(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', '1-' . 'منصور حسين الناصر');
$sheet->setCellValue('A2', '1-' . 'منصور حسين الناصر');
$sheet->setCellValue('A3', '1-' . 'منصور حسين الناصر');
$sheet->getStyle('A1')
->getAlignment()->setReadOrder(Alignment::READORDER_RTL);
$sheet->getStyle('A2')
->getAlignment()->setReadOrder(Alignment::READORDER_LTR);
$sheet->getStyle('A2')->getFont()->setName('Arial');
$sheet->getStyle('A3')->getFont()->setName('Times New Roman');
$sheet->setCellValue('A5', 'hello');
$sheet->getStyle('A5')->getFont()->setName('Tahoma');
$sheet->getStyle('A5')
->getAlignment()->setIndent(2);
$writer = new HtmlWriter($spreadsheet);
$html = $writer->generateHtmlAll();
self::assertStringContainsString(
'td.style1, th.style1 { vertical-align:bottom; direction:rtl; border-bottom',
$html
);
self::assertStringContainsString(
'td.style2, th.style2 { vertical-align:bottom; direction:ltr; border-bottom',
$html
);
self::assertStringContainsString(
'td.style3, th.style3 { vertical-align:bottom; border-bottom',
$html
);
self::assertStringContainsString(
'>&nbsp;</td>',
$html
);
self::assertStringContainsString(
'td.style4, th.style4 { vertical-align:bottom; text-indent:18px;',
$html
);
$spreadsheet->disconnectWorksheets();
// PhpSpreadsheet does not read non-inline styles
}
}
Loading