Commit b39b467b authored by antti@apple.com's avatar antti@apple.com
Browse files

Implement white-space property on simple line layout path

https://bugs.webkit.org/show_bug.cgi?id=124122

Source/WebCore: 

Reviewed by Andreas Kling.
        
Support all values of the white-space property and the tab-size property.

Tests: fast/forms/basic-textareas-simple-lines.html
       fast/text/embed-at-end-of-pre-wrap-line-simple-lines.html
       fast/text/whitespace/pre-wrap-line-test-simple-lines.html
       fast/text/whitespace/pre-wrap-long-word-simple-lines.html
       fast/text/whitespace/pre-wrap-spaces-after-newline-simple-lines.html

* rendering/SimpleLineLayout.cpp:
(WebCore::SimpleLineLayout::canUseFor):
(WebCore::SimpleLineLayout::isWhitespace):
(WebCore::SimpleLineLayout::skipWhitespaces):
(WebCore::SimpleLineLayout::textWidth):
(WebCore::SimpleLineLayout::measureWord):
(WebCore::SimpleLineLayout::createTextRuns):
* rendering/SimpleLineLayoutFunctions.cpp:
(WebCore::SimpleLineLayout::paintDebugBorders):
(WebCore::SimpleLineLayout::paintFlow):

LayoutTests: 

Reviewed by Andreas Kling.
        
The simple line layout produces slightly different runs in some pre-wrap cases compared
to the line box path (with less unnecessary boxes). To keep the test coverage this patch forces the
existing render tree dump based tests to use line boxes. It also adds new ref tests for
the same cases where the test uses the simple line path and the ref is forced on the line box path.
This ensures that the paths produce pixel-identical results.

* fast/forms/basic-textareas-simple-lines-expected.html: Added.
* fast/forms/basic-textareas-simple-lines.html: Added.
* fast/forms/basic-textareas.html:
* fast/text/embed-at-end-of-pre-wrap-line-simple-lines-expected.html: Added.
* fast/text/embed-at-end-of-pre-wrap-line-simple-lines.html: Added.
* fast/text/embed-at-end-of-pre-wrap-line.html:
* fast/text/whitespace/pre-wrap-line-test-simple-lines-expected.html: Added.
* fast/text/whitespace/pre-wrap-line-test-simple-lines.html: Added.
* fast/text/whitespace/pre-wrap-line-test.html:
* fast/text/whitespace/pre-wrap-long-word-simple-lines-expected.html: Added.
* fast/text/whitespace/pre-wrap-long-word-simple-lines.html: Added. New simple test for overflowing lines which was only covered by the very large basic-textareas.html.
* fast/text/whitespace/pre-wrap-spaces-after-newline-simple-lines-expected.html: Added.
* fast/text/whitespace/pre-wrap-spaces-after-newline-simple-lines.html: Added.
* fast/text/whitespace/pre-wrap-spaces-after-newline.html:



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@159030 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 72ceb1fb
2013-11-10 Antti Koivisto <antti@apple.com>
Implement white-space property on simple line layout path
https://bugs.webkit.org/show_bug.cgi?id=124122
Reviewed by Andreas Kling.
The simple line layout produces slightly different runs in some pre-wrap cases compared
to the line box path (with less unnecessary boxes). To keep the test coverage this patch forces the
existing render tree dump based tests to use line boxes. It also adds new ref tests for
the same cases where the test uses the simple line path and the ref is forced on the line box path.
This ensures that the paths produce pixel-identical results.
* fast/forms/basic-textareas-simple-lines-expected.html: Added.
* fast/forms/basic-textareas-simple-lines.html: Added.
* fast/forms/basic-textareas.html:
* fast/text/embed-at-end-of-pre-wrap-line-simple-lines-expected.html: Added.
* fast/text/embed-at-end-of-pre-wrap-line-simple-lines.html: Added.
* fast/text/embed-at-end-of-pre-wrap-line.html:
* fast/text/whitespace/pre-wrap-line-test-simple-lines-expected.html: Added.
* fast/text/whitespace/pre-wrap-line-test-simple-lines.html: Added.
* fast/text/whitespace/pre-wrap-line-test.html:
* fast/text/whitespace/pre-wrap-long-word-simple-lines-expected.html: Added.
* fast/text/whitespace/pre-wrap-long-word-simple-lines.html: Added. New simple test for overflowing lines which was only covered by the very large basic-textareas.html.
* fast/text/whitespace/pre-wrap-spaces-after-newline-simple-lines-expected.html: Added.
* fast/text/whitespace/pre-wrap-spaces-after-newline-simple-lines.html: Added.
* fast/text/whitespace/pre-wrap-spaces-after-newline.html:
2013-11-10 Andreas Kling <akling@apple.com>
 
