Formula to convert .NET pixels to Excel width in OpenXML format

The formula that i came up with to convert pixel width to Excel width is

    private double PixelWidthToExcel(int pixels)
    {
        var tempWidth = pixels * 0.14099;
        var correction = (tempWidth / 100) * -1.30;

        return tempWidth - correction;
    }

To me it always gives exact value conversion.

And the formula for height

    private double PixelHeightToExcel(int pixels)
    {
        return pixels * 0.75;
    }

An upgrade to the answer MikeTeeVee gave, as I was seeing discrepancies at the 3rd dp.

In the calculation of points width from the OpenXml file width the documentation states

Column width measured as the number of characters of the maximum digit width of the numbers 0, 1, 2, ..., 9 as rendered in the normal style's font. There are 4 pixels of margin padding (two on each side), plus 1 pixel padding for the gridlines.

width = Truncate([{Number of Characters} * {Maximum Digit Width} + {5 pixel padding}]/{Maximum Digit Width}*256)/256

This lead me to a mostly stab in the dark experiment and come up with a conversion from pixels to width:

double width = Math.Truncate(px / 7 * 256) / 256

This returns precisely the same value as found in the OpenXml document.


First we need to establish how Excel does the conversion (as we manually change the size within the Excel application):

//Use 7d to promote to double, otherwise int will truncate.
ExcelWidth = (Pixels - 12) / 7d +  1;//From pixels to inches.

Caveat: The formulas do not work for 0 (zero) Width or Pixels.
For those scenarios, use an "if" statement to check if zero and return zero.

It threw me for a loop too. Instead of trying to figure out Pixels per Width, I looked at the difference in Pixels between each Width whole number and found it was always 7. The exception was 0 to 1 Width; where there were 12 Pixels for Width 1.00 . From there I was able to come up with the formulas above, easy-peasy.

Now those numbers hold up in the world of Excel, but if you're setting column widths directly in the OpenXml document, it's a slightly different story. Here's why: In the documentation you link to is says this about the 5 pixels:

There are 4 pixels of margin padding (two on each side), plus 1 pixel padding for the gridlines.

Now all that means is Excel is assuming you've factored in these 5 pixels into the widths you are providing. When you open up your document in excel and resize the columns, you will see that the width is 5 pixels less than what you set it to.

To adjust for this you can update the formulas as follows:

//Use 7d to promote to double, otherwise int will truncate.
OpenXmlWidth = (Pixels - 12 + 5) / 7d + 1;//From pixels to inches.

This answers the title of your question:
"Formula to convert .NET pixels to Excel width in OpenXML format"

If you want to know how to figure an approximation of the column width to auto-fit a column based on the contents (and font) of a single cell then that's an entirely different question. The formulas supplied by the microsoft link you provided will not work. Type in 10 periods in one cell and 10 capital-M's in another column. You will see that auto-fit gives you 41 pixels and 129 pixels, respectively. The formulas provided by ms do not take into account the width of individual characters. In other words; they sent you on a wild goose chase.

The only way to Auto-Fit a column is to scan through every row in the column and calculate the width of the text based on the characters and font used. You would use something like what I found here. Then take the max value of that and pad with 5. I would avoid this approach when processing spreadsheets in a web environment because you could be exporting hundreds of thousands of rows with tens of columns - you get the idea. The best approach is to set a best-guess width for your columns and train your users on how to auto-fit from excel.

Sincerely,
"The Common Man"


Edit - 10/26/2011:

Because I'm feeling generous, here's an example of how to get an approximate width for your column. I'd prefer to avoid doing this for all rows in a column, so let's just base our width (by default) on the value in your header cell. Below is how you could do this using the example I linked to earlier. Note: These are approximations, but close enough.

using System.Drawing;
using System.Windows.Forms;//Add Reference.  Test on webserver first.
Font font = new Font("Calibri", 11.0f, FontStyle.Regular);
string header  = "Hello There World!";
int pxBaseline = TextRenderer.MeasureText("__", font).Width;
int pxHeader   = TextRenderer.MeasureText("_" + header + "_"), font).Width;
int pxColumnWidth = pxHeader - pxBaseline + 5;//Pad with 5 for Excel.

Caveat: If you use this code in the same place you're using OpenXml, then you may need to remove the "using" for System.Drawing and fully qualify "Font" and "FontStyle" as System.Drawing.Font and System.Drawing.FontStyle. Otherwise your compiler may mistake these classes as belonging to DocumentFormat.OpenXml.Spreadsheet .

Tags:

C#

Openxml