mirror of
https://github.com/danbulant/markdown-wasm
synced 2026-05-19 04:18:38 +00:00
Compare commits
17 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad5043fd69 | ||
|
|
41cf2dc97c | ||
|
|
0d99d1151f | ||
|
|
0aa6c8ff6c | ||
|
|
fa940bf432 | ||
|
|
4b48783c3e | ||
|
|
571f5ceb09 | ||
|
|
2938af6f0b | ||
|
|
da6fc41638 | ||
|
|
9e521244de | ||
|
|
b290af55e1 | ||
|
|
a67763470e | ||
|
|
cb877fce88 | ||
|
|
cce8d5c948 | ||
|
|
b407df075e | ||
|
|
6564eb12cc | ||
|
|
7397bdc87d |
33 changed files with 472 additions and 200 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.
|
|
@ -106,7 +106,7 @@ h6:hover .anchor:before {
|
|||
<c>
|
||||
<p>
|
||||
Fast Markdown parser and HTML renderer implemented in WebAssembly.
|
||||
<a href="https://github.com/rsms/markdown-wasm">Learn more & download on GitHub</a>
|
||||
<a href="https://github.com/rsms/markdown-wasm">Get it from GitHub</a>
|
||||
</p>
|
||||
</c>
|
||||
|
||||
|
|
|
|||
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.
|
|
@ -4,7 +4,7 @@
|
|||
<p>This is a paragraph</p>
|
||||
<h2><a id="another" class="anchor" aria-hidden="true" href="#another"></a>Another</h2>
|
||||
<p>This is a paragraph with style <em>italic</em> <em>italic</em> <b>bold</b> <b>bold</b></p>
|
||||
<p><img src="https://rsms.me/image.png" alt="image">
|
||||
<p><img src="https://rsms.me/raster/examples/image1.jpg" alt="image">
|
||||
<img src="https://rsms.me/image.png?without-alt" alt=""></p>
|
||||
<p><em>Hello <a href="https://rsms.me/">link</a> lol</em></p>
|
||||
<p>Hello <a href="https://rsms.me/"><em>link</em></a> lol "cat"</p>
|
||||
|
|
@ -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,10 +1,15 @@
|
|||
const RELEASE = process.argv.includes("-release")
|
||||
|
||||
const fs = require("fs")
|
||||
const md = RELEASE ? require("../build/release/md.js") : require("../build/debug/md.js")
|
||||
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, { asMemoryView: 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)
|
||||
|
|
@ -12,13 +17,37 @@ fs.writeFileSync(outfile, outbuf)
|
|||
console.log(fs.readFileSync(outfile, "utf8"))
|
||||
|
||||
// mini benchmark
|
||||
if (RELEASE) {
|
||||
console.log("benchmark start")
|
||||
if (process.argv.includes("-bench")) {
|
||||
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 iterations = 10000
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
global["dont-optimize-away"] = md.parse(source, { asMemoryView: true })
|
||||
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, options)
|
||||
}
|
||||
ntotal += N
|
||||
}
|
||||
const timeSpent = Date.now() - timeStart
|
||||
console.log(`benchmark end -- avg parse time: ${(timeSpent / iterations).toFixed(2)}ms`)
|
||||
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"))
|
||||
|
|
|
|||
34
markdown.d.ts
vendored
34
markdown.d.ts
vendored
|
|
@ -2,8 +2,8 @@
|
|||
* 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>
|
||||
|
|
@ -17,8 +17,7 @@ export interface ParseOptions {
|
|||
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,
|
||||
|
|
@ -27,9 +26,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,
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ echo "" ; echo "wasmc -clean"
|
|||
echo "" ; echo "./test/test.sh"
|
||||
./test/test.sh
|
||||
|
||||
# web site
|
||||
cp dist/markdown.js dist/markdown.js.map dist/markdown.wasm ./docs/
|
||||
|
||||
# commit, tag and push git
|
||||
echo "Ready to commit, publish & push:"
|
||||
echo ""
|
||||
|
|
|
|||
28
package-lock.json
generated
28
package-lock.json
generated
|
|
@ -1,13 +1,31 @@
|
|||
{
|
||||
"name": "markdown-wasm",
|
||||
"version": "1.0.0",
|
||||
"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.0",
|
||||
"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.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)
|
||||
}
|
||||
|
|
|
|||
72
src/md4c.c
72
src/md4c.c
|
|
@ -26,6 +26,7 @@
|
|||
#include "md4c.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
|
@ -71,6 +72,12 @@
|
|||
#define FALSE 0
|
||||
#endif
|
||||
|
||||
#if defined(__has_attribute) && __has_attribute(unused)
|
||||
#define MD_UNUSED __attribute__((unused))
|
||||
#else
|
||||
#define MD_UNUSED
|
||||
#endif
|
||||
|
||||
|
||||
/************************
|
||||
*** Internal Types ***
|
||||
|
|
@ -465,7 +472,7 @@ md_text_with_null_replacement(MD_CTX* ctx, MD_TEXTTYPE type, const CHAR* str, SZ
|
|||
typedef struct MD_UNICODE_FOLD_INFO_tag MD_UNICODE_FOLD_INFO;
|
||||
struct MD_UNICODE_FOLD_INFO_tag {
|
||||
unsigned codepoints[3];
|
||||
int n_codepoints;
|
||||
unsigned n_codepoints;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -669,7 +676,7 @@ struct MD_UNICODE_FOLD_INFO_tag {
|
|||
const unsigned* map;
|
||||
const unsigned* data;
|
||||
size_t map_size;
|
||||
int n_codepoints;
|
||||
unsigned n_codepoints;
|
||||
} FOLD_MAP_LIST[] = {
|
||||
{ FOLD_MAP_1, FOLD_MAP_1_DATA, SIZEOF_ARRAY(FOLD_MAP_1), 1 },
|
||||
{ FOLD_MAP_2, FOLD_MAP_2_DATA, SIZEOF_ARRAY(FOLD_MAP_2), 2 },
|
||||
|
|
@ -689,12 +696,12 @@ struct MD_UNICODE_FOLD_INFO_tag {
|
|||
|
||||
/* Try to locate the codepoint in any of the maps. */
|
||||
for(i = 0; i < (int) SIZEOF_ARRAY(FOLD_MAP_LIST); i++) {
|
||||
int index;
|
||||
unsigned index;
|
||||
|
||||
index = md_unicode_bsearch__(codepoint, FOLD_MAP_LIST[i].map, FOLD_MAP_LIST[i].map_size);
|
||||
if(index >= 0) {
|
||||
/* Found the mapping. */
|
||||
int n_codepoints = FOLD_MAP_LIST[i].n_codepoints;
|
||||
unsigned n_codepoints = FOLD_MAP_LIST[i].n_codepoints;
|
||||
const unsigned* map = FOLD_MAP_LIST[i].map;
|
||||
const unsigned* codepoints = FOLD_MAP_LIST[i].data + (index * n_codepoints);
|
||||
|
||||
|
|
@ -886,7 +893,7 @@ struct MD_UNICODE_FOLD_INFO_tag {
|
|||
* what the caller should allocate.)
|
||||
*/
|
||||
static void
|
||||
md_merge_lines(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines, int n_lines,
|
||||
md_merge_lines(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines,
|
||||
CHAR line_break_replacement_char, CHAR* buffer, SZ* p_size)
|
||||
{
|
||||
CHAR* ptr = buffer;
|
||||
|
|
@ -921,7 +928,7 @@ md_merge_lines(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines, int n_lines,
|
|||
/* Wrapper of md_merge_lines() which allocates new buffer for the output string.
|
||||
*/
|
||||
static int
|
||||
md_merge_lines_alloc(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines, int n_lines,
|
||||
md_merge_lines_alloc(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines,
|
||||
CHAR line_break_replacement_char, CHAR** p_str, SZ* p_size)
|
||||
{
|
||||
CHAR* buffer;
|
||||
|
|
@ -932,7 +939,7 @@ md_merge_lines_alloc(MD_CTX* ctx, OFF beg, OFF end, const MD_LINE* lines, int n_
|
|||
return -1;
|
||||
}
|
||||
|
||||
md_merge_lines(ctx, beg, end, lines, n_lines,
|
||||
md_merge_lines(ctx, beg, end, lines,
|
||||
line_break_replacement_char, buffer, p_size);
|
||||
|
||||
*p_str = buffer;
|
||||
|
|
@ -1231,7 +1238,8 @@ md_is_html_any(MD_CTX* ctx, const MD_LINE* lines, int n_lines, OFF beg, OFF max_
|
|||
****************************/
|
||||
|
||||
static int
|
||||
md_is_hex_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
|
||||
md_is_hex_entity_contents(MD_UNUSED MD_CTX* ctx, const CHAR* text,
|
||||
OFF beg, OFF max_end, OFF* p_end)
|
||||
{
|
||||
OFF off = beg;
|
||||
|
||||
|
|
@ -1247,7 +1255,8 @@ md_is_hex_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, O
|
|||
}
|
||||
|
||||
static int
|
||||
md_is_dec_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
|
||||
md_is_dec_entity_contents(MD_UNUSED MD_CTX* ctx, const CHAR* text,
|
||||
OFF beg, OFF max_end, OFF* p_end)
|
||||
{
|
||||
OFF off = beg;
|
||||
|
||||
|
|
@ -1263,7 +1272,8 @@ md_is_dec_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, O
|
|||
}
|
||||
|
||||
static int
|
||||
md_is_named_entity_contents(MD_CTX* ctx, const CHAR* text, OFF beg, OFF max_end, OFF* p_end)
|
||||
md_is_named_entity_contents(MD_UNUSED MD_CTX* ctx, const CHAR* text,
|
||||
OFF beg, OFF max_end, OFF* p_end)
|
||||
{
|
||||
OFF off = beg;
|
||||
|
||||
|
|
@ -1369,7 +1379,7 @@ md_build_attr_append_substr(MD_CTX* ctx, MD_ATTRIBUTE_BUILD* build,
|
|||
}
|
||||
|
||||
static void
|
||||
md_free_attribute(MD_CTX* ctx, MD_ATTRIBUTE_BUILD* build)
|
||||
md_free_attribute(MD_UNUSED MD_CTX* ctx, MD_ATTRIBUTE_BUILD* build)
|
||||
{
|
||||
if(build->substr_alloc > 0) {
|
||||
free(build->text);
|
||||
|
|
@ -2158,7 +2168,7 @@ md_is_link_reference_definition(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
|
|||
|
||||
if(label_is_multiline) {
|
||||
MD_CHECK(md_merge_lines_alloc(ctx, label_contents_beg, label_contents_end,
|
||||
lines + label_contents_line_index, n_lines - label_contents_line_index,
|
||||
lines + label_contents_line_index,
|
||||
_T(' '), &def->label, &def->label_size));
|
||||
def->label_needs_free = TRUE;
|
||||
} else {
|
||||
|
|
@ -2168,7 +2178,7 @@ md_is_link_reference_definition(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
|
|||
|
||||
if(title_is_multiline) {
|
||||
MD_CHECK(md_merge_lines_alloc(ctx, title_contents_beg, title_contents_end,
|
||||
lines + title_contents_line_index, n_lines - title_contents_line_index,
|
||||
lines + title_contents_line_index,
|
||||
_T('\n'), &def->title, &def->title_size));
|
||||
def->title_needs_free = TRUE;
|
||||
} else {
|
||||
|
|
@ -2221,8 +2231,7 @@ md_is_link_reference(MD_CTX* ctx, const MD_LINE* lines, int n_lines,
|
|||
end_line++;
|
||||
|
||||
if(beg_line != end_line) {
|
||||
MD_CHECK(md_merge_lines_alloc(ctx, beg, end, beg_line,
|
||||
n_lines - (beg_line - lines), _T(' '), &label, &label_size));
|
||||
MD_CHECK(md_merge_lines_alloc(ctx, beg, end, beg_line, _T(' '), &label, &label_size));
|
||||
} else {
|
||||
label = (CHAR*) STR(beg);
|
||||
label_size = end - beg;
|
||||
|
|
@ -2331,7 +2340,7 @@ md_is_inline_link_spec(MD_CTX* ctx, const MD_LINE* lines, int n_lines,
|
|||
attr->title_needs_free = FALSE;
|
||||
} else {
|
||||
MD_CHECK(md_merge_lines_alloc(ctx, title_contents_beg, title_contents_end,
|
||||
lines + title_contents_line_index, n_lines - title_contents_line_index,
|
||||
lines + title_contents_line_index,
|
||||
_T('\n'), &attr->title, &attr->title_size));
|
||||
attr->title_needs_free = TRUE;
|
||||
}
|
||||
|
|
@ -3378,8 +3387,7 @@ md_analyze_bracket(MD_CTX* ctx, int mark_index)
|
|||
}
|
||||
|
||||
/* Forward declaration. */
|
||||
static void md_analyze_link_contents(MD_CTX* ctx, const MD_LINE* lines, int n_lines,
|
||||
int mark_beg, int mark_end);
|
||||
static void md_analyze_link_contents(MD_CTX* ctx, int mark_beg, int mark_end);
|
||||
|
||||
static int
|
||||
md_resolve_links(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
|
||||
|
|
@ -3498,7 +3506,7 @@ md_resolve_links(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
|
|||
if(delim != NULL) {
|
||||
delim->flags |= MD_MARK_RESOLVED;
|
||||
md_rollback(ctx, opener_index, delim_index, MD_ROLLBACK_ALL);
|
||||
md_analyze_link_contents(ctx, lines, n_lines, opener_index+1, closer_index);
|
||||
md_analyze_link_contents(ctx, opener_index+1, closer_index);
|
||||
} else {
|
||||
md_rollback(ctx, opener_index, closer_index, MD_ROLLBACK_ALL);
|
||||
}
|
||||
|
|
@ -3523,6 +3531,10 @@ md_resolve_links(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
|
|||
if(is_link) {
|
||||
/* Eat the 2nd "[...]". */
|
||||
closer->end = next_closer->end;
|
||||
|
||||
/* Do not analyze the label as a standalone link in the next
|
||||
* iteration. */
|
||||
next_index = ctx->marks[next_index].prev;
|
||||
}
|
||||
} else {
|
||||
if(closer->end < ctx->size && CH(closer->end) == _T('(')) {
|
||||
|
|
@ -3599,7 +3611,7 @@ md_resolve_links(MD_CTX* ctx, const MD_LINE* lines, int n_lines)
|
|||
last_img_end = closer->end;
|
||||
}
|
||||
|
||||
md_analyze_link_contents(ctx, lines, n_lines, opener_index+1, closer_index);
|
||||
md_analyze_link_contents(ctx, opener_index+1, closer_index);
|
||||
}
|
||||
|
||||
opener_index = next_index;
|
||||
|
|
@ -3678,7 +3690,7 @@ md_analyze_emph(MD_CTX* ctx, int mark_index)
|
|||
/* If we can be a closer, try to resolve with the preceding opener. */
|
||||
if(mark->flags & MD_MARK_POTENTIAL_CLOSER) {
|
||||
MD_MARK* opener = NULL;
|
||||
int opener_index;
|
||||
int opener_index = 0;
|
||||
|
||||
if(mark->ch == _T('*')) {
|
||||
MD_MARKCHAIN* opener_chains[6];
|
||||
|
|
@ -3913,8 +3925,7 @@ md_analyze_permissive_email_autolink(MD_CTX* ctx, int mark_index)
|
|||
}
|
||||
|
||||
static inline void
|
||||
md_analyze_marks(MD_CTX* ctx, const MD_LINE* lines, int n_lines,
|
||||
int mark_beg, int mark_end, const CHAR* mark_chars)
|
||||
md_analyze_marks(MD_CTX* ctx, int mark_beg, int mark_end, const CHAR* mark_chars)
|
||||
{
|
||||
int i = mark_beg;
|
||||
|
||||
|
|
@ -3972,10 +3983,10 @@ md_analyze_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mod
|
|||
|
||||
/* We analyze marks in few groups to handle their precedence. */
|
||||
/* (1) Entities; code spans; autolinks; raw HTML. */
|
||||
md_analyze_marks(ctx, lines, n_lines, 0, ctx->n_marks, _T("&"));
|
||||
md_analyze_marks(ctx, 0, ctx->n_marks, _T("&"));
|
||||
|
||||
/* (2) Links. */
|
||||
md_analyze_marks(ctx, lines, n_lines, 0, ctx->n_marks, _T("[]!"));
|
||||
md_analyze_marks(ctx, 0, ctx->n_marks, _T("[]!"));
|
||||
MD_CHECK(md_resolve_links(ctx, lines, n_lines));
|
||||
BRACKET_OPENERS.head = -1;
|
||||
BRACKET_OPENERS.tail = -1;
|
||||
|
|
@ -3990,24 +4001,23 @@ md_analyze_inlines(MD_CTX* ctx, const MD_LINE* lines, int n_lines, int table_mod
|
|||
TABLECELLBOUNDARIES.head = -1;
|
||||
TABLECELLBOUNDARIES.tail = -1;
|
||||
ctx->n_table_cell_boundaries = 0;
|
||||
md_analyze_marks(ctx, lines, n_lines, 0, ctx->n_marks, _T("|"));
|
||||
md_analyze_marks(ctx, 0, ctx->n_marks, _T("|"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* (4) Emphasis and strong emphasis; permissive autolinks. */
|
||||
md_analyze_link_contents(ctx, lines, n_lines, 0, ctx->n_marks);
|
||||
md_analyze_link_contents(ctx, 0, ctx->n_marks);
|
||||
|
||||
abort:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
md_analyze_link_contents(MD_CTX* ctx, const MD_LINE* lines, int n_lines,
|
||||
int mark_beg, int mark_end)
|
||||
md_analyze_link_contents(MD_CTX* ctx, int mark_beg, int mark_end)
|
||||
{
|
||||
int i;
|
||||
|
||||
md_analyze_marks(ctx, lines, n_lines, mark_beg, mark_end, _T("*_~$@:."));
|
||||
md_analyze_marks(ctx, mark_beg, mark_end, _T("*_~$@:."));
|
||||
|
||||
for(i = OPENERS_CHAIN_FIRST; i <= OPENERS_CHAIN_LAST; i++) {
|
||||
ctx->mark_chains[i].head = -1;
|
||||
|
|
@ -5654,7 +5664,7 @@ md_line_indentation(MD_CTX* ctx, unsigned total_indent, OFF beg, OFF* p_end)
|
|||
return indent - total_indent;
|
||||
}
|
||||
|
||||
static const MD_LINE_ANALYSIS md_dummy_blank_line = { MD_LINE_BLANK, 0 };
|
||||
static const MD_LINE_ANALYSIS md_dummy_blank_line = { MD_LINE_BLANK, 0, 0, 0, 0 };
|
||||
|
||||
/* Analyze type of the line and find some its properties. This serves as a
|
||||
* main input for determining type and boundaries of a block. */
|
||||
|
|
|
|||
|
|
@ -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