Optimize baselines: svg/*
......
<html>
<body>
<script>
// Force line box path.
if (window.internals)
internals.settings.setSimpleLineLayoutEnabled(false);
var docToAppendTo;
function addTextarea(properties, opt_innerHTML) {
var title = docToAppendTo.createTextNode();
title.nodeValue = '';
var wrapper = docToAppendTo.createElement('div');
wrapper.style.cssText = 'display:inline-block;border:1px solid blue;font-size:12px;';
var textarea = docToAppendTo.createElement('textarea');
for (property in properties) {
var value = properties[property];
title.nodeValue += property + ': "' + value + '", ';
if (property == 'wrap')
textarea.setAttribute(property, value);
else if (property == 'style')
textarea.style.cssText = value;
else
textarea[property] = value;
}
textarea.innerHTML = opt_innerHTML ||
"Lorem ipsum dolor ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuv";
var span = document.createElement('span');
span.style.cssText = 'display:inline-block;width:80px;';
span.appendChild(title);
wrapper.appendChild(span);
wrapper.appendChild(document.createElement('br'));
wrapper.appendChild(textarea)
docToAppendTo.body.appendChild(wrapper);
}
function addAllTextareas(iframe, compatMode) {
iframe.style.cssText = 'width:100%;border:0;'
docToAppendTo = iframe.contentWindow.document;
docToAppendTo.body.style.cssText = 'margin:0';
if (docToAppendTo.compatMode != compatMode)
testFailed('This document should be in ' + compatMode + ' mode.');
var compatModeTitle = docToAppendTo.createElement('div');
compatModeTitle.innerHTML = 'CompatMode: ' + docToAppendTo.compatMode;
compatModeTitle.style.cssText = 'margin:5px 0;font-weight:bold;';
docToAppendTo.body.appendChild(compatModeTitle);
addTextarea({}, 'Lorem ipsum dolor');
addTextarea({disabled: 'true'});
addTextarea({style: 'padding:10px'});
addTextarea({style: 'padding:0px'});
addTextarea({style: 'margin:10px'});
addTextarea({style: 'margin:0px'});
addTextarea({style: 'width:60px'});
addTextarea({style: 'width:60px; padding:20px'});
addTextarea({style: 'width:60px; padding:0'});
addTextarea({style: 'height:60px'});
addTextarea({style: 'width:60px; height:60px'});
addTextarea({style: 'overflow:hidden'});
addTextarea({style: 'overflow:scroll'});
addTextarea({style: 'overflow:hidden; width:60px; height:60px'});
addTextarea({style: 'overflow:scroll; width:60px; height:60px'});
addTextarea({cols: 5, style: 'width:60px; height:60px'});
addTextarea({rows: 4, style: 'width:60px; height:60px'});
addTextarea({cols: 5, rows: 4, style: 'width:60px; height:60px'});
addTextarea({cols: 3});
addTextarea({rows: 3});
addTextarea({cols: 7});
addTextarea({rows: 7});
addTextarea({cols: 5, rows: 4});
addTextarea({wrap: 'off'});
addTextarea({wrap: 'hard'});
addTextarea({wrap: 'soft'});
addTextarea({style: 'white-space:normal'});
addTextarea({style: 'white-space:pre'});
addTextarea({style: 'white-space:prewrap'});
addTextarea({style: 'white-space:nowrap'});
addTextarea({style: 'white-space:pre-line'});
addTextarea({style: 'word-wrap:normal'});
addTextarea({wrap: 'off', style: 'white-space:pre-wrap'});
iframe.style.height = docToAppendTo.body.offsetHeight + 5 + 'px';
}
document.body.style.margin = 0;
var standardsIframe = document.createElement('iframe');
// Reference a page with a doctype so it's standards mode.
standardsIframe.src = 'resources/basic-textareas-standards.html';
standardsIframe.onload = function(e) {
addAllTextareas(e.target, 'CSS1Compat');
}
document.body.appendChild(standardsIframe);
var quirksIframe = document.createElement('iframe');
quirksIframe.onload = function(e) {
addAllTextareas(e.target, 'BackCompat');
}
document.body.appendChild(quirksIframe);
</script>
</body>
</html>
<html>
<body>
<script>
var docToAppendTo;
function addTextarea(properties, opt_innerHTML) {
var title = docToAppendTo.createTextNode();
title.nodeValue = '';
var wrapper = docToAppendTo.createElement('div');
wrapper.style.cssText = 'display:inline-block;border:1px solid blue;font-size:12px;';
var textarea = docToAppendTo.createElement('textarea');
for (property in properties) {
var value = properties[property];
title.nodeValue += property + ': "' + value + '", ';
if (property == 'wrap')
textarea.setAttribute(property, value);
else if (property == 'style')
textarea.style.cssText = value;
else
textarea[property] = value;
}
textarea.innerHTML = opt_innerHTML ||
"Lorem ipsum dolor ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuv";
var span = document.createElement('span');
span.style.cssText = 'display:inline-block;width:80px;';
span.appendChild(title);
wrapper.appendChild(span);
wrapper.appendChild(document.createElement('br'));
wrapper.appendChild(textarea)
docToAppendTo.body.appendChild(wrapper);
}
function addAllTextareas(iframe, compatMode) {
iframe.style.cssText = 'width:100%;border:0;'
docToAppendTo = iframe.contentWindow.document;
docToAppendTo.body.style.cssText = 'margin:0';
if (docToAppendTo.compatMode != compatMode)
testFailed('This document should be in ' + compatMode + ' mode.');
var compatModeTitle = docToAppendTo.createElement('div');
compatModeTitle.innerHTML = 'CompatMode: ' + docToAppendTo.compatMode;
compatModeTitle.style.cssText = 'margin:5px 0;font-weight:bold;';
docToAppendTo.body.appendChild(compatModeTitle);
addTextarea({}, 'Lorem ipsum dolor');
addTextarea({disabled: 'true'});
addTextarea({style: 'padding:10px'});
addTextarea({style: 'padding:0px'});
addTextarea({style: 'margin:10px'});
addTextarea({style: 'margin:0px'});
addTextarea({style: 'width:60px'});
addTextarea({style: 'width:60px; padding:20px'});
addTextarea({style: 'width:60px; padding:0'});
addTextarea({style: 'height:60px'});
addTextarea({style: 'width:60px; height:60px'});
addTextarea({style: 'overflow:hidden'});
addTextarea({style: 'overflow:scroll'});
addTextarea({style: 'overflow:hidden; width:60px; height:60px'});
addTextarea({style: 'overflow:scroll; width:60px; height:60px'});
addTextarea({cols: 5, style: 'width:60px; height:60px'});
addTextarea({rows: 4, style: 'width:60px; height:60px'});
addTextarea({cols: 5, rows: 4, style: 'width:60px; height:60px'});
addTextarea({cols: 3});
addTextarea({rows: 3});
addTextarea({cols: 7});
addTextarea({rows: 7});
addTextarea({cols: 5, rows: 4});
addTextarea({wrap: 'off'});
addTextarea({wrap: 'hard'});
addTextarea({wrap: 'soft'});
addTextarea({style: 'white-space:normal'});
addTextarea({style: 'white-space:pre'});
addTextarea({style: 'white-space:prewrap'});
addTextarea({style: 'white-space:nowrap'});
addTextarea({style: 'white-space:pre-line'});
addTextarea({style: 'word-wrap:normal'});
addTextarea({wrap: 'off', style: 'white-space:pre-wrap'});
iframe.style.height = docToAppendTo.body.offsetHeight + 5 + 'px';
}
document.body.style.margin = 0;
var standardsIframe = document.createElement('iframe');
// Reference a page with a doctype so it's standards mode.
standardsIframe.src = 'resources/basic-textareas-standards.html';
standardsIframe.onload = function(e) {
addAllTextareas(e.target, 'CSS1Compat');
}
document.body.appendChild(standardsIframe);
var quirksIframe = document.createElement('iframe');
quirksIframe.onload = function(e) {
addAllTextareas(e.target, 'BackCompat');
}
document.body.appendChild(quirksIframe);
</script>
</body>
</html>
<html>
<body>
<script>
// Force line box path.
if (window.internals)
internals.settings.setSimpleLineLayoutEnabled(false);
var docToAppendTo;
function addTextarea(properties, opt_innerHTML) {
var title = docToAppendTo.createTextNode();
......
<script>
// Force line box path.
if (window.internals)
internals.settings.setSimpleLineLayoutEnabled(false);
</script>
<style>
div { white-space: pre-wrap; border: 1px solid; padding: 4px; width: 70px; margin: 8px 0; }
</style>
<p>
The following boxes should be identical.
</p>
<div>Lorem....... <span style="direction: rtl; unicode-bidi: bidi-override;">muspi</span>
</div>
<div>Lorem....... ipsum
</div>
<style>
div { white-space: pre-wrap; border: 1px solid; padding: 4px; width: 70px; margin: 8px 0; }
</style>
<p>
The following boxes should be identical.
</p>
<div>Lorem....... <span style="direction: rtl; unicode-bidi: bidi-override;">muspi</span>
</div>
<div>Lorem....... ipsum
</div>
<script>
// Force line box path.
if (window.internals)
internals.settings.setSimpleLineLayoutEnabled(false);
</script>
<style>
div { white-space: pre-wrap; border: 1px solid; padding: 4px; width: 70px; margin: 8px 0; }
</style>
......
<script>
// Force line box path.
if (window.internals)
internals.settings.setSimpleLineLayoutEnabled(false);
</script>
<table><tr><td><div style="white-space:pre-wrap">Three cheers for OldVet and the letter he wrote to Senator Dodd (see above Comment). We all need to be proactive and contact our senators and representatives to let them know our strong feelings on this subject. I would lose what little faith I have left in our government if they engineered a tax payer bailout.
<table><tr><td><div style="white-space:pre-wrap">Three cheers for OldVet and the letter he wrote to Senator Dodd (see above Comment). We all need to be proactive and contact our senators and representatives to let them know our strong feelings on this subject. I would lose what little faith I have left in our government if they engineered a tax payer bailout.
<script>
// Force line box path.
if (window.internals)
internals.settings.setSimpleLineLayoutEnabled(false);
</script>
<table><tr><td><div style="white-space:pre-wrap">Three cheers for OldVet and the letter he wrote to Senator Dodd (see above Comment). We all need to be proactive and contact our senators and representatives to let them know our strong feelings on this subject. I would lose what little faith I have left in our government if they engineered a tax payer bailout.
<script>
// Force line box path.
if (window.internals)
internals.settings.setSimpleLineLayoutEnabled(false);
</script>
<div style="position:relative; white-space:pre-wrap; width:180px; border:1px solid blue">
Lorem ipsum dolor ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuv
</div>
<div style="position:relative; white-space:pre-wrap; width:180px; border:1px solid blue">
Lorem ipsum dolor ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuv
</div>
<script>
// Force line box path.
if (window.internals)
internals.settings.setSimpleLineLayoutEnabled(false);
</script>
<style>
pre { white-space: pre-wrap; background: silver; width: 7ex; }
</style>
<p>
Test for
<i><a href="https://bugs.webkit.org/show_bug.cgi?id=7216">http://bugzilla.opendarwin.org/show_bug.cgi?id=7216</a>
white-space: pre-wrap collapses leading whitespace following a newline</i>.
</p>
<hr>
<p>Here <code>bar</code> should be right under <code>foo</code>:</p>
<pre>foo bar</pre>
<p>Here <code>bar</code> should be on the right hand side of the second line:</p>
<pre>foo<br> bar</pre>
<pre>foo <br> bar</pre>
<pre>foo
bar</pre>
<pre>foo
bar</pre>
<pre>foo
bar</pre>
<pre>foo
bar</pre>
<pre>foo
bar</pre>
<p>Here <code>bar</code> should be on the right hand side of the third line:</p>
<pre>foo<br>
bar</pre>
<pre>foo baz
bar</pre>
<style>
pre { white-space: pre-wrap; background: silver; width: 7ex; }
</style>
<p>
Test for
<i><a href="https://bugs.webkit.org/show_bug.cgi?id=7216">http://bugzilla.opendarwin.org/show_bug.cgi?id=7216</a>
white-space: pre-wrap collapses leading whitespace following a newline</i>.
</p>
<hr>
<p>Here <code>bar</code> should be right under <code>foo</code>:</p>
<pre>foo bar</pre>
<p>Here <code>bar</code> should be on the right hand side of the second line:</p>
<pre>foo<br> bar</pre>
<pre>foo <br> bar</pre>
<pre>foo
bar</pre>
<pre>foo
bar</pre>
<pre>foo
bar</pre>
<pre>foo
bar</pre>
<pre>foo
bar</pre>
<p>Here <code>bar</code> should be on the right hand side of the third line:</p>
<pre>foo<br>
bar</pre>
<pre>foo baz
bar</pre>
<script>
// Force line box path.
if (window.internals)
internals.settings.setSimpleLineLayoutEnabled(false);
</script>
<style>
pre { white-space: pre-wrap; background: silver; width: 7ex; }
</style>
......
(repaint rects
(rect 8 13 784 15)
(rect 8 13 784 15)
(rect 8 413 784 28)
(rect 0 421 800 28)
......
2013-11-10 Antti Koivisto <antti@apple.com>
Implement white-space property on simple line layout path
https://bugs.webkit.org/show_bug.cgi?id=124122
Reviewed by Andreas Kling.
Support all values of the white-space property and the tab-size property.
Tests: fast/forms/basic-textareas-simple-lines.html
fast/text/embed-at-end-of-pre-wrap-line-simple-lines.html
fast/text/whitespace/pre-wrap-line-test-simple-lines.html
fast/text/whitespace/pre-wrap-long-word-simple-lines.html
fast/text/whitespace/pre-wrap-spaces-after-newline-simple-lines.html
* rendering/SimpleLineLayout.cpp:
(WebCore::SimpleLineLayout::canUseFor):
(WebCore::SimpleLineLayout::isWhitespace):
(WebCore::SimpleLineLayout::skipWhitespaces):
(WebCore::SimpleLineLayout::textWidth):
(WebCore::SimpleLineLayout::measureWord):
(WebCore::SimpleLineLayout::createTextRuns):
* rendering/SimpleLineLayoutFunctions.cpp:
(WebCore::SimpleLineLayout::paintDebugBorders):
(WebCore::SimpleLineLayout::paintFlow):
2013-11-10 Sergio Correia <sergio.correia@openbossa.org>
 
Fix EFL build after r159027
......
......@@ -130,9 +130,6 @@ bool canUseFor(const RenderBlockFlow& flow)
// Non-visible overflow should be pretty easy to support.
if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE)
return false;
// Pre/no-wrap would be very helpful to support.
if (style.whiteSpace() != NORMAL)
return false;
if (!style.textIndent().isZero())
return false;
if (style.wordSpacing() || style.letterSpacing())
......@@ -197,33 +194,49 @@ bool canUseFor(const RenderBlockFlow& flow)
return true;
}
static inline bool isWhitespace(UChar character)
static inline bool isWhitespace(UChar character, bool preserveNewline)
{
return character == ' ' || character == '\t' || character == '\n';
return character == ' ' || character == '\t' || (!preserveNewline && character == '\n');
}
template <typename CharacterType>
static inline unsigned skipWhitespaces(const CharacterType* text, unsigned offset, unsigned length)
static inline unsigned skipWhitespaces(const CharacterType* text, unsigned offset, unsigned length, bool preserveNewline)
{
for (; offset < length; ++offset) {
if (!isWhitespace(text[offset]))
if (!isWhitespace(text[offset], preserveNewline))
return offset;
}
return length;
}
template <typename CharacterType>
static float textWidth(const RenderText& renderText, const CharacterType* text, unsigned textLength, unsigned from, unsigned to, float xPosition, const RenderStyle& style)
static float textWidth(const RenderText& renderText, const CharacterType* text, unsigned textLength, unsigned from, unsigned to, float xPosition, const Font& font, float tabWidth)
{
if (style.font().isFixedPitch() || (!from && to == textLength))
return renderText.width(from, to - from, style.font(), xPosition, nullptr, nullptr);
// FIXME: Add templated UChar/LChar paths.
if (font.isFixedPitch() || (!from && to == textLength))
return renderText.width(from, to - from, font, xPosition, nullptr, nullptr);
TextRun run(text + from, to - from);
run.setXPos(xPosition);
run.setCharactersLength(textLength - from);
run.setTabSize(!!tabWidth, tabWidth);
ASSERT(run.charactersLength() >= run.length());
return style.font().width(run);
return font.width(run);
}
template <typename CharacterType>
static float measureWord(const RenderText& textRenderer, const CharacterType* text, unsigned textLength, unsigned start, unsigned end, float lineWidth, bool collapseWhitespace, const Font& font, float tabWidth, float spaceWidth)
{
if (text[start] == ' ' && end == start + 1)
return spaceWidth;
bool measureWithEndSpace = collapseWhitespace && end < textLength && text[end] == ' ';
if (measureWithEndSpace)
++end;
float width = textWidth(textRenderer, text, textLength, start, end, lineWidth, font, collapseWhitespace ? 0 : tabWidth);
return measureWithEndSpace ? width - spaceWidth : width;
}
static float computeLineLeft(ETextAlign textAlign, float remainingWidth)
......@@ -261,17 +274,24 @@ void createTextRuns(Layout::RunVector& runs, unsigned& lineCount, RenderBlockFlo
{
const RenderStyle& style = flow.style();
ETextAlign textAlign = style.textAlign();
float wordTrailingSpaceWidth = style.font().width(TextRun(&space, 1));
// These properties are supported.
const Font& font = style.font();
unsigned tabWidth = style.tabSize();
ETextAlign textAlign = style.textAlign(); // Not 'justify'.
bool collapseWhitespace = style.collapseWhiteSpace();
bool preserveNewline = style.preserveNewline();
bool wrapLines = style.autoWrap();
const CharacterType* text = textRenderer.text()->getCharacters<CharacterType>();
const unsigned textLength = textRenderer.textLength();
float spaceWidth = font.width(TextRun(&space, 1));
LazyLineBreakIterator lineBreakIterator(textRenderer.text(), style.locale());
unsigned lineEnd = 0;
while (lineEnd < textLength) {
lineEnd = skipWhitespaces(text, lineEnd, textLength);
if (collapseWhitespace)
lineEnd = skipWhitespaces(text, lineEnd, textLength, preserveNewline);
unsigned lineStart = lineEnd;
unsigned wordEnd = lineEnd;
LineWidth lineWidth(flow, false, DoNotIndentText);
......@@ -280,33 +300,63 @@ void createTextRuns(Layout::RunVector& runs, unsigned& lineCount, RenderBlockFlo
lineRuns.uncheckedAppend(Run(lineStart, 0));
while (wordEnd < textLength) {
ASSERT(!isWhitespace(text[wordEnd]));
bool wordIsPrecededByWhitespace = wordEnd > lineStart && isWhitespace(text[wordEnd - 1]);
unsigned wordStart = wordIsPrecededByWhitespace ? wordEnd - 1 : wordEnd;
wordEnd = nextBreakablePosition<CharacterType, false>(lineBreakIterator, text, textLength, wordEnd + 1);
ASSERT(!collapseWhitespace || !isWhitespace(text[wordEnd], preserveNewline));
unsigned wordStart = wordEnd;
if (preserveNewline && text[wordStart] == '\n') {
++wordEnd;
// FIXME: This creates a dedicated run for newline. This is wasteful and unnecessary but it keeps test results unchanged.
if (wordStart > lineStart)
lineRuns.append(Run(lineEnd, lineRuns.last().right));
lineRuns.last().right = lineRuns.last().left;
lineRuns.last().textLength = 1;
lineEnd = wordEnd;
break;
}
bool measureWithEndSpace = wordEnd < textLength && text[wordEnd] == ' ';
unsigned wordMeasureEnd = measureWithEndSpace ? wordEnd + 1 : wordEnd;
if (!collapseWhitespace && isWhitespace(text[wordStart], preserveNewline))
wordEnd = wordStart + 1;
else
wordEnd = nextBreakablePosition<CharacterType, false>(lineBreakIterator, text, textLength, wordStart + 1);
float wordWidth = textWidth(textRenderer, text, textLength, wordStart, wordMeasureEnd, lineWidth.committedWidth(), style);
bool wordIsPrecededByWhitespace = collapseWhitespace && wordStart > lineStart && isWhitespace(text[wordStart - 1], preserveNewline);
if (wordIsPrecededByWhitespace)
--wordStart;
if (measureWithEndSpace)