# Markup display for JUCE

**URL:** <https://forum.juce.com/t/markup-display-for-juce/58236>\
**Category:** General JUCE discussion\
**Created:** [October 8, 2023, 9:39pm UTC](https://forum.juce.com/t/markup-display-for-juce/58236 "2023-10-08T21:39:55Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![mnsp](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/mnsp/32/2665_2.png) [@mnsp](https://forum.juce.com/u/mnsp)\
**Post date:** [October 8, 2023, 9:39pm UTC](https://forum.juce.com/t/markup-display-for-juce/58236/1 "2023-10-08T21:39:55Z")

</div>

Hi everyone,

I’ve released BarelyML, a JUCE-based markup display on GitHub, which is useful for displaying formatted text, tables, images, and links inside JUCE apps (for example for in-app documentation, hints, etc.). My goal was flexibility and good performance on mobile devices (scrolling tables, large areas for links, imports simple Markdown, DokuWiki and AsciiDoc content, etc.). Less than 1500 lines, under MIT license, and only dependent on JUCE.

The git repository contains also an interactive demo (PIP =\> just drag BarelyMLDemo.h onto a Projucer window and it will create the project for you), as a quick way to test the rendering of documents in various formats. The demo also serves as example code for the integration of BarelyML in JUCE applications.

Best regards,  
Fritz

 ![Screenshot 2023-10-20 at 14.47.26](https://us1.discourse-cdn.com/flex026/uploads/juce/original/3X/e/5/e55ff57365abe16aa7b500ccb94f7dedff170d35.png)

> **[GitHub - MNSignalProcessing/BarelyML: Markup display for JUCE - Barely a...](https://github.com/MNSignalProcessing/BarelyML)**
>
> Markup display for JUCE - Barely a Markup Language - GitHub - MNSignalProcessing/BarelyML: Markup display for JUCE - Barely a Markup Language

[Edited for clarity and to reflect the current state of the project]

---

<div class="post-metadata">

**Author:** ![mnsp](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/mnsp/32/2665_2.png) [@mnsp](https://forum.juce.com/u/mnsp)\
**Post date:** [October 8, 2023, 9:42pm UTC](https://forum.juce.com/t/markup-display-for-juce/58236/2 "2023-10-08T21:42:36Z")

</div>

Here’s a little test:

 ![Screen Shot 2023-10-08 at 23.39.33](https://us1.discourse-cdn.com/flex026/uploads/juce/original/3X/b/7/b7bdb3474af07fd024e3218f0e7cfaf384ce5316.png)

The markup document looks like this:

```auto
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5

Here's some *bold text, _some bold italic text*, and some italic text_.

Now some <c:red>_*bold italic*_ red text</c>, and some <c#52F>arbitrarily colored *bold* text</c>.

- An unordered list item
  - And a subitem
  2. and a numbered subitem
  3. one more item

And some non-Latin text:
<c:lightblue>שלום</c>, מה שלומך? (Hebrew)
<c:green>سلام</c>، حالت چطوره؟ (Persian)
*สวัสดีค่ะ* เป็นไงบ้าง (Thai).

^ Table Heading ^ Column 2 ^ Column 3 with a really long header ^
| Row 2 | | |
| | Row 3 | |
^ Also a header | Not a header | |

INFO: This is an info paragraph (blue tab).

HINT: This is a hint paragraph (green tab).

IMPORTANT: This is an important paragraph (red tab).

CAUTION: This is a caution paragraph (yellow tab).

WARNING: This is a warning paragraph (orange tab).

[[https://juce.com|JUCE Website]]

[[http://mnsp.ch|{{sunrise.jpeg?200}}]]

```

---

<div class="post-metadata">

**Author:** ![aamf](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/aamf/32/19941_2.png) [@aamf](https://forum.juce.com/u/aamf)\
**Post date:** [October 8, 2023, 10:17pm UTC](https://forum.juce.com/t/markup-display-for-juce/58236/3 "2023-10-08T22:17:20Z")

</div>

Thanks a ton for creating and sharing this!

---

<div class="post-metadata">

**Author:** ![mnsp](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/mnsp/32/2665_2.png) [@mnsp](https://forum.juce.com/u/mnsp)\
**Post date:** [October 9, 2023, 5:27am UTC](https://forum.juce.com/t/markup-display-for-juce/58236/4 "2023-10-09T05:27:04Z")

</div>

You’re welcome. I’m happy if it is useful to other people, too!

Just as a side note: this is the very first release and there are almost certainly still bugs and missing features. So if you find any of those, please let me know (either here or via GitHub).

Best regards,  
Fritz

---

<div class="post-metadata">

**Author:** ![mnsp](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/mnsp/32/2665_2.png) [@mnsp](https://forum.juce.com/u/mnsp)\
**Post date:** [October 9, 2023, 6:16am UTC](https://forum.juce.com/t/markup-display-for-juce/58236/5 "2023-10-09T06:16:17Z")

</div>

[Update: there is an example project now, see BarelyMLDemo.h on GitHub]  
~~As there’s no example project on GitHub (yet),~~ here’s some instructions on how to use BarelyML:

1. include `BarelyML.h` and declare a display Component:  
`BarelyMLDisplay bmlDisplay;`
2. (optional, only needed if you want to display images):  
Declare one of your own classes to be a `BarelyMLDisplay::FileSource`, for example like this:  
`class MainComponent : public juce::Component, BarelyMLDisplay::FileSource`  
and override the method `getImageForFilename`:  
`Image getImageForFilename(String filename) override;`  
This method simply produces Image objects when given a filename. You could do anything in there, like programmatically painting your images. But quite likely you’d want to implement it something like this:

```auto
Image MainComponent::getImageForFilename(String filename) {
  Image image;
  int numBytes;
  const char* data = BinaryData::getNamedResource(filename.replaceCharacter('.', '_').toRawUTF8(),numBytes);
  if (data != nullptr) {
    image = ImageFileFormat::loadFrom(data, numBytes);
  } else {
    File file = File(getSharedResourceFolder().getFullPathName() + "/" + filename);
    image = ImageFileFormat::loadFrom(file);
  }
  return image;
}

File MainComponent::getSharedResourceFolder()
{
  File bundle = File::getSpecialLocation (File::invokedExecutableFile).getParentDirectory();

  // macOS uses Contents/MacOS structrue, iOS bundle structure is flat
 #if JUCE_MAC
  bundle = bundle.getParentDirectory().getParentDirectory();
 #endif

  // appex is in a PlugIns folder inside the parent bundle
  if (SystemStats::isRunningInAppExtensionSandbox())
      bundle = bundle.getParentDirectory().getParentDirectory();

 #if JUCE_MAC
  bundle = bundle.getChildFile ("Contents/Resources");
 #endif

  return bundle;
}

```

1. Finally, you need to initialize your BarelyMLDisplay object with the right parameters and the Markup String:

```auto
  addAndMakeVisible(&bmlDisplay);
  bmlDisplay.setFileSource(this);
  bmlDisplay.setMarkupString(CharPointer_UTF8(R"(# Heading 1

Here's some <c:red>_*bold italic*_ red text</c>,
and some <c#52F>arbitrarily colored *bold* text</c>.)"),
  Font("Helvetica Neue", 20.0f, Font::FontStyleFlags::plain));

```

Of course you’ll also need to properly set the bounds of the component.

Alternatively, if you have a simple Markdown String, you can directly read that using the `setMarkdownString` method:

```auto
bmlDisplay.setMarkdownString(CharPointer_UTF8(R"(# Header

### Here's an image

![This text will be ignored](sunrise.jpeg)

### And here's a table

| Table header | Another one |
| --------------- | ----------- |
| row 2 | |
| | row 3. |)"),
Font("Helvetica Neue", 20.0f, Font::FontStyleFlags::plain));

```

---

<div class="post-metadata">

**Author:** ![anon48770766](https://avatars.discourse-cdn.com/v4/letter/a/439d5e/32.png) [@anon48770766](https://forum.juce.com/u/anon48770766)\
**Post date:** [October 9, 2023, 8:04am UTC](https://forum.juce.com/t/markup-display-for-juce/58236/6 "2023-10-09T08:04:27Z")

</div>

Very cool contribution - thanks so much for making this and for making it MIT license for others to use. I’ll definitely be integrating this into some of my JUCE apps shortly.

---

<div class="post-metadata">

**Author:** ![mnsp](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/mnsp/32/2665_2.png) [@mnsp](https://forum.juce.com/u/mnsp)\
**Post date:** [October 20, 2023, 1:36am UTC](https://forum.juce.com/t/markup-display-for-juce/58236/7 "2023-10-20T01:36:02Z")

</div>

FYI, I’ve recently updated BarelyML to version 0.2 on GitHub:

> **[Release BarelyML v0.2 · MNSignalProcessing/BarelyML](https://github.com/MNSignalProcessing/BarelyML/releases/tag/0.2)**
>
> Comes now with an interactice demo (as a PIP for the Projucer)
> Fixes a bug where text may be not displayed (see JUCE Forum)
> Removed juce namespace from BarelyML.h
> Added conversion methods for DokuW...

Here’s what’s new:

- Comes now with an interactice demo (as a PIP =\> just drag BarelyMLDemo.h onto a Projucer window to create the project)
- Fixes a bug where text may be not displayed (see [AttributedString: last line disappearing when preceding line was wrapped](https://forum.juce.com/t/attributedstring-last-line-disappearing-when-preceding-line-was-wrapped/58396))
- Removed juce namespace from BarelyML.h
- Added conversion methods for DokuWiki and AsciiDoc content

---

<div class="post-metadata">

**Author:** ![mnsp](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/mnsp/32/2665_2.png) [@mnsp](https://forum.juce.com/u/mnsp)\
**Post date:** [October 20, 2023, 12:50pm UTC](https://forum.juce.com/t/markup-display-for-juce/58236/8 "2023-10-20T12:50:10Z")

</div>

I still needed some improvements, so I’ve released a second update today:

> **[Release BarelyML v0.2.1 · MNSignalProcessing/BarelyML](https://github.com/MNSignalProcessing/BarelyML/releases/tag/0.2.1)**
>
> Fixes a bug where AsciiDoc link labels with spaces in them are not recognized
> Improves BarelyML \<--\> Markdown conversion
> Adds custom colour scheme and non-standard font to BarelyMLDemo
> Improves dra...

I’d also highly recommend to try out the interactive demo (available as a PIP together with the source code). It’s just the easiest way to try out what you can do with BarelyML.

 ![Screenshot 2023-10-20 at 14.47.26](https://us1.discourse-cdn.com/flex026/uploads/juce/original/3X/e/5/e55ff57365abe16aa7b500ccb94f7dedff170d35.png)

---

<div class="post-metadata">

**Author:** ![Curlymorphic](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/curlymorphic/32/18786_2.png) [@Curlymorphic](https://forum.juce.com/u/Curlymorphic)\
**Post date:** [November 19, 2023, 4:52pm UTC](https://forum.juce.com/t/markup-display-for-juce/58236/9 "2023-11-19T16:52:46Z")

</div>

Thank you for this, very nice to be able to use markdown in juce apps.

---

<div class="post-metadata">

**Author:** ![mnsp](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/mnsp/32/2665_2.png) [@mnsp](https://forum.juce.com/u/mnsp)\
**Post date:** [November 21, 2023, 8:05am UTC](https://forum.juce.com/t/markup-display-for-juce/58236/10 "2023-11-21T08:05:13Z")

</div>

You’re welcome. Anyone interested in support for Org-mode?

---

<div class="post-metadata">

**Author:** ![timothyschoen](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/timothyschoen/32/14089_2.png) [@timothyschoen](https://forum.juce.com/u/timothyschoen)\
**Post date:** [December 10, 2023, 1:51am UTC](https://forum.juce.com/t/markup-display-for-juce/58236/11 "2023-12-10T01:51:51Z")

</div>

Thanks for your work on this, and for making it freely available! We’re planning to include it with [plugdata](https://github.com/plugdata-team/plugdata) to display in-app documentation. It’s a work-in-progress:

 ![Screenshot 2023-12-10 at 02.47.29](https://us1.discourse-cdn.com/flex026/uploads/juce/original/3X/5/a/5af3a68e74d85b5d41ec9305c1466e8f3bb2e145.jpeg)

The code was great to work with, it’s really easy to expand the syntax support yourself if needed. I added support for basic HTML-style images for example. So yeah, thanks a lot!

---

<div class="post-metadata">

**Author:** ![mnsp](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/mnsp/32/2665_2.png) [@mnsp](https://forum.juce.com/u/mnsp)\
**Post date:** [April 8, 2024, 6:03am UTC](https://forum.juce.com/t/markup-display-for-juce/58236/12 "2024-04-08T06:03:44Z")

</div>

Update: I’ve released BarelyML v0.3 on GitHub:

> **[GitHub - MNSignalProcessing/BarelyML: Markup display for JUCE - Barely a...](https://github.com/MNSignalProcessing/BarelyML)**
>
> Markup display for JUCE - Barely a Markup Language - MNSignalProcessing/BarelyML

New features:

- Changed the definition of FileSource to enable vector graphics (SVG)
- Adds a URLHandler class (for custom links)
- Adds links and images in tables
- Various minor improvements

 ![Screenshot 2024-04-08 at 08.00.03](https://us1.discourse-cdn.com/flex026/uploads/juce/original/3X/b/1/b1b62f1b50ab444d0b06eef01062aee3502fc26d.png)

---

<div class="post-metadata">

**Author:** ![mnsp](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/mnsp/32/2665_2.png) [@mnsp](https://forum.juce.com/u/mnsp)\
**Post date:** [April 10, 2024, 5:40am UTC](https://forum.juce.com/t/markup-display-for-juce/58236/13 "2024-04-10T05:40:16Z")

</div>

Hi Timothy,

I only now read the sentence under the image you posted. First of all, thanks, I’m happy to hear that the code being pretty straightforward made things easier for you. But I’m also curious why you wanted HTML-style images. Did you implement advanced formatting options, e.g. relative to the width of the document?

Best regards,  
Fritz

---

<div class="post-metadata">

**Author:** ![timothyschoen](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/timothyschoen/32/14089_2.png) [@timothyschoen](https://forum.juce.com/u/timothyschoen)\
**Post date:** [April 11, 2024, 9:54am UTC](https://forum.juce.com/t/markup-display-for-juce/58236/14 "2024-04-11T09:54:01Z")

</div>

Hi!

Yeah, that was why: I want to be able to control the width of images more exactly. We made the docs for an online documentation page like that, and this way I could just copy-paste the whole thing and have it look nearly identical.

I also implemented support for multiple links in one block of text. I can implement it in your version and send a PR if you want, but here’s how I did it:

```auto
Array<std::pair<String, Rectangle<float>>> linkBounds;
Array<std::tuple<String, int, int>> links;

// inside parsePureText, we have this lambda that is called to save links with their start and end position in the text
 auto parseLink = [this, &attributedString](String& link, String& linkText) {
                    if (link.isNotEmpty()) {
                    // Account for the different ways whitespace is handled inside an attributedString on Windows/Linux vs Mac
#if JUCE_MAC
                        auto start = attributedString.getText().length();
                        auto end = start + linkText.length();
#else
                        auto start = attributedString.getText().replace(" ", "").replace("\n", "").replace("\r", "").replace("\t", "").length();
                        auto end = start + linkText.replace(" ", "").replace("\n", "").replace("\r", "").replace("\t", "").length();
#endif
                        links.add({ link, start, end });
                        link = "";
                    }

// Calculate the bounds of all links based on the text indices, call this when text or size has changed
void updateLinkBounds(TextLayout& layout)
    {
        linkBounds.clear();
        
        // Look for clickable links
        for (auto& [link, start, end] : links) {
            int offset = 0;
            auto currentLinkBounds = Rectangle<float>();
            for (auto& line : layout) {
                for (auto* run : line.runs) {
                    for (int i = start - offset; i < end - offset; i++) {
                        if (i < 0 || i >= run->glyphs.size())
                            continue;
                        
                        auto& glyph = run->glyphs.getReference(i);
                        auto lineBounds = Rectangle<float>(glyph.width, 14).withPosition((glyph.anchor + line.lineOrigin));
                        currentLinkBounds = linkBounds.isEmpty() ? lineBounds : currentLinkBounds.getUnion(lineBounds);
                    }
                    
                    linkBounds.add({link, currentLinkBounds.translated(0, -11)});
                    offset += run->glyphs.size();
                }
            }
        }
    }

void mouseUp(MouseEvent const& event) override
    {
        for(auto& [link, bounds] : linkBounds)
        {
            if(bounds.contains(event.x, event.y))
            {
                URL(link).launchInDefaultBrowser();
                break;
            }
        }
    }

```

Though looking at this again now, I realise this will go wrong if the link gets line wrapped! Better to store a RectangleList instead of calculating the union of rectangles. I also haven’t tested this yet for tables, so there is still some work to do here.

---

<div class="post-metadata">

**Author:** ![Goodweather](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/goodweather/32/21686_2.png) [@Goodweather](https://forum.juce.com/u/Goodweather)\
**Post date:** [January 2, 2026, 1:19pm UTC](https://forum.juce.com/t/markup-display-for-juce/58236/15 "2026-01-02T13:19:09Z")

</div>

Hi! Thx for providing this and Happy 2026!

Sorry for the stupid question as I’m new to this…  
Which version of Juce did you use to make this working? For another project we are using 6.0.8 but I guess you are using another one as I got many errors when building your Demo in VS2022.  
Another question… why is your demo a header file and not a cpp?  
And then, when a new project is created, it has a main.cpp file. Is that file used and if yes, how does it know about your Demo.h file?

Thx in advance!

---

<div class="post-metadata">

**Author:** ![mnsp](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/mnsp/32/2665_2.png) [@mnsp](https://forum.juce.com/u/mnsp)\
**Post date:** [October 19, 2026, 3:23pm UTC](https://forum.juce.com/t/markup-display-for-juce/58236/16 "2026-10-19T15:23:51Z")

</div>

The header file demo is easy to explain: it’s a special format that the Producer can turn into a complete project.

The main question I have though: is anybody still using it? I’m willing to maintain and update this project on GitHub, but only if there’s a demand.

---

<div class="post-metadata">

**Author:** ![jpo](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/jpo/32/503_2.png) [@jpo](https://forum.juce.com/u/jpo)\
**Post date:** [October 20, 2026, 9:36am UTC](https://forum.juce.com/t/markup-display-for-juce/58236/17 "2026-10-20T09:36:18Z")

</div>

I’m not currently using it but it is in my list of “very interesting stuff to keep in mind” !
