All work

Tiqian / Cross-Platform CJK Layout Engine

A CJK typography layout engine and multi-target transpiler architecture, designed for low-resource E-ink readers and high-throughput web publishing.

CLREQ Specimen · Simplified Chinese with Ruby & EmphasisTiqian Engine Output
Tiqian simplified Chinese horizontal typography specimen showing ruby annotations and emphasis marks

Specimen generated by Tiqian: CLREQ punctuation squeeze, Latin-Hanzi autospacing, pinyin ruby annotation (hé), and emphasis marks.

Role

Systems architect and compiler author. Web runtime rewrite, Rust CI bindings, and the boring transpiler toolchain.

Team

Open-source collaboration with Duo123 (typography design, CLREQ specification, and initial prototype).

Timeline

2024 to present. 2,400+ commits on the compiler toolchain.

Outcome

Downstream CI prerendering cut from 25m to 4m 45s (−81%). Single-ticker budget scheduler for 60fps web resizes. Multi-target native transpilation.

01 · The problem

Why digital CJK typography breaks

Standard browser engines were built around Western text layout. For East Asian scripts like Chinese, Japanese, and Korean, text is laid out on a character grid. In default browser rendering, every punctuation mark occupies a full square cell. When two punctuation marks meet, such as a closing bracket followed by a comma, the browser leaves an unsightly gap between them.

Lines also break unevenly along the right margin because browsers lack proper CJK punctuation compression and justification rules. The W3C Chinese Layout Requirements (CLREQ) define how text should behave: consecutive punctuation must squeeze to half-width, Latin characters and Hanzi need proportional spacing, and line-breaking rules must prevent punctuation from wrapping to awkward positions.

Duo123, a designer and the project’s creator, had drafted the typography specifications and built an early Kotlin prototype for simplified Chinese. But taking those rules into production on real websites and dedicated E-ink reading devices required solving runtime, build pipeline, and compiler challenges.

Specimen comparison

Native browser vs Tiqian layout

Tiqian enhanced CJK typography showing punctuation squeeze and flush alignment
Native browser CJK typography showing punctuation gaps and ragged edges
Native Browser
Tiqian Enhanced
Native Browser: Excessive whitespace between consecutive punctuation marks; uneven line lengths along the right margin.
Tiqian Enhanced: CLREQ punctuation squeezing removes unsightly gaps; autospaced Latin glyphs; flush right margin alignment.

02 · Web scheduler rewrite

Replacing chaotic local fixes with a unified frame budget

When the first web build of Tiqian became usable, I was among the first to integrate it into a real reading interface. Performance was rough: scrolling stuttered, and resizing the browser window froze the tab.

Inspecting the implementation showed why: the prototype had accumulated fragmented, AI-generated patches that each attempted to fix local symptoms while breaking the overall system. Drawing on my two years of high-frame-rate rendering work on Recative, I designed a single-ticker execution model and we routed all layout tasks through a managed frame budget.

a

The multi-ticker collision

The prototype had several independent requestAnimationFrame loops running simultaneously. Each module scheduled its own redraw without coordinating with the others, fighting for CPU slices whenever a user scrolled. We replaced the rogue timers with a single master ticker that manages all layout work against a strict 16ms frame budget.

b

EventBus cascading loops

State changes were dispatched through a global EventBus. When one component adjusted layout properties, listener callbacks triggered secondary updates that re-emitted events back into the bus, creating circular reflow storms. We removed the EventBus in favor of explicit, unidirectional task queues.

c

Separating measurement from DOM writes

Resize handlers were reading layout geometry and modifying element styles in the same synchronous pass, forcing the browser to thrash between layout calculation and style recalculation. Staging operations into read-first, write-later batches brought window resizing and text reflow to a smooth 60fps.

03 · Downstream CI precompute

Precomputing book layout in Rust

Downstream documentation and digital reading sites took nearly 25 minutes to prerender books in CI. Every content update stalled waiting for full-text typesetting to complete.

While Kotlin Multiplatform compiles to native binaries, it had no Node.js bindings for JavaScript static site generators. To solve this, we built a native addon in Rust using Neon to connect the layout kernel directly into Node worker processes. Pairing native bindings with worker parallelization and content-hashed SQLite caching cut downstream build times to 4 minutes 45 seconds, an 81% reduction.

Rust Neon native bridge

