Writer Html/Pdf Support RTL Alignment of Table

Fix #1104, which went stale over 6 years ago, and is now reopened. This PR addresses the alignment of the table, not alignment of text. Support is added for the following:

- Html. Full support.
- Mpdf. Full support, except that, when mixed LTR and RTL worksheets are output, all tables will be on the left of the page (but will be properly aligned).
- Tcpdf. Full support when all worksheets being output are RTL; no support when mixed LTR and RTL worksheets are output.
- Dompdf. No support.
This commit is contained in:
oleibman
2025-07-14 00:22:54 -07:00
parent 747ccd1b44
commit 98f464b41f
8 changed files with 339 additions and 24 deletions
@@ -2,12 +2,10 @@
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf;
require __DIR__ . '/../Header.php';
/** @var PhpOffice\PhpSpreadsheet\Helper\Sample $helper */
require_once __DIR__ . '/Mpdf2.php';
/** @var PhpOffice\PhpSpreadsheet\Helper\Sample $helper */
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
@@ -41,11 +39,6 @@ $helper->log('Merge drawing cells for Pdf');
$spreadsheet->mergeDrawingCellsForPdf();
$helper->log('Write to Mpdf');
$writer = new Mpdf($spreadsheet);
$filename = $helper->getFileName(__FILE__, 'pdf');
$writer->save($filename);
$helper->log("Saved $filename");
if (PHP_SAPI !== 'cli') {
echo '<a href="/download.php?type=pdf&name=' . basename($filename) . '">Download ' . basename($filename) . '</a><br />';
}
$helper->write($spreadsheet, __FILE__, ['Mpdf']);
$spreadsheet->disconnectWorksheets();
+28
View File
@@ -0,0 +1,28 @@
<?php
use PhpOffice\PhpSpreadsheet\Spreadsheet;
require __DIR__ . '/../Header.php';
/** @var PhpOffice\PhpSpreadsheet\Helper\Sample $helper */
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet1 = $spreadsheet->getActiveSheet();
$sheet2 = $spreadsheet->createSheet();
$sheet3 = $spreadsheet->createSheet();
$cells = [
['a1', 'b1', 'c1'],
['a2', 'b2', 'c2'],
];
$sheet1->fromArray($cells);
$sheet1->setRightToLeft(true);
$sheet2->fromArray($cells);
$sheet3->fromArray($cells);
$sheet3->setRightToLeft(true);
$helper->log('Write to html, mpdf, tcpdf');
// Save
$helper->write($spreadsheet, __FILE__, ['Html', 'Mpdf', 'Tcpdf']);
$spreadsheet->disconnectWorksheets();
+36
View File
@@ -0,0 +1,36 @@
<?php
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;
require __DIR__ . '/../Header.php';
/** @var PhpOffice\PhpSpreadsheet\Helper\Sample $helper */
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet1 = $spreadsheet->getActiveSheet();
$sheet2 = $spreadsheet->createSheet();
$sheet3 = $spreadsheet->createSheet();
$cells = [
['a1', 'b1', 'c1'],
['a2', 'b2', 'c2'],
];
$sheet1->fromArray($cells);
$sheet1->setRightToLeft(true);
$sheet2->fromArray($cells);
$sheet3->fromArray($cells);
$sheet3->setRightToLeft(true);
$helper->log('Write to html, mpdf');
// Save
$helper->write(
$spreadsheet,
__FILE__,
['Html', 'Mpdf'],
writerCallback: function (HtmlWriter $writer): void {
$writer->writeAllSheets();
}
);
$spreadsheet->disconnectWorksheets();
+3
View File
@@ -532,6 +532,9 @@ class Html extends BaseReader
$sheet->setShowGridlines(in_array('gridlines', $classes, true));
$sheet->setPrintGridlines(in_array('gridlinesp', $classes, true));
}
if ('rtl' === ($attributeArray['dir'] ?? '')) {
$sheet->setRightToLeft(true);
}
$this->currentColumn = 'A';
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
$column = $this->setTableStartColumn($column);
+72 -9
View File
@@ -158,6 +158,10 @@ class Html extends BaseWriter
private string $getFalse = 'FALSE';
protected bool $rtlSheets = false;
protected bool $ltrSheets = false;
/**
* Create a new HTML.
*/
@@ -186,11 +190,31 @@ class Html extends BaseWriter
$this->maybeCloseFileHandle();
}
protected function checkRtlAndLtr(): void
{
$this->rtlSheets = false;
$this->ltrSheets = false;
if ($this->sheetIndex === null) {
foreach ($this->spreadsheet->getAllSheets() as $sheet) {
if ($sheet->getRightToLeft()) {
$this->rtlSheets = true;
} else {
$this->ltrSheets = true;
}
}
} else {
if ($this->spreadsheet->getSheet($this->sheetIndex)->getRightToLeft()) {
$this->rtlSheets = true;
}
}
}
/**
* Save Spreadsheet as html to variable.
*/
public function generateHtmlAll(): string
{
$this->checkRtlAndLtr();
$sheets = $this->generateSheetPrep();
foreach ($sheets as $sheet) {
$sheet->calculateArrays($this->preCalculateFormulas);
@@ -369,7 +393,8 @@ class Html extends BaseWriter
// Construct HTML
$properties = $this->spreadsheet->getProperties();
$html = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . PHP_EOL;
$html .= '<html xmlns="http://www.w3.org/1999/xhtml">' . PHP_EOL;
$rtl = ($this->rtlSheets && !$this->ltrSheets) ? " dir='rtl'" : '';
$html .= '<html xmlns="http://www.w3.org/1999/xhtml"' . $rtl . '>' . PHP_EOL;
$html .= ' <head>' . PHP_EOL;
$html .= ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . PHP_EOL;
$html .= ' <meta name="generator" content="PhpSpreadsheet, https://github.com/PHPOffice/PhpSpreadsheet" />' . PHP_EOL;
@@ -1013,6 +1038,9 @@ class Html extends BaseWriter
// .s {}
$css['.s']['text-align'] = 'left'; // STRING
$css['.floatright']['float'] = 'right';
$css['.floatleft']['float'] = 'left';
// Calculate cell style hashes
foreach ($this->spreadsheet->getCellXfCollection() as $index => $style) {
$css['td.style' . $index . ', th.style' . $index] = $this->createCSSStyle($style);
@@ -1221,21 +1249,52 @@ class Html extends BaseWriter
return $html;
}
private function getDir(Worksheet $worksheet): string
{
if ($worksheet->getRightToLeft()) {
return " dir='rtl'";
}
if ($this->rtlSheets) {
return " dir='ltr'";
}
return '';
}
private function getFloat(Worksheet $worksheet): string
{
$float = '';
if ($worksheet->getRightToLeft()) {
if ($this->ltrSheets) {
$float = ' floatright';
}
} else {
if ($this->rtlSheets) {
$float = ' floatleft';
}
}
return $float;
}
private function generateTableTagInline(Worksheet $worksheet, string $id): string
{
$style = isset($this->cssStyles['table'])
? $this->assembleCSS($this->cssStyles['table']) : '';
$rtl = $this->getDir($worksheet);
$float = $this->getFloat($worksheet);
$prntgrid = $worksheet->getPrintGridlines();
$viewgrid = $this->isPdf ? $prntgrid : $worksheet->getShowGridlines();
if ($viewgrid && $prntgrid) {
$html = " <table border='1' cellpadding='1' $id cellspacing='1' style='$style' class='gridlines gridlinesp'>" . PHP_EOL;
$html = " <table border='1' cellpadding='1'$rtl $id cellspacing='1' style='$style' class='gridlines gridlinesp$float'>" . PHP_EOL;
} elseif ($viewgrid) {
$html = " <table border='0' cellpadding='0' $id cellspacing='0' style='$style' class='gridlines'>" . PHP_EOL;
$html = " <table border='0' cellpadding='0'$rtl $id cellspacing='0' style='$style' class='gridlines$float'>" . PHP_EOL;
} elseif ($prntgrid) {
$html = " <table border='0' cellpadding='0' $id cellspacing='0' style='$style' class='gridlinesp'>" . PHP_EOL;
$html = " <table border='0' cellpadding='0'$rtl $id cellspacing='0' style='$style' class='gridlinesp$float'>" . PHP_EOL;
} elseif ($float === '') {
$html = " <table border='0' cellpadding='1'$rtl $id cellspacing='0' style='$style'>" . PHP_EOL;
} else {
$html = " <table border='0' cellpadding='1' $id cellspacing='0' style='$style'>" . PHP_EOL;
$html = " <table border='0' cellpadding='1'$rtl $id cellspacing='0' style='$style' class='$float'>" . PHP_EOL;
}
return $html;
@@ -1244,9 +1303,11 @@ class Html extends BaseWriter
private function generateTableTag(Worksheet $worksheet, string $id, string &$html, int $sheetIndex): void
{
if (!$this->useInlineCss) {
$rtl = $this->getDir($worksheet);
$float = $this->getFloat($worksheet);
$gridlines = $worksheet->getShowGridlines() ? ' gridlines' : '';
$gridlinesp = $worksheet->getPrintGridlines() ? ' gridlinesp' : '';
$html .= " <table border='0' cellpadding='0' cellspacing='0' $id class='sheet$sheetIndex$gridlines$gridlinesp'>" . PHP_EOL;
$html .= " <table border='0' cellpadding='0' cellspacing='0'$rtl $id class='sheet$sheetIndex$gridlines$gridlinesp$float'>" . PHP_EOL;
} else {
$html .= $this->generateTableTagInline($worksheet, $id);
}
@@ -1265,10 +1326,12 @@ class Html extends BaseWriter
// Construct HTML
$html = '';
$id = $showid ? "id='sheet$sheetIndex'" : '';
$clear = ($this->rtlSheets && $this->ltrSheets) ? '; clear:both' : '';
if ($showid) {
$html .= "<div style='page: page$sheetIndex'>" . PHP_EOL;
$html .= "<div style='page: page$sheetIndex$clear'>" . PHP_EOL;
} else {
$html .= "<div style='page: page$sheetIndex' class='scrpgbrk'>" . PHP_EOL;
$html .= "<div style='page: page$sheetIndex$clear' class='scrpgbrk'>" . PHP_EOL;
}
$this->generateTableTag($worksheet, $id, $html, $sheetIndex);
+19 -5
View File
@@ -67,14 +67,28 @@ class Tcpdf extends Pdf
// Set the appropriate font
$pdf->SetFont($this->getFont());
$this->checkRtlAndLtr();
if ($this->rtlSheets && !$this->ltrSheets) {
$pdf->setRTL(true);
}
$pdf->writeHTML($this->generateHTMLAll());
// Document info
$pdf->SetTitle($this->spreadsheet->getProperties()->getTitle());
$pdf->SetAuthor($this->spreadsheet->getProperties()->getCreator());
$pdf->SetSubject($this->spreadsheet->getProperties()->getSubject());
$pdf->SetKeywords($this->spreadsheet->getProperties()->getKeywords());
$pdf->SetCreator($this->spreadsheet->getProperties()->getCreator());
$pdf->SetTitle(
$this->spreadsheet->getProperties()->getTitle()
);
$pdf->SetAuthor(
$this->spreadsheet->getProperties()->getCreator()
);
$pdf->SetSubject(
$this->spreadsheet->getProperties()->getSubject()
);
$pdf->SetKeywords(
$this->spreadsheet->getProperties()->getKeywords()
);
$pdf->SetCreator(
$this->spreadsheet->getProperties()->getCreator()
);
// Write to file
fwrite($fileHandle, $pdf->output('', 'S'));
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests\Reader\Html;
use PhpOffice\PhpSpreadsheet\Reader\Html as HtmlReader;
use PHPUnit\Framework\TestCase;
class DirectionTest extends TestCase
{
public function testRtl(): void
{
$inlines = [
"<table border='0' cellpadding='0' cellspacing='0' dir='rtl' id='sheet0' class='sheet0 gridlines'>",
'<tbody>',
'<tr class="row0">',
'<td class="column0 style0 s">a1</td>',
'<td class="column1 style0 s">b1</td>',
'<td class="column2 style0 s">c1</td>',
'</tr>',
'<tr class="row1">',
'<td class="column0 style0 s">a2</td>',
'<td class="column1 style0 s">b2</td>',
'<td class="column2 style0 s">c2</td>',
'</tr>',
'</tbody></table>',
];
$html = implode("\n", $inlines);
$reader = new HtmlReader();
$spreadsheet = $reader->loadFromString($html);
$sheet = $spreadsheet->getActiveSheet();
self::assertTrue($sheet->getRightToLeft());
self::assertSame('a1', $sheet->getCell('A1')->getValue());
self::assertSame('c2', $sheet->getCell('C2')->getValue());
$spreadsheet->disconnectWorksheets();
}
public function testLtr(): void
{
$inlines = [
"<table border='0' cellpadding='0' cellspacing='0' dir='ltr' id='sheet0' class='sheet0 gridlines'>",
'<tbody>',
'<tr class="row0">',
'<td class="column0 style0 s">a1</td>',
'<td class="column1 style0 s">b1</td>',
'<td class="column2 style0 s">c1</td>',
'</tr>',
'<tr class="row1">',
'<td class="column0 style0 s">a2</td>',
'<td class="column1 style0 s">b2</td>',
'<td class="column2 style0 s">c2</td>',
'</tr>',
'</tbody></table>',
];
$html = implode("\n", $inlines);
$reader = new HtmlReader();
$spreadsheet = $reader->loadFromString($html);
$sheet = $spreadsheet->getActiveSheet();
self::assertFalse($sheet->getRightToLeft());
self::assertSame('a1', $sheet->getCell('A1')->getValue());
self::assertSame('c2', $sheet->getCell('C2')->getValue());
$spreadsheet->disconnectWorksheets();
}
public function testDefault(): void
{
$inlines = [
"<table border='0' cellpadding='0' cellspacing='0' id='sheet0' class='sheet0 gridlines'>",
'<tbody>',
'<tr class="row0">',
'<td class="column0 style0 s">a1</td>',
'<td class="column1 style0 s">b1</td>',
'<td class="column2 style0 s">c1</td>',
'</tr>',
'<tr class="row1">',
'<td class="column0 style0 s">a2</td>',
'<td class="column1 style0 s">b2</td>',
'<td class="column2 style0 s">c2</td>',
'</tr>',
'</tbody></table>',
];
$html = implode("\n", $inlines);
$reader = new HtmlReader();
$spreadsheet = $reader->loadFromString($html);
$sheet = $spreadsheet->getActiveSheet();
self::assertFalse($sheet->getRightToLeft());
self::assertSame('a1', $sheet->getCell('A1')->getValue());
self::assertSame('c2', $sheet->getCell('C2')->getValue());
$spreadsheet->disconnectWorksheets();
}
}
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests\Writer\Html;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Html;
use PHPUnit\Framework\TestCase;
class DirectionTest extends TestCase
{
public function testMixedRtlAndLtr(): void
{
$spreadsheet = new Spreadsheet();
$sheet1 = $spreadsheet->getActiveSheet();
$sheet1->setRightToLeft(true);
$sheet2 = $spreadsheet->createSheet();
$sheet3 = $spreadsheet->createSheet();
$sheet3->setRightToLeft(true);
$cells = [
['a1', 'b1', 'c1'],
['a2', 'b2', 'c2'],
];
$sheet1->fromArray($cells);
$sheet2->fromArray($cells);
$sheet3->fromArray($cells);
$writer = new Html($spreadsheet);
$writer->writeAllSheets();
$html = $writer->generateHTMLall();
$rtlCount = substr_count($html, "dir='rtl'");
self::assertSame(2, $rtlCount);
$ltrCount = substr_count($html, "dir='ltr'");
self::assertSame(1, $ltrCount);
$spreadsheet->disconnectWorksheets();
}
public function testNoRtl(): void
{
$spreadsheet = new Spreadsheet();
$sheet1 = $spreadsheet->getActiveSheet();
$sheet2 = $spreadsheet->createSheet();
$sheet3 = $spreadsheet->createSheet();
$cells = [
['a1', 'b1', 'c1'],
['a2', 'b2', 'c2'],
];
$sheet1->fromArray($cells);
$sheet2->fromArray($cells);
$sheet3->fromArray($cells);
$writer = new Html($spreadsheet);
$writer->writeAllSheets();
$html = $writer->generateHTMLall();
$rtlCount = substr_count($html, "dir='rtl'");
self::assertSame(0, $rtlCount);
$ltrCount = substr_count($html, "dir='ltr'");
self::assertSame(0, $ltrCount);
$spreadsheet->disconnectWorksheets();
}
public function testOnlyRtl(): void
{
$spreadsheet = new Spreadsheet();
$sheet1 = $spreadsheet->getActiveSheet();
$sheet2 = $spreadsheet->createSheet();
$sheet3 = $spreadsheet->createSheet();
$cells = [
['a1', 'b1', 'c1'],
['a2', 'b2', 'c2'],
];
$sheet1->fromArray($cells);
$sheet1->setRightToLeft(true);
$sheet2->fromArray($cells);
$sheet2->setRightToLeft(true);
$sheet3->fromArray($cells);
$sheet3->setRightToLeft(true);
$writer = new Html($spreadsheet);
$writer->writeAllSheets();
$html = $writer->generateHTMLall();
$rtlCount = substr_count($html, "dir='rtl'");
self::assertSame(4, $rtlCount, '3 sheets plus html tag');
$ltrCount = substr_count($html, "dir='ltr'");
self::assertSame(0, $ltrCount);
$spreadsheet->disconnectWorksheets();
}
}