Skip to content

Vertical writing systems (tategaki column layout) - #18

Merged
dani-polani merged 5 commits into
mainfrom
feat/vertical-writing-systems
Aug 8, 2026
Merged

Vertical writing systems (tategaki column layout)#18
dani-polani merged 5 commits into
mainfrom
feat/vertical-writing-systems

Conversation

@dani-polani

Copy link
Copy Markdown
Member

Several users asked to align texts written vertically: Japanese and Chinese tategaki, traditional Mongolian. The design question was what a diagram should look like when a vertical language is paired with a horizontal one.

Approach

Two things were fused into one assumption ("lines stack down, tokens flow across, connectors run vertically"). This splits them:

  • layoutAxis (project-wide) — rows as before, or columns, where every line becomes a vertical column and connectors run sideways.
  • textOrientation (per line) — upright (words stay horizontal), vertical (characters stack, for Japanese and Chinese), or sideways (line rotates).

A Japanese column stacks its glyphs while the English column beside it stays a stack of upright word boxes. That is the answer to the mixed case.

The axis is project-wide on purpose: a connector between an adjacent pair needs one consistent "between" direction, and the gap between a line pair is a single number. Mixing axes per line would break both.

Column mode sets the token row itself to writing-mode: vertical-rl, so flex direction and the logical margin between tokens map onto the block axis and DOM measurement is untouched. That is why this did not become a rewrite. Link geometry, auto-fit, and refitLayoutToFill take the axis. SVG export draws a vertical token as one <tspan> per character, because SVG 1.1 writing-mode is not honored by the rasterizer behind PNG and PDF, and a sideways token as a rotation about the box center.

Compact v4 gains one settings key and one trailing line column, both omitted at their defaults, so existing share links decode unchanged (covered by a test).

