Skip to content

fix: remove erroneous GSUB substitutions from halt/palt/vhal/vpal (kanji switching to Chinese glyphs) - #12

Open
halken wants to merge 4 commits into
mainfrom
fix/halt-palt-remove-gsub-kanji-subs
Open

fix: remove erroneous GSUB substitutions from halt/palt/vhal/vpal (kanji switching to Chinese glyphs)#12
halken wants to merge 4 commits into
mainfrom
fix/halt-palt-remove-gsub-kanji-subs

Conversation

@halken

@halken halken commented Jul 3, 2026

Copy link
Copy Markdown

Summary

Enabling halt (Alternate Half Widths) or palt (Proportional Alternate Widths) in Figma or via CSS font-feature-settings unexpectedly switched some Japanese kanji to Chinese (traditional) glyph forms (e.g. 亜→亞, 医→醫, 円→圓, 辺→邊).

Root cause: halt/palt — and their vertical counterparts vhal/vpal — were defined as GSUB substitution lookups instead of GPOS metric adjustments.
Each contained hundreds of ideograph substitutions copied from the traditional-forms (trad), JIS78/JIS90 (jp78/jp04) and vertical (vert/vrt2) features, so a feature meant only to adjust glyph width also changed the glyph shape.

The defect has existed since the initial import of the Glyphs source and affects all four weights (Thin / Regular / Bold / ExtraBold).

Changes

  • Clear the substitution code from halt, palt, vhal and vpal in all four fontinfo.plist sources so these features no longer substitute glyphs.
  • No other features are touched. Glyph-form switching remains fully available through the dedicated trad, jp78 and jp04 features.
Feature Before (subs) After (subs)
halt 784 0
palt 237 0
vhal 808 0
vpal 410 0

Verification

  • hb-shape: with halt=1 / palt=1, kanji glyphs no longer change; with trad=1, traditional-form conversion still works as expected.
  • GSUB: halt/palt/vhal/vpal substitution counts are 0 in all weights;
    trad (309) and jp78 (299) are preserved.
  • fontspector (googlefonts profile): results are identical before and after (PASS 313 / WARN 98 / FAIL 25 / ERROR 4) — no new failures introduced. The remaining FAIL/ERROR items are pre-existing (naming, versioning, metrics, vert+vrt2 coexistence) and unrelated to this change.

Notes

  • This change only stops the incorrect glyph substitution. Implementing proper half-/proportional-width GPOS metrics for halt/palt is out of scope and can be done in a follow-up.
  • Build tip: gftools builder caches LINESeedJP/sources/instance_ufos/.
    Remove that directory (and build.stamp) before rebuilding, otherwise source changes to feature code may not be reflected in the output fonts.

The `halt` (Alternate Half Widths) and `palt` (Proportional Alternate
Widths) features—together with their vertical counterparts `vhal` and
`vpal`—were defined as GSUB substitution lookups instead of GPOS metric
adjustments. These lookups contained hundreds of ideograph substitutions
copied from the traditional-forms (`trad`), JIS78/JIS90 (`jp78`/`jp04`)
and vertical (`vert`/`vrt2`) features, including many Japanese
shinjitai-to-traditional conversions (e.g. 亜→亞, 医→醫, 円→圓).

As a result, enabling `halt`/`palt` for width adjustment in Figma or via
CSS `font-feature-settings` unexpectedly switched Japanese kanji to
Chinese (traditional) glyph forms.

This defect was present from the initial import of the Glyphs source and
affects all four weights (Thin/Regular/Bold/ExtraBold).

Fix: clear the substitution code from `halt`/`palt`/`vhal`/`vpal` in all
four `fontinfo.plist` sources so these features no longer alter glyphs.
Glyph-form switching remains available through the dedicated `trad`,
`jp78` and `jp04` features, which are left untouched.

Verification:
- hb-shape: `halt=1`/`palt=1` no longer change kanji glyphs; `trad=1`
  still converts to traditional forms.
- GSUB: halt/palt/vhal/vpal substitution counts are 0 in all weights;
  trad (309) and jp78 (299) are preserved.
- fontspector (googlefonts profile): identical results before and after
  (PASS 313 / WARN 98 / FAIL 25 / ERROR 4); no new failures introduced.

Note: this change only stops the incorrect glyph substitution. Proper
half-/proportional-width GPOS metrics for halt/palt are out of scope and
can be added separately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
halken and others added 3 commits July 10, 2026 10:45
The build job only uploads the github-pages artifact on the main
branch, but the deploy job ran unconditionally. On PR branches the
deploy job tried to deploy a non-existent artifact and failed. Add
the same main-branch guard to the deploy job so it is skipped on
non-main branches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The shaping/regression check was SKIPped because no test directory was
declared, so nothing guarded against the class of bug fixed in 34ca0c2:
metric features (halt/palt/vhal/vpal) carrying GSUB substitutions.

Declare the test directory in fontspector.toml and wire --configuration
into `make test`, so both local runs and CI pick it up.

The tests cover all four features that were cleaned up, plus trad as a
positive control. Every case was chosen so that it actually discriminates:
run against the currently published Google Fonts binaries, 5 of the 6
cases fail; run against this branch, all 6 pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Google Fonts needs the version to increase before it can ship the
halt/palt fix: the published binaries are byte-for-byte the same
versions as our sources were (Thin 1.008, Regular 1.003, Bold 1.010,
ExtraBold 1.015), and fontspector's googlefonts/version_bump failed
with `same-version`.

