Skip to content

feat(cn): 新增 scale-punctuation-factor 选项,支持CJK字形与全角标点字形使用不同的缩放倍率#725

Draft
Pectics wants to merge 12 commits intosubframe7536:variablefrom
Pectics:variable
Draft

feat(cn): 新增 scale-punctuation-factor 选项,支持CJK字形与全角标点字形使用不同的缩放倍率#725
Pectics wants to merge 12 commits intosubframe7536:variablefrom
Pectics:variable

Conversation

@Pectics
Copy link

@Pectics Pectics commented Feb 16, 2026

概述

新增 --cn-scale-punctuation-factor 选项(配置文件:cn.scale_punctuation_factor(optional)),在使用 --cn-scale-factor 时可以对全角标点符号的缩放倍率进行指定。

Fixed #718

动机

使用 --cn-scale-factor 缩放中日韩字符时,全角标点符号(引号、省略号、破折号等)也会被一起缩放,可能导致以下问题:

  • 中英混排时出现明显的标点符号高度不均(如图)
  • 省略号 …… 连用时,两个符号之间的点距离过近
  • 引号等靠近字框边缘的标点符号可能溢出预期位置

本 PR 添加了一项配置参数,以允许用户使用不同北路缩放 CJK 汉字持全角标点符号。

使用 scale-punctuation-factor=1.0 调整前(scale-factor=1.15):

image

使用 scale-punctuation-factor=1.0 调整后(scale-factor=1.15):

image

使用方法

命令行:

python build.py --cn --cn-scale-factor 1.15 --cn-scale-punctuation-factor 1.0

config.json:

{
  "cn": {
    "enable": true,
    "scale_factor": 1.15,
    "scale_punctuation_factor": 1.0
  }
}

涵盖的标点符号

以下标点符号会被排除在缩放之外:

  • CJK 符号和标点 (U+3000-U+303F)
  • 全角 ASCII 标点 (U+FF01-U+FF5E)
  • cv96/cv97/cv98 对应的 .full 变体
  • cv99 对应的 .tw 变体(繁体中文居中标点)

上述标点符号使用如下方式查找:

字形名称验证方式

通过 fontTools 解析 CN base font,获取实际存在的全角标点字形名称。

步骤 1:下载 CN base font

# 从 GitHub Releases 下载
wget https://github.com/subframe7536/maple-font/releases/download/cn-base/cn-base-static.zip
unzip cn-base-static.zip -d source/cn/static/

步骤 2:解析字体并筛选标点符号

from fontTools.ttLib import TTFont

font = TTFont("source/cn/static/MapleMonoCN-Regular.ttf")
cmap = font.getBestCmap()  # Unicode -> glyph name

# 定义标点符号的 Unicode 范围
punctuation_ranges = [
    (0x3000, 0x303F, "CJK Symbols and Punctuation"),
    (0xFF01, 0xFF5E, "Fullwidth ASCII Punctuation"),
    (0x2010, 0x2027, "General Punctuation"),
]

# 遍历每个范围,获取字形名称和宽度
for start, end, category in punctuation_ranges:
    print(f"## {category}")
    for cp in range(start, end + 1):
        if cp in cmap:
            glyph_name = cmap[cp]
            width = font["hmtx"][glyph_name][0]
            print(f"  U+{cp:04X}: {glyph_name} (width={width})")

步骤 3:检查 .full 和 .tw 变体

# 检查所有 .full 变体(cv96/cv97/cv98)
for glyph_name in font.getGlyphOrder():
    if ".full" in glyph_name:
        width = font["hmtx"][glyph_name][0]
        print(f"  {glyph_name} (width={width})")

# 检查所有 .tw 变体(cv99)
for glyph_name in font.getGlyphOrder():
    if ".tw" in glyph_name:
        width = font["hmtx"][glyph_name][0]
        print(f"  {glyph_name} (width={width})")

筛选结果:

类别 字形数量 宽度
CJK Symbols and Punctuation 46 1200
Fullwidth ASCII Punctuation 14 1200
.full 变体 (cv96/cv97/cv98) 6 1200
.tw 变体 (cv99) 7 1200

注:基础版本的 ellipsis、emdash、quoteleft、quoteright、quotedblleft、quotedblright 宽度为 600(半宽),当启用 cv96/cv97/cv98
时会被替换为对应的 .full 版本(全宽),因此只需包含 .full 版本即可。

Co-worked with Claude

Define a set of glyph names for fullwidth punctuation characters that
should be excluded from scaling operations. This includes:
- CJK Symbols and Punctuation (U+3000-U+303F)
- Fullwidth ASCII Punctuation (U+FF01-U+FF5E)
- .full variants for cv96/cv97/cv98 features
- .tw variants for cv99 (Traditional Chinese centered punctuation)
When skip_punctuation is True, fullwidth punctuation glyphs are not
scaled with the scale_factor, only centered within the target width.
This preserves the visual appearance of punctuation characters while
still allowing CJK characters to be scaled.

