Skip to content

Commit 00bf848

Browse files
committed
vaev-layout: Better handling of whitespaces when building a prose.
1 parent 1dbaa39 commit 00bf848

File tree

1 file changed

+61
-32
lines changed

1 file changed

+61
-32
lines changed

src/web/vaev-layout/builder.cpp

+61-32
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ static Rc<Karm::Text::Fontface> _lookupFontface(Text::FontBook& fontBook, Style:
8787
return Text::Fontface::fallback();
8888
}
8989

90-
auto RE_SEGMENT_BREAK = Re::single('\n', '\r', '\f', '\v');
90+
bool isSegmentBreak(Rune rune) {
91+
return rune == '\n' or rune == '\r' or rune == '\f' or rune == '\v';
92+
}
9193

9294
Text::ProseStyle _buildProseStyle(Style::Computer& c, Rc<Style::Computed> parentStyle) {
9395
auto fontFace = _lookupFontface(c.fontBook, *parentStyle);
@@ -129,48 +131,75 @@ Text::ProseStyle _buildProseStyle(Style::Computer& c, Rc<Style::Computed> parent
129131
return proseStyle;
130132
}
131133

134+
void _transformAndAppendRuneToProse(Rc<Text::Prose> prose, Rune rune, TextTransform transform) {
135+
switch (transform) {
136+
case TextTransform::UPPERCASE:
137+
prose->append(toAsciiUpper(rune));
138+
break;
139+
140+
case TextTransform::LOWERCASE:
141+
prose->append(toAsciiLower(rune));
142+
break;
143+
144+
case TextTransform::NONE:
145+
default:
146+
prose->append(rune);
147+
break;
148+
}
149+
}
150+
151+
// https://www.w3.org/TR/css-text-3/#white-space-phase-1
152+
// https://www.w3.org/TR/css-text-3/#white-space-phase-2
132153
void _appendTextToInlineBox(Io::SScan scan, Rc<Style::Computed> parentStyle, Rc<Text::Prose> prose) {
133154
auto whitespace = parentStyle->text->whiteSpace;
134-
while (not scan.ended()) {
135-
switch (parentStyle->text->transform) {
136-
case TextTransform::UPPERCASE:
137-
prose->append(toAsciiUpper(scan.next()));
138-
break;
155+
bool whiteSpacesAreCollapsible =
156+
whitespace == WhiteSpace::NORMAL or
157+
whitespace == WhiteSpace::NOWRAP or
158+
whitespace == WhiteSpace::PRE_LINE;
139159

140-
case TextTransform::LOWERCASE:
141-
prose->append(toAsciiLower(scan.next()));
142-
break;
160+
// A sequence of collapsible spaces at the beginning of a line is removed.
161+
if (not prose->_runes.len())
162+
scan.eat(Re::space());
143163

144-
case TextTransform::NONE:
145-
prose->append(scan.next());
146-
break;
164+
while (not scan.ended()) {
165+
auto rune = scan.next();
147166

148-
default:
149-
break;
167+
if (not isAsciiSpace(rune)) {
168+
_transformAndAppendRuneToProse(prose, rune, parentStyle->text->transform);
169+
continue;
150170
}
151171

152-
if (whitespace == WhiteSpace::PRE) {
153-
auto tok = scan.token(Re::space());
154-
if (tok)
155-
prose->append(tok);
156-
} else if (whitespace == WhiteSpace::PRE_LINE) {
157-
bool hasBlank = false;
158-
if (scan.eat(Re::blank())) {
159-
hasBlank = true;
160-
}
172+
// https://www.w3.org/TR/css-text-3/#collapse
173+
if (whiteSpacesAreCollapsible) {
174+
// Any sequence of collapsible spaces and tabs immediately preceding or following a segment break is removed.
175+
bool visitedSegmentBreak = false;
176+
while (true) {
177+
if (isSegmentBreak(rune))
178+
visitedSegmentBreak = true;
161179

162-
if (scan.eat(RE_SEGMENT_BREAK)) {
163-
prose->append('\n');
164-
scan.eat(Re::blank());
165-
hasBlank = false;
180+
if (scan.ended() or not isAsciiSpace(scan.peek()))
181+
break;
182+
183+
rune = scan.next();
166184
}
167185

168-
if (hasBlank)
186+
// Any collapsible space immediately following another collapsible space—​even one outside the boundary
187+
// of the inline containing that space, provided both spaces are within the same inline formatting
188+
// context—​is collapsed to have zero advance width. (It is invisible, but retains its soft wrap
189+
// opportunity, if any.)
190+
// TODO: not compliant regarding wrap opportunity
191+
192+
// https://www.w3.org/TR/css-text-3/#valdef-white-space-pre-line
193+
// Collapsible segment breaks are transformed for rendering according to the segment
194+
// break transformation rules.
195+
if (whitespace == WhiteSpace::PRE_LINE and visitedSegmentBreak)
196+
prose->append('\n');
197+
else
169198
prose->append(' ');
199+
} else if (whitespace == WhiteSpace::PRE) {
200+
prose->append(rune);
170201
} else {
171-
// NORMAL
172-
if (scan.eat(Re::space()))
173-
prose->append(' ');
202+
panic("unimplemented whitespace case");
174203
}
175204
}
176205
}
@@ -181,7 +210,7 @@ void _buildText(Gc::Ref<Dom::Text> node, Rc<Style::Computed> parentStyle, Inline
181210
if (scan.ended())
182211
return;
183212

184-
_appendTextToInlineBox(scan, parentStyle, rootInlineBox.prose);
213+
_appendTextToInlineBox(node->data(), parentStyle, rootInlineBox.prose);
185214
}
186215

187216
// https://www.w3.org/TR/css-inline-3/#model

0 commit comments

Comments
 (0)