# Generating PDF from Markdown with Go

In this quick-n-dirty article, we will see how to generate a PDF from a user-submitted markdown with Go. We will build a simple service using the standard library's `net/http` for the backend and EasyMDE, a browser editor, for handling markdown on the front-end.

Markdown is a popular authoring format and has gained wide adoption in many different places. Markdown is generally converted to HTML, but there do exist tools like [Pandoc](https://pandoc.org/installing.html) which can additionally convert markdown to different formats like .docx documents, PDF etc...

In the Go ecosystem, a popular library for working with Markdown is [goldmark](https://github.com/yuin/goldmark) - we which we will use to process user-submitted markdown text and render it to a PDF file. You can imagine this being used for basic authoring of content including letters, articles, simple newsletters, basic reports etc...

## Markdown PDF generator backend

For us to process Markdown, we will rely on the popular library [goldmark](https://github.com/yuin/goldmark) which has some community-built extensions, the one we are particularly interested in is the [goldmark-pdf](http://github.com/stephenafamo/goldmark-pdf) library which will allow us to render Goldmark's Markdown AST into PDF. Firstly, we will create a bare-bones (i.e. not production-grade) web server using the standard library's `net/http`.

The complete server code is shown below:

```go
package main

import (
	"bytes"
	"context"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"text/template"

	pdf "github.com/stephenafamo/goldmark-pdf"
	"github.com/yuin/goldmark"
)

func main() {
	markdown := goldmark.New(
		goldmark.WithRenderer(
			pdf.New(
				pdf.WithTraceWriter(os.Stdout),
				pdf.WithContext(context.Background()),
				pdf.WithImageFS(os.DirFS(".")),
				pdf.WithHeadingFont(pdf.GetTextFont("Arial", pdf.FontCourier)),
				pdf.WithBodyFont(pdf.GetTextFont("Arial", pdf.FontCourier)),
				pdf.WithCodeFont(pdf.GetCodeFont("Arial", pdf.FontCourier)),
			),
		),
	)

	mux := http.NewServeMux()

	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Cache-Control", "cache")
		indexHtmlTemplate := template.Must(template.ParseFiles("index.html"))
		indexHtmlTemplate.ExecuteTemplate(w, "index.html", nil)
	})

	mux.HandleFunc("/generate", func(w http.ResponseWriter, r *http.Request) {
		source, err := ioutil.ReadAll(r.Body)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		var buf bytes.Buffer
		if err := markdown.Convert([]byte(source), &buf); err != nil {
			http.Error(w, "failed to generate pdf", http.StatusInternalServerError)
			return
		}
		base64Encoded := base64.StdEncoding.EncodeToString(buf.Bytes())
		dataURL := fmt.Sprintf("data:application/octet-stream;base64,%s", base64Encoded)
		w.Write([]byte(dataURL))
	})

	http.ListenAndServe(":8001", mux)
}
```

## The frontend - adding an in-browser editor

We are going to complete our simple service by adding a frontend to it which is going to be a simple Markdown Editor with a button for Generating and Downloading the PDF. We will use EasyMDE for this - I went through several different options for an editor and at the moment I like EasyMDE, especially for how easy it is to add extra functionality to the Editor. Will will be extending EasyMDE to enable downloading the generated PDF by adding a button with a PDF icon for the user to generate the PDF.

The complete code for the `index.html` page is shown below

```xml
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Markdown to PDF</title>
    <base href="/">

    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/default.min.css">

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
        integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.bundle.min.js"
        integrity="sha384-pjaaA8dDz/5BgdFUPX6M/9SUZv4d12SUPF0axWc+VRZkx5xU3daN+lYb49+Ax+Tl"
        crossorigin="anonymous"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js"></script>
    <script>hljs.initHighlightingOnLoad();</script>

    <script defer src="https://use.fontawesome.com/releases/v5.4.1/js/all.js"
        integrity="sha384-L469/ELG4Bg9sDQbl0hvjMq8pOcqFgkSpwhwnslzvVVGpDjYJ6wJJyYjvG3u8XW7"
        crossorigin="anonymous"></script>

    <script src="https://unpkg.com/axios@0.26.0/dist/axios.min.js"></script>

    <link rel="stylesheet" href="https://unpkg.com/easymde@2.18.0/dist/easymde.min.css">
    <script src="https://unpkg.com/easymde@2.18.0/dist/easymde.min.js"></script>
</head>

<body>
    <div class="content">

        <div id="app">
            <textarea id="editor"></textarea>
            <a id="downloadEl" href="#" style="display: none;" download="Document.pdf">Download PDF</a>
        </div>
    </div>
    <script type="text/javascript">
        let downloadEl = document.getElementById('downloadEl')
        new EasyMDE({
            autoDownloadFontAwesome: false,
            toolbar: [
            "bold", "italic", "heading", "|", "quote", 'strikethrough', 'code', 'heading','|', 'undo','redo'
            ,'|', { // Separator
                name: "generate-pdf",
                action: (editor) => {
                    axios.post("/generate", editor.value())
                    .then(response => {
                        let pdfDataURL = response.data
                        if (pdfDataURL.indexOf("base64") > 0) {
                            downloadEl.setAttribute('href', pdfDataURL)
                            downloadEl.click()
                        }
                    })
                },
                className: "fa fa-file-pdf",
                title: "Download PDF",
            }, '|', {
                name: "link",
                action: 'https://blog.nndi.cloud',
                className: "fa fa-globe",
                title: "Go to the NNDI Blog"
            }],
            element: document.getElementById('editor'),
            initialValue: '## Welcome to Markdown to PDF using Go.\nType some markdown in here and then click the PDF button to render the markdown to PDF'
        });
    </script>
</body>

</html>
```

With those two bits of code done, we can now build and run our service. Make sure you copy the Go code into a file named `main.go` and the HTML code in a file named `index.html` in the same directory - you can then run the following commands:

```bash
$ go mod init markdown-pdf
$ go mod tidy
$ go run main.go
```

The last command should start a server on http://localhost:8001 visit that in your browser and try it out.

Thanks for reading.
