slint icon indicating copy to clipboard operation
slint copied to clipboard

Add support for rendering rich text

Open tronical opened this issue 3 months ago • 16 comments

This task is derived from #2723 but with a focus solely on providing the APIs to feed attributed text into Slint and rendering, NOT to support the end-user editing said text.

The following feature set for attributes is in scope:

  • Hyperlinks (including way of handling clicks)
  • Text colors
  • Style: Bold, Italic, Underline
  • Unordered/ordered lists

List of things to do before this issue is done:

  • [ ] better interpolation handling. The following should work: @markdown("# Foo {}", @markdown("*Bar*")) / @markdown("`{}`", "Some`Code`With`Quote"); (this may mean doing the interpolation as part of the parsing instead of escaping)
  • [ ] Document the StyledText element in the language reference (including an inline example with screenshot, as well as what type of styling is supported)
  • [ ] C++ Api needs a way of constructing a StyledText from markdown (including a unit test)
  • [ ] Make sure the software renderer doesn't panic when rendering a StyledText element
  • [ ] A test in tests/cases/elements that constructs a StyledText element in Slint and the styled-text is exposed to Rust and C++, and it can be read and written from Rust and C++. Verify that the preferred-height is greater than zero when a text is set, so that when the test is run, the entire parsing and layout code path as well as the code generator is covered.
  • [ ] Produce compile errors when invalid markdown is used with @markdown(). (extend internal/compiler/tests/syntax/basic/markdown.slint)
  • [ ] One last API review before the next release.
  • [ ] Extend the widgets gallery example to show hyperlinks, text colours, styles (bold/italic/underline) and unordered/ordered lists.
  • [ ] Add a test for the StyledText::link-clicked callback. Send a synthetic mouse click in a new or existing test under tests/cases and verify the callback was invoked.

tronical avatar Sep 29 '25 07:09 tronical

We should also figure out how to define richtext in slint files and in the API.

  • Do we want to re-use Text { }, or should we have a RichText { }, or just another rich-text: property to Text
  • How to represent rich text in our slint markup. We probably want to use a @md(...) macro that would prevent "markdown" injection. Assuming we want markdown. See my comment in https://github.com/slint-ui/slint/issues/2723#issuecomment-2367714597

I would think we would need a different type like rich-text which is not a string. The internal representation of which is some data structure optimized for fast rendering.

In .slint rich-text could be constructed with @md or similar macro. In Rust/C++, we could have api like slint::RichText::from_markdown

That said, we should not lock ourselves in to markdown and allow other representation such as HTML subset or something like that. Whatever we do, we should document exactly what subset of markdown we support.

ogoffart avatar Oct 09 '25 08:10 ogoffart

Is there any possibility of also including ~~strikethrough~~ text?

U007D avatar Oct 12 '25 13:10 U007D

Is there any possibility of also including ~strikethrough~ text?

Yep, that should be possible!

expenses avatar Oct 14 '25 14:10 expenses

I've started implementing this via markdown in #9733. This gets us some of the way there, with a few notes and TODOs:

  • Indentation, bullet points and numbers for ordered/unordered lists are hardcoded
  • Strikethroughs aren't actually rendered yet
  • The internal API isn't ideal (See https://github.com/slint-ui/slint/pull/9733#discussion_r2431915637)
  • At the very least, links aren't implemented. (Not sure if we want headers, horizontal lines, etc)

Furthermore, there are a few things that markdown is incapable of giving us (unless we use a custom fork or raw html):

  • Text colors
  • Underlines
  • Changing the size of text outside of a header

expenses avatar Oct 15 '25 12:10 expenses

