markdown.go

 1//go:build cgo
 2
 3package clib
 4
 5/*
 6#include "md4c.h"
 7#include "md4c-html.h"
 8#include <stdlib.h>
 9#include <string.h>
10
11// Buffer for collecting md4c-html output.
12typedef struct {
13    char* data;
14    size_t len;
15    size_t cap;
16} MdBuf;
17
18static void md_output_cb(const MD_CHAR* text, MD_SIZE size, void* userdata) {
19    MdBuf* b = (MdBuf*)userdata;
20    size_t needed = b->len + size;
21    if (needed > b->cap) {
22        size_t newcap = b->cap ? b->cap * 2 : 4096;
23        while (newcap < needed) newcap *= 2;
24        b->data = (char*)realloc(b->data, newcap);
25        b->cap = newcap;
26    }
27    memcpy(b->data + b->len, text, size);
28    b->len += size;
29}
30
31// md4c_to_html converts Markdown to HTML using md4c.
32// Returns the HTML string (caller must free) and sets *out_len.
33// Returns NULL on failure.
34static char* md4c_to_html(const char* input, size_t input_len, size_t* out_len) {
35    MdBuf buf = {0};
36    buf.cap = input_len * 2;
37    if (buf.cap < 256) buf.cap = 256;
38    buf.data = (char*)malloc(buf.cap);
39    if (!buf.data) return NULL;
40
41    // Use permissive flags to handle raw HTML in emails.
42    unsigned parser_flags = MD_FLAG_PERMISSIVEAUTOLINKS |
43                            MD_FLAG_TABLES |
44                            MD_FLAG_STRIKETHROUGH |
45                            MD_FLAG_TASKLISTS;
46
47    int ret = md_html(input, (MD_SIZE)input_len, md_output_cb, &buf,
48                      parser_flags, 0);
49    if (ret != 0) {
50        free(buf.data);
51        return NULL;
52    }
53
54    *out_len = buf.len;
55    return buf.data;
56}
57*/
58import "C"
59import (
60	"log"
61	"unsafe"
62)
63
64// MarkdownToHTML converts Markdown bytes to HTML using md4c (C).
65// This is significantly faster than goldmark for large documents.
66func MarkdownToHTML(md []byte) []byte {
67	if len(md) == 0 {
68		return nil
69	}
70
71	cInput := C.CBytes(md)
72	defer C.free(cInput)
73
74	var outLen C.size_t
75	result := C.md4c_to_html((*C.char)(cInput), C.size_t(len(md)), &outLen)
76	if result == nil {
77		log.Printf("markdown: md4c_to_html failed, falling back to escaped plain-text HTML")
78		return markdownPlainTextHTML(md)
79	}
80	defer C.free(unsafe.Pointer(result))
81
82	return C.GoBytes(unsafe.Pointer(result), C.int(outLen))
83}