Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[web] Position spans absolutely within paragraph #31907

Merged
merged 6 commits into from
Mar 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 20 additions & 50 deletions lib/web_ui/lib/src/engine/text/canvas_paragraph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,85 +157,44 @@ class CanvasParagraph implements ui.Paragraph {
cssStyle
..position = 'absolute'
// Prevent the browser from doing any line breaks in the paragraph. We want
// to insert our own <BR> breaks based on layout results.
// to have full control of the paragraph layout.
..whiteSpace = 'pre';

if (width > longestLine) {
// In this case, we set the width so that the CSS text-align property
// works correctly.
// When `longestLine` is >= `paragraph.width` that means the DOM element
// will automatically size itself to fit the longest line, so there's no
// need to set an explicit width.
cssStyle.width = '${width}px';
}

if (paragraphStyle.maxLines != null || paragraphStyle.ellipsis != null) {
cssStyle
..overflowY = 'hidden'
..height = '${height}px';
}

// 2. Append all spans to the paragraph.

FlatTextSpan? span;

html.HtmlElement element = rootElement;
html.HtmlElement? lastSpanElement;
final List<EngineLineMetrics> lines = computeLineMetrics();

for (int i = 0; i < lines.length; i++) {
// Insert a <BR> element before each line except the first line.
if (i > 0) {
element.append(html.document.createElement('br'));
}

final EngineLineMetrics line = lines[i];
final List<RangeBox> boxes = line.boxes;
final StringBuffer buffer = StringBuffer();

int j = 0;
while (j < boxes.length) {
final RangeBox box = boxes[j++];
if (box is SpanBox && box.span == span) {
buffer.write(box.toText());
continue;
}

if (buffer.isNotEmpty) {
element.appendText(buffer.toString());
buffer.clear();
}

if (box is SpanBox) {
span = box.span;
element = html.document.createElement('span') as html.HtmlElement;
lastSpanElement = html.document.createElement('span') as html.HtmlElement;
Copy link
Contributor

@yjbanov yjbanov Mar 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not html.SpanElement()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason. Is one different from the other?

I'll keep it as html.document.createElement('span') for now so I can easily change it to flt-span next.

applyTextStyleToElement(
element: element,
element: lastSpanElement,
style: box.span.style,
isSpan: true,
);
rootElement.append(element);
_positionSpanElement(lastSpanElement, line, box);
lastSpanElement.appendText(box.toText());
rootElement.append(lastSpanElement);
buffer.write(box.toText());
} else if (box is PlaceholderBox) {
span = null;
// If there's a line-end after this placeholder, we want the <BR> to
// be inserted in the root paragraph element.
element = rootElement;
rootElement.append(
createPlaceholderElement(placeholder: box.placeholder),
);
lastSpanElement = null;
} else {
throw UnimplementedError('Unknown box type: ${box.runtimeType}');
}
}

if (buffer.isNotEmpty) {
element.appendText(buffer.toString());
buffer.clear();
}

final String? ellipsis = line.ellipsis;
if (ellipsis != null) {
element.appendText(ellipsis);
(lastSpanElement ?? rootElement).appendText(ellipsis);
}
}

Expand Down Expand Up @@ -307,6 +266,9 @@ void _applyNecessaryParagraphStyles({
}) {
final html.CssStyleDeclaration cssStyle = element.style;

// TODO(mdebbar): Now that we absolutely position each span inside the
// paragraph, do we still need these style on <p>?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we cascade the styles down to span level? In Flutter, paragraph styles and parent text styles are meant to be "inherited" to descendants.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, the classes RootStyleNode and ChildStyleNode handle the inheritance of styles.


if (style.textAlign != null) {
cssStyle.textAlign = textAlignToCssValue(
style.textAlign, style.textDirection ?? ui.TextDirection.ltr);
Expand Down Expand Up @@ -352,6 +314,14 @@ void _applySpanStylesToParagraph({
}
}

void _positionSpanElement(html.Element element, EngineLineMetrics line, RangeBox box) {
final ui.TextBox textBox = box.toTextBox(line);
element.style
..position = 'absolute'
..top = '${textBox.top}px'
..left = '${textBox.left}px';
}

/// A common interface for all types of spans that make up a paragraph.
///
/// These spans are stored as a flat list in the paragraph object.
Expand Down
4 changes: 2 additions & 2 deletions lib/web_ui/test/html/paragraph/general_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ Future<void> testMain() async {
});

void testFontFeatures(EngineCanvas canvas) {
const String text = 'Aa Bb Dd Ee Ff Difficult';
const String text = 'Bb Difficult ';
const FontFeature enableSmallCaps = FontFeature('smcp');
const FontFeature disableSmallCaps = FontFeature('smcp', 0);

Expand Down Expand Up @@ -446,7 +446,7 @@ Future<void> testMain() async {
enableOnum,
],
));
builder.addText('$text - $numeric');
builder.addText('$text $numeric');
builder.pop(); // enableSmallCaps, enableOnum
},
)..layout(constrain(double.infinity));
Expand Down
Loading