Connects the native layout engine into Node.js worker pools, letting JavaScript static site generators run CJK typesetting in parallel across CPU cores.

Content-hashed SQLite cache

Caches line-breaking geometry by chapter content hash across CI runs. Unchanged chapters load precomputed layout directly from disk.

04 · Transpiler architecture

Escaping multi-runtime FFI: Why boring generates native code

As the engine grew, Kotlin and DOM code had become tangled together through global map registries. We refactored the pipeline into a decoupled Engine and Platform Adapter linked by FFI. But as both Rust and JavaScript implementations expanded, they shared overlapping logic across three distinct memory models: Kotlin’s runtime GC, Node’s V8, and Rust’s compile-time lifetimes.

Maintaining FFI bindings across multiple platforms shifted language interoperability burdens onto product logic, requiring fragile test suites to keep parity. Rather than adding more FFI layers for future targets, I researched transpilation toolchains and chose Haxe with Reflaxe to architect a dedicated compiler called boring (2,400+ commits).

a

Why TypeScript beat WASM on the web

We tested WebAssembly for the browser target, expecting higher throughput. In practice, marshaling thousands of character layout structures across the WASM boundary negated any compute savings. Worse, in private browsing windows or corporate modes where JIT is disabled, WASM performance collapsed. Compiling to clean TypeScript proved significantly faster on real web pages.

b

Why Rust for dedicated E-ink readers

Dedicated reading devices run on low-power ARM processors with limited memory. Compiling the layout kernel directly to native Rust provides a tiny binary footprint and predictable memory usage, avoiding the runtime overhead of a garbage collector on battery-powered hardware.

c

Human-readable output and unrolled loops

Unlike Kotlin/JS or Dart, which require heavy runtime libraries, boring keeps its runtime minimal. In the TypeScript output, functional iterations are unrolled into plain indexed for loops. This avoids callback overhead and closure allocations on hot layout paths, while keeping the generated code readable and easy to inspect.

05 · Agent governance

Governing autonomous agents with rigid schemas, not prompts

Porting a typography engine and building a compiler across thousands of commits requires sustained execution across dozens of development sessions. Generative AI models can generate code quickly, but as context windows fill up, prompt engineering and “skills” break down: models lose nuance, take shortcuts, and drift away from architectural invariants.

Rather than attempting to steer models through natural language instructions, I governed the multi-agent development pipeline through mechanical constraints. I built an isolated harness dispatcher (dsh-dispatch) to run external agents in dedicated processes, and a structured board (workspace-board) to persist task state across sessions. We installed strict Git hooks across both repositories to reject any commit that fails target compiler verification.

01

Isolated runtime harnesses

Foundation models perform best inside their own dedicated CLI environments rather than nested generic sub-agents.

  • •Dispatches heavy compiler and migration tasks to external agent runtimes in isolated background processes
  • •Tracks process health, token budgets, and execution logs without polluting the primary orchestrator context
  • •Manages long-running task lifecycles through an in-house dispatch service (dsh-dispatch)
02

Structured session handoffs

A coordination board that replaces open-ended chat prompting with deterministic data schemas for task scheduling.

  • •Records task dependencies, milestones, and architectural decisions in machine-readable schemas (workspace-board)
  • •Provides fresh agent sessions with verified project state rather than noisy conversational summaries
  • •Prevents context drift during multi-hour migrations by keeping tasks grounded in explicit specifications
03

Automated repository gates

Local Git hooks installed across compiler repositories, turning architectural rules into hard compile-time barriers.

  • •Automatically aborts commits if an agent introduces type mismatches, lint errors, or broken formatting
  • •Enforces full target test suite passes across all platforms before changes enter git history
  • •Forces agents to read compiler diagnostics and self-correct instead of letting regressions slip through

REFLECTION

Systems earn trust at the edges.

CJK typography has survived for centuries through exacting visual rules. Bringing that standard to digital screens requires more than translating specification documents into code; it requires respecting how target hardware actually runs.

Micro-optimizations in inner loops rarely solve systemic latency. The real performance gains in this project came from clear architectural boundaries: a unified frame budget that stopped layout thrashing, content-hash caching that made static book generation viable in CI, and generating clean native code rather than dragging heavy runtime libraries across FFI bridges.

“Do not push multi-language complexity onto application logic. Solve it at the compiler, and govern development with mechanical verification.”