← All posts

What Breaks When You Mix Chinese, English and Emoji in a Teleprompter

A teleprompter looks like it should be simple: show text, scroll it. It is simple — until the text is not all English. Then a string of small, ordinary-looking bugs appears, and each one has a specific cause. We hit them all while building ours, and this is an honest account of what broke and what we changed.

Why mixed text is harder than either language alone

English wraps at spaces. A line ends at a word boundary, and a long word moves entirely to the next line. Chinese has no spaces, so it can wrap between almost any two characters. Each rule is easy on its own. A mixed paragraph forces both rules to run at once, and the naive way to handle it — pick one rule for the whole paragraph — produces obviously wrong output.

On top of the wrapping rules, characters have different widths. An English letter is about half an em; a Chinese character, a Japanese kana, or a Hangul syllable is a full em. Emoji are often wider still. Any line measurement has to agree with how the browser will actually lay out the text, or the numbers drift and the layout breaks.

Bug 1: at large font sizes the lines overlapped

The first version measured line width using the width of the window. That was fine for small text, but the playback area is capped at a fixed maximum width, and the window is usually wider than that. At a large font size we split text into lines that were wider than the area they had to fit in. The browser then wrapped them again, but each line slot had a fixed height, so the extra wrapped line spilled into the next slot. The result was text printed on top of text.

The fix was to compute the layout width from the actual playback container, not the window, and to clamp it to the real maximum. Once the split and the render agreed on the same width, the overlap disappeared.

Bug 2: English words were cut in half

The original code had a single check: if a paragraph contained any Chinese character, split the whole paragraph character by character. For pure Chinese that is correct. For a mixed paragraph it is a disaster — every English word in that paragraph was split mid-word. "Another" became "Ano" on one line and "ther" on the next. "Here" lost its final letter to the following line.

The fix was to stop deciding per paragraph and start deciding per token. We break each paragraph into tokens: a run of CJK characters (any single character is its own token, since CJK can wrap anywhere), a run of non-space Latin characters (a word, kept whole), and spaces. Then we fill lines token by token. English words move whole; Chinese wraps between characters. Mixed paragraphs now behave correctly because the rules are applied to the right units.

Bug 3: Japanese, Korean and emoji overflowed the screen

Our width function counted "wide" characters as two units and everything else as one. But the check for "wide" only covered CJK ideographs — the block that contains Han characters. Japanese hiragana and katakana, Korean Hangul, and full-width punctuation live in different Unicode blocks and were not covered. They were counted as narrow English-like characters while rendering at full width, so lines came out wider than expected and overflowed. Emoji had a similar problem.

The fix was to widen the definition. We now treat as "wide": Hangul jamo and syllables, CJK radicals, CJK punctuation, hiragana, katakana, Hangul compatibility jamo, CJK extension blocks, CJK compatibility ideographs, and CJK/fullwidth forms — plus anything matching the Unicode Extended Pictographic property, which catches emoji. That single change is what stopped Japanese, Korean and emoji lines from running off the edge.

What the splitter does now

  • •Tokenizes each paragraph into CJK characters, Latin/word runs, and spaces.
  • •Measures width with a wide-character table that includes kana, Hangul, full-width punctuation and emoji.
  • •Fills each line token by token; English words are never split unless a single word is wider than the whole line.
  • •Keeps punctuation off the start of a line — a closing comma, period or bracket is pulled back onto the previous line instead of opening the next one.
  • •Leaves a small safety margin, because the width table is an estimate and browsers are not perfectly uniform in how they render type.

How we test it

Because the failures only appear in specific combinations, we built a test script and run it across sizes. The content covers, in one piece: a Chinese paragraph with dense punctuation, an English paragraph, a punctuation-heavy line that mixes full-width and half-width marks, a Japanese paragraph with kana, kanji, voiced kana and small kana, a Korean paragraph, pinyin tone marks, Vietnamese tone marks, a very long English word, a URL, a hash, and a line of emoji. We render it at several font sizes from small to very large, then check every rendered line for horizontal overflow and vertical overflow.

That matrix is what caught the kana and Hangul bug. A pure-English test looked perfect, and even the Chinese and mixed tests passed. It was the Japanese and Korean lines that overflowed, and only at larger sizes.

What is still not perfect

The width table is an estimate. Fonts differ, and a character counted as two units may render slightly wider or narrower than assumed. The safety margin absorbs most of that, but an extreme font or a very narrow screen can still surprise it. A single token that is wider than an entire line — a long URL, a long unbroken hash — has to be split character by character, which is never pretty but is unavoidable. And punctuation rules are simplified: we handle the common cases for line starts, not every locale-specific typographic convention.

Every one of these bugs looked like a layout problem. None of them were. They were all cases where our idea of how wide the text is disagreed with how wide the browser actually drew it.

If you are writing a teleprompter or any tool that renders mixed-language text, the lesson is boring and useful: measure against the real container, split by the right unit, and keep your wide-character table honest. Most of the rest is detail.

Try it free — no signup, no download

Paste your script and start prompting in seconds. Adjustable speed, line tracking, mirror mode and fullscreen.

Open the Teleprompter

Related: how a teleprompter works · how to use the teleprompter · keyboard shortcuts