mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
add debugPaintTextLayoutBoxes for debugging text layout (#168876)
Text vertical alignment is a common complaint and one of the most common
causes is different type faces being used in the same paragraph (for
instance, when there's English / Arabic in the same paragraph so the
default font doesn't contain the required glyphs for some characters).
Hopefully this flag is going to make debugging alignment issues a bit
easier, and in some github issues attaching screenshots with the flag on
will be helpful for diagnosing the issue.
### What the layout boxes look like
```dart
final testText =
'Quick brown fox, نص, trailing spaces${" " * 100}'
'new line at the end.\n\n\n\n';
Column([
Text(testText, style: TextStyle(height: kTextHeightNone)),
Text(testText, style: TextStyle(height: 1.0)),
Text(
testText,
style: TextStyle(height: kTextHeightNone),
strutStyle: StrutStyle(fontSize: 30),
),
])
```
<img width="410" alt="image"
src="https://github.com/user-attachments/assets/788b8089-373f-4af0-9144-306b3e755614"
/>
## Pre-launch Checklist
- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel
on [Discord].
<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
ae0700d94a
commit
844edbcd91
@ -1291,6 +1291,22 @@ class TextPainter {
|
||||
_layoutCache = newLayoutCache;
|
||||
}
|
||||
|
||||
/// Causes the paragraph to paint the layout boxes of the text.
|
||||
///
|
||||
/// {@template flutter.painting.textPainter.debugPaintTextLayoutBoxes}
|
||||
/// Each painted box illustrates how the encompassed text contributes to the
|
||||
/// overall text layout. For instance, for paragraphs whose [StrutStyle] is
|
||||
/// disabled, the line height of a line is the smallest vertical extent that
|
||||
/// covers all text boxes on that line.
|
||||
///
|
||||
/// Typically, only characters with a non-zero horizontal advance produce
|
||||
/// these boxes. No boxes will be painted for lines that only consist of a new
|
||||
/// line character.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// The [paint] method reads this flag only in debug mode.
|
||||
bool debugPaintTextLayoutBoxes = false;
|
||||
|
||||
/// Paints the text onto the given canvas at the given offset.
|
||||
///
|
||||
/// Valid only after [layout] has been called.
|
||||
@ -1335,9 +1351,31 @@ class TextPainter {
|
||||
assert(debugSize == size);
|
||||
}
|
||||
assert(!_rebuildParagraphForPaint);
|
||||
|
||||
assert(
|
||||
!debugPaintTextLayoutBoxes || _debugPaintCharacterLayoutBoxes(canvas, layoutCache, offset),
|
||||
);
|
||||
canvas.drawParagraph(layoutCache.paragraph, offset + layoutCache.paintOffset);
|
||||
}
|
||||
|
||||
bool _debugPaintCharacterLayoutBoxes(
|
||||
Canvas canvas,
|
||||
_TextPainterLayoutCacheWithOffset layout,
|
||||
Offset offset,
|
||||
) {
|
||||
final Paint paint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.0
|
||||
..color = const Color(0xFF00FFFF);
|
||||
final List<TextBox> textBoxes = getBoxesForSelection(
|
||||
TextSelection(baseOffset: 0, extentOffset: plainText.length),
|
||||
);
|
||||
for (final TextBox textBox in textBoxes) {
|
||||
canvas.drawRect(textBox.toRect().shift(offset), paint);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns true if value falls in the valid range of the UTF16 encoding.
|
||||
static bool _isUTF16(int value) {
|
||||
return value >= 0x0 && value <= 0xFFFFF;
|
||||
@ -1441,17 +1479,22 @@ class TextPainter {
|
||||
return Offset(adjustedDx, rawOffset.dy + layoutCache.paintOffset.dy);
|
||||
}
|
||||
|
||||
// The condition is derived from
|
||||
// https://github.com/google/skia/blob/0086a17e0d4cc676cf88cae671ba5ee967eb7241/modules/skparagraph/src/TextLine.cpp#L1244-L1246
|
||||
// which is set here:
|
||||
// https://github.com/flutter/engine/blob/a821b8790c9fd0e095013cd5bd1f20273bc1ee47/third_party/txt/src/skia/paragraph_builder_skia.cc#L134
|
||||
bool get _strutDisabled => switch (strutStyle) {
|
||||
null || StrutStyle.disabled => true,
|
||||
StrutStyle(:final double? fontSize) => fontSize == 0.0,
|
||||
};
|
||||
|
||||
/// {@template flutter.painting.textPainter.getFullHeightForCaret}
|
||||
/// Returns the strut bounded height of the glyph at the given `position`.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// Valid only after [layout] has been called.
|
||||
double getFullHeightForCaret(TextPosition position, Rect caretPrototype) {
|
||||
// The if condition is derived from
|
||||
// https://github.com/google/skia/blob/0086a17e0d4cc676cf88cae671ba5ee967eb7241/modules/skparagraph/src/TextLine.cpp#L1244-L1246
|
||||
// which is set here:
|
||||
// https://github.com/flutter/engine/blob/a821b8790c9fd0e095013cd5bd1f20273bc1ee47/third_party/txt/src/skia/paragraph_builder_skia.cc#L134
|
||||
if (strutStyle == null || strutStyle == StrutStyle.disabled || strutStyle?.fontSize == 0.0) {
|
||||
if (_strutDisabled) {
|
||||
final double? heightFromCaretMetrics = _computeCaretMetrics(position)?.height;
|
||||
if (heightFromCaretMetrics != null) {
|
||||
return heightFromCaretMetrics;
|
||||
|
||||
@ -38,6 +38,15 @@ bool debugPaintSizeEnabled = false;
|
||||
/// Causes each RenderBox to paint a line at each of its baselines.
|
||||
bool debugPaintBaselinesEnabled = false;
|
||||
|
||||
/// Causes each RenderParagraph to paint the layout boxes of its text.
|
||||
///
|
||||
/// {@macro flutter.painting.textPainter.debugPaintTextLayoutBoxes}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [debugPaintBaselinesEnabled] which helps debug text alignment.
|
||||
bool debugPaintTextLayoutBoxes = false;
|
||||
|
||||
/// Causes each Layer to paint a box around its bounds.
|
||||
bool debugPaintLayerBordersEnabled = false;
|
||||
|
||||
@ -321,6 +330,7 @@ bool debugAssertAllRenderVarsUnset(String reason, {bool debugCheckIntrinsicSizes
|
||||
if (debugPaintSizeEnabled ||
|
||||
debugPaintBaselinesEnabled ||
|
||||
debugPaintLayerBordersEnabled ||
|
||||
debugPaintTextLayoutBoxes ||
|
||||
debugPaintPointersEnabled ||
|
||||
debugRepaintRainbowEnabled ||
|
||||
debugRepaintTextRainbowEnabled ||
|
||||
|
||||
@ -1007,6 +1007,11 @@ class RenderParagraph extends RenderBox
|
||||
}
|
||||
}
|
||||
|
||||
assert(() {
|
||||
_textPainter.debugPaintTextLayoutBoxes = debugPaintTextLayoutBoxes;
|
||||
return true;
|
||||
}());
|
||||
|
||||
_textPainter.paint(context.canvas, offset);
|
||||
|
||||
paintInlineChildren(context, offset);
|
||||
|
||||
@ -1957,6 +1957,30 @@ void main() {
|
||||
expect(painter.height, 10);
|
||||
});
|
||||
|
||||
test('debugPaintTextLayoutBoxes', () {
|
||||
const TextSpan span = TextSpan(
|
||||
text: 'M',
|
||||
// ascent = 96, descent = 32
|
||||
style: TextStyle(fontSize: 128),
|
||||
children: <InlineSpan>[TextSpan(text: 'M', style: TextStyle(fontSize: 64))],
|
||||
);
|
||||
|
||||
final TextPainter painter = TextPainter()
|
||||
..textDirection = TextDirection.ltr
|
||||
..text = span
|
||||
..layout();
|
||||
expect(
|
||||
(Canvas canvas) {
|
||||
painter.debugPaintTextLayoutBoxes = true;
|
||||
painter.paint(canvas, Offset.zero);
|
||||
painter.debugPaintTextLayoutBoxes = false;
|
||||
},
|
||||
paints
|
||||
..rect(rect: Offset.zero & const Size.square(128))
|
||||
..rect(rect: const Offset(128, 96 - 48) & const Size.square(64)),
|
||||
);
|
||||
});
|
||||
|
||||
test('TextPainter dispatches memory events', () async {
|
||||
await expectLater(
|
||||
await memoryEvents(() => TextPainter().dispose(), TextPainter),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user