Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- **Character-range highlight boxes:** `RenderHyperBox.getBoxesForCharRange(charStart, charEnd)` returns the rects of a character range, for drawing your own highlights (search hits, annotations, read-aloud). Offsets are local to each `RenderHyperBox`, so in virtualized / `auto` mode (>10k chars) they restart at 0 per chunk. Also new: `RenderHyperBox.debugLineFragments()` for the laid-out fragment geometry. By [@DrkXo](https://github.com/DrkXo) ([#26](https://github.com/brewkits/hyper_render/pull/26)).
- **`text-overflow: ellipsis` fixes** (in `hyper_render_core`): the `…` glyph is no longer counted as a hidden source character, a selection highlight no longer covers it, and an empty line where nothing fits before the ellipsis now shows `…`.
- **Line-breaking fixes** (in `hyper_render_core`, rendering changes): wrapped lines no longer overflow the box (every full CJK line overflowed by a fraction of a glyph), RTL paragraphs wrap properly, `word-break` / `overflow-wrap` set on a block reach its text (`break-all` had no effect), an over-long word breaks after as many characters as fit instead of after its first letter, a word that doesn't fit beside a float moves below it, lines no longer start one space in (after `<br>` or an indented tag), and centered / right-aligned lines ignore trailing spaces.
- **Line-breaking performance on massive unspaced text blocks** ([#28](https://github.com/brewkits/hyper_render/issues/28), [#29](https://github.com/brewkits/hyper_render/pull/29)): single-pass native multi-line layout via ICU `computeLineMetrics()` when lines are uniform and free of floats, eliminating layout freezes on long unspaced text blocks. Bounded candidate prefix search in the fallback line breaker prevents quadratic `O(N^2)` text shaping overhead. New debug hooks `HyperRenderDebugHooks.onTextPainterLayout` / `onLineLayoutTextPainter` for layout call profiling. By [@DrkXo](https://github.com/DrkXo).
- **Requires `hyper_render_core` `^1.13.0`.**

## 1.12.0
Expand Down
5 changes: 5 additions & 0 deletions packages/hyper_render_core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
- **A word wider than the line breaks after as many characters as fit** (like Flutter's `Text`), instead of after its first letter, which left a column of one-letter lines.
- **A word that doesn't fit beside a float moves below it** instead of being split after its first letter.
- **Spaces at the start and end of a line collapse**, as in CSS. A line after `<p>` + newline, after `<br>`, or after an indented `<dt>` started one space in, and trailing spaces counted toward the width that `text-align: center/right` positions.
- **Line-breaking performance on massive unspaced text blocks** ([#28](https://github.com/brewkits/hyper_render/issues/28), [#29](https://github.com/brewkits/hyper_render/pull/29)):
- Single-pass native multi-line layout fast path via ICU `computeLineMetrics()` when lines are uniform and free of floats, eliminating UI freezes on long unspaced text.
- Bounded candidate prefix search in the fallback line breaker loop to prevent quadratic `O(N^2)` HarfBuzz text shaping overhead.
- Robust loop termination on zero or negative line widths and trailing whitespace margin parity.
- **`HyperRenderDebugHooks.onTextPainterLayout` / `onLineLayoutTextPainter`** — hook callbacks for counting text shaping and line layout operations in tests and DevTools. By [@DrkXo](https://github.com/DrkXo) ([#29](https://github.com/brewkits/hyper_render/pull/29)).

## 1.12.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import '../model/node.dart';
/// HyperRenderDebugHooks.onLayoutComplete = (id, getFrags, getLines) { ... };
/// HyperRenderDebugHooks.onFrameTiming = (id, phase, micros) { ... };
/// HyperRenderDebugHooks.onSelectionChanged = (id, start, end) { ... };
/// HyperRenderDebugHooks.onTextPainterLayout = () { ... };
/// HyperRenderDebugHooks.onLineLayoutTextPainter = () { ... };
/// ```
abstract final class HyperRenderDebugHooks {
/// Called when a [RenderHyperBox] is attached to the pipeline.
Expand Down Expand Up @@ -63,6 +65,14 @@ abstract final class HyperRenderDebugHooks {
/// selection was cleared.
static void Function(String id, int? start, int? end)? onSelectionChanged;

/// Called in debug mode whenever a TextPainter performs layout in RenderHyperBox.
/// Used by tests and DevTools to count text shaping operations.
static void Function()? onTextPainterLayout;

/// Called in debug mode whenever a TextPainter performs layout during the line-breaking
/// pass in RenderHyperBox.
static void Function()? onLineLayoutTextPainter;

/// CSS custom property overrides set from DevTools (`{'--name': value}`).
///
/// In debug builds `HyperViewer` passes these to its
Expand All @@ -81,5 +91,7 @@ abstract final class HyperRenderDebugHooks {
onRendererDetached != null ||
onLayoutComplete != null ||
onFrameTiming != null ||
onSelectionChanged != null);
onSelectionChanged != null ||
onTextPainterLayout != null ||
onLineLayoutTextPainter != null);
}
7 changes: 7 additions & 0 deletions packages/hyper_render_core/lib/src/core/render_hyper_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ class RenderHyperBox extends RenderBox
_LruCache<_TextPainterKey, TextPainter> get _textPainters =>
_globalTextPainters;

/// Reusable TextPainter for measuring line-breaking candidate prefixes during
/// layout without polluting the global LRU cache.
TextPainter? _scratchCandidatePainter;

/// Last collapsed margin (for margin collapsing between blocks)
double _lastBlockMarginBottom = 0;

Expand Down Expand Up @@ -731,6 +735,8 @@ class RenderHyperBox extends RenderBox
}
_cancelBlockAnimationLoop();
_disposeImages();
_scratchCandidatePainter?.dispose();
_scratchCandidatePainter = null;
// Do NOT call detach() on cached semantic anchor nodes here.
// Flutter's semantics teardown already detaches them during widget
// unmounting — calling detach() again asserts inside SemanticsOwner
Expand Down Expand Up @@ -1088,6 +1094,7 @@ class RenderHyperBox extends RenderBox

measurePainter.text = TextSpan(text: candidate, style: mergedStyle);
measurePainter.layout();
if (kDebugMode) HyperRenderDebugHooks.onTextPainterLayout?.call();
if (measurePainter.width > maxWidth) {
maxWidth = measurePainter.width;
}
Expand Down
Loading