'Convert Excel column width between characters unit and pixels (points)

"One unit of column width is equal to the width of one character in the Normal style. For proportional fonts, the width of the character 0 (zero) is used."

So ColumnWidth in Excel is measured as a number of "0" characters which fits in a column. How can this value be converted into pixels and vice versa?

Image from https://bettersolutions.com/excel/rows-columns/column-widths.htm



Solution 1:[1]

As already mentioned ColumnWidth value in Excel depends on default font of a Workbook which can be obtained via Workbook.Styles("Normal").Font. Also it depends on current screen DPI.

After carrying out some research for different fonts and sizes in Excel 2013 I've found out that we have 2 linear functions (Arial cannot be seen because it overlaps with Tahoma.):

enter image description here

As it can be seen in the picture the function for ColumnWidth < 1 is different from the major part of the line chart. It's calculated as a number of pixels in a column / number of pixels needed to fit one "0" character in a column.

Now let's see what a typical cell width consists of.

enter image description here

  • A - "0" character width in the Normal Style
  • B - left and right padding
  • C - 1px right margin

A can be calculated with GetTextExtentPoint32 Windows API function, but font size should be a little bit bigger. By experiment I chose +0.3pt which worked for me for different fonts with 8-48pt base size. B is (A + 1) / 4 rounded to integer using "round half up". Also screen DPI will be needed here (see Python 3 implementation below)

Here are equations for character-pixel conversion and their implementation in Python 3:

enter image description here

enter image description here

enter image description here

import win32print, win32gui
from math import floor

def get_screen_dpi():
    dc = win32gui.GetDC(0)
    LOGPIXELSX, LOGPIXELSY = 88, 90
    dpi = [win32print.GetDeviceCaps(dc, i) for i in (LOGPIXELSX,
                                                        LOGPIXELSY)]
    win32gui.ReleaseDC(0, dc)
    return dpi

def get_text_metrics(fontname, fontsize):
    "Measures '0' char size for the specified font name and size in pt"
    dc = win32gui.GetDC(0)
    font = win32gui.LOGFONT()
    font.lfFaceName = fontname
    font.lfHeight = -fontsize * dpi[1] / 72
    hfont = win32gui.CreateFontIndirect(font)
    win32gui.SelectObject(dc, hfont)
    metrics = win32gui.GetTextExtentPoint32(dc, "0")
    win32gui.ReleaseDC(0, dc)
    return metrics

def ch_px(v, unit="ch"):
    """
    Convert between Excel character width and pixel width.
    `unit` - unit to convert from: 'ch' (default) or 'px'
    """
    rd = lambda x: floor(x + 0.5)  # round half up
    # pad = left cell padding + right cell padding + cell border(1)
    pad = rd((z + 1) / 4) * 2 + 1
    z_p = z + pad  # space (px) for "0" character with padding
    if unit == "ch":
        return v * z_p if v < 1 else v * z + pad
    else:
        return v / z_p if v < z_p else (v - pad) / z

font = "Calibri", 11
dpi = get_screen_dpi()
z = get_text_metrics(font[0], font[1] + 0.3)[0]  # "0" char width in px
px = ch_px(30, "ch")
ch = ch_px(px, "px")
print("Characters:", ch, "Pixels:", px, "for", font)

Solution 2:[2]

2022 and still the same Problem... Found threads going back to 2010 having the issue...

To start of: Pixel != Points
Points are defined as 72points/inch: https://docs.microsoft.com/en-us/office/vba/language/glossary/vbe-glossary#point
Though that definition seems stupid, as a shape with a fixed width of 100points, would display the exact same size in inch on every monitor independent of monitor configuration, which is not the case.

Characters is a unit that is defined to the number of 0 characters of the default text format. A cell set to a width of 10 characters, can fit 10 "0" characters, when the cell content is formatted to the default format.

My case is that I need to place pictures into the document and place text into cells next to it. But pictures hover over the document and cells are hidden below it. Depending on the size of the Picture, more or less cells are hidden. Thus, I can't just say I place text 5 cells to the left of the picture. Autosizing a column to the contents of the cells of the column, does not account for the hovering picture. A picture is bound to the cell that is below the top left corner of the picture. I need to set the size of that cell to the size of the picture to solve the issue.

A Picture is a Shape. A Shape returns its width as Points (Shape.Width).

A Range can be set to a cell like Worksheet.Range["A1"]. From a Range you can get the width in Characters (Range.ColumnWidth) or in Points (Range.Width). But you can only set the width of a Range in Characters (Range.ColumnWidth).

So we can retrieve the size of the Picture (Shape) in Points and need to convert them to Characters to set the cell to the correct width...

Some research showed that the Points size of a cell contains a constant for spacing (padding before and after the cell content) and probably the seperator lines between cells.

On my system:
A cell set to a width of 1 **Characters** = 9 **Points**
A cell set to a width of 2 **Characters** = 14.25 **Points**
A cell set to a width of 3 **Characters** = 19.5 **Points**
As I said, there is a constant within the Points. Thus going from 1 Characters, to 2 Characters, the difference is only the size of the letter.

SizeOfLetter = 14.25 Points - 9 Points = 5.25 Points

we can then subtract that SizeOfLetter from the Points for 1 Characters and get the Points constant.

PointsConstant = 9 Points - 5.25 Points = 3.75 Points

Verify: Points size for a cell containing 3 "0" letters = 3SizeOfLetter + PointsConstant = 35.25 Points + 3.75 Points = 19.5 Points

As the values depend on your system, YOU CAN'T USE THOSE VALUES!

Best way is to use code to calculate it for your system:

C# code:

Excel.Application excelApp = new Excel.Application();   
Excel.Workbook workbook1 = excelApp.Workbooks.Add();
Excel.Worksheet sheet1 = (Excel.Worksheet)workbook1.ActiveSheet;
    
// Evaluate the Points data for the document
double previousColumnWidth = (double)sheet1.Range["A1"].ColumnWidth;
sheet1.Range["A1"].ColumnWidth = 1; // Make the cell fit 1 character
double points1 = (double)sheet1.Range["A1"].Width;
sheet1.Range["A1"].ColumnWidth = 2; // Make the cell fit 2 characters
double points2 = (double)sheet1.Range["A1"].Width;

double SizeOfLetter = points2 - points1;
double PointsConstant = points1 - pointsPerCharater;

// Reset the column width
sheet1.Range["A1"].ColumnWidth = previousColumnWidth;

// Create a function for the conversion
Func<double, double> PointsToCharacters = (double points) => (points - PointsConstant ) / SizeOfLetter ;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Rumble