Skip to content

Commit eb450c4

Browse files
committed
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.
1 parent ad02899 commit eb450c4

File tree

6 files changed

+124
-19
lines changed

6 files changed

+124
-19
lines changed

src/PhpSpreadsheet/Writer/Ods/Cell/Style.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class Style
2020
public const COLUMN_STYLE_PREFIX = 'co';
2121
public const ROW_STYLE_PREFIX = 'ro';
2222
public const TABLE_STYLE_PREFIX = 'ta';
23+
public const INDENT_TO_INCHES = 0.1043; // undocumented, used trial and error
2324

2425
private XMLWriter $writer;
2526

@@ -28,12 +29,13 @@ public function __construct(XMLWriter $writer)
2829
$this->writer = $writer;
2930
}
3031

31-
private function mapHorizontalAlignment(string $horizontalAlignment): string
32+
private function mapHorizontalAlignment(?string $horizontalAlignment): string
3233
{
3334
return match ($horizontalAlignment) {
3435
Alignment::HORIZONTAL_CENTER, Alignment::HORIZONTAL_CENTER_CONTINUOUS, Alignment::HORIZONTAL_DISTRIBUTED => 'center',
3536
Alignment::HORIZONTAL_RIGHT => 'end',
3637
Alignment::HORIZONTAL_FILL, Alignment::HORIZONTAL_JUSTIFY => 'justify',
38+
Alignment::HORIZONTAL_GENERAL, '', null => '',
3739
default => 'start',
3840
};
3941
}
@@ -145,8 +147,10 @@ private function writeCellProperties(CellStyle $style): void
145147
{
146148
// Align
147149
$hAlign = $style->getAlignment()->getHorizontal();
150+
$hAlign = $this->mapHorizontalAlignment($hAlign);
148151
$vAlign = $style->getAlignment()->getVertical();
149152
$wrap = $style->getAlignment()->getWrapText();
153+
$indent = $style->getAlignment()->getIndent();
150154

151155
$this->writer->startElement('style:table-cell-properties');
152156
if (!empty($vAlign) || $wrap) {
@@ -168,10 +172,16 @@ private function writeCellProperties(CellStyle $style): void
168172

169173
$this->writer->endElement();
170174

171-
if (!empty($hAlign)) {
172-
$hAlign = $this->mapHorizontalAlignment($hAlign);
173-
$this->writer->startElement('style:paragraph-properties');
174-
$this->writer->writeAttribute('fo:text-align', $hAlign);
175+
if ($hAlign !== '' || !empty($indent)) {
176+
$this->writer
177+
->startElement('style:paragraph-properties');
178+
if ($hAlign !== '') {
179+
$this->writer->writeAttribute('fo:text-align', $hAlign);
180+
}
181+
if (!empty($indent)) {
182+
$indentString = sprintf('%.4f', $indent * self::INDENT_TO_INCHES) . 'in';
183+
$this->writer->writeAttribute('fo:margin-left', $indentString);
184+
}
175185
$this->writer->endElement();
176186
}
177187
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Ods;
6+
7+
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
8+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9+
use PhpOffice\PhpSpreadsheet\Writer\Ods;
10+
use PhpOffice\PhpSpreadsheet\Writer\Ods\Content;
11+
use PHPUnit\Framework\TestCase;
12+
13+
class IndentTest extends TestCase
14+
{
15+
private string $compatibilityMode;
16+
17+
protected function setUp(): void
18+
{
19+
$this->compatibilityMode = Functions::getCompatibilityMode();
20+
Functions::setCompatibilityMode(
21+
Functions::COMPATIBILITY_OPENOFFICE
22+
);
23+
}
24+
25+
protected function tearDown(): void
26+
{
27+
parent::tearDown();
28+
Functions::setCompatibilityMode($this->compatibilityMode);
29+
}
30+
31+
public function testWriteSpreadsheet(): void
32+
{
33+
$spreadsheet = new Spreadsheet();
34+
$sheet = $spreadsheet->getActiveSheet();
35+
$sheet->setCellValue('A1', 'aa');
36+
$sheet->setCellValue('B1', 'bb');
37+
$sheet->setCellValue('A2', 'cc');
38+
$sheet->setCellValue('B2', 'dd');
39+
$sheet->getStyle('A1')->getAlignment()->setIndent(2);
40+
$writer = new Ods($spreadsheet);
41+
$content = new Content($writer);
42+
$xml = $content->write();
43+
self::assertStringContainsString(
44+
'<style:style style:name="ce0" style:family="table-cell" style:parent-style-name="Default">'
45+
. '<style:table-cell-properties style:vertical-align="bottom" style:rotation-align="none"/>'
46+
. '<style:text-properties fo:color="#000000" fo:font-family="Calibri" fo:font-size="11.0pt"/>'
47+
. '</style:style>',
48+
$xml
49+
);
50+
self::assertStringContainsString(
51+
'<style:style style:name="ce1" style:family="table-cell" style:parent-style-name="Default">'
52+
. '<style:table-cell-properties style:vertical-align="bottom" style:rotation-align="none"/>'
53+
. '<style:paragraph-properties fo:margin-left="0.2086in"/>' // fo:margin-left is what we're looking for
54+
. '<style:text-properties fo:color="#000000" fo:font-family="Calibri" fo:font-size="11.0pt"/>'
55+
. '</style:style>',
56+
$xml
57+
);
58+
self::assertSame(3, substr_count($xml, 'table:style-name="ce0"'));
59+
self::assertSame(1, substr_count($xml, 'table:style-name="ce1"'));
60+
$spreadsheet->disconnectWorksheets();
61+
}
62+
}
Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,48 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2"><office:scripts/><office:font-face-decls/><office:automatic-styles><style:style style:family="table" style:name="ta1"><style:table-properties table:display="true"/></style:style><style:style style:name="ce0" style:family="table-cell" style:parent-style-name="Default"><style:table-cell-properties style:vertical-align="bottom" style:rotation-align="none"/><style:paragraph-properties fo:text-align="start"/><style:text-properties fo:color="#000000" fo:font-family="Calibri" fo:font-size="11.0pt"/></style:style></office:automatic-styles><office:body><office:spreadsheet><table:calculation-settings/><table:table table:name="Worksheet" table:style-name="ta1"><office:forms/><table:table-row><table:table-cell table:style-name="ce0" office:value-type="float" office:value="1"><text:p>1</text:p></table:table-cell><table:table-cell table:style-name="ce0" table:number-matrix-columns-spanned="1" table:number-matrix-rows-spanned="2" table:formula="of:=UNIQUE([.A1:.A3])" office:value-type="float" office:value="1"><text:p>1</text:p></table:table-cell><table:table-cell table:number-columns-repeated="1022"/></table:table-row><table:table-row><table:table-cell table:style-name="ce0" office:value-type="float" office:value="1"><text:p>1</text:p></table:table-cell><table:table-cell table:style-name="ce0" office:value-type="float" office:value="3"><text:p>3</text:p></table:table-cell><table:table-cell table:number-columns-repeated="1022"/></table:table-row><table:table-row><table:table-cell table:style-name="ce0" office:value-type="float" office:value="3"><text:p>3</text:p></table:table-cell><table:table-cell table:style-name="ce0"/><table:table-cell table:number-columns-repeated="1022"/></table:table-row></table:table><table:named-expressions/></office:spreadsheet></office:body></office:document-content>
2+
<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2">
3+
<office:scripts/>
4+
<office:font-face-decls/>
5+
<office:automatic-styles>
6+
<style:style style:family="table" style:name="ta1">
7+
<style:table-properties table:display="true"/>
8+
</style:style>
9+
<style:style style:name="ce0" style:family="table-cell" style:parent-style-name="Default">
10+
<style:table-cell-properties style:vertical-align="bottom" style:rotation-align="none"/>
11+
<style:text-properties fo:color="#000000" fo:font-family="Calibri" fo:font-size="11.0pt"/>
12+
</style:style>
13+
</office:automatic-styles>
14+
<office:body>
15+
<office:spreadsheet>
16+
<table:calculation-settings/>
17+
<table:table table:name="Worksheet" table:style-name="ta1">
18+
<office:forms/>
19+
<table:table-row>
20+
<table:table-cell table:style-name="ce0" office:value-type="float" office:value="1">
21+
<text:p>1</text:p>
22+
</table:table-cell>
23+
<table:table-cell table:style-name="ce0" table:number-matrix-columns-spanned="1" table:number-matrix-rows-spanned="2" table:formula="of:=UNIQUE([.A1:.A3])" office:value-type="float" office:value="1">
24+
<text:p>1</text:p>
25+
</table:table-cell>
26+
<table:table-cell table:number-columns-repeated="1022"/>
27+
</table:table-row>
28+
<table:table-row>
29+
<table:table-cell table:style-name="ce0" office:value-type="float" office:value="1">
30+
<text:p>1</text:p>
31+
</table:table-cell>
32+
<table:table-cell table:style-name="ce0" office:value-type="float" office:value="3">
33+
<text:p>3</text:p>
34+
</table:table-cell>
35+
<table:table-cell table:number-columns-repeated="1022"/>
36+
</table:table-row>
37+
<table:table-row>
38+
<table:table-cell table:style-name="ce0" office:value-type="float" office:value="3">
39+
<text:p>3</text:p>
40+
</table:table-cell>
41+
<table:table-cell table:style-name="ce0"/>
42+
<table:table-cell table:number-columns-repeated="1022"/>
43+
</table:table-row>
44+
</table:table>
45+
<table:named-expressions/>
46+
</office:spreadsheet>
47+
</office:body>
48+
</office:document-content>

tests/data/Writer/Ods/content-empty.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
</style:style>
99
<style:style style:name="ce0" style:family="table-cell" style:parent-style-name="Default">
1010
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" />
11-
<style:paragraph-properties fo:text-align="start" />
1211
<style:text-properties fo:color="#000000" fo:font-family="Calibri" fo:font-size="11.0pt" />
1312
</style:style>
1413
</office:automatic-styles>

tests/data/Writer/Ods/content-hidden-worksheet.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
</style:style>
1212
<style:style style:name="ce0" style:family="table-cell" style:parent-style-name="Default">
1313
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" />
14-
<style:paragraph-properties fo:text-align="start" />
1514
<style:text-properties fo:color="#000000" fo:font-family="Calibri" fo:font-size="11.0pt" />
1615
</style:style>
1716
</office:automatic-styles>

tests/data/Writer/Ods/content-with-data.xml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,57 +11,46 @@
1111
</style:style>
1212
<style:style style:family="table-cell" style:name="ce0" style:parent-style-name="Default">
1313
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" />
14-
<style:paragraph-properties fo:text-align="start" />
1514
<style:text-properties fo:color="#000000" fo:font-family="Calibri" fo:font-size="11.0pt"/>
1615
</style:style>
1716
<style:style style:family="table-cell" style:name="ce1" style:parent-style-name="Default">
1817
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" />
19-
<style:paragraph-properties fo:text-align="start" />
2018
<style:text-properties fo:color="#000000" fo:font-family="Calibri" fo:font-size="11.0pt"/>
2119
</style:style>
2220
<style:style style:family="table-cell" style:name="ce2" style:parent-style-name="Default">
2321
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" />
24-
<style:paragraph-properties fo:text-align="start" />
2522
<style:text-properties style:font-weight-asian="bold" style:font-weight-complex="bold" fo:color="#000000" fo:font-family="Calibri" fo:font-size="11.0pt" fo:font-weight="bold"/>
2623
</style:style>
2724
<style:style style:family="table-cell" style:name="ce3" style:parent-style-name="Default">
2825
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" />
29-
<style:paragraph-properties fo:text-align="start" />
3026
<style:text-properties fo:color="#000000" fo:font-family="Calibri" fo:font-size="11.0pt" fo:font-style="italic"/>
3127
</style:style>
3228
<style:style style:family="table-cell" style:name="ce4" style:parent-style-name="Default">
3329
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" />
34-
<style:paragraph-properties fo:text-align="start" />
3530
<style:text-properties fo:color="#000000" fo:font-family="Courier" fo:font-size="11.0pt"/>
3631
</style:style>
3732
<style:style style:family="table-cell" style:name="ce5" style:parent-style-name="Default">
3833
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" />
39-
<style:paragraph-properties fo:text-align="start" />
4034
<style:text-properties fo:color="#000000" fo:font-family="Courier" fo:font-size="14.0pt"/>
4135
</style:style>
4236
<style:style style:family="table-cell" style:name="ce6" style:parent-style-name="Default">
4337
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" />
44-
<style:paragraph-properties fo:text-align="start" />
4538
<style:text-properties fo:color="#0000FF" fo:font-family="Courier" fo:font-size="14.0pt"/>
4639
</style:style>
4740
<style:style style:family="table-cell" style:name="ce7" style:parent-style-name="Default">
4841
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" fo:background-color="#ffffff"/>
49-
<style:paragraph-properties fo:text-align="start" />
5042
<style:text-properties fo:color="#0000FF" fo:font-family="Courier" fo:font-size="14.0pt"/>
5143
</style:style>
5244
<style:style style:family="table-cell" style:name="ce8" style:parent-style-name="Default">
5345
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" fo:background-color="#ff0000"/>
54-
<style:paragraph-properties fo:text-align="start" />
5546
<style:text-properties fo:color="#0000FF" fo:font-family="Courier" fo:font-size="14.0pt"/>
5647
</style:style>
5748
<style:style style:family="table-cell" style:name="ce9" style:parent-style-name="Default">
5849
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" fo:background-color="#ff0000"/>
59-
<style:paragraph-properties fo:text-align="start" />
6050
<style:text-properties style:text-underline-color="font-color" style:text-underline-style="solid" style:text-underline-type="single" style:text-underline-width="auto" fo:color="#0000FF" fo:font-family="Courier" fo:font-size="14.0pt"/>
6151
</style:style>
6252
<style:style style:family="table-cell" style:name="ce10" style:parent-style-name="Default">
6353
<style:table-cell-properties style:rotation-align="none" style:vertical-align="bottom" />
64-
<style:paragraph-properties fo:text-align="start" />
6554
<style:text-properties style:text-underline-color="font-color" style:text-underline-style="solid" style:text-underline-type="double" style:text-underline-width="auto" fo:color="#000000" fo:font-family="Calibri" fo:font-size="11.0pt"/>
6655
</style:style>
6756
</office:automatic-styles>

0 commit comments

Comments
 (0)