在字符单位和像素(点)之间转换Excel列宽

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."

因此 Excel 中的 ColumnWidth 被测量为适合一列的“0”字符数。如何将此值转换为像素,反之亦然?

如前所述,ColumnWidth Excel 中的值取决于可以通过 Workbook.Styles("Normal").Font 获得的工作簿的默认字体。它还取决于当前屏幕 DPI。

在 Excel 2013 年对不同的字体和大小进行了一些研究后,我发现我们有 2 个线性函数(看不到 Arial,因为它与 Tahoma 重叠。):

如图所示,ColumnWidth < 1的功能与折线图的主要部分不同。它的计算方式是一列中的像素数/一列中适合一个“0”字符所需的像素数。

现在让我们看看典型的单元格宽度由什么组成。

  • A - 普通样式中的“0”字符宽度
  • B - 左右填充
  • C - 1px 右边距

A可以用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"计算出来。此处还需要屏幕 DPI(请参阅下面的 Python 3 实施)

这里是字符像素转换的方程式及其在 Python 3 中的实现:

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)

2022 年仍然是同样的问题...发现线程可以追溯到 2010 年有问题...

开始于:像素 != 点
点定义为 72 点/英寸:https://docs.microsoft.com/en-us/office/vba/language/glossary/vbe-glossary#point
虽然这个定义看起来很愚蠢,因为一个固定宽度为 100 点的形状,无论显示器配置如何,都会在每台显示器上显示完全相同的尺寸(以英寸为单位),但事实并非如此。

字符数是定义为默认文本格式的0字符数的单位。设置为10个字符宽度的单元格,当单元格内容格式化为默认格式时,可以容纳10个“0”字符。

我的情况是我需要将图片放入文档并将文本放入旁边的单元格中。但是图片悬停在文档上,单元格隐藏在它下面。根据图片的大小,或多或少的单元格被隐藏。因此,我不能只说我将文本 5 个单元格放在图片的左侧。将列自动调整为列单元格的内容,不考虑悬停图片。 图片绑定到图片左上角下方的单元格。我需要将该单元格的大小设置为图片的大小才能解决问题。

一张图片就是一个形状。形状 returns 其宽度为 (Shape.Width).

可以将范围设置为像 Worksheet.Range["A1"] 这样的单元格。从 Range 中,您可以获得 Characters (Range.ColumnWidth) 或 Points (Range.Width) 的宽度。但是你只能在 Characters (Range.ColumnWidth).

中设置 Range 的宽度

所以我们可以在中检索图片(形状)的大小,需要将它们转换为字符来设置单元格到正确的宽度...

一些研究表明,单元格的点大小包含一个间距常数(单元格内容前后的填充),可能还有单元格之间的分隔线。

在我的系统上:
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**
正如我所说,积分中有一个常数。因此,从 1 个字符 到 2 个字符 ,区别仅在于字母的大小。

SizeOfLetter = 14.25 - 9 = 5.25

然后我们可以从 Points 中减去 1 Characters 的 SizeOfLetter 并得到 Points常数。

PointsConstant = 9 Points - 5.25 Points = 3.75 Points

验证: 包含 3 个“0”字母的单元格的点大小 = 3SizeOfLetter + PointsConstant = 35.25 Points + 3.75 Points = 19.5

由于值取决于您的系统,您不能使用这些值!

最好的方法是使用代码为您的系统计算它:

C#代码:

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 ;