Releases: brewkits/hyper_render
Release list
HyperRender v1.5.0 — cubic-bezier/steps timing, animatable color, memory metrics
✨ New CSS Features
cubic-bezier()andsteps()timing functions —transitionandanimationnow acceptcubic-bezier(x1, y1, x2, y2)(mapped to Flutter'sCubic),steps(n, start|end), and thestep-start/step-endkeywords (via the newHyperStepsCurve). Parameters ride onHyperTimingParams(HyperCubicBezierParams/HyperStepsParams) so the enum-based API stays source-compatible. Shorthand parsing is now paren-aware, andxcontrol points are clamped to[0, 1]per spec.- Animatable
color/background-color—@keyframesandtransitioncan now animate text and background colors.HyperKeyframegainedcolor/backgroundColor(interpolated withColor.lerp); applied viaHyperAnimatedWidgetandHyperTransitionWidget. All colors flow through the sharedStyleResolver.parseCssColorgrammar.
🩺 Diagnostics
- Debug-mode memory-pressure metrics —
didHaveMemoryPressurerecords aHyperMemoryMetricssnapshot (bytes freed, images evicted, render boxes cleared, pending image loads dropped) toHyperMemoryDebug. Zero cost in release builds. AddedLazyImageQueue.pendingCount.
🐛 Bug Fixes
- #12 — zero-width
BorderSide+border-radiusassertion — an element withborder-radiusand an uneven border (e.g.border-left: 5px solidwith the other sides0px) produced a hairlineBorderSide(width: 0, style: solid)that Flutter rejects under a non-zero radius. Zero-width sides now map toBorderSide.nonevia a sharedcssBorderFromStylehelper, fixing both the flex-child and flex-container paths; the latter also now honoursborder-style: none. - Color parsing hardened —
StyleResolver.parseCssColorreturnsnullinstead of throwing on malformed hex (#zzz) or out-of-int64rgb()/rgba()channels, important now that color parsing runs on arbitrary@keyframesvalues.
📦 Packages
hyper_render1.5.0,hyper_render_core1.5.0hyper_render_html,hyper_render_markdown,hyper_render_highlight,hyper_render_clipboard,hyper_render_devtools,hyper_render_math1.5.1 — a packaging patch over 1.5.0 that removes a dev-onlydependency_overridesblock from the published archives (no code changes).
🧪 Tests
1,827 passing (golden tests excluded) — added unit, integration, system, performance, stress, and security/fuzz coverage for the new timing and color features.
HyperRender v1.4.0 — CSS transitions, aspect-ratio, infinite animation, float carryover paint
✨ New CSS Features
aspect-ratio:W/Hratio syntax (16/9) and bare-number syntax (2.5) parsed and applied to<img>/<video>sizing — overrides the image's intrinsic ratio when only one dimension (or neither) is specified in CSS/HTML.transitionexecution: CSStransitionis now actually animated, not just parsed. NewHyperTransitionWidgetdetectsopacity/transformchanges across rebuilds and animates them over the declared duration and timing function viaAnimatedOpacity/AnimatedContainer. Wired into the render pipeline through_maybeAnimateinhyper_render_widget.dart.animation-iteration-count: infinite: now loops viaAnimationController.repeat()instead of a one-shotforward(). Added a dedicatedalternateflag toHyperAnimatedWidgetsoanimation-direction: alternate/alternate-reverseno longer gets conflated with plainreverse.- Cross-Chunk Float Carryover — paint completion: the previously-unused
imagePixelOffsetonFloatCarryoveris now consumed at paint time. A tall floated image that overhangs a virtualized section boundary continues painting from the correct offset in the next chunk instead of the gap being left blank. - ~25 previously-silent CSS properties now resolved:
white-space,word-spacing,text-transform,text-decoration-color,min/max-width/height,overflow/overflow-x/overflow-y,border-top/border-right/border-bottom/border-color/border-width,animationshorthand and allanimation-*sub-properties,transition,aspect-ratio. These hadComputedStylefields but no resolver case, so values were silently dropped.
🐛 Bug Fixes
remunits silently failed —_parseLengthhad norembranch;'2rem'.endsWith('em')matched theembranch instead, producing an unparseable'2r'. Added the missingremcase ahead ofem.text-decorationincorrectly marked as inherited — not an inheritable CSS property per spec; child elements were wrongly picking up a parent's underline/strikethrough. Removed frominheritFrom(); added correct inheritance oftext-transforminstead.- Linear-gradient diagonal corners wrong — for
to top right/to bottom right/to top left/to bottom left, onlybeginwas corrected, leavingendon the wrong single axis. filteronly composed the first two entries — multiple chained filters (e.g.blur() brightness() contrast()) silently dropped everything past the second; now folds over the full list.border: noneleft a 1px border — shorthand parsing didn't zero the width when style wasnone.- Division-by-zero in
line-height— unitless line-height against a zero/null parent font size could divide by zero. - Float layout list-spread bug —
performLayoutiterated[..._leftFloats, ..._rightFloats], allocating a new list every line; replaced with two direct loops. HyperTextSelectionmissingoperator==— every selection update looked "changed" even when identical, causing redundant repaints.setGlobalTextCacheSizeleaked oldTextPainters on resize — old cache was dropped without disposal.- Delta adapter
indentoperator-precedence bug —((attributes['indent'] as int?) ?? 0 + 1)added 1 to the default before the null-check applied; also hardened theintcast tonum.toInt()against JSON-decoded doubles. - Delta underline+strikethrough mutually exclusive — applying both attributes silently dropped underline; now combined via
TextDecoration.combine. - HTML adapter shared mutable default styles —
_defaultStyles[tagName]returned a sharedComputedStyleinstance reused (and mutated) across unrelated nodes; now.copyWith()'d per node. - Markdown adapter didn't normalize
\r\n/\rline endings before splitting, breaking line-based heuristics on Windows-authored content. - Markdown adapter double-applied syntaxes when both
extensionSetand explicitblockSyntaxes/inlineSyntaxeswere supplied. _prebuiltDocumentfast-path left stale cached state (_docKeyframes,_cachedEffectiveConfig,_sectionHashes, page count, section boxes) when switching to a pre-parsed AST document.- pub.dev pana INFO: suppressed the
cacheExtentdeprecation hint with// ignore: deprecated_member_use, consistent with the existing SDK-version-gap pattern.
📝 Documentation
doc/LIMITATIONS.md: removed stale "not parsed" entries forbackground-position/background-repeat/list-style-*(shipped in v1.3.1); added v1.4.0 entries; corrected the async-parsing description (Future.microtask, notcompute()isolate).doc/ROADMAP.md: marked Cross-Chunk Float Carryover paint offset, CSStransitionexecution,animation-iteration-count: infinite, andaspect-ratioas completed.doc/CSS_PROPERTIES_MATRIX.md: corrected stale entries forbackground-position/background-repeat/transition; addedaspect-ratio,object-fit,animation-iteration-count,animation-directionrows.
📦 Published packages
All 8 packages published to pub.dev: hyper_render, hyper_render_core, hyper_render_html, hyper_render_markdown, hyper_render_highlight, hyper_render_clipboard, hyper_render_devtools, hyper_render_math.
Test count: 1,782 passing, 0 failing.
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.