The four weights were also versioned independently, which
opentype/family/equal_font_versions reported as a mismatch. Unify them
on 1.016 instead of bumping each one separately: it is higher than every
published weight, so the update is unambiguous, and the family now
carries a single version going forward.

Both checks now pass on all four weights.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@halken

halken commented Jul 30, 2026

Copy link
Copy Markdown
Author

Fontspector review of this PR

Following the suggestion to run Fontspector before submission, here is what we checked and what we found. Tool: fontspector 1.7.3, googlefonts profile, all four weights, clean builds of both main and this branch.

Summary: against a fresh build of main, exactly six checks change, and every one of them goes from FAIL/WARN to PASS. Nothing else moves.

To be precise about the claim: LINE Seed JP still has pre-existing FAILs (listed in section 4). What we verified is that this PR resolves three problems and touches none of the others.

1. Full before/after

Both builds checked with the same command (make test), 678 checks across the family:

PASS SKIP INFO WARN FAIL ERROR
main 317 239 19 80 19 4
this branch 323 239 19 79 14 4

Every one of the six differences:

check scope main this branch
shaping/regression all 4 weights FAIL shaping-regression PASS
googlefonts/version_bump Thin FAIL same-version PASS
opentype/family/equal_font_versions family WARN mismatch PASS

No other check changed status or status code, in either direction.

That the substitution removal itself is behaviour-neutral is expected: halt/palt/vhal/vpal had sub rules but zero pos rules, so width adjustment never worked in the first place. Removing the substitutions loses no working functionality — it only stops the unintended glyph swaps. Implementing real GPOS width adjustment is separate work and deliberately out of scope here.

2. The bug is now covered by a machine check

shaping/regression was being SKIPped because the repo declared no test directory. This PR adds one (fontspector.toml + qa/shaping_tests/halt-palt.json) and wires --configuration into make test, so it runs locally and in CI.

The cases were chosen so that they actually discriminate — 5 of the 6 fail on main (and on the binaries currently published on Google Fonts), all 6 pass here:

$ fontspector --profile googlefonts --configuration fontspector.toml \
    -c shaping/regression <ttf>

--- main / currently published LINESeedJP-Regular.ttf ---
FAIL  halt  亜医囲営駅円塩  expected uni4E9C|uni533B|uni56F2|…  got uni4E9E|uni91AB|uni570D|…
FAIL  halt  ¡fi¿             expected exclamdown|fi|questiondown  got exclamdown.rotat|fi.rotat|…
FAIL  palt  唖嘘欝          expected uni5516|uni5618|uni6B1D    got uni555E|uni5653|uni6B1D.jp78
FAIL  vhal  !?()            expected exclam|question|…          got uniFF01|uniFF1F|…
FAIL  vpal  !?()            expected exclam|question|…          got uniFF01|uniFF1F|…
PASS  trad  亜医囲営駅円塩  → uni4E9E|uni91AB|uni570D|…  (positive control: still converts)

--- this branch ---
PASS 6 / FAIL 0   (all four weights)

The halt cases show the two distinct symptoms of one root cause: the trad substitutions leaked in (kanji switching to traditional Chinese forms — the originally reported bug), and so did the vertical .rotat substitutions (Latin punctuation rotating in horizontal text).

trad is included as a positive control, so that a future "fix" which simply deletes form-switching features cannot pass.

3. Version bump

The published binaries carry exactly the versions our sources had (Thin 1.008, Regular 1.003, Bold 1.010, ExtraBold 1.015), so googlefonts/version_bump failed with same-version, and the four weights being versioned independently was reported by opentype/family/equal_font_versions as a mismatch.

We unified all four weights on 1.016 — higher than every published weight, so the update is unambiguous, and the family carries a single version from here on.

4. Pre-existing FAILs, untouched by this PR

For transparency, the 14 remaining FAILs — none related to this change:

check scope
no_vert_and_vrt2 all 4 weights
googlefonts/family_name_compliance all 4 weights
googlefonts/font_names Regular, Bold
googlefonts/font_copyright family
family/win_ascent_and_descent family (usWinAscent 1026, usWinDescent 317)
family/uniqueness_first_31_characters family
googlefonts/repo/dirname_matches_nameid_1 Regular

no_vert_and_vrt2 is the same class of problem as this PR — a feature definition inherited from the Glyphs sources — and it affects Japanese vertical layout, so we plan to address it in a separate PR rather than widening this one.

There are also 4 fontdata_namecheck ERRORs. Those are our network: the check calls the Google Fonts API, which our corporate proxy blocks. Not a defect in the fonts.

5. Origin of the bug

Worth noting for the record: this was not introduced in this repository. We traced the whole git history and the very first commit (2023-11-27) already had halt with 784 substitutions and zero positioning rules. It came in with the imported Glyphs sources.

Reproducing

rm -rf LINESeedJP/sources/instance_ufos build.stamp   # gftools caches these
make build
make test

The instance_ufos removal matters: without it the builder reuses cached instances and source-level feature changes never reach the binaries.

@halken
halken marked this pull request as ready for review July 30, 2026 04:48
@halken
halken requested a review from narirou August 14, 2026 01:17
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.

1 participant