如何正确实现获取字体支持的Unicode范围及例外字符的逻辑?
多语言文本字体渲染的码点范围合并实现
针对你提出的字体码点范围合并需求,以下是符合规则的具体实现方案:
前置准备
首先需要明确几个核心数据:
- 无效码点集合:包含Unicode控制字符、保留字符等不可打印字符,需提前定义好。
- 原始码点范围:通过
fontTools提取字体支持的连续码点区间,确保按起始码点升序排列。
核心合并规则实现
规则1:间隔全为无效码点则合并
遍历排序后的原始范围,检查当前范围与下一个范围的间隔区间[当前范围结束值+1, 下一个范围起始值-1]内所有码点是否都属于无效集合。若是,直接合并两个范围。
规则2:间隔占比≤5%则合并并记录例外
若规则1不满足,计算下一个范围起始值 / 当前范围结束值的比值,若≤1.05,则合并范围,同时将间隔内的有效码点(不在无效集合中的码点)加入例外列表。
完整代码实现
from fontTools.ttLib import TTFont # 提取字体支持的连续码点范围 def get_font_codepoint_ranges(font_path): font = TTFont(font_path) cmap = font.getBestCmap() codepoints = sorted(cmap.keys()) if not codepoints: return [] ranges = [] start = codepoints[0] prev_cp = start for cp in codepoints[1:]: if cp == prev_cp + 1: prev_cp = cp else: ranges.append((start, prev_cp)) start = cp prev_cp = cp ranges.append((start, prev_cp)) font.close() return ranges # 构建无效码点集合(可根据需求扩展) def get_invalid_codepoints(): invalid = set() # 基础控制字符 invalid.update(range(0x00, 0x20)) invalid.add(0x7F) # 可补充其他无效区块,如Unicode保留区、专用区等 return invalid # 执行范围合并逻辑 def merge_font_ranges(original_ranges, invalid_cps): if not original_ranges: return [], [] merged_ranges = [] exception_codepoints = [] curr_start, curr_end = original_ranges[0] for next_start, next_end in original_ranges[1:]: gap_start = curr_end + 1 gap_end = next_start - 1 # 检查间隔内是否全为无效码点 gap_valid = [] if gap_start <= gap_end: for cp in range(gap_start, gap_end + 1): if cp not in invalid_cps: gap_valid.append(cp) if not gap_valid: # 规则1:间隔全无效,合并范围 curr_end = next_end else: # 检查规则2:间隔占比是否≤5% if next_start / curr_end <= 1.05: # 合并范围,记录有效例外 exception_codepoints.extend(gap_valid) curr_end = next_end else: # 无法合并,保存当前范围 merged_ranges.append((curr_start, curr_end)) curr_start, curr_end = next_start, next_end # 加入最后一个合并后的范围 merged_ranges.append((curr_start, curr_end)) # 例外码点去重排序 exception_codepoints = sorted(list(set(exception_codepoints))) return merged_ranges, exception_codepoints # 测试Andika字体前10个范围合并 if __name__ == "__main__": andika_font_path = "Andika-Regular.ttf" invalid_cps = get_invalid_codepoints() original_ranges = get_font_codepoint_ranges(andika_font_path) test_ranges = original_ranges[:10] merged, exceptions = merge_font_ranges(test_ranges, invalid_cps) print("合并后范围:", merged) print("例外码点:", exceptions)
关键注意事项
- 原始范围排序:必须确保提取的原始码点范围是升序排列的,否则合并逻辑会出错,上述代码中已通过
sorted(cmap.keys())实现。 - 无效码点扩展:根据实际需求补充无效码点集合,比如Unicode的私人使用区、未分配码点等,确保合并逻辑的准确性。
- 例外判断逻辑:后续判断字符时,若码点在合并范围内,需额外检查是否在例外列表中,若是则需fallback到备用字体。
- 性能优化:例外列表可转为集合,提升查询效率;合并后的范围可用于二分查找,快速判断码点是否在范围内。
内容的提问来源于stack exchange,提问作者Ξένη Γήινος
相关产品推荐
相关产品推荐

