Repository navigation
feat: text wrapping, alignment and line height - #20
Merged
Merged
Conversation
.Wrap(width) breaks a text into lines no wider than width: - at spaces (Latin, Cyrillic); non-breaking spaces never break; - between any two CJK characters, and after a hyphen inside a word; - basic kinsoku: never before closing punctuation (,。!?」 , . ! ? and small kana, ー, …), never after opening punctuation (「( ( [); - a space before French ! ? : ; » % or after « does not break; - "\n" always breaks; a word wider than the line alone breaks between characters (never between a letter and its combining marks); spaces at the end of a wrapped line are dropped. The rules live in wrap.go as a pure function (wrapLines) with a table test. The wrapped text's box is a column exactly width wide and as tall as its lines, centered on the position like every object, so a left-aligned paragraph starts at X - width/2 whatever it says. Agents still read the unwrapped string. Size fixes a text area (a dialog box) whose lines start at its top. .TextAlign(engine.AlignCenter|AlignLeft|AlignRight) lines up the lines in the box (centered by default, like the single-line text anchored at its center today), on whole pixels. .LineHeight(px) sets the baseline distance; by default the fonts' own line height. "\n" now lays out real lines everywhere: it used to draw every line on top of the first (line spacing 0) and measure one line tall. Buttons wrap and align their label the same way. The layout is cached on the object and redone only when the text, the face, Wrap or LineHeight changes. Contract item 12. The dialog example wraps its speech, left-aligned in a fixed text area, in English, Portuguese and Chinese; demo re-recorded.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Three chainable calls for translated paragraphs (card effects, codex entries, dialog):
.Wrap(width float64)breaks the text into lines no wider thanwidth.0turns it off..TextAlign(a Align), withengine.AlignCenter(the default),engine.AlignLeftandengine.AlignRight, sets how the lines line up in the text's box..LineHeight(px float64)is the distance from one baseline to the next. The default is the fonts' own line height.Break rules
The rules are a small, predictable subset of UAX #14, implemented as a pure function in
wrap.go(wrapLines):sci-/fi).,。、!?)」』】》”and ASCII, . ! ? ) ] }, small kana,ー,々,…), and never after opening punctuation (「『(【《“and( [ {).! ? : ; » %, or after«, does not break, so "Bonjour !" keeps its "!"."\n"always breaks. A word wider than the whole line breaks between characters, and never between a letter and its combining marks. Spaces at the end of a wrapped line are dropped; a paragraph's leading indent is kept.Design choices
widthwide, not the longest line, and as tall as its lines, and it stays centered on the text's position like every other object. With a shrink-wrapped box, a left-aligned paragraph's left edge would move with its content, and games cannot read a text's size. With the column,TextAlign(AlignLeft)puts every line atX - width/2whatever the translation says. The same trick anchors a one-line label by its left or right edge, which a translated HUD needs now thatlen(str) * sizeno longer measures it. Clicks and agents'wuse the column, and agents still read the unwrapped string.Wraplooks exactly like one without. The name isTextAlignbecause it sits next toTextSizeandTextColor;Alignalone would read like it moves the object.LineHeightis the one number that fixes it. A single line is unaffected.Size(w, h)plusWrap(w)gives a box whose lines start at its top. That keeps a dialog's first line in place while a typewriter adds more."\n"now lays out real lines. It used to draw every line on top of the first (Ebitengine's line spacing was left at 0) and measure one line tall, so no working game relied on it.WraporLineHeightchanges. Lines are measured piece by piece, so a typewriter callingSetTextevery frame hits Ebitengine's shaping cache.Proof
wrap_test.go:LineHeight, unwrapping, empty text,"\n"lines,alignX, andSizewithWrap;SetTextre-wraps, Restart;examples/dialogwraps its speech in the dialog box, left-aligned in a fixed text area, with the speaker's name aligned to it (the column trick). It works in English, Portuguese and Chinese (one Chinese line is now long enough to wrap), anddemo.gifis re-recorded.