The function now also prints a summary of scaled and skipped glyphs
for debugging purposes.
Add command line argument and config.json support for skipping
punctuation scaling when using --cn-scale-factor. This allows
users to scale CJK characters while keeping fullwidth punctuation
glyphs at their original size.

Usage:
  python build.py --cn --cn-scale-factor 1.15 --cn-scale-skip-punctuation

Or in config.json:
  "cn": {
    "scale_skip_punctuation": true
  }
Copilot AI review requested due to automatic review settings February 16, 2026 10:15
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new CN build option (--cn-scale-skip-punctuation / cn.scale_skip_punctuation) to avoid scaling fullwidth punctuation when applying --cn-scale-factor, improving mixed CJK/Latin typography consistency.

Changes:

  • Added a curated FULLWIDTH_PUNCTUATION_NAMES glyph-name set and used it to optionally skip punctuation scaling.
  • Extended change_glyph_width_or_scale() with a skip_punctuation flag and summary stats output.
  • Added CLI/config wiring for cn.scale_skip_punctuation (including default config update).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
source/py/utils.py Introduces the fullwidth punctuation glyph-name allowlist constant.
source/py/transform.py Adds skip_punctuation behavior to the glyph scaling routine and prints scaling/skipping stats.
build.py Adds CLI flag, config default, and passes the new option into CN build transform calls.
config.json Adds the new cn.scale_skip_punctuation default setting.
Comments suppressed due to low confidence (1)

source/py/transform.py:287

  • skip_punctuation is checked only after the special_names early-continue. Since build.py passes special_names=["ellipsis.full"] and ellipsis.full is also in FULLWIDTH_PUNCTUATION_NAMES, enabling skip_punctuation will still scale ellipsis.full via _change_glyph_width, which defeats the purpose of skipping punctuation scaling. Consider applying the punctuation-skip check before the special_names block, or conditionally bypass special handling when the glyph is in the punctuation set.
    for glyph_name in font.getGlyphOrder():
        # Handle special names (legacy behavior)
        if glyph_name in special_names:
            _change_glyph_width(
                glyf=glyf,
                hmtx=hmtx,
                glyph_name=glyph_name,
                scale_x=factor,
                scale_y=1.0,
                match_width=match_width,
                target_width=target_width,
            )
            continue

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

build.py Outdated
Comment on lines 407 to 408
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new config key cn.scale_skip_punctuation is introduced, but source/schema.json currently defines cn.scale_factor without this new property. If schema.json is used for config validation / tooling, it should be updated to include scale_skip_punctuation (type boolean, default false) to keep the schema in sync with runtime behavior.

Suggested change
# whether to skip scaling for fullwidth punctuation glyphs
"scale_skip_punctuation": False,

Copilot uses AI. Check for mistakes.
Update documentation in English, Chinese, and Japanese README files
to include the new --cn-scale-skip-punctuation command line option.
Also update config.json schema to include scale_skip_punctuation.
@subframe7536
Copy link
Owner

我觉得调整后的引号高度太低了,不太合适

如果想保留高度的话,可以分别设置宽和高的缩放比例,比如 --cn-scale-factor 1.15,1 或者 [1.15, 1]

@Pectics
Copy link
Author

Pectics commented Feb 16, 2026

我觉得调整后的引号高度太低了,不太合适

如果想保留高度的话,可以分别设置宽和高的缩放比例,比如 --cn-scale-factor 1.15,1 或者 [1.15, 1]

好吧,感觉涉及到我个人审美原因了。

(个人感觉宽高比不一致拉伸后会比较奇怪)

不过这或许能解决 #718 的问题。

@Pectics
Copy link
Author

Pectics commented Feb 16, 2026

主要功能还是把全宽标点全宽文字字符在scale功能上区别开,这样另外使用“给全宽标点使用比scale_factor稍小的比例缩放”等方案也会比较好实现。

@subframe7536
Copy link
Owner

subframe7536 commented Feb 16, 2026

我想了一下, #718 的问题应该可以通过优化 transform.py 来实现,针对四个全宽引号特殊处理

主要功能还是把全宽标点全宽文字字符在scale功能上区别开,这样另外使用“给全宽标点使用比scale_factor稍小的比例缩放”等方案也会比较好实现。

这样的话选项应该是 scale_punctation_factor 了

@Pectics
Copy link
Author

Pectics commented Feb 16, 2026

这样的话选项应该是 scale_punctation_factor 了

有道理,这样做更灵活。我调整一下修改内容

