diff --git a/packages/flutter/lib/src/painting/inline_span.dart b/packages/flutter/lib/src/painting/inline_span.dart index 0c4a0be6177..77baa589699 100644 --- a/packages/flutter/lib/src/painting/inline_span.dart +++ b/packages/flutter/lib/src/painting/inline_span.dart @@ -315,6 +315,21 @@ abstract class InlineSpan extends DiagnosticableTree { return buffer.toString(); } + (String, Map) toPlainTextV2({ + bool includeSemanticsLabels = true, + bool includePlaceholders = true, + }) { + final buffer = StringBuffer(); + final map = {}; + computeToPlainTextV2( + buffer, + map, + includeSemanticsLabels: includeSemanticsLabels, + includePlaceholders: includePlaceholders, + ); + return (buffer.toString(), map); + } + /// Flattens the [InlineSpan] tree to a list of /// [InlineSpanSemanticsInformation] objects. /// @@ -358,6 +373,14 @@ abstract class InlineSpan extends DiagnosticableTree { bool includePlaceholders = true, }); + @protected + void computeToPlainTextV2( + StringBuffer buffer, + Map map, { + bool includeSemanticsLabels = true, + bool includePlaceholders = true, + }); + /// Returns the UTF-16 code unit at the given `index` in the flattened string. /// /// This only accounts for the [TextSpan.text] values and ignores [PlaceholderSpan]s. diff --git a/packages/flutter/lib/src/painting/placeholder_span.dart b/packages/flutter/lib/src/painting/placeholder_span.dart index 110ff860310..7ad808d05bc 100644 --- a/packages/flutter/lib/src/painting/placeholder_span.dart +++ b/packages/flutter/lib/src/painting/placeholder_span.dart @@ -45,6 +45,7 @@ abstract class PlaceholderSpan extends InlineSpan { this.alignment = ui.PlaceholderAlignment.bottom, this.baseline, super.style, + this.rawText, }); /// The unicode character to represent a placeholder. @@ -61,6 +62,8 @@ abstract class PlaceholderSpan extends InlineSpan { /// This is ignored when using other alignment modes. final TextBaseline? baseline; + final String? rawText; + /// [PlaceholderSpan]s are flattened to a `0xFFFC` object replacement character in the /// plain text representation when `includePlaceholders` is true. @override @@ -74,6 +77,21 @@ abstract class PlaceholderSpan extends InlineSpan { } } + @override + void computeToPlainTextV2( + StringBuffer buffer, + Map map, { + bool includeSemanticsLabels = true, + bool includePlaceholders = true, + }) { + if (includePlaceholders) { + if (rawText != null) { + map[buffer.length] = rawText!; + } + buffer.writeCharCode(placeholderCodeUnit); + } + } + @override void computeSemanticsInformation(List collector) { collector.add(InlineSpanSemanticsInformation.placeholder); diff --git a/packages/flutter/lib/src/painting/text_span.dart b/packages/flutter/lib/src/painting/text_span.dart index 09fba7fc4fb..57ee469358e 100644 --- a/packages/flutter/lib/src/painting/text_span.dart +++ b/packages/flutter/lib/src/painting/text_span.dart @@ -398,6 +398,31 @@ class TextSpan extends InlineSpan implements HitTestTarget, MouseTrackerAnnotati } } + @override + void computeToPlainTextV2( + StringBuffer buffer, + Map map, { + bool includeSemanticsLabels = true, + bool includePlaceholders = true, + }) { + assert(debugAssertIsValid()); + if (semanticsLabel != null && includeSemanticsLabels) { + buffer.write(semanticsLabel); + } else if (text != null) { + buffer.write(text); + } + if (children != null) { + for (final InlineSpan child in children!) { + child.computeToPlainTextV2( + buffer, + map, + includeSemanticsLabels: includeSemanticsLabels, + includePlaceholders: includePlaceholders, + ); + } + } + } + @override void computeSemanticsInformation( List collector, { diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index 97a41dafb8d..266dd6f0283 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -502,27 +502,17 @@ class RenderParagraph extends RenderBox } List<_SelectableFragment> _getSelectableFragments() { - final String plainText = text.toPlainText(includeSemanticsLabels: false); - final result = <_SelectableFragment>[]; - var start = 0; - while (start < plainText.length) { - int end = plainText.indexOf(_placeholderCharacter, start); - if (start != end) { - if (end == -1) { - end = plainText.length; - } - result.add( - _SelectableFragment( - paragraph: this, - range: TextRange(start: start, end: end), - fullText: plainText, - ), - ); - start = end; - } - start += 1; - } - return result; + final (String plainText, Map placeHolder) = text.toPlainTextV2( + includeSemanticsLabels: false, + ); + return [ + _SelectableFragment( + paragraph: this, + range: TextRange(start: 0, end: plainText.length), + fullText: plainText, + placeholder: placeHolder, + ), + ]; } /// Determines whether the given [Selectable] was created by this @@ -1467,7 +1457,7 @@ class RenderParagraph extends RenderBox class _SelectableFragment with Selectable, Diagnosticable, ChangeNotifier implements TextLayoutMetrics { - _SelectableFragment({required this.paragraph, required this.fullText, required this.range}) + _SelectableFragment({required this.paragraph, required this.fullText, required this.range, required this.placeholder}) : assert(range.isValid && !range.isCollapsed && range.isNormalized) { if (kFlutterMemoryAllocationsEnabled) { ChangeNotifier.maybeDispatchObjectCreation(this); @@ -1478,6 +1468,7 @@ class _SelectableFragment final TextRange range; final RenderParagraph paragraph; final String fullText; + final Map placeholder; TextPosition? _textSelectionStart; TextPosition? _textSelectionEnd; @@ -1509,18 +1500,36 @@ class _SelectableFragment final int selectionStart = _textSelectionStart!.offset; final int selectionEnd = _textSelectionEnd!.offset; final bool isReversed = selectionStart > selectionEnd; - final Offset startOffsetInParagraphCoordinates = paragraph._getOffsetForPosition( - TextPosition(offset: selectionStart), - ); - final Offset endOffsetInParagraphCoordinates = selectionStart == selectionEnd - ? startOffsetInParagraphCoordinates - : paragraph._getOffsetForPosition(TextPosition(offset: selectionEnd)); + final flipHandles = isReversed != (TextDirection.rtl == paragraph.textDirection); final selection = TextSelection(baseOffset: selectionStart, extentOffset: selectionEnd); + final List boxes = paragraph.getBoxesForSelection(selection, boxHeightStyle: .max); final selectionRects = []; - for (final TextBox textBox in paragraph.getBoxesForSelection(selection)) { + for (final textBox in boxes) { selectionRects.add(textBox.toRect()); } + + final Offset startOffsetInParagraphCoordinates; + final Offset endOffsetInParagraphCoordinates; + if (boxes.isEmpty) { + final Offset offset = + paragraph._textPainter.getOffsetForCaret(selection.extent, Rect.zero) + + Offset(0, paragraph._textPainter.getFullHeightForCaret(selection.extent, Rect.zero)); + startOffsetInParagraphCoordinates = endOffsetInParagraphCoordinates = Offset( + clampDouble(offset.dx, 0, paragraph._textPainter.width), + clampDouble(offset.dy, 0, paragraph._textPainter.height), + ); + } else { + startOffsetInParagraphCoordinates = Offset( + clampDouble(boxes.first.start, 0, paragraph._textPainter.width), + clampDouble(boxes.first.bottom, 0, paragraph._textPainter.height), + ); + endOffsetInParagraphCoordinates = Offset( + clampDouble(boxes.last.end, 0, paragraph._textPainter.width), + clampDouble(boxes.last.bottom, 0, paragraph._textPainter.height), + ); + } + final selectionCollapsed = selectionStart == selectionEnd; final ( TextSelectionHandleType startSelectionHandleType, @@ -1564,12 +1573,14 @@ class _SelectableFragment result = _updateSelectionEdge( edgeUpdate.globalPosition, isEnd: edgeUpdate.type == SelectionEventType.endEdgeUpdate, + isSingle: edgeUpdate.isSingle, ); case TextGranularity.word: result = _updateSelectionEdgeByTextBoundary( edgeUpdate.globalPosition, isEnd: edgeUpdate.type == SelectionEventType.endEdgeUpdate, getTextBoundary: _getWordBoundaryAtPosition, + isSingle: edgeUpdate.isSingle, ); case TextGranularity.paragraph: result = _updateSelectionEdgeByMultiSelectableTextBoundary( @@ -1626,9 +1637,33 @@ class _SelectableFragment if (_textSelectionStart == null || _textSelectionEnd == null) { return null; } - final int start = math.min(_textSelectionStart!.offset, _textSelectionEnd!.offset); + int start = math.min(_textSelectionStart!.offset, _textSelectionEnd!.offset); final int end = math.max(_textSelectionStart!.offset, _textSelectionEnd!.offset); - return SelectedContent(plainText: fullText.substring(start, end)); + final String selectedText; + if (placeholder.isEmpty) { + selectedText = fullText.substring(start, end); + } else { + final buffer = StringBuffer(); + for (final e in placeholder.entries) { + final i = e.key; + if (i < start) { + continue; + } + if (i >= end) { + break; + } + if (i != start) { + buffer.write(fullText.substring(start, i)); + } + buffer.write(e.value); + start = i + 1; + } + if (start != end) { + buffer.write(fullText.substring(start, end)); + } + selectedText = buffer.toString(); + } + return SelectedContent(plainText: selectedText); } @override @@ -1848,6 +1883,7 @@ class _SelectableFragment Offset globalPosition, { required bool isEnd, required _TextBoundaryAtPosition getTextBoundary, + bool isSingle = false, }) { // When the start/end edges are swapped, i.e. the start is after the end, and // the scrollable synthesizes an event for the opposite edge, this will potentially @@ -1866,6 +1902,7 @@ class _SelectableFragment _rect, localPosition, direction: paragraph.textDirection, + isSingle: isSingle, ); final TextPosition position = paragraph.getPositionForOffset(adjustedOffset); @@ -1920,7 +1957,7 @@ class _SelectableFragment return SelectionUtils.getResultBasedOnRect(_rect, localPosition); } - SelectionResult _updateSelectionEdge(Offset globalPosition, {required bool isEnd}) { + SelectionResult _updateSelectionEdge(Offset globalPosition, {required bool isEnd, bool isSingle = false}) { _setSelectionPosition(null, isEnd: isEnd); final Matrix4 transform = paragraph.getTransformTo(null); transform.invert(); @@ -1932,11 +1969,21 @@ class _SelectableFragment _rect, localPosition, direction: paragraph.textDirection, + isSingle: isSingle, ); final TextPosition position = _clampTextPosition( paragraph.getPositionForOffset(adjustedOffset), ); + // if (rawText != null) { + // position = _clampTextPosition( + // adjustedOffset.dx * 2 > _rect.left + _rect.right + // ? TextPosition(offset: range.end) + // : TextPosition(offset: range.start), + // ); + // } else { + // position = _clampTextPosition(paragraph.getPositionForOffset(adjustedOffset)); + // } _setSelectionPosition(position, isEnd: isEnd); if (position.offset == range.end) { return SelectionResult.next; @@ -3568,7 +3615,10 @@ class _SelectableFragment final selectionPaint = Paint() ..style = PaintingStyle.fill ..color = paragraph.selectionColor!; - for (final TextBox textBox in paragraph.getBoxesForSelection(selection)) { + for (final TextBox textBox in paragraph.getBoxesForSelection( + selection, + boxHeightStyle: ui.BoxHeightStyle.max, + )) { context.canvas.drawRect(textBox.toRect().shift(offset), selectionPaint); } } diff --git a/packages/flutter/lib/src/rendering/selection.dart b/packages/flutter/lib/src/rendering/selection.dart index a813e141dc4..e5ef2103645 100644 --- a/packages/flutter/lib/src/rendering/selection.dart +++ b/packages/flutter/lib/src/rendering/selection.dart @@ -6,6 +6,8 @@ /// @docImport 'package:flutter/material.dart'; library; +import 'dart:math' as math; + import 'package:flutter/foundation.dart'; import 'package:vector_math/vector_math_64.dart'; @@ -114,6 +116,8 @@ abstract class SelectionHandler implements ValueListenable { /// The length of the content in this object. int get contentLength; + + Object? get separator; } /// This class stores the range information of the selection under a [Selectable] @@ -236,6 +240,8 @@ class SelectedContent with Diagnosticable { /// See also: /// * [SelectableRegion], which provides an overview of selection system. mixin Selectable implements SelectionHandler { + Object? get separator => null; + /// {@macro flutter.rendering.RenderObject.getTransformTo} Matrix4 getTransformTo(RenderObject? ancestor); @@ -356,10 +362,19 @@ abstract final class SelectionUtils { Rect targetRect, Offset point, { TextDirection direction = TextDirection.ltr, + bool isSingle = false, }) { if (targetRect.contains(point)) { return point; } + + if (isSingle || (point.dy >= targetRect.top && point.dy <= targetRect.bottom)) { + return Offset( + math.min(point.dx, targetRect.right), + math.min(point.dy, targetRect.bottom - .1), + ); + } + if (point.dy <= targetRect.top || point.dy <= targetRect.bottom && point.dx <= targetRect.left) { // Area 1 @@ -520,7 +535,7 @@ class SelectionEdgeUpdateEvent extends SelectionEvent { /// /// The [granularity] contains the granularity which the selection edge should move by. /// This value defaults to [TextGranularity.character]. - const SelectionEdgeUpdateEvent.forStart({ + SelectionEdgeUpdateEvent.forStart({ required this.globalPosition, TextGranularity? granularity, }) : granularity = granularity ?? TextGranularity.character, @@ -532,7 +547,7 @@ class SelectionEdgeUpdateEvent extends SelectionEvent { /// /// The [granularity] contains the granularity which the selection edge should move by. /// This value defaults to [TextGranularity.character]. - const SelectionEdgeUpdateEvent.forEnd({ + SelectionEdgeUpdateEvent.forEnd({ required this.globalPosition, TextGranularity? granularity, }) : granularity = granularity ?? TextGranularity.character, @@ -547,6 +562,8 @@ class SelectionEdgeUpdateEvent extends SelectionEvent { /// /// Defaults to [TextGranularity.character]. final TextGranularity granularity; + + bool isSingle = false; } /// Extends the start or end of the selection by a given [TextGranularity]. diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 9df2d633f16..54d30883245 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -1177,6 +1177,9 @@ class _ScrollableSelectionContainerDelegate extends MultiSelectableSelectionCont _position.addListener(_scheduleLayoutChange); } + @override + Object separator = '\n'; + // Pointer drag is a single point, it should not have a size. static const double _kDefaultDragTargetSize = 0; diff --git a/packages/flutter/lib/src/widgets/selectable_region.dart b/packages/flutter/lib/src/widgets/selectable_region.dart index 59de8bae20b..9ce4810aa68 100644 --- a/packages/flutter/lib/src/widgets/selectable_region.dart +++ b/packages/flutter/lib/src/widgets/selectable_region.dart @@ -401,9 +401,11 @@ class SelectableRegionState extends State final LayerLink _startHandleLayerLink = LayerLink(); final LayerLink _endHandleLayerLink = LayerLink(); final LayerLink _toolbarLayerLink = LayerLink(); - final StaticSelectionContainerDelegate _selectionDelegate = StaticSelectionContainerDelegate(); + final StaticSelectionContainerDelegate _selectionDelegate = StaticSelectionContainerDelegate(separator: '\n'); + StaticSelectionContainerDelegate get selectionDelegate => _selectionDelegate; // there should only ever be one selectable, which is the SelectionContainer. Selectable? _selectable; + Selectable? get selectable => _selectable; bool get _hasSelectionOverlayGeometry => _selectionDelegate.value.startSelectionPoint != null || @@ -599,48 +601,32 @@ class SelectableRegionState extends State // // This method should be used in all instances when details.consecutiveTapCount // would be used. - int _getEffectiveConsecutiveTapCount(int rawCount) { - var maxConsecutiveTap = 3; + static int _getEffectiveConsecutiveTapCount(int rawCount) { switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.fuchsia: - if (_lastPointerDeviceKind != null && _lastPointerDeviceKind != PointerDeviceKind.mouse) { - // When the pointer device kind is not precise like a mouse, native - // Android resets the tap count at 2. For example, this is so the - // selection can collapse on the third tap. - maxConsecutiveTap = 2; - } - // From observation, these platforms reset their tap count to 0 when - // the number of consecutive taps exceeds the max consecutive tap supported. - // For example on native Android, when going past a triple click, - // on the fourth click the selection is moved to the precise click - // position, on the fifth click the word at the position is selected, and - // on the sixth click the paragraph at the position is selected. - return rawCount <= maxConsecutiveTap - ? rawCount - : (rawCount % maxConsecutiveTap == 0 - ? maxConsecutiveTap - : rawCount % maxConsecutiveTap); case TargetPlatform.linux: - // From observation, these platforms reset their tap count to 0 when - // the number of consecutive taps exceeds the max consecutive tap supported. - // For example on Debian Linux with GTK, when going past a triple click, - // on the fourth click the selection is moved to the precise click - // position, on the fifth click the word at the position is selected, and - // on the sixth click the paragraph at the position is selected. - return rawCount <= maxConsecutiveTap - ? rawCount - : (rawCount % maxConsecutiveTap == 0 - ? maxConsecutiveTap - : rawCount % maxConsecutiveTap); + // From observation, these platform's reset their tap count to 0 when + // the number of consecutive taps exceeds 3. For example on Debian Linux + // with GTK, when going past a triple click, on the fourth click the + // selection is moved to the precise click position, on the fifth click + // the word at the position is selected, and on the sixth click the + // paragraph at the position is selected. + return rawCount <= 3 ? rawCount : (rawCount % 3 == 0 ? 3 : rawCount % 3); case TargetPlatform.iOS: case TargetPlatform.macOS: + // From observation, these platform's either hold their tap count at 3. + // For example on macOS, when going past a triple click, the selection + // should be retained at the paragraph that was first selected on triple + // click. + return min(rawCount, 3); case TargetPlatform.windows: - // From observation, these platforms hold their tap count at the max - // consecutive tap supported. For example on macOS, when going past a triple - // click, the selection should be retained at the paragraph that was first - // selected on triple click. - return min(rawCount, maxConsecutiveTap); + // From observation, this platform's consecutive tap actions alternate + // between double click and triple click actions. For example, after a + // triple click has selected a paragraph, on the next click the word at + // the clicked position will be selected, and on the next click the + // paragraph at the position is selected. + return rawCount < 2 ? rawCount : 2 + rawCount % 2; } } @@ -782,22 +768,8 @@ class SelectableRegionState extends State _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; } case 3: - switch (defaultTargetPlatform) { - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.iOS: - if (details.kind != null && _isPrecisePointerDevice(details.kind!)) { - // Triple tap on static text is only supported on mobile - // platforms using a precise pointer device. - _selectParagraphAt(offset: details.globalPosition); - _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; - } - case TargetPlatform.macOS: - case TargetPlatform.linux: - case TargetPlatform.windows: - _selectParagraphAt(offset: details.globalPosition); - _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; - } + _selectParagraphAt(offset: details.globalPosition); + _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; } _updateSelectedContentIfNeeded(); } @@ -1049,10 +1021,10 @@ class SelectableRegionState extends State case TargetPlatform.windows: // If _lastSecondaryTapDownPosition is within the current selection then // keep the current selection, if not then collapse it. - final bool lastSecondaryTapDownPositionWasOnActiveSelection = _positionIsOnActiveSelection( - globalPosition: details.globalPosition, - ); - if (lastSecondaryTapDownPositionWasOnActiveSelection) { + // final bool lastSecondaryTapDownPositionWasOnActiveSelection = _positionIsOnActiveSelection( + // globalPosition: details.globalPosition, + // ); + if (_selectionDelegate.value.selectionRects.isNotEmpty) { // Restore _lastSecondaryTapDownPosition since it may be cleared if a user // accesses contextMenuAnchors. _lastSecondaryTapDownPosition = details.globalPosition; @@ -1957,6 +1929,8 @@ class SelectableRegionState extends State // the region on non-web platforms. if (kIsWeb) { _focusNode.unfocus(); + } else { + clearSelection(); } }, child: CompositedTransformTarget( @@ -2087,6 +2061,11 @@ class _DirectionallyExtendCaretSelectionAction _hasReceivedStartEvent = {}; @@ -2271,6 +2250,18 @@ class StaticSelectionContainerDelegate extends MultiSelectableSelectionContainer super.dispose(); } + void handleSelectionEdgeUpdateEvent(SelectionEdgeUpdateEvent event) { + event.isSingle = isSingle; + } + + @override + SelectionResult dispatchSelectionEvent(SelectionEvent event) { + if (event is SelectionEdgeUpdateEvent) { + handleSelectionEdgeUpdateEvent(event); + } + return super.dispatchSelectionEvent(event); + } + @override SelectionResult dispatchSelectionEventToChild(Selectable selectable, SelectionEvent event) { switch (event.type) { @@ -2367,6 +2358,8 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai /// Gets the list of [Selectable]s this delegate is managing. List selectables = []; + bool get isSingle => selectables.length == 1; + /// The number of additional pixels added to the selection handle drawable /// area. /// @@ -2822,8 +2815,17 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai return null; } final buffer = StringBuffer(); - for (final selection in selections) { - buffer.write(selection.plainText); + if (separator != null) { + for (final (i, selection) in selections.indexed) { + if (i != 0) { + buffer.write(separator); + } + buffer.write(selection.plainText); + } + } else { + for (final selection in selections) { + buffer.write(selection.plainText); + } } return SelectedContent(plainText: buffer.toString()); } diff --git a/packages/flutter/lib/src/widgets/selection_container.dart b/packages/flutter/lib/src/widgets/selection_container.dart index 5d5a2386a7a..92e7d1eb70a 100644 --- a/packages/flutter/lib/src/widgets/selection_container.dart +++ b/packages/flutter/lib/src/widgets/selection_container.dart @@ -270,6 +270,8 @@ class SelectionRegistrarScope extends InheritedWidget { /// This delegate needs to implement [SelectionRegistrar] to register /// [Selectable]s in the [SelectionContainer] subtree. abstract class SelectionContainerDelegate implements SelectionHandler, SelectionRegistrar { + Object? get separator => null; + BuildContext? _selectionContainerContext; /// Gets the paint transform from the [Selectable] child to diff --git a/packages/flutter/lib/src/widgets/text.dart b/packages/flutter/lib/src/widgets/text.dart index a3aa5fde862..8fbf6a1b16d 100644 --- a/packages/flutter/lib/src/widgets/text.dart +++ b/packages/flutter/lib/src/widgets/text.dart @@ -992,6 +992,9 @@ class _SelectableTextContainerDelegate extends StaticSelectionContainerDelegate final GlobalKey _textKey; RenderParagraph get paragraph => _textKey.currentContext!.findRenderObject()! as RenderParagraph; + @override + void handleSelectionEdgeUpdateEvent(SelectionEdgeUpdateEvent event) {} + @override SelectionResult handleSelectParagraph(SelectParagraphSelectionEvent event) { final SelectionResult result = _handleSelectParagraph(event); diff --git a/packages/flutter/lib/src/widgets/widget_span.dart b/packages/flutter/lib/src/widgets/widget_span.dart index 09da3b76525..4a83efb0392 100644 --- a/packages/flutter/lib/src/widgets/widget_span.dart +++ b/packages/flutter/lib/src/widgets/widget_span.dart @@ -78,7 +78,7 @@ class WidgetSpan extends PlaceholderSpan { /// /// A [TextStyle] may be provided with the [style] property, but only the /// decoration, foreground, background, and spacing options will be used. - const WidgetSpan({required this.child, super.alignment, super.baseline, super.style}) + const WidgetSpan({required this.child, super.alignment, super.baseline, super.style, super.rawText}) : assert( baseline != null || !(identical(alignment, ui.PlaceholderAlignment.aboveBaseline) ||