Skip to content

[WebParagraph] Support for more styles, placeholders, decorations, etc #172853

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

Draft
wants to merge 86 commits into
base: master
Choose a base branch
from

Conversation

Rusino
Copy link
Contributor

@Rusino Rusino commented Jul 28, 2025

This is the second version of WebParagraph. It includes pretty much all SkParagraph functionality except struts and justifications (eventually they will be implemented, too).

Part of #172561

Rusino and others added 30 commits March 17, 2025 11:41
Moving the test file
Test still not working
Drawing paragraph on Canvas2D (not working)
Drawing paragraph on Canvas2D (not working)
Drawing paragraph on Canvas2D (not working)
on DrawImage
No bidi, no hard line breaks
(Required flattening the style ranges)
(it breaks but somewhere down the road)
@Rusino Rusino requested a review from mdebbar July 28, 2025 15:37
@github-actions github-actions bot added a: text input Entering text in a text field or keyboard related problems engine flutter/engine repository. See also e: labels. platform-web Web applications specifically labels Jul 28, 2025
@Rusino Rusino self-assigned this Jul 30, 2025
@mdebbar mdebbar changed the title WebParagraph as another implementation of SkParagraph API based on extended browser TextCluster API. [WebParagraph] Support for more styles, placeholders, decorations, etc Aug 8, 2025
@mdebbar
Copy link
Contributor

mdebbar commented Aug 13, 2025

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is a significant refactoring that adds a lot of functionality to WebParagraph, bringing it closer to the SkParagraph implementation. The code is well-structured, separating layout, painting, and data models. The new implementation seems much more robust and feature-complete.

I've found a couple of potential issues regarding an unsafe method and a fixed-size canvas that could lead to bugs. Additionally, many new public APIs are missing documentation, which is required by the Flutter style guide. I've added a few comments highlighting examples of where documentation should be added.

Comment on lines +251 to +255
ClusterRange convertTextToClusterRange(TextRange textRange) {
final int clusterStart = textToClusterMap[textRange.start]!;
final int clusterEnd = textToClusterMap[textRange.end - 1]!;
return ClusterRange(start: clusterStart, end: clusterEnd + 1);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This method can throw an exception if textRange is empty. For an empty range, textRange.start == textRange.end. If textRange.end is N, then textRange.end - 1 will be N - 1, which could be a valid index for a different part of the text, or an invalid index if N is 0. This can lead to incorrect behavior or exceptions.

While call sites currently seem to check for empty ranges, it would be safer to handle this case within the method itself to make it more robust.

  ClusterRange convertTextToClusterRange(TextRange textRange) {
    if (textRange.isEmpty) {
      final int clusterIndex = textToClusterMap[textRange.start]!;
      return ClusterRange(start: clusterIndex, end: clusterIndex);
    }
    final int clusterStart = textToClusterMap[textRange.start]!;
    final int clusterEnd = textToClusterMap[textRange.end - 1]!;
    return ClusterRange(start: clusterStart, end: clusterEnd + 1);
  }

Comment on lines +37 to +38
final DomOffscreenCanvas _paintCanvas = createDomOffscreenCanvas(500, 500);
final paintContext = _paintCanvas.getContext('2d')! as DomCanvasRenderingContext2D;
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The offscreen canvas _paintCanvas is created with a fixed size of 500x500. If a text block or decoration is larger than this, it will be clipped when rendered on this canvas. This could lead to visual bugs for large text or long lines.

The canvas should be dynamically resized to fit the content being drawn onto it before each use.

Comment on lines +17 to +35
abstract class Painter {
Painter();

void fillTextCluster(ExtendedTextCluster webTextCluster, bool isDefaultLtr);
void paintTextCluster(ui.Canvas canvas, ui.Rect sourceRect, ui.Rect targetRect);

void fillShadow(ExtendedTextCluster webTextCluster, ui.Shadow shadow, bool isDefaultLtr);
void paintShadow(ui.Canvas canvas, ui.Rect sourceRect, ui.Rect targetRect);

void paintBackground(
ui.Canvas canvas,
LineClusterBlock block,
ui.Rect sourceRect,
ui.Rect targetRect,
);

void fillDecorations(LineClusterBlock block, ui.Rect sourceRect);
void paintDecorations(ui.Canvas canvas, ui.Rect sourceRect, ui.Rect targetRect);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The Painter abstract class and its methods are public but lack documentation. According to the Flutter Style Guide, all public members should be documented.1

Style Guide References

Footnotes

  1. All public members should have documentation. (link)

@@ -76,21 +104,83 @@ class WebParagraphStyle implements ui.ParagraphStyle {
}
}

enum StyleElements { background, shadows, decorations, text }
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This public enum StyleElements should have documentation explaining what each element represents.1

Style Guide References

Footnotes

  1. All public members should have documentation. (link)

Comment on lines +488 to +521
class TextRange extends _RangeStartEnd {
TextRange({required int start, required int end}) : super(start, end);

TextRange.collapsed(super.offset) : super.collapsed();

TextRange.zero() : super.zero();

TextRange clone() {
return TextRange(start: start, end: end);
}

@override
// No need to override hashCode, since _RangeStartEnd already does it.
// ignore: hash_and_equals
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
return other is TextRange && super == other;
}

@override
String toString() {
return '[$start:$end)';
}

TextRange translate(int offset) {
return TextRange(start: start + offset, end: end + offset);
}

static TextRange intersectTextRange(TextRange a, TextRange b) {
return TextRange(start: math.max(a.start, b.start), end: math.min(a.end, b.end));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The new public class TextRange and its methods should have documentation explaining their purpose, especially given the TODO comment above it.1

Style Guide References

Footnotes

  1. All public members should have documentation. (link)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: text input Entering text in a text field or keyboard related problems engine flutter/engine repository. See also e: labels. platform-web Web applications specifically
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants