字符串的显示宽度

字符宽度数据库:http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt
字符宽度文档:http://www.unicode.org/reports/tr11/

文档中定义了Unicode字符的显示宽度如下:
A  : Ambiguous    不确定
F  : Fullwidth    全角
H  : Halfwidth    半角
N  : Neutral      中性
Na : Narrow       窄
W  : Wide         宽

总体来说:
In a broad sense, wide characters include W, F,
and A (when in East Asian context), and narrow characters include N, Na, H,
and A (when not in East Asian context).

其中比较难处理的是A类型,典型的字符是中文的引号。
中文输入法所谓“半角中文引号”其实是A,在有的场合下和一个英文字符等宽,
有的场合和一个汉字等宽。

这里由于使用需要,认为A类型与一个汉字等宽。

#!/usr/bin/env python3
#-*- coding: utf-8 -*-

East_Asian_Width = {
    'A': 2,
    'F': 2,
    'H': 1,
    'N': 1,
    'Na': 1,
    'W': 2
}

ilast = 0
jlast = 0
ealast = 1

def eaw_operation(i, j, ea, f):
    global ilast
    global jlast
    global ealast
    # 如果与上一范围宽度不同,打印上一范围并记录当前范围
    # 否则当前范围并入上一范围
    if ealast != East_Asian_Width[ea]:
        print('    {{{:#x}, {}}},'.format(jlast, ealast), file=f)
        ilast = i
        ealast = East_Asian_Width[ea]
    jlast = j

if __name__ == '__main__':
    with open('EastAsianWidth.txt', 'r', newline='\n') as DATABASE, open('east_asian_width.table', 'w', encoding='utf-8') as f:
        print('eaw_data east_asian_width[] = {', file=f)
        for line in DATABASE:
            line = line.strip('\n')
            # 过滤注释和空行
            if not line or line.startswith('#'):
                continue
            # 第一个分号前为范围,第一个引号后的第一个空格前为类型
            # 范围可能是一个值,或以点号分隔的两个值
            # 第一个值为当前范围的下限i
            # 最后一个值为当前范围的上限j
            crange, ea = line.split(';', maxsplit=1)
            crange = crange.split('.')
            ea = ea.split(' ', maxsplit=1)[0]
            i = int(crange[0], base=16)
            j = int(crange[-1], base=16)
            # 先检查是否缺失范围jlast+1 .. i-1,有则为N类型
            if i > jlast + 1:
                eaw_operation(jlast + 1, i - 1, 'N', f)
            eaw_operation(i, j, ea, f)
        # 将0x10ffff作为上限更新最后一行之后的缓存
        eaw_operation(jlast + 1, 0x10ffff, 'N', f)
        # 最后再打印一次缓存
        print('    {{{:#x}, {}}}'.format(jlast, ealast), file=f)
        print('};', file=f, end='')

然后east_asian_width.table里就是Uncode范围与对应宽度。

我在项目里这么用:

#include "windows.h"
#include <stdexcept>

namespace {

typedef struct{
    int value;
    int width;
} eaw_data;

#include "east_asian_width.table"

const int TABLESIZE = sizeof(east_asian_width) / sizeof(eaw_data);

/**
 * @brief  一个字符的显示宽度
 * @param  字符的Unicode值
 * @return 字符的显示宽度,1或2
**/
int eaw(int value)
{
    if(value > 0x10ffff)
        throw std::domain_error("Character value out of range");
    // 二分查找
    int imin = 0;
    int imax = TABLESIZE - 1;
    int imid = (imin + imax) / 2;
    for(;;)
    {
        if(imid == imin)
            return value < east_asian_width[imin].value 
                ? east_asian_width[imin].width : east_asian_width[imax].width;
        if(value < east_asian_width[imid].value)
        {
            imax = imid;
        }
        else if(value == east_asian_width[imid].value)
        {
            return east_asian_width[imid].width;
        }
        else
        {
            imin = imid;
        }
        imid = (imin + imax) / 2;
    }
}

} // anonymous namespace

/**
 * @brief  计算字符串的显示宽度
 * @param  s 字符串,必须以'\0'结尾
 * @return 字符串的显示宽度,以一个半角字符的宽度为单位
 *
 * 函数内部先转换字符串为Unicode再计算
 * 在ANSI工程中宏eaw_len被定义为eaw_len_a
**/
int eaw_len_a(char *s)
{
    wchar_t buff[MAX_PATH];
    ::MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED | MB_USEGLYPHCHARS, s, -1,
        buff, MAX_PATH);
    return eaw_len_w(buff);
}

/**
 * @brief  计算字符串的显示宽度
 * @param  s 字符串,必须以L'\0'结尾
 * @return 字符串的显示宽度,以一个半角字符的宽度为单位
 *
 * 在Unicode工程中宏eaw_len被定义为eaw_len_w
**/
int eaw_len_w(wchar_t *s)
{
    int len = 0;
    for(wchar_t *i = s; *i; ++i)
    {
        len += eaw(static_cast<int>(*i));
    }
    return len;
}
因为是Windows项目,所以用WinSDK转码,非Windows用libiconv吧。

eaw_len是一个宏,UNICODE时为eaw_len_w,非UNICODE时为eaw_len_a。

然后在调用处:

// 假定字符的宽度为6px
int len_visible = eaw_len(some_str);
len_visible = len_visible > 20 ? 120 : len_visible * 6;

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值