Skip to content

Commit fcd3584

Browse files
release: v1.3.1 — decouple plugins, CSS list/background, selection perf, demo polish
release: v1.3.1 — decouple plugins, CSS list/background, selection perf, demo polish
2 parents dccb152 + 1c3b18a commit fcd3584

79 files changed

Lines changed: 1522 additions & 809 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,42 @@
11
# Changelog
22

3+
## [1.3.1] - 2026-05-14
4+
5+
### ⚠️ Migration from 1.3.0
6+
7+
`hyper_render_clipboard` and `hyper_render_math` are no longer transitive dependencies of `hyper_render`. If you use either, add them explicitly:
8+
9+
```yaml
10+
dependencies:
11+
hyper_render: ^1.3.1
12+
hyper_render_clipboard: ^1.3.1 # only if you use SuperClipboardHandler
13+
hyper_render_math: ^1.3.1 # only if you use MathNodePlugin / LatexNodePlugin
14+
```
15+
16+
### ✨ New CSS Properties
17+
18+
- **`list-style-type`**: All 11 marker types — `disc`, `circle`, `square`, `decimal`, `decimal-leading-zero`, `lower-alpha`, `upper-alpha`, `lower-latin`, `upper-latin`, `lower-roman`, `upper-roman`, `none`
19+
- **`list-style-position`**: `inside` / `outside`
20+
- **`list-style` shorthand**: parses type and position in any order
21+
- **`background-repeat`**: `repeat`, `repeat-x`, `repeat-y`, `no-repeat`, `space`, `round`
22+
- **`background-position`**: keyword (`center`, `top left`, etc.) and percentage values
23+
24+
### 🚀 Performance
25+
26+
- **Selection rects cached**: `getSelectionRects()` now called once per drag event (was 3×) — stored in `_selectionRects` field, eliminating redundant layout walks during selection drag
27+
- **Auto-scroll proportional speed**: `_autoScrollIfNearEdge` scales 0–20 px/frame based on finger proximity to edge (was fixed 15 px/frame)
28+
- **`HyperTeardropHandlePainter` deduplicated**: renamed, made public, and exported from `hyper_render_core`; duplicate implementation in the virtualized overlay removed
29+
30+
### 🐛 Bug Fixes
31+
32+
- **Edge-to-edge images**: `width: 100%` images now truly fill their container — no internal margin offset
33+
34+
### 🏗️ Build Fixes
35+
36+
- **Decoupled native dependencies**: `hyper_render_clipboard` and `hyper_render_math` removed from root `hyper_render` default dependencies — eliminates the `compileSdk = 34` Gradle requirement for basic usage
37+
- **Removed outdated `compileSdk` workaround** from example app's Android Gradle config
38+
39+
340
## [1.3.0] - 2026-05-03
441

542
### ✨ New Features

README.md

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
# HyperRender
66

7-
### Fast, robust HTML rendering for Flutter.
7+
### The only Flutter HTML renderer with CSS float layout.
88

