Repository navigation
Conversation
Adds the extension surface an optional feature needs to render node types core does not: a global registry that publishes an immutable snapshot, hooks for node renderers and block segments, a keyed style extension map on StyleConfig, and a generic per-view PluginEvent channel. Core keeps no knowledge of any particular plugin. The Class.forName lookup for MathInlineSpan in HTMLGenerator gives way to a PluginInlineSpan interface, and the dead IS_MATH_ENABLED flag is gone. Segment signatures are computed by core from plugin-supplied bytes rather than invented by plugins, because an unstable signature means wrong view recycling on every token of streamed content. "No plugin installed" is the default state, so every hook has a fallback: unclaimed latex nodes echo their own source, delimiters included, and a plugin that declines a node has it rendered as text rather than dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MarkdownStyleBuilder gains updatePluginPatch, so a plugin module can ship its own style block as an extension function that merges into an earlier one the way core's own blocks do, and the resolved value lands in StyleConfig's extension map. Patches are data classes rather than lambdas: style layers are compared for equality to decide whether to recompose, and a lambda is never equal to an identically written one. Adds MarkdownStyle.resolveStyleConfig, which does outside a composition what EnrichedMarkdownText does inside one. A plugin module otherwise has no way to assert on what its DSL produced without waiting on a Dispatchers.Default hop the test scheduler does not control. EnrichedMarkdownText gains onPluginEvent, appended last so no positional call site shifts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LaTeX math moves out of the core rendering path into a published artifact of its own, so an app that renders no math pays neither the download nor RaTeX's missing 32-bit x86 native library. Ported from the work on #814 and adapted to the plugin seam: display math is a block segment, inline and mid-line math are node renderers, and errors travel on core's per-view event channel as LatexErrorEvent. Every entry into RaTeX goes through one helper that catches LinkageError as well as Exception. A missing native library arrives as an Error rather than an Exception, so without that the source-echoing fallback would not have held on exactly the ABI that motivated splitting math out. RaTeXUnavailableTest pins the behaviour: a JVM test host has no native library either. The Compose DSL ships from here as extension functions on MarkdownStyleBuilder, compiled against :compose with compileOnly, so the published POM carries :ui and ratex-android and no Compose at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The example app installs LatexMathPlugin from its Application, turns on the latexMath flag, styles math through the DSL on two screens so the difference is visible, and logs LatexErrorEvents. Its sample markdown covers display, inline and mid-line math, a wide equation that scrolls, and two deliberately broken expressions that fall back to their source. README documents the artifact, the install call, the two switches it takes to render math, and onPluginEvent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # apps/android-example/app/src/main/java/swmansion/enriched/markdown/android/example/TextScreen.kt # packages/enriched-markdown-android/README.md # packages/enriched-markdown-android/build.gradle # packages/enriched-markdown-android/compose/src/main/java/com/swmansion/enriched/markdown/compose/EnrichedMarkdownText.kt # packages/enriched-markdown-android/compose/src/main/java/com/swmansion/enriched/markdown/compose/MarkdownStyle.kt # packages/enriched-markdown-android/compose/src/main/java/com/swmansion/enriched/markdown/compose/MarkdownStyleBuilder.kt # packages/enriched-markdown-android/compose/src/main/java/com/swmansion/enriched/markdown/compose/MarkdownStyleLayer.kt # packages/enriched-markdown-android/compose/src/main/java/com/swmansion/enriched/markdown/compose/style/StyleConfigMerger.kt # packages/enriched-markdown-android/settings.gradle # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/segments/RenderedSegment.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/segments/SegmentViewCreators.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt
…ocks Plain-text copy left the object-replacement character in place of inline equations; plugin spans now supply their own plain text. Display math's long-press menu never fired over most of the block because the scroll view swallowed the touch; it now listens on the viewport-filling wrapper and uses the same ContextMenuPopup as tables.
Freeze the plugin registry on the first render and drop runtime uninstall and change listeners. Resolve math style defaults from the Context like core does, pass MathContainerView its config through the constructor, remove the empty consumer-rules.pro, and tighten the plugin tests.
- Make the RendererFactory constructor internal; only core builds one. - Move StyleConfig.extensions after spoilerStyle so positional callers keep working. - Drop PluginInlineSpan.toHtml(), which no plugin used. - Keep $$ delimiters and displayMode for mid-line display math on copy, fallback and error events. - Drop plugin events from a render whose markdown has since been replaced or recycled. - Apply the current selection menu config when a math block view is updated.
The table serializer had no case for LaTeX nodes, so Copy as Markdown dropped the $/$$ delimiters (or the whole equation) and the pasted table rendered its math as plain text.
…into feat/android-math-plugin
… repeat per document
…gin-follow-ups # Conflicts: # packages/enriched-markdown-android/README.md # packages/enriched-markdown-android/compose/src/main/java/com/swmansion/enriched/markdown/compose/EnrichedMarkdownText.kt # packages/enriched-markdown-android/compose/src/main/java/com/swmansion/enriched/markdown/compose/MarkdownStyleBuilder.kt # packages/enriched-markdown-android/compose/src/main/java/com/swmansion/enriched/markdown/compose/MarkdownStyleLayer.kt # packages/enriched-markdown-android/math/src/main/java/com/swmansion/enriched/markdown/math/MathBlockSegment.kt # packages/enriched-markdown-android/math/src/main/java/com/swmansion/enriched/markdown/math/MathContainerView.kt # packages/enriched-markdown-android/math/src/main/java/com/swmansion/enriched/markdown/math/MathInlineSpan.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/plugin/PluginInlineSpan.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/renderer/LatexSourceRenderer.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/segments/RenderedSegment.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/segments/SegmentViewCreators.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/segments/TableContainerView.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/spoiler/SpoilerDrawUtils.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt # packages/enriched-markdown-android/ui/src/test/java/com/swmansion/enriched/markdown/plugin/PluginInlineSpanExportTest.kt # packages/enriched-markdown-android/ui/src/test/java/com/swmansion/enriched/markdown/plugin/PluginNodeRendererTest.kt # packages/enriched-markdown-android/ui/src/test/java/com/swmansion/enriched/markdown/plugin/PluginViewIntegrationTest.kt # packages/enriched-markdown-android/ui/src/test/java/com/swmansion/enriched/markdown/segments/TableSegmentTest.kt # packages/enriched-markdown-android/ui/src/test/java/com/swmansion/enriched/markdown/styles/StyleConfigExtensionTest.kt # packages/enriched-markdown-android/ui/src/test/java/com/swmansion/enriched/markdown/test/FakePluginSupport.kt
…gin-follow-ups # Conflicts: # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/segments/RenderedSegment.kt # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/segments/TableContainerView.kt # packages/enriched-markdown-android/ui/src/test/java/com/swmansion/enriched/markdown/plugin/PluginViewIntegrationTest.kt # packages/enriched-markdown-android/ui/src/test/java/com/swmansion/enriched/markdown/segments/TableContainerViewTest.kt # packages/enriched-markdown-android/ui/src/test/java/com/swmansion/enriched/markdown/segments/TableSegmentTest.kt
…gin-follow-ups # Conflicts: # packages/enriched-markdown-android/ui/src/main/java/com/swmansion/enriched/markdown/segments/TableContainerView.kt
There was a problem hiding this comment.
🟡 Changes recommended
LaTeX trimming can introduce a control space absent from the original source, changing rendering and copied text.
1 open finding
What changed in this PR
Addresses Android math-plugin follow-ups from #873, improving rendering, accessibility and copy behavior.
Changes:
- Moves table rendering and measurement off the main thread using copied styles.
- Caches parsed equations and normalizes LaTeX whitespace.
- Shares readable plugin text across accessibility and copying, and propagates selection-menu settings.
Paths below are relative to packages/enriched-markdown-android/.
| File | Description |
|---|---|
| ui/src/test/java/com/swmansion/enriched/markdown/test/FakePluginSupport.kt | Supports selection-menu configuration in test views. |
| ui/src/test/java/com/swmansion/enriched/markdown/styles/StyleConfigExtensionTest.kt | Tests paragraph-style copy isolation. |
| ui/src/test/java/com/swmansion/enriched/markdown/segments/TableSegmentTest.kt | Tests plugin rendering in table cells. |
| ui/src/test/java/com/swmansion/enriched/markdown/segments/TableContainerViewTest.kt | Tests deferred link callbacks. |
| ui/src/test/java/com/swmansion/enriched/markdown/plugin/PluginViewIntegrationTest.kt | Tests menu-setting propagation. |
| ui/src/test/java/com/swmansion/enriched/markdown/plugin/PluginNodeRendererTest.kt | Tests LaTeX whitespace handling. |
| ui/src/test/java/com/swmansion/enriched/markdown/plugin/PluginInlineSpanExportTest.kt | Uses shared readable-text conversion. |
| ui/src/test/java/com/swmansion/enriched/markdown/accessibility/PluginSpanAccessibilityTest.kt | Tests screen-reader text for plugin spans. |
| ui/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt | Adds configurable menus and shared copy conversion. |
| ui/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt | Copies paragraph styles and preserves highlighting. |
| ui/src/main/java/com/swmansion/enriched/markdown/segments/TableContainerView.kt | Builds grids from rendered tables. |
| ui/src/main/java/com/swmansion/enriched/markdown/segments/SegmentViewCreators.kt | Applies pre-rendered tables. |
| ui/src/main/java/com/swmansion/enriched/markdown/segments/RenderedTable.kt | Renders and measures table cells. |
| ui/src/main/java/com/swmansion/enriched/markdown/segments/RenderedSegment.kt | Carries rendered table data. |
| ui/src/main/java/com/swmansion/enriched/markdown/segments/CodeBlockContainerView.kt | Implements configurable selection menus. |
| ui/src/main/java/com/swmansion/enriched/markdown/renderer/LatexSourceRenderer.kt | Trims delimiter-adjacent whitespace. |
| ui/src/main/java/com/swmansion/enriched/markdown/plugin/PluginInlineSpan.kt | Adds shared readable-text extraction. |
| ui/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt | Implements configurable selection menus. |
| ui/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt | Propagates menu settings to plugin views. |
| ui/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt | Reads plugin spans as plain text. |
| math/src/test/java/com/swmansion/enriched/markdown/math/DisplayListCacheTest.kt | Tests cache keys, reuse and eviction. |
| math/src/main/java/com/swmansion/enriched/markdown/math/MathInlineSpan.kt | Uses cached inline equations. |
| math/src/main/java/com/swmansion/enriched/markdown/math/MathContainerView.kt | Implements configurable selection menus. |
| math/src/main/java/com/swmansion/enriched/markdown/math/MathBlockSegment.kt | Uses cached display equations. |
| math/src/main/java/com/swmansion/enriched/markdown/math/DisplayListCache.kt | Adds a shared 256-entry equation cache. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| /** [String.trim], except that it keeps the space of a trailing control space (`\ `), which TeX typesets. */ | ||
| private fun trimLatex(source: String): String { | ||
| val trimmed = source.trim() | ||
| if (trimmed.length == source.length || !trimmed.endsWith('\\')) return trimmed |
Comment on lines
292
to
293
| val beforeText = spanned.readableText(segmentPos, span.start).trim() | ||
| if (beforeText.isNotEmpty() && beforeText.any { it.isLetterOrDigit() }) { |
jnowakow
marked this pull request as ready for review
October 22, 2026 07:30
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What/Why?
Review follow-ups to the Android math plugin (#873):
RenderedTablerenders and measures cells off the main thread, like text segments. Cell renderers include plugin renderers, which must not run on the main thread.TableContainerViewnow only builds the grid.StyleConfig.withParagraphOverride, which mutated a shared config, is replaced by awithParagraphStylecopy. This also fixeswithExtensiondroppinghighlightStyle.DisplayListCache, LRU of 256), so streaming no longer re-parses every equation on each token.Spanned.readableTextis shared with copy.setSelectionMenuConfigreaches plugin segment views through a newSelectionMenuConfigurableinterface, whichMathContainerViewimplements.$$block), so it stays out of error events, copies and the fallback. A trailing\is kept.Testing
DisplayListCacheTest,PluginSpanAccessibilityTest,TableSegmentTest,TableContainerViewTest,PluginViewIntegrationTest,PluginNodeRendererTest,StyleConfigExtensionTest.ui,mathandcomposeunit tests and ktlint pass after syncing withmain.math-tables-small.mp4
PR Checklist