New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add the lineBreak
option
#86
Conversation
This commit adds `lineBreak` option to specify how to break texts into lines. `OnSpaces` is the default and breaks texts at spaces. `OnCharacters` breaks texts at characters, even in the middle of a word. This is useful for handling CJK texts, though it does not follow [the line breaking rules](https://en.wikipedia.org/wiki/Line_breaking_rules_in_East_Asian_languages).
src/Monomer/Graphics/Text.hs
Outdated
splitGroups :: LineBreak -> Seq GlyphPos -> Seq GlyphGroup | ||
splitGroups _ Empty = Empty | ||
splitGroups break glyphs = group <| splitGroups break rest where | ||
g :<| gs = glyphs | ||
groupWordFn = not . isWordDelimiter . _glpGlyph | ||
atAnyPos = break == OnCharacters | ||
(group, rest) | ||
| isWordDelimiter (_glpGlyph g) = (Seq.singleton g, gs) | ||
| atAnyPos || isWordDelimiter (_glpGlyph g) = (Seq.singleton g, gs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fitGroups
will take times if a long text and the OnCharacters
option (like the example code I pasted) are given because this function splits the text into characters. I don't think this affects the most use cases, though. Should I make this fast?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please!
This is something I was supposed to optimize at some point but, since it worked well enough for my uses cases, I never went back to review it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I couldn't come up with a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried a couple of things, and this looks a bit faster (I have not benchmarked it with Criterion or similar):
fitLineToW fontMgr font fSize fSpcH fSpcV metrics break top width trim text = res where
spaces = T.replicate 4 " "
newText = T.replace "\t" spaces text
!glyphs = computeGlyphsPos fontMgr font fSize fSpcH newText
-- Do not break line on trailing spaces, they are removed in the next step
-- In the case of KeepSpaces, lines with only spaces (empty looking) are valid
keepTailSpaces = trim == TrimSpaces
groups
| break == OnCharacters = splitGroups break width glyphs
| otherwise = fitGroups (splitGroups break width glyphs) width keepTailSpaces
resetGroups
| trim == TrimSpaces = fmap (resetGlyphs . trimGlyphs) groups
| otherwise = fmap resetGlyphs groups
buildLine = buildTextLine font fSize fSpcH fSpcV metrics top
res
| text /= "" = Seq.mapWithIndex buildLine resetGroups
| otherwise = Seq.singleton (buildLine 0 Empty)
...
splitGroups :: LineBreak -> Double -> Seq GlyphPos -> Seq GlyphGroup
splitGroups _ _ Empty = Empty
splitGroups break width glyphs = group <| splitGroups break width rest where
g :<| gs = glyphs
groupWordFn = not . isWordDelimiter . _glpGlyph
groupWidthFn g2 = _glpXMax g2 - _glpXMin g <= width
atWord = break == OnSpaces
atAnyPos = break == OnCharacters
(group, rest)
| atWord && isWordDelimiter (_glpGlyph g) = (Seq.singleton g, gs)
| atWord = Seq.spanl groupWordFn glyphs
| otherwise = Seq.spanl groupWidthFn glyphs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked your code, and I feel it's faster, although I haven't run any benchmarks (and TBH, I don't know how to run benchmarks in Haskell).
src/Monomer/Graphics/Text.hs
Outdated
@@ -53,25 +53,26 @@ calcTextSize | |||
-> Text -- ^ The text to calculate. | |||
-> Size -- ^ The calculated size. | |||
calcTextSize fontMgr style !text = size where | |||
size = calcTextSize_ fontMgr style SingleLine KeepSpaces Nothing Nothing text | |||
size = calcTextSize_ fontMgr style SingleLine KeepSpaces OnSpaces Nothing Nothing text |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in this case it would be worth trying to use the new attribute from StyleState
.
There are a few utilities in StyleUtil.hs
for accessing StyleState
fields (styleTextAlignH
, styleTextAlignV
, etc). Adding one for styleTextLineBreak
would simplify retrieving this value (or the default if not available).
@@ -197,7 +197,7 @@ makeTooltip caption config state = widget where | |||
targetW = fromMaybe maxW (_ttcMaxWidth config) | |||
targetH = fromMaybe maxH (_ttcMaxHeight config) | |||
targetSize = Size targetW targetH | |||
fittedLines = fitTextToSize fontMgr style Ellipsis MultiLine TrimSpaces | |||
fittedLines = fitTextToSize fontMgr style Ellipsis MultiLine TrimSpaces OnSpaces |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, it would be nice to use styleTextLineBreak
(the new utility mentioned earlier).
@@ -919,7 +919,7 @@ updatePlaceholder wenv node !state !config = newState where | |||
& L.text . non def . L.alignH ?~ inputFieldAlignH style | |||
& L.text . non def . L.alignV ?~ inputFieldAlignV style | |||
text = _ifcPlaceholder config | |||
fitText = fitTextToSize fontMgr pstyle Ellipsis MultiLine KeepSpaces Nothing | |||
fitText = fitTextToSize fontMgr pstyle Ellipsis MultiLine KeepSpaces OnSpaces Nothing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to be clear, I think it's fine if we ignore the new text attribute here since it's a single line field
@@ -762,7 +762,7 @@ stateFromText wenv node state text = newState where | |||
style = currentStyle wenv node | |||
fontMgr = wenv ^. L.fontManager | |||
newTextMetrics = getTextMetrics wenv style | |||
tmpTextLines = fitTextToWidth fontMgr style maxNumericValue KeepSpaces text | |||
tmpTextLines = fitTextToWidth fontMgr style maxNumericValue KeepSpaces OnSpaces text |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's also fine if we ignore the new text attribute here since it never forces a break unless a \n
is provided (it passes the largest number available as the line break width)
src/Monomer/Widgets/Util/Text.hs
Outdated
@@ -40,21 +40,22 @@ getTextMetrics wenv style = textMetrics where | |||
getTextSize :: WidgetEnv s e -> StyleState -> Text -> Size | |||
getTextSize wenv style text = size where | |||
fontMgr = wenv ^. L.fontManager | |||
size = calcTextSize_ fontMgr style SingleLine KeepSpaces Nothing Nothing text | |||
size = calcTextSize_ fontMgr style SingleLine KeepSpaces OnSpaces Nothing Nothing text |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one would also benefit from using styleTextLineBreak
.
Looks good! I forgot to mention, but there is a |
Now I understand what you said in the issue. I should've asked for the detail, sorry. You said:
So are we going to add the |
Not to create instance of each widget.
I updated the code example to specify line break options with |
Yes, I think it's easier to use that way. A single place needs updating, and every widget that relies on Let me know when you want me to review the PR again! I noticed you marked it as a draft, so I don't want to bug you until you are ready (looks great so far, by the way). |
Looks good! I added a comment regarding performance. One thing about the example: |
Yes, you're right. Thank you. I fixed the example code and the output. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@toku-sa-n looks good! 👍
Thanks!
This commit adds the
lineBreak
option to specify how to break texts into lines.OnSpaces
is the default and breaks texts at spaces.OnCharacters
breaks texts at characters, even in the middle of a word. It is useful for handling CJK texts, though it does not follow the line breaking rules. It is equivalent toanywhere
of CSS'line-break
property.Below is the example output and its source code.
Discussed in #84