spout icon indicating copy to clipboard operation
spout copied to clipboard

Automatically calculate columns width / rows height

Open adrilo opened this issue 8 years ago • 60 comments

Instead of using the default width/height, Spout could calculate the optimal width/height for each cell, based on its content.

This involves using a graphics library, render the text content with the chosen font and get the width/height of the rendered text.

This calculation may be slow so it should be optional. It can also be used when using "wrap text" as wrapping does not work well without a custom row height in LibreOffice.

Spout should expose an option to automatically set the optimal width/height but should also allow developers to specify a max width / height. When the max width is reached, the content will automatically wrap and the cell height will increase. It will increase until it reaches the max height.

adrilo avatar Oct 22 '15 18:10 adrilo

+1

zendre4 avatar Feb 25 '16 09:02 zendre4

+1

linaspasv avatar Mar 02 '16 22:03 linaspasv

+1 Would be a really good feature!

ttkulm avatar Apr 20 '16 06:04 ttkulm

+10

arisdario avatar Apr 27 '16 16:04 arisdario

I don't need auto-fit, just a way to set widths for each column! Any way to workaround/hack this for now?

jordanade avatar May 25 '16 23:05 jordanade

Oh, figured it out--just added this line to Worksheet.php, startSheet(), between the header and the data: fwrite($this->sheetFilePointer, '<cols><col min="1" max="1" width="100" customWidth="1"/><col min="2" max="6" width="30" customWidth="1"/></cols>');

jordanade avatar May 25 '16 23:05 jordanade

@jordanade You can also look at #225 if you need to hack something that works. I'm not a big fan of this as it creates an API that is specific to XLSX. Also, if Spout could automatically calculate the width of each column, it would solve 95% of the problems. So I'd rather have this transparent solution in place, instead of having to manually specify the width of each column.

adrilo avatar May 26 '16 01:05 adrilo

Surely there is room for APIs that may not work for all possible file outputs? Best effort, etc...

jordanade avatar Jun 09 '16 23:06 jordanade

I'm actually trying to avoid this as much as possible. Although I'll definitely reconsider after implementing the automated method.

adrilo avatar Jun 15 '16 05:06 adrilo

Hi there, have you been able to come up with a solution for the autosizing of column widths and row heights already?

If not, I've come with an intermediary solution, which - while less accurate than imagettfbbox - is IMHO a decent tradeoff between performance and accuracy. On average it increases the runtime by about 50%-100% and the widths are on average about 1 character off (usually too wide).

It's a bit hacky though (requires serious refinement! =) and involves a csv that holds base sizes for each font and character in the ASCII range, which is why I'm hesitating to fork->code->pull spout, since I dont know if it's a viable solution to integrate that into spout.

nimmneun avatar Aug 29 '16 17:08 nimmneun

@nimmneun Nobody started working on this enhancement. If you already built something, feel free to share it. Either share a link to your commit or create a pull request. It's always good to get inspiration from others :)

adrilo avatar Aug 30 '16 00:08 adrilo

@adrilo ok cool =) Since I'm not a fan of modifying anything within the vendor folder I hacked a "proof" of concept" (outside of spout) over the past couple of weekends. I haven't decided on how to integrate it into spout yet. I think I'd like to introduce 2, maybe 3 new classes ... Font or Fontname with just the supported fonts as constants like so Font + at least a class SizeCalculator as property of the worksheet and maybe a SizeCollection to hold character widths ... allthough this one could probably be kept inside of the SizeCalculator class =)

Since the <cols> tag must be placed before <sheetdata> the only viable way to do this is IMHO to add whitespaces at the beginning of the sheet.xml. While not a perfect solution its probably better than writing all sheetdata into a temp file just to read and write it again into a final sheet.xml. Also 1mb of spaces (to allow up to ~16k columns) effectivly account for just a couple of bytes in the final xlsx.

Would the above be cool with you? =)

edit: ... just noticed github doesnt escape < > :D

nimmneun avatar Aug 30 '16 23:08 nimmneun

+1

ljudina avatar Sep 14 '16 09:09 ljudina

+1