Markdown might just be the worst possible format for this purpose.

  • It has a spec (CommonMark), but most implementations in the wild aren't fully conforming

  • CommonMark allows raw HTML

  • CommonMark has multiple ways to represent something (* or - for bullet lists, --- or *** for rulers, ``` or ~~~ or indentation for code blocks, ATX or setext headings, backslash or two spaces for hard line breaks, ...)

  • CommonMark is notoriously difficult to parse correctly in all the edge cases, especially regarding indentation

  • The most popular implementations have various extensions (strikethrough, tables, footnotes, autolinks, task lists, callouts, definition lists, subscript, superscript, ...) that aren't part of the CommonMark spec

By contrast, HTML is relatively straightforward to parse and does not have hundreds of edge cases. It is consistently parsed by all applications that support HTML. Supported features can be easily configured with an allowlist of HTML tags and attributes. If Slint adds a new feature at a later date (e.g. blockquotes), the corresponding tag can simply be added to the allowlist; no changes to the HTML parser are required.

Aloso avatar Oct 16 '25 22:10 Aloso

To be fair, Markdown can be very easily compiled to HTML. There are at least two Rust crates (comrak and pulldown_cmark) that implement all the corner cases of the CommonMark specification and most of the extensions.

I do agree that implementing rich text rendering from a parsed HTML tree would likely be easier than doing it directly from markdown. But it would come with the cost of having to include an HTML parser.

nicoburns avatar Oct 18 '25 00:10 nicoburns

And if the rich text editor spits out Markdown, what are users going to do with it? Many will send it to a web server, I assume. There, it will likely be converted to HTML and stored in a DB, or the Markdown will be stored in a DB directly. If it's the latter, it probably has to be converted to HTML later, e.g. when displaying it in a browser using JavaScript.

If the web server is written in Python, they will probably use the markdown package, which is not CommonMark compliant and lacks sanitization.

If the web server is written in PHP, they'll likely use the parsedown package, but it is unmaintained and has serious bugs. Maybe they'll use php-markdown instead, which also has lots of bugs and isn't CommonMark compliant.

I could continue this list with other languages, but you get the point.

Markdown is not interoperable. It is a security nightmare – several packages don't offer sanitization, or their parser can exhibit exponential backtracking, which enables DoS attacks.

What even is the point of producing Markdown, if users are expected to convert it to HTML anyway?

Aloso avatar Oct 18 '25 10:10 Aloso

And if the rich text editor spits out Markdown

Just to clarify: this issue is not about rich text support in the text input element.

What even is the point of producing Markdown, if users are expected to convert it to HTML anyway?

The point right now is not about producing markdown but consuming it for text display.

Rich text editing is an interesting problem to solve, especially with regards to the user interaction, associated controls for formatting, etc.

tronical avatar Oct 18 '25 10:10 tronical

I assumed there would also be an input with rich text support. If that's not the case, please forget everything I said.

Aloso avatar Oct 18 '25 10:10 Aloso

If we go for markdown, which doesn't have an easy-to-use syntax for underlining, we should at least make sure <u>foo</u> works. https://stackoverflow.com/a/4019303/758288

dfaure-kdab avatar Oct 21 '25 19:10 dfaure-kdab

I believe the current draft PR implements <u>…</u>.

tronical avatar Oct 21 '25 23:10 tronical

Another option is to go for a custom markup lang that is familiar enough to be easily picked up. Qt, for example, uses an XHTML subset. And apps like Discord and MS Teams also just implement something similar to CommonMark but not really the full CommonMark.

The obvious downside of going for custom markup is:

  • For edge cases users can’t just copy-paste markup text they already have losslessly. No biggie, imo.
  • This new markup language/dialect needs to be well-documented.
  • It takes extra dev effort, for we can’t just pull in some random existing crates.

This, however, comes at a few upsides for the future:

  • The markup language supports exactly what Slint can render.
  • Text segments can be styled based on properties of the currently active theme and Material metrics.
  • We could add Slint-specific things without surprise to users.
    • For example: Injecting @trs
    • Or: Repositioning adjacent components to anchor mark-ups, to e.g. inject an image or button into the middle of some text.
      • This could e.g. be done by rich text having a list-of-layout-markers property, which other components can then index and bind to.
    • Have a routing handler/strategy for app-internal linking. E.g. path properties on components, letting you automatically resolve app:// links, bringing a certain component into focus if it exists.
      • See Qt’s URL scheme handling.
      • Very useful for Wiki-like apps, which is what i’m working on.

Evrey avatar Oct 24 '25 00:10 Evrey

Does the discussion of rich text include rendering mathematical formulas? Is there no means to mix formulas with text?

yisusheng avatar Nov 10 '25 12:11 yisusheng

Does the discussion of rich text include rendering mathematical formulas? Is there no means to mix formulas with text?

LaTeX parsing would be cool and should be possible to add, but isn't on the agenda currently

expenses avatar Nov 10 '25 13:11 expenses

It shouldn't be too hard to add math support. Here is a snippet integrating it into to pulldown_cmark: https://gist.github.com/akar1ngo/7d4ea6e9dfc369526beb75092cbd6cb8. Except, I would recommend using https://lib.rs/crates/katex-rs instead of katex as the former is a Rust port, the latter runs the javascript version in quickjs.

nicoburns avatar Nov 10 '25 14:11 nicoburns

Just for info: one more Rust based rich text rendering/editing software https://github.com/typst/

ikod avatar Dec 11 '25 19:12 ikod