Replace the boolean skip_punctuation parameter with a more flexible
punctuation_scale_factor parameter. This allows users to specify a
separate scale factor for fullwidth punctuation glyphs instead of
just skipping scaling entirely.

When punctuation_scale_factor is None, punctuation uses the same
scale_factor as other glyphs. When set, punctuation uses the
specified factor.
Change the CLI argument and config option from a boolean flag to a
scale factor parameter. This provides more flexibility for users
to control punctuation glyph scaling independently.

Usage:
  python build.py --cn --cn-scale-factor 1.15 --cn-scale-punctuation-factor 1.0

Or in config.json:
  "cn": {
    "scale_factor": 1.15,
    "scale_punctuation_factor": 1.0
  }
Update documentation to reflect the new scale_punctuation_factor
option, replacing the previous scale_skip_punctuation boolean.
@subframe7536
Copy link
Owner

我想了一下, #718 的问题应该可以通过优化 transform.py 来实现,针对四个全宽引号特殊处理

这个 patch 可以修复

diff --git a/build.py b/build.py
index 673f354..3fb4ac9 100755
--- a/build.py
+++ b/build.py
@@ -1365,6 +1365,13 @@ def build_cn(f: str, font_config: FontConfig, build_option: BuildOption):
         if font_config.cn["scale_factor"] != (1.0, 1.0)
         else None
     )
+    special_scale_names = [
+        "ellipsis.full",
+        "quoteleft.full",
+        "quoteright.full",
+        "quotedblleft.full",
+        "quotedblright.full",
+    ]
     if target_width or scale_factor:
         match_width = 2 * font_config.glyph_width
 
@@ -1392,7 +1399,7 @@ def build_cn(f: str, font_config: FontConfig, build_option: BuildOption):
             match_width=match_width,
             target_width=target_width,
             scale_factor=scale_factor,
-            special_names=["ellipsis.full"],
+            special_names=special_scale_names,
         )
     elif font_config.get_width_name():
         change_glyph_width_or_scale(
@@ -1400,7 +1407,7 @@ def build_cn(f: str, font_config: FontConfig, build_option: BuildOption):
             match_width=2 * font_config.glyph_width,
             target_width=2 * font_config.get_target_width(),
             scale_factor=(1.0, 1.0),
-            special_names=["ellipsis.full"],
+            special_names=special_scale_names,
         )
 
     # https://github.com/subframe7536/maple-font/issues/239
diff --git a/source/py/transform.py b/source/py/transform.py
index 49d2c2e..b2ec9e5 100644
--- a/source/py/transform.py
+++ b/source/py/transform.py
@@ -99,6 +99,7 @@ def _process_glyph_geometry(
     scale_x: float,
     scale_y: float,
     thicken_strength: float = 0.0,
+    translate_x: float = 0.0,
 ) -> int:
     """
     Shared core logic to transform a glyph.
@@ -126,6 +127,9 @@ def _process_glyph_geometry(
     # Scale in-place
     glyph.coordinates.scale((scale_x, scale_y))
 
+    if translate_x != 0:
+        glyph.coordinates.translate((translate_x, 0))
+
     # 4. Apply Smart Thickening (if requested)
     # We only thicken if scaling down usually, or explicit request
     if thicken_strength != 0 and hasattr(glyph, "endPtsOfContours"):
@@ -158,6 +162,7 @@ def _change_glyph_width(
     scale_y: float,
     match_width: int,
     target_width: int,
+    translate_x: float = 0.0,
 ) -> None:
     """
     Global font resizer. Scales target glyphs horizontally and applies
@@ -183,6 +188,7 @@ def _change_glyph_width(
         # Heuristic: If we compress the font (scale < 1), lines get thin.
         # We add weight back based on how much we squeezed.
         thicken_strength=(1 - scale_x) / 3,
+        translate_x=translate_x,
     )
 
     # If the glyph was empty or composite, new_lsb comes from calculation
@@ -269,6 +275,13 @@ def change_glyph_width_or_scale(
                 scale_y=1.0,
                 match_width=match_width,
                 target_width=target_width,
+                translate_x=(
+                    target_width * 0.15
+                    if "right" in glyph_name and "quote" in glyph_name
+                    else target_width * -0.15
+                    if "left" in glyph_name and "quote" in glyph_name
+                    else 0
+                ),
             )
             continue
 

@subframe7536 subframe7536 marked this pull request as draft February 17, 2026 03:29
@Pectics Pectics changed the title feat(cn): 新增 scale-skip-punctuation 选项,支持跳过标点符号的缩放 feat(cn): 新增 scale-punctuation-factor 选项,支持CJK字形与全角标点字形使用不同的缩放倍率 Feb 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

在打开 --width slim 和 --cn-narrow 之后,如果启用 cv96 全角引号,引号和文字太近了

2 participants

Comments