Decisions worth flagging

  • No separate column-direction setting. It expressed nothing that line order did not. The first line is always at the start of the stack: topmost in rows, leftmost in columns. Japanese and Chinese read columns right to left, so those projects list the translation first; Mongolian reads left to right and needs no reordering.
  • Mongolian uses sideways, not vertical. Its fonts store glyphs rotated a quarter turn counter-clockwise and expect vertical layout to rotate the line back (UAX #50), unlike CJK where each character is set upright. This is the case the three-way enum exists for.
  • upper/lower not renamed. In column mode "upper" means "earlier in stack order". Those names are part of the /api/align and MCP surface, so renaming would break clients for no user benefit.

Not included

  • API, MCP, and OpenAPI do not expose the feature, and the docs say so.
  • Vertical HarfBuzz shaping for uploaded custom fonts in PNG/PDF export. Affects CJK brackets and punctuation; Google Fonts are unaffected.
  • Automatic CJK segmentation and furigana.

Verification

Driven in Chromium (Playwright): preview and downloaded SVG match, the Japanese column sits right with stacked glyphs, English stacks upright beside it, connectors cross correctly on SOV/SVO. Mongolian loads Noto Sans Mongolian and orients correctly. Horizontal mode unchanged. check, lint, and test green (189 tests, 26 added); npm run build prerenders all three new pages.

Docs and content

Gallery pages for both examples, a guide at /guide/vertical-writing-alignment, plus README, /about, CHANGELOG, and llms.txt (which also gained a Guides section it was missing). Preview PNGs are rendered and already uploaded to the CDN.

Two fixes fell out of rendering those previews: the 70vh frame height that auto-fit needs in column mode is now scoped to the editor, since a readonly embed was screenshotting mostly blank canvas, and the credit line no longer sits beside the diagram as a flex item. The render script also accepts CHROMIUM_PATH, because Playwright's bundled Chromium cannot start on NixOS, which made the documented pipeline unrunnable there.

🤖 Generated with Claude Code

dani-polani and others added 5 commits August 8, 2026 02:11
Splits two things that were fused into one assumption ("lines stack down,
tokens flow across, connectors run vertically"):

- `layoutAxis` (project-wide): `rows` as before, or `columns` where every
  line becomes a vertical column and connectors run sideways. Plus
  `columnOrder` for which edge the first line sits on (right for CJK, left
  for Mongolian). The axis is project-wide on purpose: a connector between
  an adjacent pair needs one consistent "between" direction, and a line pair
  gap is a single number.
- `textOrientation` (per line): `upright` keeps words horizontal, `vertical`
  stacks characters (tategaki), `sideways` rotates the line. So a Japanese
  column stacks glyphs while the English column next to it stays a stack of
  upright word boxes.

Column mode sets the token row itself vertically, so flex direction and the
logical margin between tokens map to the block axis and DOM measurement is
unchanged. Link geometry, auto-fit, and `refitLayoutToFill` take the axis;
SVG export draws vertical tokens as one tspan per character (SVG 1.1
writing-mode is not honored by the PNG/PDF rasterizer) and sideways tokens
as a rotation about the box center.

Compact v4 gains two settings keys and one trailing line column, both
omitted at their defaults, so existing share links decode unchanged.

Not included, tracked as follow-ups: API/MCP/OpenAPI surface, vertical
HarfBuzz shaping for custom-font path export (CJK brackets and punctuation
need `vert`/`vrt2` and vertical advances), automatic CJK segmentation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Turning on vertical writing needed two controls in two panels: Direction
buried below Canvas and Colors in the Style tab, and each line's glyph
setting behind its own gear popover. Nobody found it.

Layout is now the first section of the Style tab (it is structural, not
decorative), and switching to Columns reveals a per-line "Stack characters"
list right underneath. Two clicks, one place. The per-line popover keeps the
full three-way setting, since `sideways` stays the advanced option.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`columnOrder` expressed nothing that line order did not: reversing the lines
produces the same mirror. Removed it. The first line is now always at the
start of the stack — topmost in rows, leftmost in columns — which is the one
consistent rule and matches the reorder arrows.

The tategaki example therefore lists English first so the Japanese column
still lands on the right, where a vertical text belongs.

New Mongolian example. Traditional Mongolian runs top-to-bottom with columns
progressing left to right, so the script line comes first with no reordering.
Its setting is `sideways`, not `vertical`: Mongolian fonts store glyphs
rotated 90° counter-clockwise and expect the line to be rotated back
(UAX #50), unlike CJK where each character is set upright. That is the case
the three-way enum was kept for.

Text is the masthead of the newspaper Buriyad Mongɣol-un ünen (1925), taken
from the Wikipedia article on the Mongolian script, with a transliteration
tier. The genitive ᠤᠨ has no English word of its own, so it stays unlinked.

Verified in Chromium: Noto Sans Mongolian loads, glyphs orient correctly, and
the exported SVG matches the preview.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Gallery pages for both new examples, with a word-by-word table and the
reasoning each one demonstrates: Japanese particles that link to nothing,
and the Mongolian genitive that does the same. New guide at
/guide/vertical-writing-alignment covering when to use columns, the three
per-line text settings, mixing a vertical script with a horizontal
translation, why line order decides the column side, and splitting spaceless
text into words. Both surfaces cross-link.

Also: README and about feature lists, CHANGELOG, llms.txt (new walkthroughs
plus a Guides section that was missing entirely). Guide catalog entry gives
the index card and sitemap URL for free.

Rendered the gallery previews. Two fixes fell out of doing that:

- The 70vh frame height that auto-fit needs in column mode made a readonly
  embed screenshot mostly blank canvas. It is now scoped to the editor, and
  columns stretch to the tallest one instead of to a percentage height, so
  the same rules work bounded or hugging content. The credit line was
  landing beside the diagram as a flex item; it now wraps underneath.
- The render script can use a system Chromium via CHROMIUM_PATH. Playwright's
  bundled build cannot start on NixOS, which made the documented pipeline
  unrunnable there.

Preview PNGs still need `make examples-upload` (object storage credentials)
before the two new gallery pages show their images.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`npm run audit:ci` started failing on advisories published against unchanged
code, the same way it did earlier this week:

- nanoid <3.3.17 — GHSA-2v37-7h3g-55p8, high. Custom generators loop
  indefinitely when size is zero. 3.3.16 → 3.3.18.
- dompurify <=3.4.12 — GHSA-55q2-fjhq-7xh7, moderate. Came along with the
  fix. 3.4.12 → 3.4.13.

Lockfile only; package.json untouched. The three remaining low advisories on
`cookie` are reachable through @sveltejs/kit and npm's only offered fix
downgrades it to 0.0.30, so they stay below the --audit-level=high gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dani-polani
dani-polani merged commit f1d9f1e into main Aug 8, 2026
2 checks passed
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