Skip to content

Width Policy

Sanghyuk Jung edited this page Oct 2, 2026 · 2 revisions

Width policy

The grid

D2 Coding has exactly two advance widths for visible characters:

  • 500 units (half width): Latin, digits, ASCII punctuation, Greek, Cyrillic, most symbols
  • 1000 units (full width): Hangul, Hanja, kana, fullwidth forms, and the symbols listed below

One Hangul syllable is exactly two Latin columns wide. This is what keeps Korean comments and string literals aligned with code, and it is the one property every change has to preserve.

How applications decide on columns

A text editor in a proportional layout just uses the advance widths from the font. A terminal does not. It decides how many cells each character takes before it looks at the font, usually with wcwidth(), which follows the Unicode East Asian Width property:

Class Meaning Terminal columns
W Wide 2
F Fullwidth 2
Na Narrow 1
H Halfwidth 1
N Neutral (not East Asian) 1
A Ambiguous 1, or 2 if the terminal has an "ambiguous width is wide" setting turned on

If the font's width and the terminal's column count disagree, the glyph either fills only the left half of its cell (font 500, terminal 2) or spills into the next cell (font 1000, terminal 1). So in practice the safe rule is:

  • W and F: 1000. The terminal always gives them two columns.
  • N, Na and H: 500. The terminal always gives them one column.
  • A: leave as drawn. No setting works everywhere. Widening an Ambiguous character breaks it in most terminals, and it also widens curly quotes, dashes and the ellipsis inside Western text in an editor.

Current state

Counts of mapped code points in Regular, by class and advance width, in 1.3.5:

Class 500 1000 Notes
W 0 16,533 all wide characters are full width since 1.3.4
F 0 101 fullwidth forms such as ! (
Na 109 0 ASCII
H 1 0 ₩ U+20A9
N 1,432 11 the 11 are ➊ to ➓ and ʼ U+02BC
A 629 1,118 see below

The 1,118 Ambiguous characters at 1000 are mostly private use area code points (about 980) and the enclosed alphanumerics ① to ⑳ and friends in U+2460 to U+24FF (128). They were drawn that way in the original font and are left alone. The 629 at 500 include § ° ± ×, Greek and Cyrillic letters, arrows such as →, and symbols such as ★ ◆ ※.

History of width decisions

  • 1.3.4: 20 East Asian Wide characters drawn at 500 were widened to 1000: ◽ ◾ ☔ ☕ ♈ ♉ ♊ ♋ ♌ ♍ ♎ ♏ ♐ ♑ ♒ ♓ ♿ ⚓ ⚡ and the wave dash 〜 (U+301C). The outlines were moved right by 250 units to sit centered, not scaled. The wave dash shared its glyph with ∼ (U+223C, Ambiguous), so it got a new glyph uni301C at the end of the glyph order.
  • Not adopted: a community build (linked from issues #72 and #91) widened 578 symbols, including 444 Neutral and 115 Ambiguous ones, and scaled the outlines by about 1.7. It matches what some Windows applications with legacy Korean fonts expect, but it would overlap in terminals and make the widened symbols heavier than the text around them.

To check that nothing has slipped, this lists any Wide or Fullwidth character that is not 1000 units wide. It should print nothing:

import unicodedata
from fontTools.ttLib import TTFont

font = TTFont("fonts/ttf/D2Coding-Regular.ttf")
hmtx = font["hmtx"]
for cp, glyph in sorted(font.getBestCmap().items()):
    if unicodedata.east_asian_width(chr(cp)) in "WF" and hmtx[glyph][0] != 1000:
        print(f"U+{cp:04X} {chr(cp)} {hmtx[glyph][0]}")

Python's unicodedata follows the Unicode version of the interpreter, so a newer Python can report new Wide characters.

Consequences that cannot be fixed in the font

These follow directly from the 500/1000 grid. Changing them would break the 1:2 alignment.

  • Half pixel widths at odd sizes. 500 units is 0.5 em, so at 13px a column is 6.5px. Some editors round the half pixel and leave a one pixel line at the end of a row (issue #82). Even sizes are always whole pixels. Most monospaced fonts are about 0.6 em wide and do not land on exact halves.
  • "Not really monospaced" checks. The maximum advance is 1000 and the average is 500. fontconfig therefore classifies the font as spacing: dual (90) rather than mono (100), so fc-list :spacing=mono does not list it (issue #76). neovim-qt compares the average and maximum width and prints "bad fixed pitch metrics" but still uses the font (issue #77).
  • OS/2.xAvgCharWidth must stay 500. Windows Notepad spaces characters by this value. OS/2 version 3 defines it as the average of all glyph widths, and font editors such as FontForge and FontLab recompute it on export. With 11,172 Hangul syllables at 1000 that average is far above 500, and Notepad then draws wide gaps between letters (issue #84). If you build or export the font with any tool, set this value back to 500 afterwards.
  • post.isFixedPitch is 1 and PANOSE proportion is 9 (monospaced). Keep both; they are what tells applications that this is a monospaced font despite the two widths.

Clone this wiki locally