The initial fix only converted column indices to 0-based, but overlooked
that row indices also need the same treatment per BIFF8 specification.
Changes:
- Convert firstRowIndex from 1-based to 0-based (subtract 1)
- Convert lastRowIndex from 1-based to 0-based (subtract 1)
- Update row capping logic for 65536 limit
- Fix test assertions to expect rwMic=0 and rwMac=5 (not 1 and 6)
- Enhanced documentation to clarify all DIMENSIONS indices are 0-based
This now matches the behavior observed in Excel-generated XLS files,
where a file with 3 rows × 5 columns shows: rwMic=0, rwMac=3, colMic=0, colMac=5
All tests pass (53 tests, 244 assertions).
Add proper type assertions to handle potential false returns:
- Assert file_get_contents() returns string, not false
- Assert strpos() returns int, not false
- Assert unpack() returns array, not false
This resolves all 7 PHPStan errors reported in CI while maintaining
test functionality (12 assertions, all passing).
This commit refines the BIFF8 DIMENSIONS record fix by:
1. **Optimized column index capping**: Replaced the if-statement with min()
function to ensure lastColumnIndex never exceeds 255, improving code
coverage and eliminating unreachable branches in unit tests.
2. **Added comprehensive unit tests**: Created DimensionsRecordTest.php which
directly parses the binary DIMENSIONS record (0x0200) from XLS files to
verify correct 0-based column indices.
The tests validate:
- colMic (first column) = 0 for column A (was incorrectly 1 before fix)
- colMac (last column + 1) uses proper 0-based indexing
- Column indices are correctly capped at 255 (BIFF8 limit)
These tests fail without the fix and pass with it, ensuring the DIMENSIONS
record is correctly written for compatibility with legacy XLS parsers that
expect 0-based column indices per the BIFF8 specification.
By default, TCPDF will die sometimes rather than throwing exception. And this is controlled by a defined constant in the global namespace, not by an instance property. Ugh! Using this class instead of the class which it extends will probably be suitable for most users. But not for those who have customized their config file. Which is why this isn't the default, so that there is no breaking change for those users. Note that if both TCPDF and TcpdfNoDie are used in the same process, the first one used "wins" the battle of the defines.
Fix#850 (marked stale many years ago, but now reopened). User had a typo in their script which would have caused problems no matter what. However, it exposed another problem. Style Alignment Read Order was supported only by the Xlsx Reader and Writer, but it could have been supported pretty easily for most other formats. This PR adds support for the following:
- Xls (read and write)
- Html (write, and read using inline styles). Html reader does not yet process most classes.
- Pdf (write). PhpSpreadsheet does not have a Pdf reader.
- Ods (write). PhpSpreadsheet does not yet support reading most Ods styles.
- Xml (read). PhpSpreadsheet does not have an Xml writer.
- Gnumeric (no change). It appears that the Gnumeric product does not support this attribute.
- Csv (no change). Csv does not support any styles.
- Slk (no change). Slk does not support non-Latin characters, so this attribute doesn't make sense for it.
Fix#4584. As discussed there, MS has implemented this situation in a way that I frankly do not understand. Marking a row with a wrapped cell to *not* use the default row height seems to affect every other populated row. Very odd. Nevertheless, adding a new `customFormat` boolean property to RowDimension seems to be a way to resolve this problem. Note that the Xml generated by Excel already outputs a `customFormat` attribute when a style is applied to a row. This PR just adds that property for when it is needed for rowHeight. It need not, and should not, be set by the application when a style is applied to the row; PhpSpreadsheet will take care of that on its own.
Although the problem was raised for Xlsx format (I think), I investigated other relevant output formats as well. Html tends to do its own cell wrapping. I think the results after this change match the results before.
Xls already handled the output side of this situation without difficulty. A change was needed on the input side, and is included as part of this PR.
Ods did not handle default row height at all. As with other style properties, it is difficult to handle this on the input side, and this PR does nothing to improve that situation. However, we are, at least, able to provide some relief on the output side. Not complete relief - in order to apply the default size to unpopulated rows, we'd need to at least populate them with the `table:number-rows-repeated` attribute, and that has often been a source of problems, so I'm not willing to go there yet. This PR just ensures that all populated rows will have the correct height.
This is a theoretical problem. Building FalseTrueArray only looks at languages, not countries. The only example we have is `pt` vs `pt_br`, and since True and False have the same translation for both locales, there is no problem. This PR will help it from becoming a problem in future if a new language/country combo is added.
Fix#484. Fix#1318. Both went stale a long time ago. The images can currently specify their location as Left, Center, or Right. However, they also need to specify their type - first page, even pages, or odd pages (which is what is used when neither of the others is applicable). This is accomplished by adding 18 new constants to `Worksheet\HeaderFooter`, any of which can be specified as the second argument to `HeaderFooter::addImage`. Somewhat surprisingly, no changes are required to Xlsx Writer or Reader.
Xls Reader and Writer support header/footer, but do not support using images in them. No other readers nor writers support header/footer.
Fix#4542. PhpSpreadsheet has been writing attribute `xml:space="preserve"` to the `table` tag when writing a Table. According to the issue, Excel 2016 is treating the resulting file as corrupt. I do not have access to a version of Excel 2016 to confirm. This seems to be a bug with that release. Nevertheless, the OOXML spec, with over 100 references to `xml:space` does not indicate that it is a permitted attribute for `table`. It should only be specified for text nodes. This PR eliminates the undocumented, and unneeded, usage.
Investigating further, PhpSpreadsheet also writes this attribute for `workbook`, `styleSheet`, and `worksheet` tags. It is again undocumented and unneeded in those cases. Although all Excel releases, including 2016, apparently tolerate such usage, this PR also eliminates those.
Finally, there is one case where PhpSpreadsheet omits this tag when it is needed. When writing a cell whose data type is an inline string, and the string contains leading or trailing whitespace, the text tag needs to specify `xml:space`, and is now changed to do so.
In Theme, change COLOR_SCHEME_2013_PLUS_NAME to private (use COLOR_SCHEME_2013_2022_NAME instead), and delete COLOR_SCHEME_2013_PLUS (use COLOR_SCHEME_2013_2022).
Xlsx conditional styles cannot be used to change alignment, nor font family, nor font size.
Conditional::getStyle currently allocates a new style as an unconditional style; it is changed to do so a conditional ctyle.
Border should not be conditionally merged if borderStyle is OMIT.
Inline Css was generating `class="gridlines gridlinesp"` for all cells. It is changed to use the worksheet settings to decide which classes to use.
Fix#4539. Conditional Formatting was recently added to Html Writer. It works fine when not using inline Css. However, when using inline Css, the code inadvertently added 2 different `style` attributes (one for the unconditional style and one for the conditional style) to the same cell. This is not valid html, and results in losing the conditional styling. This PR combines the two `style` attributes into one, which will now come after the `class`, `colspan`, and `rowspan` attributes.
Aside from the new tests, this PR changes an unusually large number of existing tests. While this might normally be considered a red flag, it is not a problem here. All of the changes involve merely changing the order of attributes within html tags; none of them affect how the generated html would appear in a browser.
Fix#4537. PhpSpreadsheet currently changes apostrophes in text values to `'`. This is perfectly valid Xml. Issue was opened because R does not handle this correctly; this is unquestionably a bug on R's part. So I was not inclined to do anything about it. However ...
User suggested a change to how `htmlspecialchars` was called. Investigating the use of that routine in PhpSpreadsheet, I found that there was some double escaping going on for cells whose type was set to `TYPE_INLINE` - `htmlspecialchars` escaped the string correctly, but it was later written as Xml using a method which escaped the data a second time. So, a real bug in PhpSpreadsheet after all.
There was one call to `htmlspecialchars` in `Shared\XmlWriter`. I replaced `writeRaw(htmlspecialchars(...))` with `text(...)`. And one call in `Writer\Xlsx\Worksheet`, the source of the double escaping bug above; the call to `htmlspecialchars` can just be eliminated there.
Making those changes, the only remaining calls to `htmlspecialchars` are in `Writer\Html`, where they belong. As a bonus, apostrophes now wind up unescaped, so R will be satisfied (even though they should fix their bug).
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.
Fix#4522. Although this is technically a breaking change, it is expected that very few, if any, existing programs will be affected. Nevertheless, DefaultValueBinder is implicitly used by the vast majority of programs out there, and I am reluctant to install a breaking change for something so widespread. So I will save this change for the next breaking release, which should be PhpSpreadsheet 5.0.0. There is no current schedule for that release.
For strings consisting entirely of digits, DefaultValueBinder treats the value as a string if greater than PHP_INT_MAX, or an int otherwise. This prevents the loss of precision in large integers. This treatment dates back to PHPExcel, and has been in place since 2014.
There are several problems with this approach. Excel itself maintains
[15 digits of precision](https://support.microsoft.com/en-us/office/excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3).
So, string-vs-int should be decided at 999_999_999_999_999. This is much lower than PHP_INT_MAX for 64-bit, and much higher than for 32-bit. DefaultValueBinder is changed to use that new limit.
A second problem is that DefaultValueBinder is only making this adjustment for positive integers. It is changed to test absolute value.
A third problem is that DefaultValueBinder is making this adjustment only for strings, so that if you pass the number in as an int, it will not be adjusted (and will lose precision). It is changed to apply the same test for int.
In our unit tests, we can test the Worksheet Xml which PhpSpreadsheet will generate without having to actually write the spreadsheet to and read it from disk. This refactoring allows us to do the same for Styles, as demonstrated in the new StylesWriterTest.
See discussion #4502. I am not sure of what needs to be involved here. This is especially so because Excel also offers AutoColor, but its dark mode looks significantly different than LibreOffice's.
So here's what I've come up with thus far.
- Add boolean autoColor with setter and getter to Font.
- If autoColor is true, ODS writer and XLSX Writer will record it in the xml, and will not write a font color. It is possible that both can co-exist; I'm just not sure how they are supposed to interact if that is the case.
- It makes most sense to me if the spreadsheet's default font specifies autoColor, but PhpSpreadsheet will not insist on that.
- Xlsx Reader will process autoColor. Like most other styles, Ods Reader is not yet set up to handle it.
- LibreOffice Calc should follow the autoColor declarations when it reads either an Xlsx or Ods spreadsheet created by PhpSpreadsheet.
A while back, Microsoft introduced changes to the default theme colors and fonts. We are adding support for this new theme so that users can use it easily on new spreadsheets if they wish. (It is already supported when using the Xlsx Reader to load an appropriate file.) The old theme was described by constants COLOR_SCHEME_2013_PLUS_NAME and COLOR_SCHEME_2013_PLUS; these are deprecated in favor of COLOR_SCHEME_2013_2022_NAME and COLOR_SCHEME_2013_2022. The new theme is described by constants COLOR_SCHEME_2023_PLUS_NAME and COLOR_SCHEME_2023_PLUS. PhpSpreadsheet's default theme remains COLOR_SCHEME_2007_2010, to avoid breaking changes.
A third optional parameter `$spreadsheet` is added to setThemeColorName. If specified, the default font names for the theme will be applied to the default style for the spreadsheet. You can thus use the new theme with its relatively new default `Aptos Narrow` font. IMHO, that isn't necessarily a good choice, but it is available. MS stores the new font in a different location than other system fonts, and that can lead to portability problems, e.g. if your spreadsheet uses Aptos and you export it to Html, browsers will not be able to find the font and a substitute font will be used.
Fix#1154, which went stale over 5 years ago, and is now reopened. RichText elements can be a Run, which sets its own style, or a TextElement, which doesn't. As the issue states, TextElement handling is inconsistent. This PR forces it to inherit the style of the cell. As implemented, this will change it to a Run when it is read in, but there should be no practical difference as far as the end-user is concerned. There is no change as far as Run is concerned. The user suggested 3 options - TextElement and Run both inherit for any unspecified style elements, TextElement inherits and Run does not, neither inherits. Option 2 makes most sense to me. Option 1 may not even be possible (we can't tell if, say, the user explicityl set Italic to false, or if that was just the default choice).
Tests are added for all of Xlsx, Xls, and Html. Html reading of RichText elements is not well-supported. The tests are fairly difficult to understand. A new sample is added to demonstrate this change.
Fix#3899. Supersedes PR #4476, which will be changed to draft status and closed if this PR is merged.
A standard cast from float to string in PHP can drop trailing decimal positions. This can lead to problems above and beyond the usual problems associated with floating point. See the superseded PR for a more complete explanation.
`StringHelper::convertToString` is changed for how it handles floats. It will now do separate casts for the whole and decimal parts, and then combine the results. This affects `Cell::getValueString` and `Cell::getCalculatedValueString`. Xlsx Writer will now invoke `convertToString` before writing a float to Xml. Ods Writer already uses `getValueString`, so no change is needed there. Xls Writer writes its float values in binary, so no change is needed there. Tests are added for all 3 writers.
Aside from fixing some problems, it might appear that this change introduces some new problems. For instance, setting a cell to `12345.6789` will now result in `12345.67890000000079` in the Xml. This difference is an illusion, merely a consequence of floating point rounding. If you run the following check under PhpUnit, it will pass:
```php
self::assertSame(12345.6789, 12345.67890000000079);
```
Fix#56, which had gone stale but is now reopened. The problem which it reported with Xlsx Writer was fixed long ago. However, Xls Writer has 2 problems regarding hyperlinks. First, some logic which should have been `if ... elseif ... else ...` was coded as `if ... if ... unconditional ...`, resulting in multiple writes and a corrupt worksheet (which Excel does fix correctly). Second, it treated a hyperlink url starting with `#` (pointer to a cell in the same spreadsheet) as external, when it should be treated as internal.
Also changed `Hyperlink::isInternal` to recognize a starting `#`, and to use `str_starts_with('sheet://')` rather than `str_contains`.