From eb450c489ed72234c06a4f1075e7e11be25a7a8e Mon Sep 17 00:00:00 2001
From: oleibman <10341515+oleibman@users.noreply.github.com>
Date: Thu, 5 Dec 2024 20:10:42 -0800
Subject: [PATCH] Ods Writer Horizontal Alignment
Fix #4261. Ods does nothing with the `indent` property of Alignment. The fix is easy, but not as easy as it should be. Excel treats indent as an int in some unspecified unit. Ods, on the other hand, specifies it as a float with unit (inches, ems, etc.). Since MS does not reveal what an indent unit is, I resorted to some experimentation. It appears that 1 unit is equal to 0.1043 inches. That is not a guaranteed relationship - the conversion might be non-linear, or it might be affected by external factors like font size. I think multiplying by 0.1043 is adequate for now, and unquestionably better than what we're currently doing (ignoring the property). If it breaks down at some point, we'll look at it again. BTW, Shared\Drawing uses some conversion values of 9525, which, reciprocating and sliding the decimal point along yields a value of 0.10498..., which is close but not close enough for me to use.
The other property used in conjunction with indent is `text-align`, and here we have not been doing the right thing. If that property has the default value (`General`), it is treated as `start` (i.e. `left` on LTR docs and presumably `right` on RTL). This will align numbers as if they were text, which does no harm (they're still usable as numbers), but is not how LibreOffice handles them by default. Ods writer is changed to omit `text-align` when it is set to `General`, and let LibreOffice choose the appropriate alignment.
These changes are for Writer only. Ods Reader support for styles remains severely lacking.
---
src/PhpSpreadsheet/Writer/Ods/Cell/Style.php | 20 ++++--
.../Writer/Ods/IndentTest.php | 62 +++++++++++++++++++
tests/data/Writer/Ods/content-arrays.xml | 48 +++++++++++++-
tests/data/Writer/Ods/content-empty.xml | 1 -
.../Writer/Ods/content-hidden-worksheet.xml | 1 -
tests/data/Writer/Ods/content-with-data.xml | 11 ----
6 files changed, 124 insertions(+), 19 deletions(-)
create mode 100644 tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php
diff --git a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php
index 757323068..1cc68faba 100644
--- a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php
+++ b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php
@@ -20,6 +20,7 @@ class Style
public const COLUMN_STYLE_PREFIX = 'co';
public const ROW_STYLE_PREFIX = 'ro';
public const TABLE_STYLE_PREFIX = 'ta';
+ public const INDENT_TO_INCHES = 0.1043; // undocumented, used trial and error
private XMLWriter $writer;
@@ -28,12 +29,13 @@ class Style
$this->writer = $writer;
}
- private function mapHorizontalAlignment(string $horizontalAlignment): string
+ private function mapHorizontalAlignment(?string $horizontalAlignment): string
{
return match ($horizontalAlignment) {
Alignment::HORIZONTAL_CENTER, Alignment::HORIZONTAL_CENTER_CONTINUOUS, Alignment::HORIZONTAL_DISTRIBUTED => 'center',
Alignment::HORIZONTAL_RIGHT => 'end',
Alignment::HORIZONTAL_FILL, Alignment::HORIZONTAL_JUSTIFY => 'justify',
+ Alignment::HORIZONTAL_GENERAL, '', null => '',
default => 'start',
};
}
@@ -145,8 +147,10 @@ class Style
{
// Align
$hAlign = $style->getAlignment()->getHorizontal();
+ $hAlign = $this->mapHorizontalAlignment($hAlign);
$vAlign = $style->getAlignment()->getVertical();
$wrap = $style->getAlignment()->getWrapText();
+ $indent = $style->getAlignment()->getIndent();
$this->writer->startElement('style:table-cell-properties');
if (!empty($vAlign) || $wrap) {
@@ -168,10 +172,16 @@ class Style
$this->writer->endElement();
- if (!empty($hAlign)) {
- $hAlign = $this->mapHorizontalAlignment($hAlign);
- $this->writer->startElement('style:paragraph-properties');
- $this->writer->writeAttribute('fo:text-align', $hAlign);
+ if ($hAlign !== '' || !empty($indent)) {
+ $this->writer
+ ->startElement('style:paragraph-properties');
+ if ($hAlign !== '') {
+ $this->writer->writeAttribute('fo:text-align', $hAlign);
+ }
+ if (!empty($indent)) {
+ $indentString = sprintf('%.4f', $indent * self::INDENT_TO_INCHES) . 'in';
+ $this->writer->writeAttribute('fo:margin-left', $indentString);
+ }
$this->writer->endElement();
}
}
diff --git a/tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php b/tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php
new file mode 100644
index 000000000..7b6a62baa
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php
@@ -0,0 +1,62 @@
+compatibilityMode = Functions::getCompatibilityMode();
+ Functions::setCompatibilityMode(
+ Functions::COMPATIBILITY_OPENOFFICE
+ );
+ }
+
+ protected function tearDown(): void
+ {
+ parent::tearDown();
+ Functions::setCompatibilityMode($this->compatibilityMode);
+ }
+
+ public function testWriteSpreadsheet(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $sheet->setCellValue('A1', 'aa');
+ $sheet->setCellValue('B1', 'bb');
+ $sheet->setCellValue('A2', 'cc');
+ $sheet->setCellValue('B2', 'dd');
+ $sheet->getStyle('A1')->getAlignment()->setIndent(2);
+ $writer = new Ods($spreadsheet);
+ $content = new Content($writer);
+ $xml = $content->write();
+ self::assertStringContainsString(
+ ''
+ . ''
+ . ''
+ . '',
+ $xml
+ );
+ self::assertStringContainsString(
+ ''
+ . ''
+ . '' // fo:margin-left is what we're looking for
+ . ''
+ . '',
+ $xml
+ );
+ self::assertSame(3, substr_count($xml, 'table:style-name="ce0"'));
+ self::assertSame(1, substr_count($xml, 'table:style-name="ce1"'));
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/data/Writer/Ods/content-arrays.xml b/tests/data/Writer/Ods/content-arrays.xml
index a33b7dbfc..a363d199d 100644
--- a/tests/data/Writer/Ods/content-arrays.xml
+++ b/tests/data/Writer/Ods/content-arrays.xml
@@ -1,2 +1,48 @@
-11133
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1
+
+
+1
+
+
+
+
+
+1
+
+
+3
+
+
+
+
+
+3
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/data/Writer/Ods/content-empty.xml b/tests/data/Writer/Ods/content-empty.xml
index 84f4c2397..ee82953d1 100644
--- a/tests/data/Writer/Ods/content-empty.xml
+++ b/tests/data/Writer/Ods/content-empty.xml
@@ -8,7 +8,6 @@
-
diff --git a/tests/data/Writer/Ods/content-hidden-worksheet.xml b/tests/data/Writer/Ods/content-hidden-worksheet.xml
index 88a53257a..89985861c 100644
--- a/tests/data/Writer/Ods/content-hidden-worksheet.xml
+++ b/tests/data/Writer/Ods/content-hidden-worksheet.xml
@@ -11,7 +11,6 @@
-
diff --git a/tests/data/Writer/Ods/content-with-data.xml b/tests/data/Writer/Ods/content-with-data.xml
index db7d75a74..5b2f0677e 100644
--- a/tests/data/Writer/Ods/content-with-data.xml
+++ b/tests/data/Writer/Ods/content-with-data.xml
@@ -11,57 +11,46 @@
-
-
-
-
-
-
-
-
-
-
-