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
7 changes: 7 additions & 0 deletions .pubignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ build/
*.dill.track.dill


# Crash reports / logs (.pubignore replaces .gitignore, so ignored files would
# otherwise be published — flutter_0N.log carries local paths and commands)
*.log
**/*.log
.DS_Store
**/.DS_Store

# Tests — not needed by package consumers
test/

Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## 1.11.0

- **Dark mode: `HyperViewer` text is readable on dark surfaces** ([#20](https://github.com/brewkits/hyper_render/issues/20)). Unstyled text was always the fixed dark gray `#1F2937`, with no way to change it from `HyperViewer`. Under a `Brightness.dark` `Theme` the default is now `colorScheme.onSurface`; under a light theme nothing changes. A theme toggle re-resolves styles in place (no loading state, scroll position kept). `EpubReader` goes through `HyperViewer`, so it follows the theme the same way.
- **Built-in light surfaces stay readable.** `<blockquote>`, `<kbd>`, `<th>` and any author `background` without a `color` assume dark text; once a light default is in play they would inherit white on near-white. An element with its own opaque background and no color of its own now falls back to dark gray / white when the inherited text would be under 3:1 contrast. Only active when a host-supplied color exists (dark theme or `textColor`), so light-theme output without `textColor` is unchanged. An element's own `color` always wins.
- **New `HyperViewer(textColor:)`** — a host override of the document's text color. It wins over the content's own `html` / `:root` / `body` color (so an app can force a reader theme over a publisher stylesheet) but not over an element's own color (`p { color }`, inline `style`). Not available on `HyperViewer.fromNode`.
- **`body { color }` and `html { color }` now work.** The adapters only keep `<body>`'s children, so those selectors could never match and were silently ignored. They now set the document root color, layered as in a browser (`html` < `:root` < `body`; a `body` declaration beats `html` / `:root` even when theirs is `!important`). **Behavior change:** content that declares `body { color: … }` (EPUB stylesheets often do) now renders in that color. Only `color` is honoured; other `body` properties (`display`, `margin`, `background`) are still ignored. Consequence: a publisher `body { color: #000 }` stays black on a dark theme — set `HyperViewer(textColor:)` / `EpubReader(textColor:)`, which win over it. On `HyperViewer`, `customCss` comes *before* the document's own `<style>`, so to override a document `body` color with CSS use `!important`; `EpubReader` appends your `customCss` after the chapter's stylesheet, so a plain `body { color }` there wins.
- **`:root` now matches the document root only.** 1.10.0 intended this but tested `parent == null`, which is also true for top-level blocks, so `:root { color: … }` still applied directly to them and beat type selectors (`:root { color: #fff } p { color: red }` rendered white). Rules that relied on that, such as a `:root { margin: 0 }` hitting every top-level block, no longer apply. `:root { --var: … }` and inherited properties are unaffected.
- **Block-tier plugins on custom tags now render** (fix in `hyper_render_core`, [#22](https://github.com/brewkits/hyper_render/pull/22)). A registered block plugin on a tag like `<info-box>` or `<x-badge>` was built but laid out at 0×0 and never painted, because the HTML adapters build custom tags as inline nodes. Found by running the demo app: the Plugin API demo showed blank gaps and logged `More child widgets than fragments`. Plugins on tags that were already block (`figure`, `div`) were never affected.
- The `customCss` doc example advertised `body { font-size: 18px; }`, which is still ignored; it now shows `p { font-size: 18px; }`.
- **Requires `hyper_render_core` `^1.11.0`** (`StyleResolver.rootColorOverride`, `ensureReadableOnOwnBackground`).
- **`hyper_render_epub` 0.1.3** pairs with this release (`EpubReader(textColor:)`).
- **`hyper_render_clipboard` 1.7.3** is a docs-only release: its README, usage guide and dartdoc showed `HyperViewer(imageClipboardHandler:)` and `onImageLongPress`, which never existed.
- **Deprecated:** `HyperRenderTheme` / `HyperRenderThemeData` (in `hyper_render_core`). Nothing reads them, so setting them never had any effect. Use `HyperViewer(textColor:)` or the ambient `Theme`.
- **Known limitation:** only the default text color (and text on light UA surfaces) follows a dark theme. Link blue, inline `<code>` / `<pre>` colors and the `<mark>` highlight are still tuned for light surfaces.

## 1.10.0

- **`var()` in `<style>` / `customCss` now resolves** (fix in `hyper_render_core`). It previously produced nothing; only inline `style=""` worked. Pages that used it will now render with those values.
Expand Down
48 changes: 35 additions & 13 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.10.0
// hyper_render: ^1.11.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.10.0
hyper_render: ^1.11.0
```

```dart
Expand Down Expand Up @@ -229,7 +229,7 @@ HyperViewer(html: '''
```

CSS custom properties work in `<style>` blocks and `customCss`, not just inline
(as of 1.10.0), and `url()` / `calc()` too:
(since 1.10.0), and `url()` / `calc()` too:

```dart
HyperViewer(
Expand All @@ -253,6 +253,19 @@ void main() {
}
```

### Dark Mode

Under a dark `Theme`, unstyled text uses `colorScheme.onSurface` and re-resolves when the theme toggles (scroll position kept). Elements that paint their own light background (`<blockquote>`, `<kbd>`, `<th>`, `style="background:#eee"`) keep readable text automatically.

```dart
// A surface that stays light under a dark theme (an email pane, a paper page):
HyperViewer(html: html, textColor: Colors.black87)
```

Known limitation: link blue, inline `<code>` / `<pre>` colors and the `<mark>` highlight are still tuned for light surfaces.

`textColor` wins over the content's own `html` / `:root` / `body` color (so an app can force a reader theme over a publisher stylesheet) but not over an element's own `color`. `body { color }` and `html { color }` are honoured; other `body` properties are not.

### CSS `@keyframes` Animation

```html
Expand Down Expand Up @@ -330,7 +343,8 @@ HyperViewer(
HyperViewer({
required String html,
String? baseUrl, // resolves relative <img src> and <a href>
String? customCss, // injected after the document's own <style> tags
String? customCss, // lower priority than the document's own <style> tags (they win at equal specificity; use !important to force)
Color? textColor, // document text color; wins over html/body/:root color, not over element colors
bool selectable = true,
bool sanitize = true,
List<String>? allowedTags,
Expand Down Expand Up @@ -518,36 +532,44 @@ These packages bring specialized dependencies and are **not bundled** by default

```yaml
dependencies:
hyper_render_epub: ^0.1.2
hyper_render_epub: ^0.1.3
```

```dart
import 'package:hyper_render_epub/hyper_render_epub.dart';

// 1. Open EPUB from file bytes or asset
final book = await EpubBook.openBytes(epubBytes);
// 1. Open the .epub (any Uint8List: File, asset, network)
final book = await EpubBook.open(epubBytes);

// 2. A controller holds the position; dispose() it when done
final controller = EpubReaderController(book: book);

// 2. Render book with chapter navigation
// 3. Render the current chapter; drive next()/previous()/goTo() from your own UI
EpubReader(
book: book,
controller: EpubReaderController(),
onChapterChanged: (chapter) => print('Now reading: ${chapter.title}'),
controller: controller,
textColor: null, // null follows the theme; set it for a sepia/paper page
)
```

#### `hyper_render_clipboard` — Native image copy / share

```yaml
dependencies:
hyper_render_clipboard: ^1.7.0
hyper_render_clipboard: ^1.7.3
```

```dart
import 'package:hyper_render_clipboard/hyper_render_clipboard.dart';

// HyperViewer has no clipboard parameter: hand images to HyperImage yourself.
HyperViewer(
html: html,
imageClipboardHandler: SuperClipboardHandler(),
widgetBuilder: (node) {
if (node is AtomicNode && node.tagName == 'img' && node.src != null) {
return HyperImage(src: node.src!, clipboardHandler: SuperClipboardHandler());
}
return null;
},
)
```

Expand Down
1 change: 1 addition & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This document outlines the architectural roadmap for **HyperRender** to become t
| **v1.8.0** | Shipped | **AI & LLM Token-Streaming Engine** | Frame-throttled token updates with adaptive backoff, transient syntax auto-repair, auto-scroll locking. Tail-only layout invalidation remains a separate, unscheduled epic — see below. |
| **v1.9.0** | Shipped | **Wrapping Flexbox** | `flex-wrap: wrap` on a dedicated `RenderFlexWrap` (#15). |
| **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** | Release candidate | **Dark Mode & Root Colors** | Dark-`Theme` text default, `HyperViewer(textColor:)` / `EpubReader(textColor:)`, `body`/`html` `color`, `:root` fix, contrast guard (#20). |
| **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
6 changes: 3 additions & 3 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 2, 2026
Version: 1.10.0
Last Updated: October 3, 2026
Version: 1.11.0

This document lists CSS property support in HyperRender.

Expand Down Expand Up @@ -108,7 +108,7 @@ This document lists CSS property support in HyperRender.

| Property | Status | Supported Values | Notes |
|----------|--------|------------------|-------|
| `color` | ✅ | All CSS colors | hex, rgb, rgba, named colors |
| `color` | ✅ | All CSS colors | hex, rgb, rgba, named colors. `html { color }` / `body { color }` set the document root color (the only `body` property honoured; `html` < `:root` < `body`). Default: dark gray in a light theme, `onSurface` in a dark `Theme`; `HyperViewer(textColor:)` overrides all of these |
| `font-size` | ✅ | px, em, rem, % | |
| `font-family` | ✅ | Any font name | Falls back to system fonts |
| `font-weight` | ✅ | 100-900, normal, bold | |
Expand Down
24 changes: 23 additions & 1 deletion doc/MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
# Migration Guide

> **Current version: v1.10.0**
> **Current version: v1.11.0**

## Upgrading to v1.11.0

One new parameter (`HyperViewer(textColor:)`, and `EpubReader(textColor:)`), but **rendering changes in three cases** — check them before you ship:

- **Dark `Theme` → unstyled text is now `colorScheme.onSurface`** (it was always dark gray `#1F2937`). This fixes [#20](https://github.com/brewkits/hyper_render/issues/20), but if your app puts `HyperViewer` on a surface that stays light under a dark theme (a white email pane, a paper-colored reader page), the text is now light on light. Fix: pass `textColor: Colors.black87` (or any color) on that viewer. Light themes are unchanged.
- **`body { color }` / `html { color }` now apply.** They were silently ignored. Content that declares one (EPUB stylesheets often do) renders in that color. On a dark theme a publisher `body { color: #000 }` stays black unless you pass `textColor`, which wins over it.
- **`:root` now matches only the document root.** 1.10.0 intended this, but top-level blocks (`parent == null`) still matched, so `:root { color: #fff } p { color: red }` rendered white. A rule that relied on `:root` styling every top-level block no longer does; `:root { --var }` and inherited properties are unaffected.

Also new: elements with their own opaque background and no `color` (`<blockquote>`, `<kbd>`, `<th>`, `style="background:#eee"`) never inherit text below 3:1 contrast once a theme or `textColor` default is in play.

```yaml
dependencies:
hyper_render: ^1.11.0
hyper_render_epub: ^0.1.3 # optional: EpubReader(textColor:)
```

## Upgrading to v1.10.0

Expand Down Expand Up @@ -191,6 +207,12 @@ These APIs are stable and will remain backward-compatible in v2.0:

## Version History

### v1.11.0 (October 2026)
- Dark-theme text default, `HyperViewer(textColor:)`, `body`/`html` `color`, `:root` fix, contrast guard ([#20](https://github.com/brewkits/hyper_render/issues/20))

### v1.10.0 (October 2026)
- Stylesheet `var()` / `url()` / `calc()`, `:root` scoping, DevTools v2

### v1.3.0 (April 2026)
- High Coverage Milestone: >80% total line coverage (900+ tests)
- Fixed missing `foundation` import for `compute` function
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-02
**Current Stable**: v1.10.0
**Last Updated**: 2026-10-03
**Current Stable**: v1.10.0 (v1.11.0 is the release candidate)
**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.
- **Dark mode & root colors** (1.11.0, release candidate) — 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.
- **Golden test coverage** — Float layout, RTL/BiDi, CJK + Ruby suites pinned to ubuntu-22.04 + Flutter 3.29.2 + Noto fonts for pixel-stable CI
- **Layout regression CI tracking** — 6 fixtures (simple paragraph → 100-paragraph article) measured against a 16 ms (60 FPS) budget on every PR. Results are recorded and posted to the PR, but are **advisory and do not block the build**: GitHub runners use software rendering and are 2–3× slower than the target hardware, so a hard gate there would fail on runner noise rather than on real regressions. Enforcing this properly requires release-mode measurement on a real device, which is not yet wired up.
Expand Down
53 changes: 29 additions & 24 deletions example/MULTIMEDIA_EXAMPLES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# HyperRender Multimedia Integration Examples

This guide demonstrates how to integrate video, audio, iframes, and custom widgets into HyperRender v1.0.
This guide demonstrates how to integrate video, audio, iframes, and custom widgets into HyperRender.

> **API note.** `HyperViewer` has **no** `mediaBuilder` parameter. Every example below routes media through `widgetBuilder` (`Widget? Function(UDTNode)`), which receives `<video>`, `<audio>`, `<iframe>` and custom tags as `AtomicNode`s; use `MediaInfo.fromNode(node)` to read their attributes. (`mediaBuilder` is a parameter of `HtmlToSpanConverter`, a different class.) Returning `null` falls back to the built-in placeholder.

## 🎯 What's Unique About HyperRender?

Expand All @@ -18,9 +20,11 @@ While FWFH struggles with floated media elements, HyperRender's architecture han

## Running the Examples

The demo app's **Images & Video** hub (`example/lib/main.dart`) runs the snippets below:

```bash
cd example
flutter run lib/multimedia_example.dart
flutter run
```

## 1. Default Placeholders
Expand All @@ -33,7 +37,7 @@ HyperViewer(
<video src="sample.mp4" poster="poster.jpg" width="640" height="360"></video>
<audio src="sample.mp3" title="My Audio Track"></audio>
''',
// No mediaBuilder needed - default placeholders shown
// No widgetBuilder needed - default placeholders shown
)
```

Expand All @@ -46,7 +50,7 @@ HyperViewer(

## 2. Video Player Integration

Use `mediaBuilder` to plug in the `video_player` package.
Use `widgetBuilder` to plug in the `video_player` package.

### Setup

Expand All @@ -71,14 +75,11 @@ HyperViewer(
loop>
</video>
''',
mediaBuilder: (context, mediaInfo) {
if (mediaInfo.isVideo) {
return VideoPlayerWidget(
mediaInfo: mediaInfo,
);
widgetBuilder: (node) {
if (node is AtomicNode && node.tagName == 'video') {
return VideoPlayerWidget(mediaInfo: MediaInfo.fromNode(node));
}
// Fall back to default for audio
return DefaultMediaWidget(mediaInfo: mediaInfo);
return null; // audio and everything else: built-in rendering
},
)
```
Expand Down Expand Up @@ -289,8 +290,8 @@ HyperViewer(

| Approach | Use Case | Callback |
|----------|----------|----------|
| **mediaBuilder** | Specifically for `<video>` and `<audio>` tags | `MediaWidgetBuilder` |
| **widgetBuilder** | Generic - any tag (`<iframe>`, custom elements) | `HyperWidgetBuilder` |
| **widgetBuilder** on `HyperViewer` | Any tag: `<video>`, `<audio>`, `<iframe>`, custom elements | `HyperWidgetBuilder` |
| `mediaBuilder` on `HtmlToSpanConverter` | Only when you use that converter directly | `MediaWidgetBuilder` |

### Type Signatures

Expand All @@ -314,7 +315,7 @@ HTML Parser
↓
UDTNode (AtomicNode for video/iframe)
↓
widgetBuilder/mediaBuilder callback
widgetBuilder callback
↓
Flutter Widget
↓
Expand Down Expand Up @@ -360,12 +361,16 @@ widgetBuilder: (node) {

```dart
// ✅ GOOD: Fall back to default on error
mediaBuilder: (context, mediaInfo) {
try {
return VideoPlayerWidget(mediaInfo: mediaInfo);
} catch (e) {
return DefaultMediaWidget(mediaInfo: mediaInfo);
widgetBuilder: (node) {
if (node is AtomicNode && node.tagName == 'video') {
final info = MediaInfo.fromNode(node);
try {
return VideoPlayerWidget(mediaInfo: info);
} catch (e) {
return DefaultMediaWidget(mediaInfo: info);
}
}
return null;
}
```

Expand All @@ -391,7 +396,7 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {

## Future: hyper_render_media Plugin

We're planning a dedicated plugin:
A dedicated plugin is **planned, not released** — nothing named `hyper_render_media` exists on pub.dev yet, and the snippet below is a sketch of the intended API (it would take a `widgetBuilder`, since `HyperViewer` has no `mediaBuilder`):

```yaml
dependencies:
Expand All @@ -404,9 +409,9 @@ import 'package:hyper_render_media/hyper_render_media.dart';
HyperViewer(
html: htmlWithVideo,
// One-line integration with sensible defaults:
mediaBuilder: HyperMediaBuilder.videoPlayer(),
widgetBuilder: HyperMediaBuilder.videoPlayer(),
// Or customize:
mediaBuilder: HyperMediaBuilder.videoPlayer(
widgetBuilder: HyperMediaBuilder.videoPlayer(
autoplayPolicy: AutoplayPolicy.allowOnWiFi,
cachingStrategy: CachingStrategy.aggressive,
),
Expand All @@ -417,7 +422,7 @@ HyperViewer(

Have a multimedia integration example to share? Submit a PR!

- Add your example to `multimedia_example.dart`
- Add your example to the demo app (`example/lib/`)
- Update this README
- Include screenshots/GIFs if possible

Expand All @@ -427,4 +432,4 @@ MIT License - see [LICENSE](../LICENSE) for details.

---

**Questions?** Open an issue at https://github.com/vietnguyentuan/hyper_render/issues
**Questions?** Open an issue at https://github.com/brewkits/hyper_render/issues
Loading
Loading