Skip to content
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

Text wrapping #300

Merged
merged 14 commits into from Feb 14, 2019
5 changes: 5 additions & 0 deletions examples/Examples.re
Expand Up @@ -107,6 +107,11 @@ let state: state = {
render: _ => DropdownExample.render(),
source: "DropdownExample.re",
},
{
name: "Text",
render: _w => TextExample.render(),
source: "TextExample.re",
},
],
selectedExample: "Animation",
};
Expand Down
101 changes: 101 additions & 0 deletions examples/TextExample.re
@@ -0,0 +1,101 @@
open Revery.UI;
open Revery.Core;
open Revery.UI.Components;

let containerStyle =
Style.[
position(`Absolute),
top(0),
bottom(0),
left(0),
right(0),
alignItems(`Center),
justifyContent(`Center),
flexDirection(`Column),
];

let slidersViewStyle = Style.[height(200)];

let textStyle =
Style.[
color(Colors.white),
width(100),
fontFamily("Roboto-Regular.ttf"),
fontSize(16),
margin(14),
textWrap(TextWrapping.NoWrap),
];

let controlsStyle =
Style.[
margin(10),
flexDirection(`Row),
justifyContent(`Center),
alignItems(`Center),
];

module SampleText = {
let component = React.component("Example");

let createElement = (~children as _, ()) =>
component(hooks => {
let (fontSizeSliderVal, setFontSize, hooks) =
React.Hooks.state(20., hooks);
let (widthSliderVal, setWidth, hooks) = React.Hooks.state(200., hooks);

let textContent = "All work and no play makes Jack a dull boy";
let maxFontSize = 40.;
let maxWidth = 400.;

(
hooks,
<View style=containerStyle>
<View style=slidersViewStyle>
<Text
style=Style.[
color(Colors.white),
fontFamily("Roboto-Regular.ttf"),
fontSize(int_of_float(fontSizeSliderVal)),
lineHeight(1.5),
textWrap(TextWrapping.WhitespaceWrap),
width(int_of_float(widthSliderVal)),
border(~color=Colors.blueViolet, ~width=1),
]
text=textContent
/>
</View>
<View style=controlsStyle>
<Text style=textStyle text="Font size: " />
<Slider
onValueChanged=setFontSize
value=fontSizeSliderVal
maximumValue=maxFontSize
/>
<Text
style=textStyle
text={
"Value: "
++ (fontSizeSliderVal |> int_of_float |> string_of_int)
}
/>
</View>
<View style=controlsStyle>
<Text style=textStyle text="Width: " />
<Slider
onValueChanged=setWidth
value=widthSliderVal
maximumValue=maxWidth
/>
<Text
style=textStyle
text={
"Value: " ++ (widthSliderVal |> int_of_float |> string_of_int)
}
/>
</View>
</View>,
);
});
};

let render = () => <SampleText />;
2 changes: 2 additions & 0 deletions src/Core/Revery_Core.re
Expand Up @@ -17,6 +17,8 @@ module Events = Events;
module Performance = Performance;
module UniqueId = UniqueId;

module TextWrapping = TextWrapping;

/*
* Internally exposed modules, just for testing.
*/
Expand Down
110 changes: 110 additions & 0 deletions src/Core/TextWrapping.re
@@ -0,0 +1,110 @@
type wrapType =
| NoWrap
| WhitespaceWrap
| UserDefined((string, string => int, int) => (list(string), int));

type linesFolderAccumulator = {
lines: list(string),
currMaxWidth: int,
beginIndex: int,
endIndex: int,
};

/* See https://www.fileformat.info/info/unicode/category/Zs/list.htm */
let space = " ";
let nbsp = "\xa0";

/* TODO Because we're not using regex for space matching we should take all kinds of spaces into conderation
However, I can't make unicode escape work, it's giving me 'Warning 14: illegal backslash escape in string.' error
*/

/*
let oghamSpace = "\u{1680}";
let enQuad = "\u{2000}";
let emQuad = "\u{2001}";
let enSpace = "\u{2002}";
let emSpace = "\u{2003}";
let threePerEmSpace = "\u{2004}";
let fourPerEmSpace = "\u{2005}";
let sixPerEmSpace = "\u{2006}";
let figureSpace = "\u{2007}";
let punctuationSpace = "\u{2008}";
let thinSpace = "\u{2009}";
let hairSpace = "\u{200A}";
let narrowNbsp = "\u{202f}";
let mediumMathSpace = "\u{205f}";
let ideographicSpace = "\u{3000}";
*/
let lineBreak = "\n";
let tab = "\t";
let lineFeed = "\x0c";

let isWhitespaceWrapPoint = str =>
str != nbsp
&& (str == space || str == lineBreak || str == lineFeed || str == tab);

let wrapText = (~text, ~measureWidth, ~maxWidth, ~wrapHere) => {
let textLength = String.length(text);

let isEnd = i => textLength == i + 1;

let subAndMeasure = (beginIndex, endIndex) => {
let substr = String.sub(text, beginIndex, endIndex - beginIndex + 1);
let substrWidth = measureWidth(substr);
(substr, substrWidth);
};

let foldIntoLines = (acc, (index, char)) =>
if (isEnd(index)) {
let currEndIndex = index;
let (_substr, width) = subAndMeasure(acc.beginIndex, currEndIndex);

if (width >= maxWidth) {
let (line, lineWidth) = subAndMeasure(acc.beginIndex, acc.endIndex);
let (lastLine, lastLineWidth) =
subAndMeasure(acc.endIndex + 2, index);

let currMaxWidth =
max(lastLineWidth, max(acc.currMaxWidth, lineWidth));

{...acc, lines: [lastLine, line, ...acc.lines], currMaxWidth};
} else {
let (line, lineWidth) = subAndMeasure(acc.beginIndex, index);

{
...acc,
lines: [line, ...acc.lines],
currMaxWidth: max(acc.currMaxWidth, lineWidth),
};
};
} else if (!wrapHere(Char.escaped(char))) {
acc;
} else {
let currEndIndex = index - 1;
let (_substr, width) = subAndMeasure(acc.beginIndex, currEndIndex);

if (width >= maxWidth) {
let beginningOfCurrentWordIndex = acc.endIndex + 2;
saitonakamura marked this conversation as resolved.
Show resolved Hide resolved
let (lineWithoutCurrentWord, lineWithoutCurrentWordWidth) =
subAndMeasure(acc.beginIndex, acc.endIndex);

{
lines: [lineWithoutCurrentWord, ...acc.lines],
currMaxWidth: max(acc.currMaxWidth, lineWithoutCurrentWordWidth),
beginIndex: beginningOfCurrentWordIndex,
endIndex: currEndIndex,
};
} else {
{...acc, endIndex: currEndIndex};
};
};

let linesAndMaxWidth =
String.to_seqi(text)
|> Seq.fold_left(
foldIntoLines,
{lines: [], currMaxWidth: 0, beginIndex: 0, endIndex: 0},
);

(List.rev(linesAndMaxWidth.lines), linesAndMaxWidth.currMaxWidth);
};
17 changes: 15 additions & 2 deletions src/UI/Style.re
Expand Up @@ -46,6 +46,8 @@ type t = {
right: int,
fontFamily,
fontSize: int,
lineHeight: float,
textWrap: TextWrapping.wrapType,
marginTop: int,
marginLeft: int,
marginRight: int,
Expand Down Expand Up @@ -95,6 +97,8 @@ let make =
~right=Encoding.cssUndefined,
~fontFamily="",
~fontSize=Encoding.cssUndefined,
~lineHeight=1.2,
~textWrap=TextWrapping.WhitespaceWrap,
~marginTop=Encoding.cssUndefined,
~marginLeft=Encoding.cssUndefined,
~marginRight=Encoding.cssUndefined,
Expand Down Expand Up @@ -149,6 +153,8 @@ let make =
right,
fontFamily,
fontSize,
lineHeight,
textWrap,
transform,
marginTop,
marginLeft,
Expand Down Expand Up @@ -289,16 +295,19 @@ type coreStyleProps = [
];

type fontProps = [ | `FontFamily(string) | `FontSize(int)];

type textProps = [ | `LineHeight(float) | `TextWrap(TextWrapping.wrapType)];

/*
Text and View props take different style properties as such
these nodes are typed to only allow styles to be specified
which are relevant to each
*/
type textStyleProps = [ fontProps | coreStyleProps];
type textStyleProps = [ textProps | fontProps | coreStyleProps];
type viewStyleProps = [ coreStyleProps];
type imageStyleProps = [ coreStyleProps];

type allProps = [ coreStyleProps | fontProps];
type allProps = [ coreStyleProps | fontProps | textProps];

let emptyTextStyle: list(textStyleProps) = [];
let emptyViewStyle: list(viewStyleProps) = [];
Expand Down Expand Up @@ -342,6 +351,8 @@ let top = f => `Top(f);

let fontSize = f => `FontSize(f);
let fontFamily = f => `FontFamily(f);
let lineHeight = h => `LineHeight(h);
let textWrap = w => `TextWrap(w);

let height = h => `Height(h);
let width = w => `Width(w);
Expand Down Expand Up @@ -481,6 +492,8 @@ let applyStyle = (style, styleRule) =>
| `Transform(transform) => {...style, transform}
| `FontFamily(fontFamily) => {...style, fontFamily}
| `FontSize(fontSize) => {...style, fontSize}
| `LineHeight(lineHeight) => {...style, lineHeight}
| `TextWrap(textWrap) => {...style, textWrap}
| `Cursor(cursor) => {...style, cursor}
| `Color(color) => {...style, color}
| `BackgroundColor(backgroundColor) => {...style, backgroundColor}
Expand Down