How to show markdown formatted text in tkinter?
Asked Answered
S

2

13

In python-3.x with tkinter GUI, I developed a program with a regular simple window.

I want to show a markdown format string saved in a string called markdownText on program window:

markdownText='_italic_ or **bold**'

desired output is:

italic or bold

Is there any solution?

Sterilant answered 1/3, 2016 at 20:55 Comment(10)
Are italicized/bold strings always on a single line or can they span multiple lines? Either way you need to use the Text widget, but the multiple-line case requires more work.Marindamarinduque
It is multi-line with hyperlinks ... I am not looking for a complicated solution. so I suppose there is no solution,Sterilant
Maybe other GUI has a direct solution for markdown format? do you know?Sterilant
No, I don't think there is a very easy one with pure tkinter. You write your own lexer, find where the marked parts are and apply tags... That's how I'd do it, anyway. Maybe 40 lines of code for just italics and bold.Marindamarinduque
Sorry I'm not familiar with other GUI frameworks.Marindamarinduque
Do you want to show the formatted text in the same widget where you edit the text, or would the formatted text be in a separate widget from where the text is edited? The answer is very different and it is not clear to me which answer you are looking for.Kingsbury
@Kingsbury separate widget. I just want to show markdown text and there is no need for user to modify it.Sterilant
Maybe I can convert markdown to html or other suitable format and then convert it to tkinter ?Sterilant
Okay, it you just want to display the text, convert it to HTML and display that. Maybe embed a browser window into your app and have that display the HTML. I'm not familiar enough with tkinter to offer any more, but that is the method I would use on any GUI framework.Kingsbury
@Kingsbury I will do this approach if there is no straight forward solution. btw it is strange there is no other questions with both markdown and tikinter together : stackoverflow.com/questions/tagged/tkinter+markdownSterilant
H
11

I was just searching for a similar solution, and it does indeed not seem like there is a default module/class/library for the combination of Python, TkInter and markdown. However a continued search revealed the following options:

  • Python-Markdown – This a module capable of parsing markdown into the following output formats: Python-Markdown can output documents in HTML4, XHTML and HTML5.
  • TkInter displaying html – As the linked answer indicates, Tkhtml can display html "pretty well", and it has a python wrapper

In other words, if you are willing to use an intermediate step of converting into html that might be a viable route for you to display markdown strings within a tkinter GUI.

Humble answered 20/3, 2016 at 22:28 Comment(1)
@Woeitg, If it works out, please do come back and comment on how it worked out. :-)Humble
C
2

If you don't want holroy's great suggestion of converting to html and displaying that, and only want markdown for basic formatting, you can write a basic markdown parser.

However, if you want full markdown support then use a real parser like marko or python-markdown and figure out how to walk the document to add each item to a tk.Text.

Otherwise, have a look:

import re
import tkinter as tk
import tkinter.font as tkfont
import tkinter.scrolledtext as tkscroll


class SimpleMarkdownText(tkscroll.ScrolledText):
    """
    Really basic Markdown display. Thanks to Bryan Oakley's RichText:
    https://mcmap.net/q/907087/-fomatted-text-in-tkinter
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        default_font = tkfont.nametofont(self.cget("font"))

        em = default_font.measure("m")
        default_size = default_font.cget("size")
        bold_font = tkfont.Font(**default_font.configure())
        italic_font = tkfont.Font(**default_font.configure())

        bold_font.configure(weight="bold")
        italic_font.configure(slant="italic")

        # Small subset of markdown. Just enough to make text look nice.
        self.tag_configure("**", font=bold_font)
        self.tag_configure("*", font=italic_font)
        self.tag_configure("_", font=italic_font)
        self.tag_chars = "*_"
        self.tag_char_re = re.compile(r"[*_]")

        max_heading = 3
        for i in range(1, max_heading + 1):
            header_font = tkfont.Font(**default_font.configure())
            header_font.configure(size=int(default_size * i + 3), weight="bold")
            self.tag_configure(
                "#" * (max_heading - i), font=header_font, spacing3=default_size
            )

        lmargin2 = em + default_font.measure("\u2022 ")
        self.tag_configure("bullet", lmargin1=em, lmargin2=lmargin2)
        lmargin2 = em + default_font.measure("1. ")
        self.tag_configure("numbered", lmargin1=em, lmargin2=lmargin2)

        self.numbered_index = 1

    def insert_bullet(self, position, text):
        self.insert(position, f"\u2022 {text}", "bullet")

    def insert_numbered(self, position, text):
        self.insert(position, f"{self.numbered_index}. {text}", "numbered")
        self.numbered_index += 1

    def insert_markdown(self, mkd_text):
        """A very basic markdown parser.

        Helpful to easily set formatted text in tk. If you want actual markdown
        support then use a real parser.
        """
        for line in mkd_text.split("\n"):
            if line == "":
                # Blank lines reset numbering
                self.numbered_index = 1
                self.insert("end", line)

            elif line.startswith("#"):
                tag = re.match(r"(#+) (.*)", line)
                line = tag.group(2)
                self.insert("end", line, tag.group(1))

            elif line.startswith("* "):
                line = line[2:]
                self.insert_bullet("end", line)

            elif line.startswith("1. "):
                line = line[2:]
                self.insert_numbered("end", line)

            elif not self.tag_char_re.search(line):
                self.insert("end", line)

            else:
                tag = None
                accumulated = []
                skip_next = False
                for i, c in enumerate(line):
                    if skip_next:
                        skip_next = False
                        continue
                    if c in self.tag_chars and (not tag or c == tag[0]):
                        if tag:
                            self.insert("end", "".join(accumulated), tag)
                            accumulated = []
                            tag = None
                        else:
                            self.insert("end", "".join(accumulated))
                            accumulated = []
                            tag = c
                            next_i = i + 1
                            if len(line) > next_i and line[next_i] == tag:
                                tag = line[i : next_i + 1]
                                skip_next = True

                    else:
                        accumulated.append(c)
                self.insert("end", "".join(accumulated), tag)

            self.insert("end", "\n")


def test_markdown_display():
    root = tk.Tk()

    default_font = tkfont.nametofont("TkDefaultFont")
    default_font.configure(size=12)

    text = SimpleMarkdownText(root, width=45, height=22, font=default_font)
    text.pack(fill="both", expand=True)

    text.insert_markdown(
        """
# Rich Text Example
Hello, world
This line **has bold** text.
This line _has italicized_ text. Also *italics* using asterisks.
Text _with italics **and bold** does_ not work.

## Sub Heading
This is a more interesting line with _some_ italics, but also **some bold text**.
* Create a list
* With bullets
1. Create a list
1. Or numbers

1. Use blank lines
1. to restart numbering
"""
    )

    root.mainloop()


if __name__ == "__main__":
    test_markdown_display()

Screenshot of SimpleMarkdownText in action

You can see there are lots of limitations: no combined italics+bold, no word-boundary text wrapping. But it seems a lot better for displaying a big block of text.

Chun answered 23/1 at 20:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.