Releases: brewkits/hyper_render
v1.3.4
🔧 Fixes & Optimizations
- Static Analysis Compliance: Suppressed deprecated
SizeTransition.axisAlignmentlints with// ignore: deprecated_member_useto maintain backwards compatibility with older Flutter SDKs (>=3.10) while securing 160/160 points on pub.dev. - Dependency Widening: Widened
share_plusdependency constraint to^12.0.2 || ^13.0.0inhyper_render_clipboardand root packages to allow compatibility with latest stable release.
HyperRender v1.3.3 — object-fit, CSS attribute selectors, memory-pressure callback
HyperRender v1.3.3 — Production Readiness Release
A production-readiness release focusing on new CSS properties, rendering correctness, memory management, and publish tooling modernisation. 973 tests passing across all 8 packages.
✨ New CSS Properties
object-fit:cover,contain,fill,none,scale-down— applies to<img>elements. Controls how the image content is resized to fit its layout box. Previously, images incorrectly fell through to thebackground-sizemapping;object-fitnow takes priority as the semantically correct property for replaced elements.- CSS Attribute Selectors: Full support for
[attr],[attr="val"],[attr^="val"],[attr$="val"],[attr*="val"],[attr~="val"],[attr|="val"]— enables attribute matching in custom CSS rules and<style>blocks.
✨ New Features
onMemoryPressurecallback:HyperViewernow exposes an optionalVoidCallback? onMemoryPressureparameter (available on all constructors: default,.delta,.markdown,.fromNode). Invoked after HyperRender clears its internal TextPainter, image, and painting caches in response todidHaveMemoryPressure. Enables host apps to release their own resources (video players, download queues, custom caches) in the same memory-pressure cycle.
🐛 Bug Fixes
rgb()/rgba()color parsing: csslib'sFunctionTerm.span?.textonly covers the argument list (e.g."0, 255, 0)") — the function name is stored separately inFunctionTerm.text("rgb"). The old inner-iteration code therefore produced"0, 255, 0)"which_parseColorcouldn't recognise. Fixed by reconstructing the canonicalname(args)form fromFunctionTerm.text+span?.text.- Ref-counted global TextPainter cache: Multiple
HyperViewerinstances with differenttextPainterCacheSizeno longer clobber each other's cache size. Each viewer registers its requested size; when a viewer disposes, the largest remaining request is applied. Prevents subtle layout regressions in multi-viewer scenarios. imageConcurrencyconfig wired:HyperRenderConfig.imageConcurrencynow actually drivesLazyImageQueue.instance.maxConcurrent. Previously the config field was read but never applied to the singleton queue.HyperAnimationControllerdispose hardening: Prevents controller double-dispose race conditions during fast widget rebuilds.
🔧 Improvements
- Float carryover
imagePixelOffset:FloatCarryovernow carries animagePixelOffsetfield computed from the originating section's layout. When a tall float image overhangs a virtualized section boundary, the offset records how many pixels were already painted — enabling future rendering of the remaining portion without repeating the top. FloatCarryovervalue equality: Added properoperator==/hashCodeso the_onFloatCarryovercomparison correctly detects changes (including the newimagePixelOffset).ComputedStyle.objectFit: NewObjectFitenum (cover,contain,fill,none,scaleDown) added to the computed style model and resolved from CSS declarations.
📱 Example App — Reader Demo Polish
- Enhanced bookmarking with persistent state
- Multi-theme reader (sepia, dark, custom)
- Table of Contents side panel with smooth page transitions
- Reading progress indicator
🛠️ Publish Tooling
publish.shsimplified: Instead of swapping entire pubspec files or patching path deps, now only removes thedependency_overrides:block beforedart pub publish— thedependencies:section already has the correct version constraints (e.g.hyper_render_core: ^1.3.3). Restores original pubspec viagit checkout --after each package.prepare_publish.shupdated: Same approach — stripsdependency_overridesblocks instead of the old full-file swap withpubspec_publish_ready.yaml.
📝 Documentation
- ROADMAP corrected:
list-style-typeandlist-style-positionwere marked as incomplete ([ ]) despite being fully shipped in v1.3.1. Now correctly marked as[x].object-fitmoved from Backlog to Completed. - COMPARISON_MATRIX: Updated with latest feature parity data vs flutter_html, flutter_widget_from_html.
- MIGRATION_GUIDE: Updated for 1.3.2 → 1.3.3 (new
onMemoryPressureparameter,object-fitCSS support). - PERFORMANCE_TUNING: Added
imageConcurrencytuning guidance.
📦 Packages Updated
| Package | Version |
|---|---|
hyper_render |
1.3.3 |
hyper_render_core |
1.3.3 |
hyper_render_html |
1.3.3 |
hyper_render_markdown |
1.3.3 |
hyper_render_highlight |
1.3.3 |
hyper_render_clipboard |
1.3.3 |
hyper_render_devtools |
1.3.3 |
hyper_render_math |
1.3.3 |
🧪 Test Summary
- 973 tests passing across all packages
- New test files:
packages/hyper_render_core/test/object_fit_test.dart— object-fit CSS resolutiontest/review_fixes_v1_3_3_test.dart— comprehensive v1.3.3 regression teststest/v1_3_3_comprehensive_testing_test.dart— integration + edge case suite
70 files changed, 1853 insertions(+), 412 deletions(-)
Full changelog: https://github.com/brewkits/hyper_render/blob/main/CHANGELOG.md
HyperRender v1.3.2
[1.3.2] - 2026-05-18
Bug Fixes (Critical)
- [DEADLOCK] LazyImageQueue no longer deadlocks on a synchronously-throwing loader — if a user-supplied
HyperImageLoaderthrew before invoking its onLoad/onError callback,_activewas never decremented; aftermaxConcurrentsuch throws the queue stopped processing every subsequent image until app restart._startLoadnow wraps the loader call in try/catch and routes any synchronous exception through the same idempotent error path used by the async callback. - [SECURITY] Sanitizer now validates ALL URL-bearing attributes — previously only
hrefandsrcwere checked, leavingposter,data,cite,background,longdesc,usemap,manifest,xlink:href,formaction,action,icon, andsrcsetas XSS bypass vectors (e.g.<video poster="javascript:...">). AddedurlBearingAttributesconstant and routes every match throughisSafeUrl.srcsetis split into candidates and each candidate's URL is validated independently. - [SECURITY]
isTapno longer fires when the pointer never went down inside the widget —handleEventpreviously treateddownPosition == nullas a valid tap, so a finger swiping into the widget from outside and lifting up would triggeronLinkTapon whatever fragment was under the lift point. Now requires BOTH a recorded down position AND a movement withintapSlop. - [BUG-1] Images no longer permanently disappear after a Low Memory Warning —
clearMemoryCaches()disposed the image cache but never re-triggered_loadImages(). Visible images were stuck in the empty-placeholder state until the user scrolled the section out of view and back to force a detach+attach cycle. The cache-clear path now re-enqueues image loads viaLazyImageQueueso visible images reload through the normal priority pipeline. - [BUG-2]
_hashSectionnow invalidates on attribute changes — the previous fingerprint only hashed text content + child count, so changing only<img src="a.jpg">→<img src="b.jpg">(or class/id/style) produced the same hash._mergeSectionswould silently reuse the staleDocumentNode, freezing dynamic UI at the first rendered version. The new recursive hash walks the subtree and includes tagName, type, text, atomic src/alt, all attributes (keys sorted), and per-depth child counts. - [BUG-3] Eliminated 1-frame layout flash with dangling floats —
_onFloatCarryoverpreviously deferred the cross-section update viaaddPostFrameCallback + setState, so section N+1 always laid out once with empty initialFloats before the corrected pass. AddedonRenderBoxReadycallback onHyperRenderWidgetandVirtualizedChunk;_HyperViewerStatekeeps aMap<int, RenderHyperBox>registry and pushes new floats directly onto section N+1's RenderObject during section N's layout, so the pipeline owner picks up the change in the same frame. - [C-1] HyperSelectionOverlay now forwards
config,pluginRegistry,enableComplexFilters— plugins, custom link schemes, keyframe animations and filter settings were silently ignored in sync+selectable and paged+selectable modes. All three params are now accepted byHyperSelectionOverlayand forwarded to the innerHyperRenderWidget. - [C-2] Fixed GPU memory leak in image cache —
_imageCachewas missing anonEvictcallback, soui.ImageGPU textures were never disposed when entries were evicted from the LRU. AddedonEvict: (ci) => ci.image?.dispose()to free GPU memory promptly on eviction. - [C-3] Removed dead
_parseIsolate/_parseReceivePortcode — these fields were declared but never assigned, making_cancelParsing()a no-op. Cleaned up unuseddart:isolateimport and fields;_parseIdcounter remains the mechanism for discarding stale parse results. - [C-4] TextPainter global cache now respects
HyperRenderConfig.textPainterCacheSize— was hardcoded to 500 regardless of config (default 5000). AddedRenderHyperBox.setGlobalTextCacheSize()static method;HyperViewercalls it ininitStateanddidUpdateWidget.
Bug Fixes (High)
- [H-1]
HyperRenderConfig.operator==andhashCodenow includeuseMicrotaskParsing— changing only this field no longer fails to trigger a re-parse. - [H-2]
ComputedStyle.copyWith()now copies_explicitlySet— previously the result had an empty explicit-set, causinginheritFrom()to overwrite all copyWith'd properties with parent styles, breaking the CSS cascade. - [H-3]
_containsFloatChilddetectsfloat:left(no space) and Bootstrap/Tailwind class names —float:left,float-left,float-right,float-start,float-end,pull-left,pull-rightare now detected, preventing incorrect section splits in virtualized mode. - [H-4]
isSafeUrl()blocksfile:,mhtml:, andabout:schemes — these can access local filesystem, trigger MHTML exploits, or enable sandbox-escape viaabout:blankon Android/iOS.
Bug Fixes (Medium)
- [M-1]
_effectiveConfigis now cached — was allocating a newHyperRenderConfigon everybuild()call (every scroll frame). Cache is invalidated whenrenderConfig,allowedCustomSchemes, or document keyframes change. - [M-2]
HyperViewer.fromNodenow acceptspluginRegistryandonError— previously hardcoded tonull, making plugins and error handling unavailable for pre-parsed AST consumers. - [M-3]
_buildPagedContentno longer allocates a discardedHyperRenderWidget— restructured to if/else so only one widget is built per page in selectable mode. - [M-4]
_TextPainterKeynow includeswordSpacing— two fragments with identical text but differentword-spacingno longer share the sameTextPainter, preventing incorrect layout widths.
Performance (Low)
- [L-1]
LazyImageQueue._findQueuedis now O(1) — added_urlToQueuedsecondary index; previously O(N) causing O(N²) batch behavior with many simultaneous image loads. - [L-2]
_hasDetailFragmentsflag replaces O(N) scan —performLayoutno longer scans all fragments to check for<details>elements; flag is set during tokenization.
Fixes (Low)
- [L-3]
_splitIntoSectionsno longer overwrites existing node parents — changedchild.parent = currenttoif (child.parent == null) child.parent = currentto avoid corrupting ancestor-chain traversal on reused section nodes. - [L-4] Removed dead
_draggingHandlefield fromHyperSelectionOverlayState.
Correctness & Robustness
- Hash collision resilience on Web —
_accumulateHashPartsnow also mixes intext.lengthfor everyTextNode, significantly reducing the chance that two long-but-distinct strings hash to the same slot on the JS target (whereObject.hashAllhas weaker dispersion than the Dart VM). computeMinIntrinsicWidthhandles icon fonts, emoji, and dingbats — the previous "longest-by-char-count word" heuristic miscalculated when a single PUA glyph (Material Icons, Font Awesome) or emoji renders far wider than a Latin letter. When the fragment contains any code point in U+E000–U+F8FF, U+2600–U+27BF, or U+1F000+, the entire fragment is measured instead of just the longest word.RenderHyperBox.detach()now cancels shimmer state — aListViewitem that detached mid-shimmer (scrolled out of cache) and later re-attached kept a stale_shimmerEpoch, producing a 1-frame phase jump on re-mount. The frame callback is now cancelled and_shimmerEpochreset.
New
HyperRenderConfig.useRepaintBoundary(defaulttrue) — opt out of the outer-sectionRepaintBoundarywrapper.RenderHyperBoxis already an internal repaint boundary, so this is mostly an escape hatch for very low-RAM Android devices (≤ 1.5 GB) rendering image-heavy long documents with a custom smallvirtualizationChunkSize, where many concurrent GPU layers could exhaust VRAM before the texture cache evicts.
Second-Pass Senior Review (2026-05-18 → 2026-05-19)
A second multi-disciplinary review (PM/BA/SA/principal mobile) surfaced a further batch of issues addressed in this same release. Highlights:
Security
UrlSafetyconsolidated inhyper_render_core/util/url_safety.dart— rootHtmlSanitizer.isSafeUrland thehyper_render_markdownsub-package's URL gate previously had independent copies that drifted: the sub-package missedfile:/mhtml:/about:. Both now delegate to the shared helper; no future drift is possible.HtmlAdapterdefence-in-depth URL gate —<img src>and<a href>are now routed throughUrlSafety.isSafeeven when the upstreamHtmlSanitizeris bypassed (callers that invokeHtmlAdapter().parse()directly or render withsanitize: false). Blockedhrefcollapses to#; blockedsrccollapses to''.hyper_render_clipboardfilename hardening (path traversal) —_getFilenameFromUrlalready stripped path separators from URL-decoded filenames, butsaveImageBytes(filename:)andshareImageBytes(filename:)concatenated caller-supplied strings raw. Every save/share path now runs through a single_sanitiseFilenamehelper.- Markdown inline HTML pre-sanitised — when
HyperViewer.markdown(sanitize: true)(default) is used withenableInlineHtml: true(default), raw<script>/<style>/<iframe>blocks are now stripped viaHtmlSanitizerbefore reaching the markdown parser, so they can no longer flash as visible text or become a self-rendering plugin's XSS surface.
Layout & Selection
- Unbounded-width crash fixed —
RenderHyperBox.performLayoutand_computeHeightForWidthclamp_maxWidthto a finite fallback when the constraint isdouble.infinity(Row without Expanded, horizontalSingleChildScrollView). Before this,_FlexFragment.layoutpropagated infinity into aBoxConstraints(minWidth: ∞)and tripped Flutter'sminWidth < double.infinityassertion. text-overflow: ellipsisno longer leaks hidden text via copy —Fragment.ellipsisVisibleLengthtrack...
v1.3.1 — Decouple plugins, CSS list-style & background, selection performance
⚠️ Migration from 1.3.0
hyper_render_clipboard and hyper_render_math are now opt-in — no longer bundled in the root hyper_render package. Add them explicitly if needed:
dependencies:
hyper_render: ^1.3.1
hyper_render_clipboard: ^1.3.1 # image copy/save/share
hyper_render_math: ^1.3.1 # LaTeX/MathMLIf you don't use either feature, just bump the version — no other changes required.
What's New
New CSS Properties
list-style-type:disc,circle,square,decimal,decimal-leading-zero,lower-alpha,upper-alpha,lower-latin,upper-latin,lower-roman,upper-roman,nonelist-style-position:inside/outsidelist-styleshorthandbackground-repeat:repeat,repeat-x,repeat-y,no-repeat,space,roundbackground-position: keyword and percentage values
Performance
- Selection drag:
getSelectionRects()cached (1× per event, was 3×) - Auto-scroll speed now proportional to finger distance from edge
HyperTeardropHandlePainterdeduplicated — exported fromhyper_render_core
Bug Fixes
- Edge-to-edge images:
width: 100%now truly fills container (no internal margin) - Android build: no longer requires
compileSdk = 35workaround for default usage
Packages & Docs
- All 7 sub-package CHANGELOGs updated with
[1.3.1]entries - All READMEs: ecosystem cross-link table, corrected version references, fixed plugin API docs
- Migration guide updated for 1.3.0 → 1.3.1
Full changelog: https://github.com/brewkits/hyper_render/blob/main/CHANGELOG.md
v1.3.0 — Math plugin, CSS cascade fix, customCss for Markdown/Delta
What's New in v1.3.0
This is a significant release that ships a new first-party math rendering package, fixes two CSS correctness bugs reported by users, completes the hyper_render_math package with full documentation and examples, and consolidates all improvements from the v1.2.x patch cycle.
🆕 New Package: hyper_render_math
A first-party plugin package for rendering LaTeX / MathML formulas inside HyperRender documents.
MathNodePluginhandles both<math>and<latex>tags via theHyperNodePluginAPI- Renders using
flutter_math_fork— KaTeX-quality output - Supports display mode (block, centered) and inline mode (flows inside text lines) via
isInline - Formula source passed through
srcattribute or tag text content - Ships with LICENSE, CHANGELOG, example app, and pub.dev topics
final registry = HyperPluginRegistry()..register(const MathNodePlugin());
HyperViewer(
html: r'<math src="x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}" />',
pluginRegistry: registry,
)🐛 Bug Fixes
Issue #9 — CSS width/height overridden by HTML attributes (#9)
Root cause: render_hyper_box_layout.dart used node.intrinsicWidth ?? node.style.width, so HTML presentation attributes (width="345") always shadowed CSS rules (img { width: 600px }). This violated the CSS cascade spec where author stylesheets take priority over element attributes.
Fix: Swapped priority to node.style.width ?? node.intrinsicWidth at all 4 layout paths in render_hyper_box_layout.dart — normal layout, secondary layout pass, float layout (image loaded), and float layout (image loading placeholder).
<!-- Before: width was 345px regardless of CSS -->
<!-- After: CSS wins, width is 600px -->
<img src="photo.jpg" width="345" height="345">
<style>img { width: 600px; height: 300px; }</style>Issue #8 — customCss ignored for Markdown and Delta content (#8)
Root cause: In hyper_viewer.dart, the cssToApply variable was only populated inside the if (contentType == html) block. For Markdown and Delta content types, customCss was silently discarded — the StyleResolver received an empty string.
Fix: Initialized cssToApply = widget.customCss ?? '' before the content-type check, so all three content types (html, markdown, delta) correctly receive and apply custom CSS rules.
// Now works for Markdown and Delta too
HyperViewer(
content: markdownContent,
contentType: HyperContentType.markdown,
customCss: 'a { color: #0066cc; } h1 { font-size: 28px; }',
)Additional fixes
<hr>returned wrong node type:html_adapter.dartnow returns aBlockNodewith a solid bottom border instead ofLineBreakNodeAtomicNode.svg()wrong constructor params: Fixed named parameter mismatch inhtml_adapter.dart- Duplicate
[1.3.0]CHANGELOG headers: Consolidated across all sub-packages
🏗️ Infrastructure & Quality
- Tests relocated to sub-packages:
html_adapter_test.dart,markdown_adapter_test.dart,code_highlighter_test.dartmoved into their respective packagetest/directories hyper_render_highlightexample: Corrected class reference toDefaultCodeHighlighterpublish.sh/prepare_publish.sh: Updated for v1.3.0 and now includehyper_render_mathin all publish/analysis steps- All READMEs updated to reference
^1.3.0 - 1,637 tests passing, 0 failures (+ 5 math package tests)
flutter analyze— 0 issues across all packages
📦 Packages in this release
| Package | Version |
|---|---|
hyper_render |
1.3.0 |
hyper_render_core |
1.3.0 |
hyper_render_html |
1.3.0 |
hyper_render_markdown |
1.3.0 |
hyper_render_highlight |
1.3.0 |
hyper_render_clipboard |
1.3.0 |
hyper_render_devtools |
1.3.0 |
hyper_render_math |
1.3.0 (new) |
Upgrading
dependencies:
hyper_render: ^1.3.0
# or individual packages:
hyper_render_core: ^1.3.0
hyper_render_math: ^1.3.0 # newNo breaking changes. Drop-in upgrade from v1.2.x.
v1.2.2 — 8 Bug Fixes + Android Build Fix
What's Changed
🐛 Bug Fixes
- Android build failure (
example/android/build.gradle.kts):irondash_engine_context 0.5.5compiled against android-31 conflicts withandroidx.fragment:1.7.1(minCompileSdk=34). Addedsubprojects { compileSdk = 35 }override. Closes #5. - SVG invisible with
sanitize: true(html_sanitizer.dart): Added atomic SVG sanitization path that preserves structure while stripping<script>and dangerous attributes. HyperRenderConfigidentity-compare (hyper_render_config.dart): Addedoperator==/hashCode— prevents unnecessary re-layouts on every frame when_effectiveConfigmerges@keyframesinto a new object.selectabletoggle ignored (hyper_viewer.dart):didUpdateWidgetnow creates/disposesVirtualizedSelectionControllercorrectly whenselectablechanges.- Deep-link taps silently blocked (
hyper_viewer.dart):_safeOnLinkTapnow checks bothallowedCustomSchemesANDrenderConfig.extraLinkSchemes. - CSS change bypassed section cache (
hyper_viewer.dart):_sectionHashesnow reset indidUpdateWidgetwhencustomCsschanges. - Markdown/Delta rendered as single section (
hyper_viewer.dart): Added_splitIntoSections()— large Markdown/Delta docs now chunk correctly in virtualized/paged mode. renderConfigchange partially detected (hyper_viewer.dart):didUpdateWidgetnow uses full value equality instead of only comparingvirtualizationChunkSize.- CSS float class names not detected (
html_adapter.dart):_containsFloatChildnow recognises Bootstrap/Tailwind patterns (float-left,pull-right,alignleft, etc.).
🔧 CI Fixes
benchmark.yml: Fixed JSSyntaxErrorfrom backtick-quoted fixture names inside template literal — useprocess.envinstead of direct interpolation.benchmark.yml+golden.yml: Addedpull-requests: writepermission for comment-posting steps.test.yml: Guard sub-package test steps with[ -d test ]check —hyper_render_markdown,hyper_render_highlight,hyper_render_clipboardhave no test directories.
Full Changelog: https://github.com/brewkits/hyper_render/blob/main/CHANGELOG.md
v1.2.0 — Plugin API, Paged Mode, Incremental Layout
What's new in v1.2.0
✨ New Features
Plugin API — First-class extensibility for custom HTML tags:
final registry = HyperPluginRegistry()
..register(MyChartPlugin())
..register(MyMapPlugin());
HyperViewer(html: content, pluginRegistry: registry)Paged mode — Built-in e-book / reader UI:
final controller = HyperPageController();
HyperViewer(
html: content,
mode: HyperRenderMode.paged,
pageController: controller,
)Incremental layout — Dirty-flag fingerprinting means ~90% fewer rebuilds for live-updating feeds.
CSS @Keyframes — Full animation support with custom keyframe lookup from <style> tags.
♿ Accessibility (WCAG 2.1 AA)
<img alt="…">→ discreteSemanticsNodeat image rect (WCAG 1.1.1)aria-labelon<a>elements honored (WCAG 4.1.2)
🏗️ Refactor
- Removed 31 duplicate files from root
lib/src/— canonical source now inhyper_render_core LazyImageQueuesingleton consolidated (single shared instance)
🐛 Bug Fixes
- XSS:
javascript:URLs blocked in HTML, Markdown, and Delta adapters display:noneelements no longer produce layout fragments- Selection-vs-scroll conflict resolved
- Context menu position clamped to visible bounds
📦 Infra
- All packages:
sdk >=3.5.0,csslib ^1.0.2,flutter_lints ^5.0.0 share_plus ^12.0.0, Android AGP 8.12.1, 16 KB page alignment- 36 new tests for v1.2.0 features
Migration
See MIGRATION_GUIDE for details. The public API is backwards-compatible — upgrading from v1.1.x requires no code changes.
Upgrade
dependencies:
hyper_render: ^1.2.0v1.1.2 — Binary Search Selection · DevTools v1.0.0 · 3-Pipeline CI
What's New
⚡ O(log N) Binary Search Text Selection
Hit-testing now uses _lineStartOffsets[] precomputed at layout time. Selection stays instant even on 1,000-line documents — no more O(N) linear scan per touch event.
🈶 Ruby Clipboard Format
Fully-selected ruby fragments are copied as base(ふりがな) (e.g. 東京(とうきょう)). Partial selections copy base text only, keeping character offsets consistent. 5 ruby selection pipeline bugs fixed that caused offset desynchronisation for all content after a ruby fragment.
🔭 hyper_render_devtools v1.0.0 — First Full Release
- UDT Tree inspector — browse the full document model in Flutter DevTools
- Computed Style panel — see inherited vs. declared values and specificity winners
- Float region visualizer — highlight floated-block boundaries in layout
- Demo mode — explore the inspector without a live app
🔁 3-Pipeline CI Architecture
| Pipeline | Trigger | Purpose |
|---|---|---|
Pre-flight (analyze.yml) |
Every PR/push | dart format + flutter analyze --fatal-infos, skip docs-only |
Core Validation (test.yml) |
PR: selective per-package on ubuntu-22.04 | Fast gate < 5 min |
Core Validation (test.yml) |
Push to main: 3-OS × 2-channel matrix | Full platform coverage |
Visual regression (golden.yml) |
Every PR/push | Pixel-stable, pinned Noto fonts |
Layout regression (benchmark.yml) |
Every PR/push | Hard 16 ms / 60 FPS budget per fixture |
🧪 Golden Tests — Float · RTL · CJK Coverage
9 new pixel-stable test cases:
- Float layout:
float_left,float_right,float_clear - RTL/BiDi:
rtl_arabic,rtl_hebrew,rtl_mixed - CJK + Ruby:
cjk_ruby,cjk_kinsoku,float_cjk
📊 Layout Regression Benchmark
6 HTML fixtures (simple paragraph → 100-paragraph article) with hard millisecond budgets run on every PR. Any fixture exceeding 16 ms (60 FPS) fails the build.
Upgrade
dependencies:
hyper_render: ^1.1.2No breaking changes. All previous HyperViewer APIs remain identical.
Full details in CHANGELOG.md.
v1.1.0
What's new
Rendering
- Premium visual polish: precision borders, skeleton shimmer gradient, adaptive selection colors
- Typography overhaul — font features (ligatures, proportional figures), consistent
TextHeightBehavior, retina-readyFilterQuality.mediumon all images - Anti-aliasing explicitly enabled on all paint operations
Bug fixes
- Copy/paste across block elements (
<li>,<h3>,<p>) now correctly inserts newlines - Fix
_sameLinkContext()guard — incorrect link tap targets when fragments merge across<a>boundaries - Fix float crash on unconstrained parent width
- Fix
TapGestureRecognizerleak when document is replaced - Fix
_fragmentChildMapO(N) scan → O(1) lookup in paint cycle - Fix
_nodeRectCacheO(N²) accessibility rect computation → O(N) - Fix
display:nonenot respected in_tokenizeNodeand_collectAtomicChildren
pub.dev
- All sub-packages bumped to v1.1.0 (html, markdown, highlight, clipboard, devtools)
screenshots:field added with 6 demo GIFs visible in pub.dev galleryvector_mathupdated to^2.2.0- README rewritten with absolute image URLs (GIFs now render correctly on pub.dev)
- Package description optimized for search discoverability
Tests
- 678 tests passing, 0 failures
- Added virtualized mode integration tests
- Added real URL assertions to link tap tests
- Updated golden images to match current rendering output
Demos
- New CJK languages demo
- Why HyperRender showcase with live 16-feature comparison matrix
Install
dependencies:
hyper_render: ^1.1.0Quick start
import 'package:hyper_render/hyper_render.dart';
HyperViewer(
html: articleHtml,
onLinkTap: (url) => launchUrl(Uri.parse(url)),
)HyperRender v1.0.0
HyperRender v1.0.0 — Initial Public Release
A custom RenderObject-based engine that renders HTML, Markdown, and Quill Delta natively on Flutter Canvas — without WebViews, without widget trees.
What's included
- CSS Float layout — text wrapping around floated images, architecturally impossible in widget-tree renderers
- CJK typography — Ruby/Furigana with proper centering, Kinsoku line-breaking across the full line
- Continuous text selection — select across headings, paragraphs, and table cells without widget-boundary breaks
- CSS Variables +
calc()— full custom property cascade - Flexbox and CSS Grid layout
- Smart table layout — two-pass W3C column-width algorithm, colspan/rowspan, three overflow strategies
<details>/<summary>— collapsible sections, no JavaScript required- Multi-format input — HTML, Markdown, Quill Delta
- Built-in XSS sanitization —
javascript:,vbscript:, SVG data URIs,expression()blocked by default - Virtualized rendering —
ListView.buildermode for large documents - Screenshot export —
GlobalKey.toPngBytes() - WebView fallback via
HtmlHeuristics.isComplex()
Packages published
| Package | pub.dev |
|---|---|
hyper_render |
https://pub.dev/packages/hyper_render |
hyper_render_core |
https://pub.dev/packages/hyper_render_core |
hyper_render_html |
https://pub.dev/packages/hyper_render_html |
hyper_render_markdown |
https://pub.dev/packages/hyper_render_markdown |
hyper_render_highlight |
https://pub.dev/packages/hyper_render_highlight |
hyper_render_clipboard |
https://pub.dev/packages/hyper_render_clipboard |
Benchmarks (macOS Desktop, Apple Silicon, Flutter release mode)
| Document | Parse time |
|---|---|
| 1 KB | 27 ms |
| 10 KB | 69 ms |
| 50 KB | 276 ms |
Mobile performance has not been independently verified — run benchmarks on your own hardware.