99
[![pub.dev](https://img.shields.io/pub/v/hyper_render.svg?label=pub.dev&color=0175C2)](https://pub.dev/packages/hyper_render)
1010
[![pub points](https://img.shields.io/pub/points/hyper_render?label=pub%20points&color=00b4ab)](https://pub.dev/packages/hyper_render/score)
@@ -13,7 +13,7 @@
1313
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
1414
[![Flutter](https://img.shields.io/badge/Flutter-3.10+-54C5F8.svg?logo=flutter)](https://flutter.dev)
1515

16-
**CSS float · crash-free selection · CJK/Furigana · `@keyframes` · 80%+ Test Coverage · XSS-safe**
16+
**CSS float · crash-free selection · CJK/Furigana · `@keyframes` · 1 646 tests · XSS-safe · Zero Gradle config**
1717

1818
[**Quick Start**](#-quick-start) · [**Why Switch?**](#️-why-switch-the-architecture-argument) · [**API**](#-api-reference) · [**Packages**](#-packages)
1919

@@ -26,7 +26,7 @@
2626
| CSS Float Layout | Ruby / Furigana | Crash-Free Selection |
2727
|:---:|:---:|:---:|
2828
| ![CSS Float Demo](https://raw.githubusercontent.com/brewkits/hyper_render/main/assets/float_demo.gif) | ![Ruby Demo](https://raw.githubusercontent.com/brewkits/hyper_render/main/assets/ruby_demo.gif) | ![Selection Demo](https://raw.githubusercontent.com/brewkits/hyper_render/main/assets/selection_demo.gif) |
29-
| Text wraps around floated images — no other Flutter HTML renderer does this | Furigana centered above base glyphs, full Kinsoku line-breaking | Select across headings, paragraphs, tables — tested to 100 000 chars |
29+
| Text wraps around floated images — **no other Flutter HTML renderer does this** | Furigana centered above base glyphs, full Kinsoku line-breaking | Select across headings, paragraphs, tables — tested to 100 000 chars |
3030

3131
| Advanced Tables | Head-to-Head | Virtualized Mode |
3232
|:---:|:---:|:---:|
@@ -39,7 +39,7 @@
3939

4040
```yaml
4141
dependencies:
42-
hyper_render: ^1.3.0
42+
hyper_render: ^1.3.1
4343
```
4444
4545
```dart
@@ -51,47 +51,37 @@ HyperViewer(
5151
)
5252
```
5353

54-
Zero configuration. XSS sanitization is **on by default**.
55-
56-
> **Android note:** `hyper_render` depends on `super_clipboard` which transitively pulls in `irondash_engine_context`. That library was compiled against Android SDK 31, but its `androidx.fragment:1.7.1` dependency requires `compileSdk ≥ 34`. Add this one-time workaround to your `android/build.gradle.kts`:
57-
>
58-
> ```kotlin
59-
> // android/build.gradle.kts (root — not app/build.gradle.kts)
60-
> subprojects {
61-
> afterEvaluate {
62-
> extensions.findByType(com.android.build.gradle.LibraryExtension::class.java)?.apply {
63-
> compileSdk = 35
64-
> }
65-
> }
66-
> }
67-
> ```
68-
>
69-
> This overrides `compileSdk` for all library sub-projects so AGP's `checkAarMetadata` passes. Tracked in [#5](https://github.com/brewkits/hyper_render/issues/5).
54+
Zero configuration. XSS sanitization is **on by default**. No Gradle setup required.
7055

7156
---
7257

7358
## 🏗️ Why Switch? The Architecture Argument
7459

7560
Most Flutter HTML libraries map each HTML tag to a Flutter widget. A 3 000-word article becomes **500+ nested widgets** — and some layout primitives simply cannot be expressed that way:
7661

77-
> **CSS `float` is not possible in a widget tree.**
78-
> Wrapping text around a floated image requires every fragment's coordinates before adjacent text can be composed. That geometry only exists when a single `RenderObject` owns the entire layout.
62+
> **CSS `float` is architecturally impossible in a widget tree.**
63+
> Wrapping text around a floated image requires every fragment's coordinates before adjacent text can be composed. That geometry only exists when a single `RenderObject` owns the entire layout pass.
7964
80-
HyperRender renders the whole document inside **one custom `RenderObject`**. Float, crash-free selection, and sub-millisecond binary-search hit-testing all follow from that single design decision.
65+
HyperRender renders the whole document inside **one custom `RenderObject`**. CSS float, crash-free selection, O(log N) binary-search hit-testing, and `@keyframes` animations all follow directly from that single architectural decision.
8166

8267
### Feature Matrix
8368

8469
| Feature | `flutter_html` | `flutter_widget_from_html` | **HyperRender** |
8570
|---|:---:|:---:|:---:|
8671
| `float: left / right` ||||
8772
| Text selection — large docs | ❌ Crashes | ❌ Crashes | ✅ Crash-free |
88-
| Ruby / Furigana | ❌ Raw text | ❌ Raw text | ✅ |
89-
| `<details>` / `<summary>` | ❌ | | ✅ Interactive |
73+
| Ruby / Furigana + Kinsoku | ❌ Raw text | ❌ Raw text ||
74+
| RTL / BiDi (Arabic, Hebrew) | ⚠️ | ⚠️ ||
9075
| CSS Variables `var()` ||||
91-
| CSS `@keyframes` | ❌ | ❌ | ✅ |
76+
| CSS `@keyframes` animation ||||
9277
| Flexbox / Grid | ⚠️ Partial | ⚠️ Partial | ✅ Full |
93-
| Box shadow · `filter` | ❌ | ❌ | ✅ |
94-
| SVG `<img src="*.svg">` | ⚠️ | ⚠️ | ✅ |
78+
| `box-shadow` · `filter` ||||
79+
| `list-style-type` (all 11 values) | ⚠️ disc only | ⚠️ disc only ||
80+
| `<details>` / `<summary>` ||| ✅ Interactive |
81+
| Quill Delta input ||||
82+
| Markdown input ||| ✅ GFM |
83+
| Modular packages | ❌ monolith | ❌ monolith | ✅ opt-in add-ons |
84+
| Zero Gradle config ||||
9585

9686
### Benchmarks
9787

@@ -368,10 +358,11 @@ HTML / Markdown / Quill Delta
368358
Kinsoku · O(log N) binary-search selection
369359
```
370360

371-
- **Single RenderObject** — float layout and crash-free selection require one shared coordinate system
361+
- **Single RenderObject** — float layout and crash-free selection require one shared coordinate system; a widget tree cannot provide this
372362
- **O(1) CSS rule lookup** — rules indexed by tag / class / ID; constant time regardless of stylesheet size
373-
- **O(log N) hit-testing**`_lineStartOffsets[]` precomputed at layout time; each touch is a binary search
374-
- **RepaintBoundary per chunk** — unmodified chunks are composited, not repainted
363+
- **O(log N) hit-testing**`_lineStartOffsets[]` precomputed at layout time; each touch is a binary search, not a linear scan
364+
- **RepaintBoundary per chunk** — unmodified chunks are composited, not repainted; incremental layout caches unchanged sections by content hash
365+
- **1 646 passing tests** — unit, widget, integration, fuzz (43 cases), and golden pixel tests across 3 OS platforms
375366

376367
---
377368

@@ -403,13 +394,65 @@ HTML / Markdown / Quill Delta
403394

404395
| Package | pub.dev | Description |
405396
|---------|---------|-------------|
406-
| [`hyper_render`](https://pub.dev/packages/hyper_render) | [![pub](https://img.shields.io/pub/v/hyper_render.svg)](https://pub.dev/packages/hyper_render) | Convenience wrapper — one dependency, everything included |
407-
| [`hyper_render_core`](https://pub.dev/packages/hyper_render_core) | [![pub](https://img.shields.io/pub/v/hyper_render_core.svg)](https://pub.dev/packages/hyper_render_core) | Core engine — UDT model, CSS resolver, RenderObject |
397+
| [`hyper_render`](https://pub.dev/packages/hyper_render) | [![pub](https://img.shields.io/pub/v/hyper_render.svg)](https://pub.dev/packages/hyper_render) | Convenience wrapper — HTML, Markdown, Delta, syntax highlight |
398+
| [`hyper_render_core`](https://pub.dev/packages/hyper_render_core) | [![pub](https://img.shields.io/pub/v/hyper_render_core.svg)](https://pub.dev/packages/hyper_render_core) | Core engine — UDT model, CSS resolver, RenderObject; zero native deps |
408399
| [`hyper_render_html`](https://pub.dev/packages/hyper_render_html) | [![pub](https://img.shields.io/pub/v/hyper_render_html.svg)](https://pub.dev/packages/hyper_render_html) | HTML + CSS parser |
409400
| [`hyper_render_markdown`](https://pub.dev/packages/hyper_render_markdown) | [![pub](https://img.shields.io/pub/v/hyper_render_markdown.svg)](https://pub.dev/packages/hyper_render_markdown) | Markdown adapter (GFM) |
410401
| [`hyper_render_highlight`](https://pub.dev/packages/hyper_render_highlight) | [![pub](https://img.shields.io/pub/v/hyper_render_highlight.svg)](https://pub.dev/packages/hyper_render_highlight) | Syntax highlighting for `<code>` / `<pre>` blocks |
411-
| [`hyper_render_clipboard`](https://pub.dev/packages/hyper_render_clipboard) | [![pub](https://img.shields.io/pub/v/hyper_render_clipboard.svg)](https://pub.dev/packages/hyper_render_clipboard) | Image copy / share |
412-
| [`hyper_render_devtools`](https://pub.dev/packages/hyper_render_devtools) | [![pub](https://img.shields.io/pub/v/hyper_render_devtools.svg)](https://pub.dev/packages/hyper_render_devtools) | Flutter DevTools extension — UDT inspector, computed styles |
402+
| [`hyper_render_devtools`](https://pub.dev/packages/hyper_render_devtools) | [![pub](https://img.shields.io/pub/v/hyper_render_devtools.svg)](https://pub.dev/packages/hyper_render_devtools) | Flutter DevTools extension — UDT inspector, computed styles, float visualizer |
403+
404+
### Optional add-ons
405+
406+
These packages bring native dependencies and are **not bundled** by default. Install only what you need.
407+
408+
| Package | pub.dev | Description |
409+
|---------|---------|-------------|
410+
| [`hyper_render_clipboard`](https://pub.dev/packages/hyper_render_clipboard) | [![pub](https://img.shields.io/pub/v/hyper_render_clipboard.svg)](https://pub.dev/packages/hyper_render_clipboard) | Native image copy / share via `super_clipboard` |
411+
| [`hyper_render_math`](https://pub.dev/packages/hyper_render_math) | [![pub](https://img.shields.io/pub/v/hyper_render_math.svg)](https://pub.dev/packages/hyper_render_math) | LaTeX / MathML via `flutter_math_fork` |
412+
413+
#### `hyper_render_clipboard` — Native image copy / share
414+
415+
```yaml
416+
dependencies:
417+
hyper_render_clipboard: ^1.3.1
418+
```
419+
420+
```dart
421+
import 'package:hyper_render_clipboard/hyper_render_clipboard.dart';
422+
423+
HyperViewer(
424+
html: html,
425+
imageClipboardHandler: SuperClipboardHandler(),
426+
)
427+
```
428+
429+
> **Android setup required:** `super_clipboard` transitively pulls in `irondash_engine_context`, which requires `compileSdk ≥ 34`. Add this to `android/build.gradle.kts` (root file, not `app/`):
430+
>
431+
> ```kotlin
432+
> subprojects {
433+
> afterEvaluate {
434+
> extensions.findByType(com.android.build.gradle.LibraryExtension::class.java)?.apply {
435+
> compileSdk = 35
436+
> }
437+
> }
438+
> }
439+
> ```
440+
>
441+
> Tracked in [#5](https://github.com/brewkits/hyper_render/issues/5).
442+
443+
#### `hyper_render_math` — LaTeX / MathML rendering
444+
445+
```yaml
446+
dependencies:
447+
hyper_render_math: ^1.3.1
448+
```
449+
450+
```dart
451+
import 'package:hyper_render_math/hyper_render_math.dart';
452+
453+
final registry = HyperPluginRegistry()..register(const MathPlugin());
454+
HyperViewer(html: html, pluginRegistry: registry)
455+
```
413456
414457
---
415458

doc/MIGRATION_GUIDE.md

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
11
# Migration Guide
22

3-
> **Current version: v1.2.0** — All v1.x releases are additive and backward-compatible. No breaking API changes.
3+
> **Current version: v1.3.1**
44
5-
## Current Version: 1.2.0
5+
## Upgrading to 1.3.1
66

7-
**No migration needed!** If you're starting fresh with HyperRender v1.2.0:
7+
### ⚠️ Breaking change — clipboard and math are now opt-in
8+
9+
`hyper_render_clipboard` and `hyper_render_math` are no longer transitive dependencies of the root `hyper_render` package. If you use either, add them explicitly:
10+
11+
```yaml
12+
dependencies:
13+
hyper_render: ^1.3.1
14+
hyper_render_clipboard: ^1.3.1 # only if you use SuperClipboardHandler
15+
hyper_render_math: ^1.3.1 # only if you use MathNodePlugin / LatexNodePlugin
16+
```
17+
18+
If you don't use either feature, **no changes are needed** — just bump the version and your Android build will no longer require a `compileSdk = 35` workaround.
19+
20+
### New in 1.3.1
21+
22+
- `list-style-type`, `list-style-position`, `list-style` shorthand CSS support
23+
- `background-repeat`, `background-position` CSS support
24+
- Edge-to-edge images: `width: 100%` now truly fills the container
25+
- Selection drag performance improved (rects cached, auto-scroll proportional)
26+
27+
---
28+
29+
## Starting fresh with 1.3.1
30+
31+
**No migration needed!** If you're starting fresh:
832

933
```yaml
1034
dependencies:
11-
hyper_render: ^1.2.0
12-
# or use individual packages:
13-
hyper_render_core: ^1.2.0
14-
hyper_render_clipboard: ^1.2.0
35+
hyper_render: ^1.3.1
36+
# opt-in extras:
37+
hyper_render_clipboard: ^1.3.1 # image copy/save/share
38+
hyper_render_math: ^1.3.1 # LaTeX/MathML
1539
```
1640

1741
```dart
@@ -156,28 +180,12 @@ These APIs are stable and will remain backward-compatible in v2.0:
156180

157181
## Getting Help
158182

159-
For the current v1.3.0 release:
183+
For the current v1.3.1 release:
160184
- See [README](../README.md) for usage
161185
- Check [CHANGELOG](../CHANGELOG.md) for version history
162186
- Review [Plugin Development Guide](PLUGIN_DEVELOPMENT.md) for extending
163187
- File issues at [GitHub Issues](https://github.com/brewkits/hyper_render/issues)
164188

165189
---
166190

167-
*Last Updated: April 29, 2026 for v1.3.0*
168-
ard support
169-
- Cross-platform (iOS, Android, Web, Desktop)
170-
171-
---
172-
173-
## Getting Help
174-
175-
For the current v1.2.0 release:
176-
- See [README](../README.md) for usage
177-
- Check [CHANGELOG](../CHANGELOG.md) for version history
178-
- Review [Plugin Development Guide](PLUGIN_DEVELOPMENT.md) for extending
179-
- File issues at [GitHub Issues](https://github.com/your-repo/issues)
180-
181-
---
182-
183-
*Last Updated: March 30, 2026 for v1.2.0*
191+
*Last Updated: May 14, 2026 for v1.3.1*

doc/ROADMAP.md

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ For detailed CSS property tracking, see [`internal/CSS_SUPPORT_ROADMAP.md`](inte
1313

1414
- Single `RenderObject` pipeline (Parse → Style → Layout → Paint)
1515
- Float layout algorithm (`float: left/right`, `clear`) — unique advantage over FWFH
16-
- Isolate-based HTML parsing (non-blocking UI thread)
16+
- Async microtask-based HTML parsing (non-blocking UI thread; uses `Future.microtask` instead of a real isolate so `FakeAsync` works in widget tests)
1717
- `ListView.builder` virtualization (low RAM on large documents)
1818
- Full Flexbox support (90% coverage: direction, wrap, gap, align, grow/shrink/basis)
1919
- CSS Variables `var()`, `transition`, `animation-*` parsing
@@ -79,12 +79,12 @@ jarring for large images. A workaround is to increase `chunkSize` so fewer split
7979
occur near floats.
8080

8181
Scope:
82-
- [ ] Add `FloatCarryover` data class to `render_hyper_box_types.dart`
83-
- [ ] Add `danglingFloats` getter to `RenderHyperBox`
84-
- [ ] Add `initialFloats` parameter to `RenderHyperBox` / `HyperRenderWidget`
85-
- [ ] Seed initial floats in `_performLineLayout`
86-
- [ ] Wire `FloatCarryover` callbacks through `VirtualizedChunk``HyperViewer`
87-
- [ ] Add offset rendering support in `_paintFloatImages` for image floats
82+
- [x] Add `FloatCarryover` data class to `render_hyper_box_types.dart`
83+
- [x] Add `danglingFloats` getter to `RenderHyperBox`
84+
- [x] Add `initialFloats` parameter to `RenderHyperBox` / `HyperRenderWidget`
85+
- [x] Seed initial floats in `_performLineLayout`
86+
- [x] Wire `FloatCarryover` callbacks through `VirtualizedChunk``HyperViewer`
87+
- [ ] Add offset rendering support in `_paintFloatImages` for image floats (imagePixelOffset not yet read by painter)
8888
- [ ] Integration test: tall float at chunk boundary shows no wasted space
8989

9090
---
@@ -98,22 +98,8 @@ Scope:
9898
**Source**: Expert review recommendation
9999
**Priority**: High — directly impacts stability on low-end devices (2 GB RAM)
100100

101-
The image cache is currently tuned manually. `WidgetsBindingObserver` should be
102-
integrated so HyperRender automatically evicts caches when the OS signals memory pressure.
103-
104-
```dart
105-
class HyperRenderController with WidgetsBindingObserver {
106-
@override
107-
void didHaveMemoryPressure() {
108-
imageCache.evictAll(); // Flutter image cache
109-
_internalSpanCache.clear(); // HyperRender internal span cache
110-
super.didHaveMemoryPressure();
111-
}
112-
}
113-
```
114-
115101
Scope:
116-
- [ ] Implement `WidgetsBindingObserver` in `HyperRenderController`
102+
- [x] Implement `WidgetsBindingObserver` in `HyperViewer``didHaveMemoryPressure` clears TextPainter cache, `LazyImageQueue.clearPending()`, and `PaintingBinding.imageCache.clear()`
117103
- [ ] Expose `onMemoryPressure` callback for host-app customization
118104
- [ ] Debug-mode metrics: eviction count, bytes freed
119105
- [ ] Smoke test on a 2 GB RAM device
@@ -122,12 +108,14 @@ Scope:
122108

123109
Properties deferred from Phase 3 in [`internal/CSS_SUPPORT_ROADMAP.md`](internal/CSS_SUPPORT_ROADMAP.md):
124110

125-
- [ ] `text-shadow` — high visual impact, 1-day effort
126-
- [ ] `text-overflow: ellipsis` — extremely common, 4-hour effort
127-
- [ ] `box-shadow` — design system compatibility
128-
- [ ] `list-style-type`, `list-style-position` — better `<ul>` / `<ol>` rendering
129-
- [ ] `word-break`, `overflow-wrap` — CJK and long-URL handling
130-
- [ ] `background-repeat`, `background-position`, `background-size`
111+
- [x] `text-shadow` — parsed + applied to `TextStyle.shadows` in `ComputedStyle`
112+
- [x] `text-overflow: ellipsis` — parsed + executed in `render_hyper_box_fragments.dart`
113+
- [x] `box-shadow` — parsed + applied in `render_hyper_box_paint.dart`
114+
- [x] `word-break`, `overflow-wrap` — parsed + executed in `render_hyper_box_layout.dart` (L1339–1375)
115+
- [ ] `list-style-type`, `list-style-position` — not yet in resolver or painter
116+
- [x] `background-repeat` — parsed + mapped to `ImageRepeat` in `paintImage()`
117+
- [x] `background-position` — parsed + mapped to `Alignment` in `paintImage()` (keyword values: top/center/bottom/left/right and combinations)
118+
- [x] `background-size` — parsed and applied
131119

132120
---
133121

0 commit comments

Comments
 (0)