mirror of
https://github.com/danbulant/markdown-wasm
synced 2026-05-19 12:29:04 +00:00
Compare commits
10 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad5043fd69 | ||
|
|
41cf2dc97c | ||
|
|
0d99d1151f | ||
|
|
0aa6c8ff6c | ||
|
|
fa940bf432 | ||
|
|
4b48783c3e | ||
|
|
571f5ceb09 | ||
|
|
2938af6f0b | ||
|
|
da6fc41638 | ||
|
|
9e521244de |
30 changed files with 412 additions and 157 deletions
77
README.md
77
README.md
|
|
@ -2,13 +2,11 @@
|
|||
|
||||
Very fast Markdown parser & HTML renderer implemented in WebAssembly
|
||||
|
||||
- Zero dependencies
|
||||
- Portable & safe
|
||||
- Zero dependencies (31 kB gzipped)
|
||||
- Portable & safe (WASM executes in isolated memory and can run almost anywhere)
|
||||
- [Simple API](#api)
|
||||
- [Fast and efficient](#benchmarks)
|
||||
- JS + WASM is only 31 kB gzipped
|
||||
|
||||
Based on [md4c](http://github.com/mity/md4c)
|
||||
- [Very fast](#benchmarks)
|
||||
- Based on [md4c](http://github.com/mity/md4c) — compliant to the CommonMark specification
|
||||
|
||||
|
||||
## Examples
|
||||
|
|
@ -91,29 +89,22 @@ See [`test/benchmark`](test/benchmark#readme) for more information.
|
|||
* parse reads markdown source at s and converts it to HTML.
|
||||
* When output is a byte array, it will be a reference.
|
||||
*/
|
||||
export function parse(s :Source, o? :ParseOptions & { asMemoryView? :never|false }) :string
|
||||
export function parse(s :Source, o? :ParseOptions & { asMemoryView :true }) :Uint8Array
|
||||
export function parse(s :Source, o? :ParseOptions & { bytes? :never|false }) :string
|
||||
export function parse(s :Source, o? :ParseOptions & { bytes :true }) :Uint8Array
|
||||
|
||||
/** Markdown source code can be provided as a JavaScript string or UTF8 encoded data */
|
||||
type Source = string|ArrayLike<number>
|
||||
type Source = string | ArrayLike<number>
|
||||
|
||||
/** Options for the parse function */
|
||||
export interface ParseOptions {
|
||||
/**
|
||||
* Customize parsing.
|
||||
* If not provided, the following flags are used, equating to github-style parsing:
|
||||
* COLLAPSE_WHITESPACE
|
||||
* PERMISSIVE_ATX_HEADERS
|
||||
* PERMISSIVE_URL_AUTO_LINKS
|
||||
* STRIKETHROUGH
|
||||
* TABLES
|
||||
* TASK_LISTS
|
||||
*/
|
||||
/** Customize parsing. Defaults to ParseFlags.DEFAULT */
|
||||
parseFlags? :ParseFlags
|
||||
|
||||
/** Select output format. Defaults to "html" */
|
||||
format? : "html" | "xhtml"
|
||||
|
||||
/**
|
||||
* asMemoryView=true causes parse() to return a view of heap memory as a Uint8Array,
|
||||
* instead of a string.
|
||||
* bytes=true causes parse() to return the result as a Uint8Array instead of a string.
|
||||
*
|
||||
* The returned Uint8Array is only valid until the next call to parse().
|
||||
* If you need to keep the returned data around, call Uint8Array.slice() to make a copy,
|
||||
|
|
@ -122,9 +113,36 @@ export interface ParseOptions {
|
|||
* This only provides a performance benefit when you never need to convert the output
|
||||
* to a string. In most cases you're better off leaving this unset or false.
|
||||
*/
|
||||
bytes? :boolean
|
||||
|
||||
/** Allow "javascript:" in links */
|
||||
allowJSURIs? :boolean
|
||||
|
||||
/**
|
||||
* Optional callback which if provided is called for each code block.
|
||||
* langname holds the "language tag", if any, of the block.
|
||||
*
|
||||
* The returned value is inserted into the resulting HTML verbatim, without HTML escaping.
|
||||
* Thus, you should take care of properly escaping any special HTML characters.
|
||||
*
|
||||
* If the function returns null or undefined, or an exception occurs, the body will be
|
||||
* included as-is after going through HTML escaping.
|
||||
*
|
||||
* Note that use of this callback has an adverse impact on performance as it casues
|
||||
* calls and data to be bridged between WASM and JS on every invocation.
|
||||
*/
|
||||
onCodeBlock? :(langname :string, body :UTF8Bytes) => Uint8Array|string|null|undefined
|
||||
|
||||
/** @depreceated use "bytes" instead (v1.1.1) */
|
||||
asMemoryView? :boolean
|
||||
}
|
||||
|
||||
/** UTF8Bytes is a Uint8Array representing UTF8 text */
|
||||
export interface UTF8Bytes extends Uint8Array {
|
||||
/** toString returns a UTF8 decoded string (lazily decoded and cached) */
|
||||
toString() :string
|
||||
}
|
||||
|
||||
/** Flags that customize Markdown parsing */
|
||||
export enum ParseFlags {
|
||||
/** In TEXT, collapse non-trivial whitespace into single ' ' */ COLLAPSE_WHITESPACE,
|
||||
|
|
@ -140,13 +158,24 @@ export enum ParseFlags {
|
|||
/** Enable tables extension. */ TABLES,
|
||||
/** Enable task list extension. */ TASK_LISTS,
|
||||
/** Enable wiki links extension. */ WIKI_LINKS,
|
||||
/** Enable underline extension (disables '_' for emphasis) */ UNDERLINE,
|
||||
|
||||
/** Default flags */ DEFAULT,
|
||||
/** Shorthand for NO_HTML_BLOCKS | NO_HTML_SPANS */ NO_HTML,
|
||||
/** Default flags are:
|
||||
* COLLAPSE_WHITESPACE |
|
||||
* PERMISSIVE_ATX_HEADERS |
|
||||
* PERMISSIVE_URL_AUTO_LINKS |
|
||||
* STRIKETHROUGH |
|
||||
* TABLES |
|
||||
* TASK_LISTS
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/** Shorthand for NO_HTML_BLOCKS | NO_HTML_SPANS */
|
||||
NO_HTML,
|
||||
}
|
||||
```
|
||||
|
||||
See `markdown.d.ts`
|
||||
See [`markdown.d.ts`](markdown.d.ts)
|
||||
|
||||
|
||||
## Building from source
|
||||
|
|
|
|||
2
dist/markdown.es.js
vendored
2
dist/markdown.es.js
vendored
File diff suppressed because one or more lines are too long
2
dist/markdown.es.js.map
vendored
2
dist/markdown.es.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/markdown.js
vendored
2
dist/markdown.js
vendored
File diff suppressed because one or more lines are too long
2
dist/markdown.js.map
vendored
2
dist/markdown.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/markdown.node.js
vendored
4
dist/markdown.node.js
vendored
File diff suppressed because one or more lines are too long
2
dist/markdown.node.js.map
vendored
2
dist/markdown.node.js.map
vendored
File diff suppressed because one or more lines are too long
BIN
dist/markdown.wasm
vendored
Normal file → Executable file
BIN
dist/markdown.wasm
vendored
Normal file → Executable file
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -13,16 +13,16 @@
|
|||
<p>line 1
|
||||
line 2</p>
|
||||
<h2><a id="code-poetry" class="anchor" aria-hidden="true" href="#code-poetry"></a>Code & Poetry</h2>
|
||||
<pre><code>You can also indent
|
||||
blocks to display
|
||||
code or poetry.
|
||||
<pre><code>YOU CAN ALSO INDENT
|
||||
BLOCKS TO DISPLAY
|
||||
CODE OR POETRY.
|
||||
|
||||
Indented code/poetry blocks
|
||||
can be hard-wrapped.
|
||||
INDENTED CODE/POETRY BLOCKS
|
||||
CAN BE HARD-WRAPPED.
|
||||
</code></pre>
|
||||
<p><b>Or, wrap your code in three backticks:</b></p>
|
||||
<pre><code class="language-js">function codeBlocks() {
|
||||
return "Can be inserted"
|
||||
<pre><code class="language-js">FUNCTION CODEBLOCKS() {
|
||||
RETURN "CAN BE INSERTED"
|
||||
}
|
||||
</code></pre>
|
||||
<h3><a id="block-quotes" class="anchor" aria-hidden="true" href="#block-quotes"></a>Block Quotes</h3>
|
||||
|
|
@ -79,3 +79,4 @@ breaks.</p>
|
|||
<h2><a id="anot-her" class="anchor" aria-hidden="true" href="#anot-her"></a>Anöt######her!</h2>
|
||||
<h2><a id="anot-her" class="anchor" aria-hidden="true" href="#anot-her"></a>?!Anöt//her!!</h2>
|
||||
<h2><a id="" class="anchor" aria-hidden="true" href="#"></a>?!!</h2>
|
||||
<p><a href="">XSS test</a></p>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
const fs = require("fs")
|
||||
const md = require("../dist/markdown.node.js")
|
||||
// const md = require("../build/debug/markdown.node.js")
|
||||
|
||||
const source = fs.readFileSync(__dirname + "/example.md")
|
||||
const outbuf = md.parse(source, { bytes: true })
|
||||
const outbuf = md.parse(source, {
|
||||
bytes: true,
|
||||
onCodeBlock(lang, body) {
|
||||
console.log(`onCodeBlock (${lang})`)
|
||||
return html_escape(body.toString().toUpperCase())
|
||||
},
|
||||
})
|
||||
const outfile = __dirname + "/example.html"
|
||||
console.log("write", outfile)
|
||||
fs.writeFileSync(outfile, outbuf)
|
||||
|
|
@ -11,16 +18,36 @@ console.log(fs.readFileSync(outfile, "utf8"))
|
|||
|
||||
// mini benchmark
|
||||
if (process.argv.includes("-bench")) {
|
||||
console.log("benchmark start (sampling ~2s of data)")
|
||||
benchmark("bytes", {
|
||||
bytes: true,
|
||||
})
|
||||
benchmark("bytes + onCodeBlock", {
|
||||
bytes: true,
|
||||
onCodeBlock(lang, body) {
|
||||
return null // causes the body to be HTML-escaped & included as is
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function benchmark(name, options) {
|
||||
console.log(`benchmark start ${name} (sampling ~2s of data)`)
|
||||
const timeStart = Date.now()
|
||||
const N = 1000, T = 2000
|
||||
let ntotal = 0
|
||||
while (Date.now() - timeStart < 2000) {
|
||||
for (let i = 0; i < N; i++) {
|
||||
global["dont-optimize-away"] = md.parse(source, { bytes: true })
|
||||
global["dont-optimize-away"] = md.parse(source, options)
|
||||
}
|
||||
ntotal += N
|
||||
}
|
||||
const timeSpent = Date.now() - timeStart
|
||||
console.log(`benchmark end -- avg parse time: ${((timeSpent / ntotal) * 1000).toFixed(1)}us`)
|
||||
console.log(
|
||||
`benchmark end ${name} -- avg parse time: ` +
|
||||
`${((timeSpent / ntotal) * 1000).toFixed(1)}us`)
|
||||
}
|
||||
|
||||
function html_escape(str) {
|
||||
return str.replace(/[&<>'"]/g, tag => ({
|
||||
'&': '&','<': '<','>': '>',"'": ''','"': '"'
|
||||
}[tag]))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,3 +82,5 @@ function codeBlocks() {
|
|||
## ?!Anöt//her!!
|
||||
|
||||
## ?!!
|
||||
|
||||
[XSS test](javAscRipt:alert("xss"))
|
||||
|
|
|
|||
24
markdown.d.ts
vendored
24
markdown.d.ts
vendored
|
|
@ -28,10 +28,34 @@ export interface ParseOptions {
|
|||
*/
|
||||
bytes? :boolean
|
||||
|
||||
/** Allow "javascript:" in links */
|
||||
allowJSURIs? :boolean
|
||||
|
||||
/**
|
||||
* Optional callback which if provided is called for each code block.
|
||||
* langname holds the "language tag", if any, of the block.
|
||||
*
|
||||
* The returned value is inserted into the resulting HTML verbatim, without HTML escaping.
|
||||
* Thus, you should take care of properly escaping any special HTML characters.
|
||||
*
|
||||
* If the function returns null or undefined, or an exception occurs, the body will be
|
||||
* included as-is after going through HTML escaping.
|
||||
*
|
||||
* Note that use of this callback has an adverse impact on performance as it casues
|
||||
* calls and data to be bridged between WASM and JS on every invocation.
|
||||
*/
|
||||
onCodeBlock? :(langname :string, body :UTF8Bytes) => Uint8Array|string|null|undefined
|
||||
|
||||
/** @depreceated use "bytes" instead (v1.1.1) */
|
||||
asMemoryView? :boolean
|
||||
}
|
||||
|
||||
/** UTF8Bytes is a Uint8Array representing UTF8 text */
|
||||
export interface UTF8Bytes extends Uint8Array {
|
||||
/** toString returns a UTF8 decoded string (lazily decoded and cached) */
|
||||
toString() :string
|
||||
}
|
||||
|
||||
/** Flags that customize Markdown parsing */
|
||||
export enum ParseFlags {
|
||||
/** In TEXT, collapse non-trivial whitespace into single ' ' */ COLLAPSE_WHITESPACE,
|
||||
|
|
|
|||
28
package-lock.json
generated
28
package-lock.json
generated
|
|
@ -1,13 +1,31 @@
|
|||
{
|
||||
"name": "markdown-wasm",
|
||||
"version": "1.1.2",
|
||||
"lockfileVersion": 1,
|
||||
"version": "1.2.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"wasmc": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/wasmc": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/wasmc/-/wasmc-2.2.1.tgz",
|
||||
"integrity": "sha512-uV/nhZdfnffBfLv2WJWWfRoJwk+5phhPobg7fqnJoRpXdUV5eShRb6tOXoco987cTxarkBymYwGufMdC4dPXAw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"wasmc": "wasmc"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"wasmc": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wasmc/-/wasmc-2.2.0.tgz",
|
||||
"integrity": "sha512-Pp2B42WuhL7JjslKDlGbpoV8WYpGAz0ct1XYTQWIN99cVsj7nfEuhD/jKwivLJHOFSOzMZZqvQH4m5BPQmTmCg==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/wasmc/-/wasmc-2.2.1.tgz",
|
||||
"integrity": "sha512-uV/nhZdfnffBfLv2WJWWfRoJwk+5phhPobg7fqnJoRpXdUV5eShRb6tOXoco987cTxarkBymYwGufMdC4dPXAw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "markdown-wasm",
|
||||
"version": "1.1.2",
|
||||
"version": "1.2.0",
|
||||
"description": "Markdown parser and html generator implemented in WebAssembly",
|
||||
"browser": "dist/markdown.js",
|
||||
"main": "dist/markdown.node.js",
|
||||
|
|
@ -35,6 +35,6 @@
|
|||
"author": "Rasmus Andersson <https://rsms.me/>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"wasmc": "^2.2.0"
|
||||
"wasmc": "^2.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
src/common.h
16
src/common.h
|
|
@ -54,7 +54,21 @@ typedef int32_t i32;
|
|||
#endif
|
||||
#if DEBUG
|
||||
#include <stdio.h>
|
||||
#define dlog(...) printf(__VA_ARGS__)
|
||||
#define dlog(fmt, ...) printf(fmt "\n", ##__VA_ARGS__)
|
||||
#else
|
||||
#define dlog(...)
|
||||
#endif /* DEBUG > 0 */
|
||||
|
||||
#include "wbuf.h"
|
||||
|
||||
// these should be in sync with "OutputFlags" in md.js
|
||||
typedef enum OutputFlags {
|
||||
OutputFlagHTML = 1 << 0,
|
||||
OutputFlagXHTML = 1 << 1,
|
||||
OutputFlagAllowJSURI = 1 << 2, // allow "javascript:" URIs in links
|
||||
} OutputFlags;
|
||||
|
||||
typedef int(*JSTextFilterFun)(
|
||||
const char* metaptr, u32 metalen,
|
||||
const char* inptr, u32 inlen,
|
||||
const char** outptrp); // return outlen
|
||||
|
|
|
|||
197
src/fmt_html.c
197
src/fmt_html.c
|
|
@ -25,17 +25,18 @@
|
|||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "fmt_html.h"
|
||||
#include "md4c.h"
|
||||
|
||||
typedef struct HtmlRenderer_st {
|
||||
WBuf* outbuf;
|
||||
int imgnest;
|
||||
int addanchor;
|
||||
u32 flags;
|
||||
} HtmlRenderer;
|
||||
// typedef struct FmtHTML_st {
|
||||
// WBuf* outbuf;
|
||||
// int imgnest;
|
||||
// int addanchor;
|
||||
// u32 flags;
|
||||
// } FmtHTML;
|
||||
|
||||
|
||||
static char htmlEscapeMap[256] = {
|
||||
|
|
@ -61,20 +62,20 @@ static char htmlEscapeMap[256] = {
|
|||
static const char ucReplacementUTF8[] = { 0xef, 0xbf, 0xbd };
|
||||
|
||||
|
||||
static inline void render_text(HtmlRenderer* r, const char* pch, size_t len) {
|
||||
static inline void render_text(FmtHTML* r, const char* pch, size_t len) {
|
||||
WBufAppendBytes(r->outbuf, pch, len);
|
||||
}
|
||||
|
||||
static inline void render_literal(HtmlRenderer* r, const char* cs) {
|
||||
static inline void render_literal(FmtHTML* r, const char* cs) {
|
||||
WBufAppendBytes(r->outbuf, cs, strlen(cs));
|
||||
}
|
||||
|
||||
static inline void render_char(HtmlRenderer* r, char c) {
|
||||
static inline void render_char(FmtHTML* r, char c) {
|
||||
WBufAppendc(r->outbuf, c);
|
||||
}
|
||||
|
||||
|
||||
static void render_html_escaped(HtmlRenderer* r, const char* data, size_t size) {
|
||||
static void render_html_escaped(FmtHTML* r, const char* data, size_t size) {
|
||||
MD_OFFSET beg = 0;
|
||||
MD_OFFSET off = 0;
|
||||
|
||||
|
|
@ -167,7 +168,7 @@ static size_t WBufAppendSlug(WBuf* b, const char* pch, size_t len) {
|
|||
}
|
||||
|
||||
|
||||
static void render_attribute(HtmlRenderer* r, const MD_ATTRIBUTE* attr) {
|
||||
static void render_attribute(FmtHTML* r, const MD_ATTRIBUTE* attr) {
|
||||
int i;
|
||||
for (i = 0; attr->substr_offsets[i] < attr->size; i++) {
|
||||
MD_TEXTTYPE type = attr->substr_types[i];
|
||||
|
|
@ -183,7 +184,7 @@ static void render_attribute(HtmlRenderer* r, const MD_ATTRIBUTE* attr) {
|
|||
}
|
||||
|
||||
|
||||
static void render_open_ol_block(HtmlRenderer* r, const MD_BLOCK_OL_DETAIL* det) {
|
||||
static void render_open_ol_block(FmtHTML* r, const MD_BLOCK_OL_DETAIL* det) {
|
||||
if (det->start == 1) {
|
||||
render_literal(r, "<ol>\n");
|
||||
} else {
|
||||
|
|
@ -193,7 +194,7 @@ static void render_open_ol_block(HtmlRenderer* r, const MD_BLOCK_OL_DETAIL* det)
|
|||
}
|
||||
}
|
||||
|
||||
static void render_open_li_block(HtmlRenderer* r, const MD_BLOCK_LI_DETAIL* det) {
|
||||
static void render_open_li_block(FmtHTML* r, const MD_BLOCK_LI_DETAIL* det) {
|
||||
if (det->is_task) {
|
||||
render_literal(r, "<li class=\"task-list-item\"><input type=\"checkbox\" disabled");
|
||||
if (det->task_mark == 'x' || det->task_mark == 'X') {
|
||||
|
|
@ -205,7 +206,7 @@ static void render_open_li_block(HtmlRenderer* r, const MD_BLOCK_LI_DETAIL* det)
|
|||
}
|
||||
}
|
||||
|
||||
static void render_open_code_block(HtmlRenderer* r, const MD_BLOCK_CODE_DETAIL* det) {
|
||||
static void render_open_code_block(FmtHTML* r, const MD_BLOCK_CODE_DETAIL* det) {
|
||||
render_literal(r, "<pre><code");
|
||||
if (det->lang.text != NULL) {
|
||||
render_literal(r, " class=\"language-");
|
||||
|
|
@ -213,9 +214,41 @@ static void render_open_code_block(HtmlRenderer* r, const MD_BLOCK_CODE_DETAIL*
|
|||
render_char(r, '"');
|
||||
}
|
||||
render_char(r, '>');
|
||||
r->codeBlockNest++;
|
||||
}
|
||||
|
||||
static void render_open_td_block(HtmlRenderer* r, bool isTH, const MD_BLOCK_TD_DETAIL* det) {
|
||||
static void render_close_code_block(FmtHTML* r, const MD_BLOCK_CODE_DETAIL* det) {
|
||||
dlog("end code block (lang \"%.*s\")", (int)det->lang.size, det->lang.text);
|
||||
|
||||
r->codeBlockNest--;
|
||||
|
||||
if (r->onCodeBlock) {
|
||||
const char* text = r->tmpbuf.start;
|
||||
size_t len = WBufLen(&r->tmpbuf);
|
||||
|
||||
int outlen = -1;
|
||||
|
||||
if (len <= 0x7FFFFFFF) {
|
||||
const char* outptr = NULL;
|
||||
outlen = r->onCodeBlock(det->lang.text, (u32)det->lang.size, text, (u32)len, &outptr);
|
||||
if (outlen > 0 && outptr != NULL)
|
||||
WBufAppendBytes(r->outbuf, outptr, (size_t)outlen);
|
||||
if (outptr != NULL)
|
||||
free((void*)outptr);
|
||||
}
|
||||
|
||||
if (outlen < 0) {
|
||||
// The function failed or opted out of taking care of formatting
|
||||
render_html_escaped(r, text, len);
|
||||
}
|
||||
|
||||
WBufReset(&r->tmpbuf);
|
||||
}
|
||||
|
||||
render_literal(r, "</code></pre>\n");
|
||||
}
|
||||
|
||||
static void render_open_td_block(FmtHTML* r, bool isTH, const MD_BLOCK_TD_DETAIL* det) {
|
||||
render_text(r, isTH ? "<th" : "<td", 3);
|
||||
switch (det->align) {
|
||||
case MD_ALIGN_LEFT: render_literal(r, " align=\"left\">"); break;
|
||||
|
|
@ -225,9 +258,21 @@ static void render_open_td_block(HtmlRenderer* r, bool isTH, const MD_BLOCK_TD_D
|
|||
}
|
||||
}
|
||||
|
||||
static void render_open_a_span(HtmlRenderer* r, const MD_SPAN_A_DETAIL* det) {
|
||||
static bool is_javascript_uri(const MD_CHAR* text, size_t len) {
|
||||
return (
|
||||
len >= strlen("javascript:") &&
|
||||
strncasecmp(text, "javascript:", strlen("javascript:")) == 0
|
||||
);
|
||||
}
|
||||
|
||||
static void render_open_a_span(FmtHTML* r, const MD_SPAN_A_DETAIL* det) {
|
||||
render_literal(r, "<a href=\"");
|
||||
render_attribute(r, &det->href);
|
||||
// skip "javascript:" URIs unless explicitly allowed
|
||||
if ((r->flags & OutputFlagAllowJSURI) != 0 ||
|
||||
!is_javascript_uri(det->href.text, det->href.size))
|
||||
{
|
||||
render_attribute(r, &det->href);
|
||||
}
|
||||
if (det->title.text != NULL) {
|
||||
render_literal(r, "\" title=\"");
|
||||
render_attribute(r, &det->title);
|
||||
|
|
@ -235,23 +280,23 @@ static void render_open_a_span(HtmlRenderer* r, const MD_SPAN_A_DETAIL* det) {
|
|||
render_literal(r, "\">");
|
||||
}
|
||||
|
||||
static void render_open_img_span(HtmlRenderer* r, const MD_SPAN_IMG_DETAIL* det) {
|
||||
static void render_open_img_span(FmtHTML* r, const MD_SPAN_IMG_DETAIL* det) {
|
||||
render_literal(r, "<img src=\"");
|
||||
render_attribute(r, &det->src);
|
||||
render_literal(r, "\" alt=\"");
|
||||
r->imgnest++;
|
||||
}
|
||||
|
||||
static void render_close_img_span(HtmlRenderer* r, const MD_SPAN_IMG_DETAIL* det) {
|
||||
static void render_close_img_span(FmtHTML* r, const MD_SPAN_IMG_DETAIL* det) {
|
||||
if(det->title.text != NULL) {
|
||||
render_literal(r, "\" title=\"");
|
||||
render_attribute(r, &det->title);
|
||||
}
|
||||
render_literal(r, (r->flags & MD_HTML_FLAG_XHTML) ? "\"/>" : "\">");
|
||||
render_literal(r, (r->flags & OutputFlagXHTML) ? "\"/>" : "\">");
|
||||
r->imgnest--;
|
||||
}
|
||||
|
||||
static void render_open_wikilink_span(HtmlRenderer* r, const MD_SPAN_WIKILINK_DETAIL* det) {
|
||||
static void render_open_wikilink_span(FmtHTML* r, const MD_SPAN_WIKILINK_DETAIL* det) {
|
||||
render_literal(r, "<x-wikilink data-target=\"");
|
||||
render_attribute(r, &det->target);
|
||||
render_literal(r, "\">");
|
||||
|
|
@ -266,30 +311,30 @@ static void render_open_wikilink_span(HtmlRenderer* r, const MD_SPAN_WIKILINK_DE
|
|||
|
||||
static int enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata) {
|
||||
static const MD_CHAR* head[6] = { "<h1>", "<h2>", "<h3>", "<h4>", "<h5>", "<h6>" };
|
||||
HtmlRenderer* r = (HtmlRenderer*) userdata;
|
||||
FmtHTML* r = (FmtHTML*) userdata;
|
||||
|
||||
switch(type) {
|
||||
case MD_BLOCK_DOC: /* noop */ break;
|
||||
case MD_BLOCK_QUOTE: render_literal(r, "<blockquote>\n"); break;
|
||||
case MD_BLOCK_UL: render_literal(r, "<ul>\n"); break;
|
||||
case MD_BLOCK_OL: render_open_ol_block(r, (const MD_BLOCK_OL_DETAIL*)detail); break;
|
||||
case MD_BLOCK_LI: render_open_li_block(r, (const MD_BLOCK_LI_DETAIL*)detail); break;
|
||||
case MD_BLOCK_HR: render_literal(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr/>\n" : "<hr>\n"); break;
|
||||
case MD_BLOCK_DOC: /* noop */ break;
|
||||
case MD_BLOCK_QUOTE: render_literal(r, "<blockquote>\n"); break;
|
||||
case MD_BLOCK_UL: render_literal(r, "<ul>\n"); break;
|
||||
case MD_BLOCK_OL: render_open_ol_block(r, (const MD_BLOCK_OL_DETAIL*)detail); break;
|
||||
case MD_BLOCK_LI: render_open_li_block(r, (const MD_BLOCK_LI_DETAIL*)detail); break;
|
||||
case MD_BLOCK_HR: render_literal(r, (r->flags & OutputFlagXHTML) ? "<hr/>\n" : "<hr>\n"); break;
|
||||
case MD_BLOCK_H:
|
||||
{
|
||||
render_literal(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]);
|
||||
r->addanchor = 1;
|
||||
break;
|
||||
}
|
||||
case MD_BLOCK_CODE: render_open_code_block(r, (const MD_BLOCK_CODE_DETAIL*) detail); break;
|
||||
case MD_BLOCK_HTML: /* noop */ break;
|
||||
case MD_BLOCK_P: render_literal(r, "<p>"); break;
|
||||
case MD_BLOCK_TABLE: render_literal(r, "<table>\n"); break;
|
||||
case MD_BLOCK_THEAD: render_literal(r, "<thead>\n"); break;
|
||||
case MD_BLOCK_TBODY: render_literal(r, "<tbody>\n"); break;
|
||||
case MD_BLOCK_TR: render_literal(r, "<tr>\n"); break;
|
||||
case MD_BLOCK_TH: render_open_td_block(r, true, (MD_BLOCK_TD_DETAIL*)detail); break;
|
||||
case MD_BLOCK_TD: render_open_td_block(r, false, (MD_BLOCK_TD_DETAIL*)detail); break;
|
||||
case MD_BLOCK_CODE: render_open_code_block(r, (const MD_BLOCK_CODE_DETAIL*) detail); break;
|
||||
case MD_BLOCK_HTML: /* noop */ break;
|
||||
case MD_BLOCK_P: render_literal(r, "<p>"); break;
|
||||
case MD_BLOCK_TABLE: render_literal(r, "<table>\n"); break;
|
||||
case MD_BLOCK_THEAD: render_literal(r, "<thead>\n"); break;
|
||||
case MD_BLOCK_TBODY: render_literal(r, "<tbody>\n"); break;
|
||||
case MD_BLOCK_TR: render_literal(r, "<tr>\n"); break;
|
||||
case MD_BLOCK_TH: render_open_td_block(r, true, (MD_BLOCK_TD_DETAIL*)detail); break;
|
||||
case MD_BLOCK_TD: render_open_td_block(r, false, (MD_BLOCK_TD_DETAIL*)detail); break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -297,32 +342,32 @@ static int enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
|
|||
|
||||
static int leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata) {
|
||||
static const MD_CHAR* head[6] = { "</h1>\n", "</h2>\n", "</h3>\n", "</h4>\n", "</h5>\n", "</h6>\n" };
|
||||
HtmlRenderer* r = (HtmlRenderer*) userdata;
|
||||
FmtHTML* r = (FmtHTML*) userdata;
|
||||
|
||||
switch(type) {
|
||||
case MD_BLOCK_DOC: /*noop*/ break;
|
||||
case MD_BLOCK_QUOTE: render_literal(r, "</blockquote>\n"); break;
|
||||
case MD_BLOCK_UL: render_literal(r, "</ul>\n"); break;
|
||||
case MD_BLOCK_OL: render_literal(r, "</ol>\n"); break;
|
||||
case MD_BLOCK_LI: render_literal(r, "</li>\n"); break;
|
||||
case MD_BLOCK_HR: /*noop*/ break;
|
||||
case MD_BLOCK_H: render_literal(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
|
||||
case MD_BLOCK_CODE: render_literal(r, "</code></pre>\n"); break;
|
||||
case MD_BLOCK_HTML: /* noop */ break;
|
||||
case MD_BLOCK_P: render_literal(r, "</p>\n"); break;
|
||||
case MD_BLOCK_TABLE: render_literal(r, "</table>\n"); break;
|
||||
case MD_BLOCK_THEAD: render_literal(r, "</thead>\n"); break;
|
||||
case MD_BLOCK_TBODY: render_literal(r, "</tbody>\n"); break;
|
||||
case MD_BLOCK_TR: render_literal(r, "</tr>\n"); break;
|
||||
case MD_BLOCK_TH: render_literal(r, "</th>\n"); break;
|
||||
case MD_BLOCK_TD: render_literal(r, "</td>\n"); break;
|
||||
case MD_BLOCK_DOC: /*noop*/ break;
|
||||
case MD_BLOCK_QUOTE: render_literal(r, "</blockquote>\n"); break;
|
||||
case MD_BLOCK_UL: render_literal(r, "</ul>\n"); break;
|
||||
case MD_BLOCK_OL: render_literal(r, "</ol>\n"); break;
|
||||
case MD_BLOCK_LI: render_literal(r, "</li>\n"); break;
|
||||
case MD_BLOCK_HR: /*noop*/ break;
|
||||
case MD_BLOCK_H: render_literal(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
|
||||
case MD_BLOCK_CODE: render_close_code_block(r, (const MD_BLOCK_CODE_DETAIL*)detail); break;
|
||||
case MD_BLOCK_HTML: /* noop */ break;
|
||||
case MD_BLOCK_P: render_literal(r, "</p>\n"); break;
|
||||
case MD_BLOCK_TABLE: render_literal(r, "</table>\n"); break;
|
||||
case MD_BLOCK_THEAD: render_literal(r, "</thead>\n"); break;
|
||||
case MD_BLOCK_TBODY: render_literal(r, "</tbody>\n"); break;
|
||||
case MD_BLOCK_TR: render_literal(r, "</tr>\n"); break;
|
||||
case MD_BLOCK_TH: render_literal(r, "</th>\n"); break;
|
||||
case MD_BLOCK_TD: render_literal(r, "</td>\n"); break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
|
||||
HtmlRenderer* r = (HtmlRenderer*) userdata;
|
||||
FmtHTML* r = (FmtHTML*) userdata;
|
||||
|
||||
if(r->imgnest > 0) {
|
||||
/* We are inside a Markdown image label. Markdown allows to use any
|
||||
|
|
@ -347,8 +392,8 @@ static int enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
|
|||
case MD_SPAN_EM: render_literal(r, "<em>"); break;
|
||||
case MD_SPAN_STRONG: render_literal(r, "<b>"); break;
|
||||
case MD_SPAN_U: render_literal(r, "<u>"); break;
|
||||
case MD_SPAN_A: render_open_a_span(r, (MD_SPAN_A_DETAIL*) detail); break;
|
||||
case MD_SPAN_IMG: render_open_img_span(r, (MD_SPAN_IMG_DETAIL*) detail); break;
|
||||
case MD_SPAN_A: render_open_a_span(r, (MD_SPAN_A_DETAIL*)detail); break;
|
||||
case MD_SPAN_IMG: render_open_img_span(r, (MD_SPAN_IMG_DETAIL*)detail); break;
|
||||
case MD_SPAN_CODE: render_literal(r, "<code>"); break;
|
||||
case MD_SPAN_DEL: render_literal(r, "<del>"); break;
|
||||
case MD_SPAN_LATEXMATH: render_literal(r, "<x-equation>"); break;
|
||||
|
|
@ -360,7 +405,7 @@ static int enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
|
|||
}
|
||||
|
||||
static int leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
|
||||
HtmlRenderer* r = (HtmlRenderer*) userdata;
|
||||
FmtHTML* r = (FmtHTML*) userdata;
|
||||
|
||||
if(r->imgnest > 0) {
|
||||
/* Ditto as in enter_span_callback(), except we have to allow the
|
||||
|
|
@ -387,7 +432,12 @@ static int leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata) {
|
|||
}
|
||||
|
||||
static int text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata) {
|
||||
HtmlRenderer* r = (HtmlRenderer*) userdata;
|
||||
FmtHTML* r = (FmtHTML*) userdata;
|
||||
|
||||
if (r->codeBlockNest && r->onCodeBlock) {
|
||||
WBufAppendBytes(&r->tmpbuf, text, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (r->addanchor) {
|
||||
r->addanchor = 0;
|
||||
|
|
@ -409,18 +459,18 @@ static int text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, vo
|
|||
}
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case MD_TEXT_NULLCHAR: render_text(r, ucReplacementUTF8, sizeof(ucReplacementUTF8)); break;
|
||||
case MD_TEXT_BR:
|
||||
render_literal(
|
||||
r,
|
||||
r->imgnest == 0 ?
|
||||
((r->flags & MD_HTML_FLAG_XHTML) ? "<br/>\n" : "<br>\n") :
|
||||
((r->flags & OutputFlagXHTML) ? "<br/>\n" : "<br>\n") :
|
||||
" "
|
||||
);
|
||||
break;
|
||||
|
||||
render_literal(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr/>\n" : "<hr>\n"); break;
|
||||
render_literal(r, (r->flags & OutputFlagXHTML) ? "<hr/>\n" : "<hr>\n"); break;
|
||||
|
||||
case MD_TEXT_SOFTBR: render_literal(r, (r->imgnest == 0 ? "\n" : " ")); break;
|
||||
case MD_TEXT_HTML: render_text(r, text, size); break;
|
||||
|
|
@ -435,18 +485,15 @@ static int text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, vo
|
|||
// dlog("MD4C: %s\n", msg);
|
||||
// }
|
||||
|
||||
int fmt_html(
|
||||
const MD_CHAR* input,
|
||||
MD_SIZE input_size,
|
||||
WBuf* outbuf,
|
||||
u32 parser_flags,
|
||||
u32 render_flags
|
||||
) {
|
||||
HtmlRenderer render = { outbuf, 0, 0, render_flags };
|
||||
int fmt_html(const MD_CHAR* input, MD_SIZE input_size, FmtHTML* fmt) {
|
||||
fmt->imgnest = 0;
|
||||
fmt->addanchor = 0;
|
||||
fmt->codeBlockNest = 0;
|
||||
fmt->tmpbuf = (WBuf){0};
|
||||
|
||||
MD_PARSER parser = {
|
||||
0,
|
||||
parser_flags,
|
||||
fmt->parserFlags,
|
||||
enter_block_callback,
|
||||
leave_block_callback,
|
||||
enter_span_callback,
|
||||
|
|
@ -456,5 +503,11 @@ int fmt_html(
|
|||
NULL
|
||||
};
|
||||
|
||||
return md_parse(input, input_size, &parser, (void*) &render);
|
||||
WBufInit(&fmt->tmpbuf);
|
||||
|
||||
int res = md_parse(input, input_size, &parser, (void*)fmt);
|
||||
|
||||
WBufFree(&fmt->tmpbuf);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,18 @@
|
|||
#pragma once
|
||||
#include "wbuf.h"
|
||||
|
||||
#define MD_HTML_FLAG_XHTML 0x0008 // instead of e.g. <br>, generate <br/>
|
||||
typedef struct FmtHTML {
|
||||
OutputFlags flags;
|
||||
u32 parserFlags; // passed along to md_parse
|
||||
WBuf* outbuf;
|
||||
|
||||
int fmt_html(const char* input, u32 inputlen, WBuf* outbuf, u32 parserFlags, u32 renderFlags);
|
||||
// optional callbacks
|
||||
JSTextFilterFun onCodeBlock;
|
||||
|
||||
// internal state
|
||||
int imgnest;
|
||||
int addanchor;
|
||||
int codeBlockNest;
|
||||
WBuf tmpbuf;
|
||||
} FmtHTML;
|
||||
|
||||
int fmt_html(const char* input, u32 inputlen, FmtHTML* fmt);
|
||||
|
|
|
|||
28
src/md.c
28
src/md.c
|
|
@ -1,16 +1,9 @@
|
|||
#include <ctype.h>
|
||||
#include "common.h"
|
||||
#include "wlib.h"
|
||||
#include "wbuf.h"
|
||||
#include "fmt_html.h"
|
||||
// #include "fmt_json.h"
|
||||
|
||||
// these should be in sync with "OutputFlags" in md.js
|
||||
typedef enum OutputFlags {
|
||||
OutputFlagHTML = 1 << 0,
|
||||
OutputFlagXHTML = 1 << 1,
|
||||
} OutputFlags;
|
||||
|
||||
typedef enum ErrorCode {
|
||||
ERR_NONE,
|
||||
ERR_MD_PARSE,
|
||||
|
|
@ -20,7 +13,7 @@ typedef enum ErrorCode {
|
|||
|
||||
#if DEBUG
|
||||
void __attribute__((constructor)) init() {
|
||||
dlog("WASM INIT\n");
|
||||
dlog("WASM INIT");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -35,21 +28,24 @@ export size_t parseUTF8(
|
|||
u32 inbuflen,
|
||||
u32 parser_flags,
|
||||
OutputFlags outflags,
|
||||
const char** outptr
|
||||
const char** outptr,
|
||||
JSTextFilterFun onCodeBlock
|
||||
) {
|
||||
dlog("parseUTF8 called with inbufptr=%p inbuflen=%u\n", inbufptr, inbuflen);
|
||||
dlog("parseUTF8 called with inbufptr=%p inbuflen=%u", inbufptr, inbuflen);
|
||||
|
||||
WBufReset(&outbuf);
|
||||
|
||||
if (outflags & OutputFlagHTML) {
|
||||
if ((outflags & OutputFlagHTML) || (outflags & OutputFlagXHTML)) {
|
||||
WBufReserve(&outbuf, inbuflen * 2); // approximate output size to minimize reallocations
|
||||
|
||||
u32 render_flags = 0;
|
||||
if (outflags & OutputFlagXHTML) {
|
||||
render_flags |= MD_HTML_FLAG_XHTML;
|
||||
}
|
||||
FmtHTML fmt = {
|
||||
.flags = outflags,
|
||||
.parserFlags = parser_flags,
|
||||
.outbuf = &outbuf,
|
||||
.onCodeBlock = onCodeBlock,
|
||||
};
|
||||
|
||||
if (fmt_html(inbufptr, inbuflen, &outbuf, parser_flags, render_flags) != 0) {
|
||||
if (fmt_html(inbufptr, inbuflen, &fmt) != 0) {
|
||||
// fmt_html returns status of md_parse which only fails in extreme cases
|
||||
// like when out of memory. md4c does not provide error codes or error messages.
|
||||
WErrSet(ERR_MD_PARSE, "md parser error");
|
||||
|
|
|
|||
86
src/md.js
86
src/md.js
|
|
@ -1,4 +1,10 @@
|
|||
import { utf8, withTmpBytePtr, withOutPtr, werrCheck } from "./wlib"
|
||||
import {
|
||||
utf8,
|
||||
withTmpBytePtr,
|
||||
withOutPtr,
|
||||
werrCheck,
|
||||
mallocbuf,
|
||||
} from "./wlib"
|
||||
|
||||
export const ready = Module.ready
|
||||
|
||||
|
|
@ -35,12 +41,14 @@ export const ParseFlags = {
|
|||
NO_HTML: 0x0020 | 0x0040, // NO_HTML_BLOCKS | NO_HTML_SPANS
|
||||
}
|
||||
|
||||
// these should be in sync with "OutputFlags" in md.c
|
||||
// these should be in sync with "OutputFlags" in common.h
|
||||
const OutputFlags = {
|
||||
HTML: 1 << 0, // Output HTML
|
||||
XHTML: 1 << 1, // Output XHTML (only has effect with HTML flag set)
|
||||
HTML: 1 << 0, // Output HTML
|
||||
XHTML: 1 << 1, // Output XHTML (only has effect with HTML flag set)
|
||||
AllowJSURI: 1 << 2, // Allow "javascript:" URIs
|
||||
}
|
||||
|
||||
|
||||
export function parse(source, options) {
|
||||
options = options || {}
|
||||
|
||||
|
|
@ -49,7 +57,8 @@ export function parse(source, options) {
|
|||
options.parseFlags
|
||||
)
|
||||
|
||||
let outputFlags = 0
|
||||
let outputFlags = options.allowJSURIs ? OutputFlags.AllowJSURI : 0
|
||||
|
||||
switch (options.format) {
|
||||
case "xhtml":
|
||||
outputFlags |= OutputFlags.HTML | OutputFlags.XHTML
|
||||
|
|
@ -66,11 +75,16 @@ export function parse(source, options) {
|
|||
throw new Error(`invalid format "${options.format}"`)
|
||||
}
|
||||
|
||||
let buf = typeof source == "string" ? utf8.encode(source) : source
|
||||
let onCodeBlockPtr = options.onCodeBlock ? create_onCodeBlock_fn(options.onCodeBlock) : 0
|
||||
|
||||
let buf = as_byte_array(source)
|
||||
let outbuf = withOutPtr(outptr => withTmpBytePtr(buf, (inptr, inlen) =>
|
||||
_parseUTF8(inptr, inlen, parseFlags, outputFlags, outptr)
|
||||
_parseUTF8(inptr, inlen, parseFlags, outputFlags, outptr, onCodeBlockPtr)
|
||||
))
|
||||
|
||||
if (options.onCodeBlock)
|
||||
removeFunction(onCodeBlockPtr)
|
||||
|
||||
// check for error and throw if needed
|
||||
werrCheck()
|
||||
|
||||
|
|
@ -79,8 +93,62 @@ export function parse(source, options) {
|
|||
// console.log(utf8.decode(outbuf))
|
||||
// }
|
||||
|
||||
if (options.bytes || options.asMemoryView) {
|
||||
if (options.bytes || options.asMemoryView)
|
||||
return outbuf
|
||||
}
|
||||
|
||||
return utf8.decode(outbuf)
|
||||
}
|
||||
|
||||
|
||||
function create_onCodeBlock_fn(onCodeBlock) {
|
||||
// See https://emscripten.org/docs/porting/connecting_cpp_and_javascript/
|
||||
// Interacting-with-code.html#calling-javascript-functions-as-function-pointers-from-c
|
||||
//
|
||||
// Function's C type: JSTextFilterFun
|
||||
// (metaptr ptr, metalen ptr, inptr ptr, inlen ptr, outptr ptr) -> outlen int
|
||||
const fnptr = addFunction(function(metaptr, metalen, inptr, inlen, outptr) {
|
||||
try {
|
||||
// lang is the "language" tag, if any, provided with the code block
|
||||
const lang = metalen > 0 ? utf8.decode(HEAPU8.subarray(metaptr, metaptr + metalen)) : ""
|
||||
|
||||
// body is a view into heap memory of the segment of source (UTF8 bytes)
|
||||
const body = HEAPU8.subarray(inptr, inptr + inlen)
|
||||
let bodystr = undefined
|
||||
body.toString = () => (bodystr || (bodystr = utf8.decode(body)))
|
||||
|
||||
// result is the result from the onCodeBlock function
|
||||
let result = null
|
||||
result = onCodeBlock(lang, body)
|
||||
|
||||
if (result === null || result === undefined) {
|
||||
// Callback indicates that it does not wish to filter.
|
||||
// The md.c implementation will html-encode the body.
|
||||
return -1
|
||||
}
|
||||
|
||||
let resbuf = as_byte_array(result)
|
||||
if (resbuf.length > 0) {
|
||||
// copy resbuf to WASM heap memory
|
||||
const resptr = mallocbuf(resbuf, resbuf.length)
|
||||
// write pointer value
|
||||
HEAPU32[outptr >> 2 /* == outptr / 4 */] = resptr
|
||||
// Note: fmt_html.c calls free(resptr)
|
||||
}
|
||||
|
||||
return resbuf.length
|
||||
} catch (err) {
|
||||
console.error(`error in markdown onCodeBlock callback: ${err.stack||err}`)
|
||||
return -1
|
||||
}
|
||||
}, "iiiiii")
|
||||
return fnptr
|
||||
}
|
||||
|
||||
|
||||
function as_byte_array(something) {
|
||||
if (typeof something == "string")
|
||||
return utf8.encode(something)
|
||||
if (something instanceof Uint8Array)
|
||||
return something
|
||||
return new Uint8Array(something)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#include "wbuf.h"
|
||||
#include "common.h"
|
||||
|
||||
void WBufInit(WBuf* b) {
|
||||
b->start = 0;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
typedef struct WBuf_s {
|
||||
char* start; // pointer to start of data
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const Path = require('path')
|
|||
const commonmark = require('commonmark')
|
||||
const Showdown = require('showdown')
|
||||
const marked = require('marked')
|
||||
const { Remarkable } = require('remarkable');
|
||||
const markdownit = require('markdown-it')('commonmark')
|
||||
const markdown_wasm = require('../../dist/markdown.node.js')
|
||||
|
||||
|
|
@ -22,6 +23,9 @@ var showdown = new Showdown.Converter()
|
|||
var parser = new commonmark.Parser()
|
||||
var renderer = new commonmark.HtmlRenderer()
|
||||
|
||||
// setup remarkable
|
||||
var remarkable = new Remarkable();
|
||||
|
||||
// parse CLI input
|
||||
let filename = process.argv[2]
|
||||
if (!filename) {
|
||||
|
|
@ -76,6 +80,9 @@ function benchmarkFile(benchfile) {
|
|||
.add('marked', function() {
|
||||
marked(contents);
|
||||
})
|
||||
.add('remarkable', function() {
|
||||
remarkable.render(contents);
|
||||
})
|
||||
.add('markdown-it', function() {
|
||||
markdownit.render(contents);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"d3-node": "^2.2.2",
|
||||
"markdown-it": "^10.0.0",
|
||||
"marked": "^0.7.0",
|
||||
"remarkable": "^2.0.1",
|
||||
"showdown": "^1.9.1",
|
||||
"svgo": "^1.3.2"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="840" height="264" font-family="-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif" font-size="16"><g text-anchor="end" font-weight="500"><text alignment-baseline="central" x="156" y="68" dx="-10">markdown-wasm</text><text alignment-baseline="central" x="156" y="104" dx="-10">markdown-it</text><text alignment-baseline="central" x="156" y="140" dx="-10">marked</text><text alignment-baseline="central" x="156" y="176" dx="-10">commonmark</text><text alignment-baseline="central" x="156" y="212" dx="-10">showdown</text></g><path fill="#4e79a7" d="M156 52h668v32H156z"/><path fill="#f28e2c" d="M156 88h290.307v32H156z"/><path fill="#e15759" d="M156 124h263.486v32H156z"/><path fill="#76b7b2" d="M156 160h226.65v32H156z"/><path fill="#59a14f" d="M156 196h29.116v32H156z"/><g fill="#fff" text-anchor="end" font-weight="500"><text x="824" y="68" alignment-baseline="central" dx="-5">195,065</text><text x="446.307" y="104" alignment-baseline="central" dx="-5">84,774</text><text x="419.486" y="140" alignment-baseline="central" dx="-5">76,942</text><text x="382.65" y="176" alignment-baseline="central" dx="-5">66,185</text><text x="185.116" y="212" alignment-baseline="central" dx="5" fill="#000" text-anchor="start">8,502</text></g><g fill="none" font-size="12.8" text-anchor="middle" opacity=".5"><g class="tick"><path stroke="currentColor" d="M156.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(156.5 48)">0</text></g><g class="tick"><path stroke="currentColor" d="M224.99 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(224.99 48)">20,000</text></g><g class="tick"><path stroke="currentColor" d="M293.48 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(293.48 48)">40,000</text></g><g class="tick"><path stroke="currentColor" d="M361.97 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(361.97 48)">60,000</text></g><g class="tick"><path stroke="currentColor" d="M430.46 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(430.46 48)">80,000</text></g><g class="tick"><path stroke="currentColor" d="M498.95 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(498.95 48)">100,000</text></g><g class="tick"><path stroke="currentColor" d="M567.44 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(567.44 48)">120,000</text></g><g class="tick"><path stroke="currentColor" d="M635.93 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(635.93 48)">140,000</text></g><g class="tick"><path stroke="currentColor" d="M704.42 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(704.42 48)">160,000</text></g><g class="tick"><path stroke="currentColor" d="M772.909 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(772.909 48)">180,000</text></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="840" height="300" font-family="-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif" font-size="16"><g text-anchor="end" font-weight="500"><text alignment-baseline="central" x="156" y="68" dx="-10">markdown-wasm</text><text alignment-baseline="central" x="156" y="104" dx="-10">remarkable</text><text alignment-baseline="central" x="156" y="140" dx="-10">markdown-it</text><text alignment-baseline="central" x="156" y="176" dx="-10">marked</text><text alignment-baseline="central" x="156" y="212" dx="-10">commonmark</text><text alignment-baseline="central" x="156" y="248" dx="-10">showdown</text></g><path fill="#4e79a7" d="M156 52h668v32H156z"/><path fill="#f28e2c" d="M156 88h453.666v32H156z"/><path fill="#e15759" d="M156 124h300.527v32H156z"/><path fill="#76b7b2" d="M156 160h259.363v32H156z"/><path fill="#59a14f" d="M156 196h210.906v32H156z"/><path fill="#edc949" d="M156 232h30.517v32H156z"/><g fill="#fff" text-anchor="end" font-weight="500"><text x="824" y="68" alignment-baseline="central" dx="-5">136,888</text><text x="609.666" y="104" alignment-baseline="central" dx="-5">92,966</text><text x="456.527" y="140" alignment-baseline="central" dx="-5">61,584</text><text x="415.363" y="176" alignment-baseline="central" dx="-5">53,149</text><text x="366.906" y="212" alignment-baseline="central" dx="-5">43,219</text><text x="186.517" y="248" alignment-baseline="central" dx="5" fill="#000" text-anchor="start">6,254</text></g><g fill="none" font-size="12.8" text-anchor="middle" opacity=".5"><g class="tick"><path stroke="currentColor" d="M156.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(156.5 48)">0</text></g><g class="tick"><path stroke="currentColor" d="M254.098 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(254.098 48)">20,000</text></g><g class="tick"><path stroke="currentColor" d="M351.697 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(351.697 48)">40,000</text></g><g class="tick"><path stroke="currentColor" d="M449.295 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(449.295 48)">60,000</text></g><g class="tick"><path stroke="currentColor" d="M546.893 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(546.893 48)">80,000</text></g><g class="tick"><path stroke="currentColor" d="M644.491 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(644.491 48)">100,000</text></g><g class="tick"><path stroke="currentColor" d="M742.09 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(742.09 48)">120,000</text></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.6 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 4.2 KiB |
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="840" height="264" font-family="-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif" font-size="16"><path fill="#4e79a7" d="M21 52h486v32H21z"/><path fill="#e15759" d="M16 88h576v32H16z"/><path fill="#f28e2c" d="M21 124h573v32H21z"/><path fill="#76b7b2" d="M70 160h541v32H70z"/><path fill="#59a14f" d="M227 196h597v32H227z"/><g fill="#fff" font-weight="500" text-anchor="middle"><text x="264" y="68" alignment-baseline="central">markdown-wasm</text><text x="304" y="104" alignment-baseline="central">marked</text><text x="307.5" y="140" alignment-baseline="central">markdown-it</text><text x="340.5" y="176" alignment-baseline="central">commonmark</text><text x="525.5" y="212" alignment-baseline="central">showdown</text></g><g fill="#fff" font-weight="500"><text x="21" y="68" alignment-baseline="central" dx="5">2.0us</text><text x="16" y="104" alignment-baseline="central" dx="5">1.8us</text><text x="21" y="140" alignment-baseline="central" dx="5">2.0us</text><text x="70" y="176" alignment-baseline="central" dx="5">4.0us</text><text x="227" y="212" alignment-baseline="central" dx="5">40.8us</text></g><g fill="#fff" font-weight="500" text-anchor="end"><text x="507" y="68" alignment-baseline="central" dx="-5">2.5ms</text><text x="592" y="104" alignment-baseline="central" dx="-5">8.6ms</text><text x="594" y="140" alignment-baseline="central" dx="-5">8.9ms</text><text x="611" y="176" alignment-baseline="central" dx="-5">11.4ms</text><text x="824" y="212" alignment-baseline="central" dx="-5">262.8ms</text></g><g fill="none" font-size="12.8" text-anchor="middle" opacity=".5"><path stroke="currentColor" d="M22.5 48v-6M50.5 48v-6M69.5 48v-6M85.5 48v-6M97.5 48v-6M108.5 48v-6M117.5 48v-6M125.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M132.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(132.5 48)">10.0us</text></g><path stroke="currentColor" d="M179.5 48v-6M207.5 48v-6M226.5 48v-6M241.5 48v-6M254.5 48v-6M264.5 48v-6M273.5 48v-6M281.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M288.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(288.5 48)">100.0us</text></g><path stroke="currentColor" d="M336.5 48v-6M363.5 48v-6M383.5 48v-6M398.5 48v-6M410.5 48v-6M421.5 48v-6M430.5 48v-6M438.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M445.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(445.5 48)">1000.0us</text></g><path stroke="currentColor" d="M492.5 48v-6M520.5 48v-6M539.5 48v-6M555.5 48v-6M567.5 48v-6M577.5 48v-6M586.5 48v-6M594.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M602.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(602.5 48)">10.0ms</text></g><path stroke="currentColor" d="M649.5 48v-6M676.5 48v-6M696.5 48v-6M711.5 48v-6M724.5 48v-6M734.5 48v-6M743.5 48v-6M751.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M758.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(758.5 48)">100.0ms</text></g><path stroke="currentColor" d="M805.5 48v-6"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="840" height="300" font-family="-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif" font-size="16"><path fill="#4e79a7" d="M45 52h483v32H45z"/><path fill="#f28e2c" d="M16 88h582v32H16z"/><path fill="#76b7b2" d="M38 124h584v32H38z"/><path fill="#e15759" d="M47 160h578v32H47z"/><path fill="#59a14f" d="M96 196h553v32H96z"/><path fill="#edc949" d="M258 232h566v32H258z"/><g fill="#fff" font-weight="500" text-anchor="middle"><text x="286.5" y="68" alignment-baseline="central">markdown-wasm</text><text x="307" y="104" alignment-baseline="central">remarkable</text><text x="330" y="140" alignment-baseline="central">marked</text><text x="336" y="176" alignment-baseline="central">markdown-it</text><text x="372.5" y="212" alignment-baseline="central">commonmark</text><text x="541" y="248" alignment-baseline="central">showdown</text></g><g fill="#fff" font-weight="500"><text x="45" y="68" alignment-baseline="central" dx="5">2.7us</text><text x="16" y="104" alignment-baseline="central" dx="5">1.8us</text><text x="38" y="140" alignment-baseline="central" dx="5">2.5us</text><text x="47" y="176" alignment-baseline="central" dx="5">2.8us</text><text x="96" y="212" alignment-baseline="central" dx="5">5.7us</text><text x="258" y="248" alignment-baseline="central" dx="5">59.6us</text></g><g fill="#fff" font-weight="500" text-anchor="end"><text x="528" y="68" alignment-baseline="central" dx="-5">3.1ms</text><text x="598" y="104" alignment-baseline="central" dx="-5">8.4ms</text><text x="622" y="140" alignment-baseline="central" dx="-5">12.0ms</text><text x="625" y="176" alignment-baseline="central" dx="-5">12.5ms</text><text x="649" y="212" alignment-baseline="central" dx="-5">17.9ms</text><text x="824" y="248" alignment-baseline="central" dx="-5">226.4ms</text></g><g fill="none" font-size="12.8" text-anchor="middle" opacity=".5"><path stroke="currentColor" d="M24.5 48v-6M52.5 48v-6M72.5 48v-6M87.5 48v-6M100.5 48v-6M111.5 48v-6M120.5 48v-6M128.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M135.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(135.5 48)">10.0us</text></g><path stroke="currentColor" d="M183.5 48v-6M211.5 48v-6M230.5 48v-6M246.5 48v-6M258.5 48v-6M269.5 48v-6M278.5 48v-6M286.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M293.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(293.5 48)">100.0us</text></g><path stroke="currentColor" d="M341.5 48v-6M369.5 48v-6M388.5 48v-6M404.5 48v-6M416.5 48v-6M427.5 48v-6M436.5 48v-6M444.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M451.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(451.5 48)">1000.0us</text></g><path stroke="currentColor" d="M499.5 48v-6M527.5 48v-6M547.5 48v-6M562.5 48v-6M575.5 48v-6M585.5 48v-6M594.5 48v-6M602.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M610.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(610.5 48)">10.0ms</text></g><path stroke="currentColor" d="M657.5 48v-6M685.5 48v-6M705.5 48v-6M720.5 48v-6M733.5 48v-6M743.5 48v-6M753.5 48v-6M761.5 48v-6"/><g class="tick"><path stroke="currentColor" d="M768.5 48v-6"/><text fill="currentColor" y="-9" dy="0em" transform="translate(768.5 48)">100.0ms</text></g><path stroke="currentColor" d="M815.5 48v-6"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.3 KiB |
4
wasmc.js
4
wasmc.js
|
|
@ -23,6 +23,10 @@ const m = {
|
|||
] : [
|
||||
// release flags
|
||||
]),
|
||||
lflags: [
|
||||
// force inclusion of addFunction & removeFunction in release builds (emcc bug?)
|
||||
"-s","EXPORTED_RUNTIME_METHODS=addFunction,removeFunction",
|
||||
],
|
||||
constants: {
|
||||
VERSION: package.version,
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue