Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
f04945d
release: 1.13.0 (core 1.13.0, root 1.13.0)
vietnguyentuan2019 Oct 8, 2026
fe4a0fd
fix(core): inherit word-break and overflow-wrap
vietnguyentuan2019 Oct 8, 2026
d2a5851
fix(core): line breaking overflowed, ignored RTL, and split words aft…
vietnguyentuan2019 Oct 8, 2026
4dac547
docs: 1.13.0 changelog and migration notes for the line-breaking fixes
vietnguyentuan2019 Oct 8, 2026
a797dc6
chore: regenerate golden references (Flutter 3.41.5) [skip ci]
github-actions[bot] Oct 8, 2026
1536eb4
fix(core): collapse spaces at the start and end of a line
vietnguyentuan2019 Oct 8, 2026
71ad6e9
chore: regenerate golden references (Flutter 3.41.5) [skip ci]
github-actions[bot] Oct 8, 2026
1d5d110
fix(core): collapse the leading space of a fragment moved to a new line
vietnguyentuan2019 Oct 8, 2026
52b9e8d
chore: regenerate golden references (Flutter 3.41.5) [skip ci]
github-actions[bot] Oct 8, 2026
499e387
ci: run the full checks on the regenerated goldens
vietnguyentuan2019 Oct 8, 2026
2a98a2f
perf(core): eliminate O(N^2) line-layout bottleneck on massive unspac…
DrkXo Oct 8, 2026
1feb2fc
Merge pull request #29: eliminate the O(N^2) line-breaking bottleneck…
vietnguyentuan2019 Oct 10, 2026
fee60d7
fix(core): don't measure a floated block's unmeasured text (follow-up…
vietnguyentuan2019 Oct 10, 2026
87ffb69
chore: regenerate golden references (Flutter 3.41.5) [skip ci]
github-actions[bot] Oct 10, 2026
d2fa5cf
ci: run the full checks with #29 merged
vietnguyentuan2019 Oct 10, 2026
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 1.13.0

- **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

- **Dark surfaces: links, `<h6>`, `<code>` / `<pre>` and `<mark>` now use dark-surface colors** ([#23](https://github.com/brewkits/hyper_render/issues/23)). In 1.11.0 only the default text color followed a dark theme; these built-in colors were still tuned for light surfaces and measured 4.1:1 (links), 3.9:1 (`<h6>`) and **2.5:1** (`<pre><code>`) on `#121212`. They now keep at least 4.5:1, and the inline `<code>` / `<mark>` chips become dark chips instead of bright blocks. The palette is chosen from the *effective text color*: light text means a dark surface, so a dark `Theme` with `textColor: Colors.black87` (a white pane) keeps the light-surface palette, and a light `Theme` with a light `textColor` gets the dark one. Light-surface output is unchanged. Author CSS and inline styles still win. `<blockquote>`, `<kbd>` and table headers keep their light backgrounds (their text is kept readable by the 1.11.0 contrast guard). **Requires `hyper_render_core` `^1.12.0`** (`StyleResolver.darkSurface`). `hyper_render_epub` 0.1.3 needs no update: it goes through `HyperViewer`, so it picks this up automatically.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Already using `flutter_html`? You don't need to rewrite your widget tree or lear
```dart
// 1. In your pubspec.yaml:
// dependencies:
// hyper_render: ^1.12.0
// hyper_render: ^1.13.0

// 2. In your Dart file — replace this single line:
// ❌ import 'package:flutter_html/flutter_html.dart';
Expand Down Expand Up @@ -68,7 +68,7 @@ Html(

```yaml
dependencies:
hyper_render: ^1.12.0
hyper_render: ^1.13.0
```

```dart
Expand Down
1 change: 1 addition & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This document outlines the architectural roadmap for **HyperRender** to become t
| **v1.10.0** | Shipped 2026-10-02 | **DevTools v2 & Stylesheet CSS Correctness** | Timeline / Selection / live CSS-variable / snapshot DevTools tabs; stylesheet `var()`, `url()`, `calc()` and `:root` fixed. |
| **v1.11.0** | Shipped 2026-10-04 | **Dark Mode & Root Colors** | Dark-`Theme` text default, `HyperViewer(textColor:)` / `EpubReader(textColor:)`, `body`/`html` `color`, `:root` fix, contrast guard (#20). |
| **v1.12.0** | Shipped 2026-10-04 | **Dark-Surface Element Colors** | Links, `<h6>`, `<code>` / `<pre>`, `<mark>` keep >= 4.5:1 on dark surfaces (#23). |
| **v1.13.0** | Shipped 2026-10-08 | **Character-Range Highlight Geometry** | `getBoxesForCharRange` / `debugLineFragments` for app-drawn highlights; ellipsis character-count fix (#26). |
| **Next** | Unscheduled | **Native Vector Diagramming & Headless Export** | Pure Canvas/Vector Mermaid.js & GraphViz (Zero-WebView), Headless Image & PDF byte stream generator. |
| **v2.0.0** | Q1 2027 | **Interactive Editorial & Magazine Typography** | Medium-style Text Annotation/Highlighting layer, Multi-column layout (`column-count`), Z-Index Stacking Context, Vertical Text (`writing-mode: vertical-rl`). |

Expand Down
4 changes: 2 additions & 2 deletions doc/CSS_PROPERTIES_MATRIX.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# CSS Properties Support Matrix

Last Updated: October 4, 2026
Version: 1.12.0
Last Updated: October 8, 2026
Version: 1.13.0

This document lists CSS property support in HyperRender.

Expand Down
16 changes: 15 additions & 1 deletion doc/MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# Migration Guide

> **Current version: v1.12.0**
> **Current version: v1.13.0**

## Upgrading to v1.13.0

No breaking changes. New: `RenderHyperBox.getBoxesForCharRange` and `debugLineFragments` ([#26](https://github.com/brewkits/hyper_render/pull/26)). Two small `text-overflow: ellipsis` changes: selecting truncated text no longer highlights the `…` glyph, and a line where not even one character fits before the ellipsis now shows `…` instead of staying empty.

**Line breaking changed in a few cases**, so wrapped text can land on different lines than in 1.12.0: lines no longer overflow the box by part of a glyph (most visible in CJK), RTL paragraphs wrap properly, `word-break` / `overflow-wrap` on a block now apply to its text, an over-long word breaks after as many characters as fit, 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 no longer count trailing spaces. If you have pixel tests of wrapped text, expect to regenerate them.

```yaml
dependencies:
hyper_render: ^1.13.0
```

## Upgrading to v1.12.0

Expand Down Expand Up @@ -216,6 +227,9 @@ These APIs are stable and will remain backward-compatible in v2.0:

## Version History

### v1.13.0 (October 2026)
- `RenderHyperBox.getBoxesForCharRange` / `debugLineFragments`, ellipsis character-count fix ([#26](https://github.com/brewkits/hyper_render/pull/26))

### v1.12.0 (October 2026)
- Dark-surface colors for links, `<h6>`, `<code>` / `<pre>` and `<mark>` ([#23](https://github.com/brewkits/hyper_render/issues/23))

Expand Down
5 changes: 3 additions & 2 deletions doc/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# HyperRender — Product Roadmap

**Last Updated**: 2026-10-03
**Current Stable**: v1.12.0
**Last Updated**: 2026-10-08
**Current Stable**: v1.13.0
**Repository**: [github.com/brewkits/hyper_render](https://github.com/brewkits/hyper_render)

This document tracks the long-term direction of the HyperRender ecosystem.
Expand Down Expand Up @@ -33,6 +33,7 @@ For detailed CSS property tracking, see [`CSS_PROPERTIES_MATRIX.md`](CSS_PROPERT
`hyper_render_markdown`, `hyper_render_highlight`, `hyper_render_clipboard`
- **`hyper_render_devtools` v1.0.0** — UDT Tree inspector, Computed Style panel, Layout fragment/line data, demo mode (no live app required); published to pub.dev
- **`hyper_render_devtools` v1.8.0** (2026-10-02) — the panel actually connects now (#17), plus Timeline, Selection debugger, live CSS-variable editing and JSON snapshot export. Requires `hyper_render_core` 1.10.0.
- **Character-range highlight geometry** (1.13.0) — `RenderHyperBox.getBoxesForCharRange` returns line-aligned rects for any character range (search hits, annotations), and `debugLineFragments()` exposes laid-out fragment geometry; `text-overflow: ellipsis` no longer counts the `…` glyph as a source character (#26, by @DrkXo).
- **Dark-surface element colors** (1.12.0) — links, `<h6>`, `<code>` / `<pre>` and `<mark>` switch to dark-surface variants (>= 4.5:1) when the effective text color is light (#23).
- **Dark mode & root colors** (1.11.0) — unstyled text follows a dark `Theme`, `HyperViewer(textColor:)` / `EpubReader(textColor:)`, `body`/`html` `color` honoured, `:root` matches only the document root, readable text on light UA surfaces (#20). Block plugins on custom tags (`<info-box>`) now render (#22).
- **Stylesheet CSS correctness** (core 1.10.0) — `var()`, `url()`, `calc()` in `<style>` / `customCss` now resolve (inline-only before), custom properties cascade before substitution, `:root` matches only the document root, `var()` expansion capped.
Expand Down
21 changes: 21 additions & 0 deletions packages/hyper_render_core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog — hyper_render_core

## 1.13.0

- **`RenderHyperBox.getBoxesForCharRange(charStart, charEnd)`** — bounding boxes for a character range, for app-drawn highlights (search hits, annotations, read-aloud). Offsets are the selection/IME character space, local to this `RenderHyperBox`: in virtualized / `auto` mode (>10k chars) each chunk has its own box and offsets restart at 0. Glyph x-bounds come from `TextPainter` (so they follow `textScaler` and justified spacing); y-bounds span the full line. Rects of adjacent words on a line merge, except across an inline atom such as an image. Spaces inside `white-space: pre` / `pre-wrap` / `break-spaces` are kept. By [@DrkXo](https://github.com/DrkXo) ([#26](https://github.com/brewkits/hyper_render/pull/26), follow-up to [#17](https://github.com/brewkits/hyper_render/pull/17)).
- **`RenderHyperBox.debugLineFragments()`** — the fragments as laid out on lines, with the position actually painted, `charStart` / `charEnd`, line index / top / height, ruby text and `ellipsisVisibleLength`. `debugFragments()` reports pre-layout fragments and misses wrapped and truncated pieces.
- **`text-overflow: ellipsis`: the truncated fragment now records how many source characters it shows.** The `ellipsisVisibleLength` was only set on the original fragment, which never reaches a line, so the `…` glyph counted as a source character.
- **Behavior changes:** a selection highlight on truncated text no longer covers the `…` glyph, matching `getSelectedText`; and when not even one character fits before the ellipsis on an empty line, the line now shows `…` instead of nothing.

- **Line-breaking fixes** (rendering changes, found while reviewing [#29](https://github.com/brewkits/hyper_render/pull/29)):
- **Lines no longer run past the box.** A wrap point was taken from the nearest caret rather than the last one that fits, so a line could overflow by up to half a glyph; every full CJK line did.
- **RTL paragraphs wrap.** The line breaker read an RTL paragraph from its logical end, putting most of it on line one and then one glyph per line.
- **`word-break` and `overflow-wrap` are inherited**, as in CSS. Set on a `<p>`, they never reached its text, so `word-break: break-all` had no effect.
- **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.
- **Behavior change from the native layout:** CJK text now fills the line after a space between two sentences or runs (as a browser does), where it used to start the next run on a new line, and a first-in-document block with `text-overflow: ellipsis` now truncates instead of wrapping (it emitted no block-start fragment, so the ellipsis state was never entered).
- **`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

- **`StyleResolver.darkSurface`** (default `false`) — switches the built-in link, `<h6>`, `<code>`, `<pre><code>` and `<mark>` colors to dark-surface variants with at least 4.5:1 contrast on `#121212` ([#23](https://github.com/brewkits/hyper_render/issues/23)). `HyperViewer` sets it whenever its effective default text color is light. Author CSS still wins; an `<a>` without `href` is untouched.
Expand Down
2 changes: 1 addition & 1 deletion packages/hyper_render_core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Most apps should depend on [`hyper_render`](https://pub.dev/packages/hyper_rende

```yaml
dependencies:
hyper_render_core: ^1.12.0
hyper_render_core: ^1.13.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
Loading