wnasich avatar Sep 30 '16 22:09 wnasich

@nimmneun I'm cool with this approach :) My only concern would be with the SizeCollection data. I saw that you get the different character sizes from a CSV file. Do you know where this data is coming from? Also, it does not cover much of the non-english characters (which is an ok tradeoff before we can build a more robust system for SizeCalculator).

This is also an interesting choice:

Define minimum and maximum column widths to keep exceptionally large or small cell contents in check.

adrilo avatar Oct 06 '16 07:10 adrilo

@adrilo ok cool =) Yeah - I'm not perfectly happy with the precalculated solution as well :-/ I calculated the sizes using imagettfbbox() combined with several switch cases *lol Unfortunately imagettfbbox is not as accurate as I was hoping and works only for a limited number of fonts. For others it was way off, especially with fonts that had character spacing.

I implemented the width calculation like this at first

    /**
     * @param mixed $value
     * @param Font  $font
     * @return float
     */
    public function getRenderedCellWidth($value, Font $font)
    {
        $box = imageftbbox($font->getSize(), 0, $this->getFontPath($font), $value);
        $pixels = abs($box[4] - $box[0]);
        $width = round(($pixels + 5) / 7, 3);

        return $width + 0.7;
    }

    /**
     * @param Font $font
     * @return string
     */
    public function getFontPath(Font $font)
    {
        return __DIR__ . '/fonts/' . $this->fontMap[$font->getName()] . '.ttf';
    }

But it was just painfully slow :sob: and runtimes for larger datasets would end up running 20-30 minutes instead of 5 minutes. The FontMap was necessary because the font file name often differs from the actual font name. Later on I wrote this FontMeta "extractor" from code snippets I found @stackoverflow: https://gist.github.com/nimmneun/2ba1a6e64cc2c075c155f65fee9422c0, which at least made the FontMap superfluous. But that didn't solve the performance issue.

I think I'll have some spare time this coming weekend and look forward to getting a few LOC written =)

nimmneun avatar Nov 10 '16 20:11 nimmneun

pushed an intermediary commit to https://github.com/nimmneun/spout/tree/cell-autosizing

nimmneun avatar Dec 15 '16 17:12 nimmneun

Are you still working on this?

darrinsworlds avatar Mar 06 '17 15:03 darrinsworlds

Up. Any news on implementing this feature?

germain-italic avatar Apr 14 '17 19:04 germain-italic

Would love to see this implemented soon!

harrygulliford avatar Apr 20 '17 06:04 harrygulliford

+1

pedrofsantoscom avatar May 24 '17 11:05 pedrofsantoscom

+1

gmmarc avatar May 25 '17 07:05 gmmarc

+1

dmitryuk avatar May 26 '17 06:05 dmitryuk

+1

bugalot avatar Jun 15 '17 10:06 bugalot

+1

savdeep avatar Jun 20 '17 09:06 savdeep

I need this! :(

gabbanaesteban avatar Jun 23 '17 16:06 gabbanaesteban

+1

podorozhny avatar Jul 19 '17 16:07 podorozhny

+1

NicolasN avatar Aug 02 '17 14:08 NicolasN

Can I suggest a fit-all: $writer->setColumnWidths(aFixedWidths|'autosize-char'|'autosize-font');

so the 'autosize-font' could be used for XLSX/ODS (but you could use the 'autosize-char' if you wanted quicker width calculating or the aFixedWidths for no speed reduction)

for the 'autosize-xxx' methods, you could:

  • write cell-data to a temp file (a), whilst recording some statistics. If 'autosize-char'; maxChars for each column based on strlen(), if 'autosize-font'; maxWidth via font based string measurements.
  • write column-defs with the widths to a new temp file (b) and append data from temp file (a)

As I needed column-widths and alignment, I had to go with https://github.com/mk-j/PHP_XLSXWriter and code my own writer for outputting delimited text ...

AzzaAzza69 avatar Aug 17 '17 08:08 AzzaAzza69

This would be a really nice feature.

BTW Great work with this lib!

aocneanu avatar Oct 16 '17 11:10 aocneanu