diff --git a/.gitignore b/.gitignore index 75623dcc..d59da84e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ _obj _test* markdown -tags +tags \ No newline at end of file diff --git a/README.md b/README.md index 8ee3aa5a..055f397d 100644 --- a/README.md +++ b/README.md @@ -184,11 +184,11 @@ implements the following extensions: and supply a language (to make syntax highlighting simple). Just mark it like this: - ```go + ```go func getTrue() bool { return true } - ``` + ``` You can use 3 or more backticks to mark the beginning of the block, and the same number to mark the end of the block. @@ -209,7 +209,7 @@ implements the following extensions: end of the document. A footnote looks like this: This is a footnote.[^1] - + [^1]: the footnote text. * **Autolinking**. Blackfriday can find URLs that have not been @@ -237,6 +237,24 @@ implements the following extensions: becomes `4⁄5`, which renders as 4⁄5. +* **MathJaX Support** is an additional feature which is supported by + many markdown editor. It translate inline math equation quoted by `$` + and display math block quoted by `$$` into MathJax compatible format. + hyphen `_` won't break LaTeX render within a math element any more. + ``` + $$ + \left[ \begin{array}{a} a^l_1 \\ ⋮ \\ a^l_{d_l} \end{array}\right] + = \sigma( + \left[ \begin{matrix} + w^l_{1,1} & ⋯ & w^l_{1,d_{l-1}} \\ + ⋮ & ⋱ & ⋮ \\ + w^l_{d_l,1} & ⋯ & w^l_{d_l,d_{l-1}} \\ + \end{matrix}\right] · + \left[ \begin{array}{x} a^{l-1}_1 \\ ⋮ \\ ⋮ \\ a^{l-1}_{d_{l-1}} \end{array}\right] + + \left[ \begin{array}{b} b^l_1 \\ ⋮ \\ b^l_{d_l} \end{array}\right]) + $$ + ``` + Other renderers --------------- @@ -275,9 +293,9 @@ License [Blackfriday is distributed under the Simplified BSD License](LICENSE.txt) - [1]: https://daringfireball.net/projects/markdown/ "Markdown" - [2]: https://golang.org/ "Go Language" - [3]: https://github.com/vmg/sundown "Sundown" - [4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func" - [5]: https://github.com/microcosm-cc/bluemonday "Bluemonday" - [6]: https://labix.org/gopkg.in "gopkg.in" +[1]: https://daringfireball.net/projects/markdown/ "Markdown" +[2]: https://golang.org/ "Go Language" +[3]: https://github.com/vmg/sundown "Sundown" +[4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func" +[5]: https://github.com/microcosm-cc/bluemonday "Bluemonday" +[6]: https://labix.org/gopkg.in "gopkg.in" diff --git a/block.go b/block.go index d7da33f2..8a5d0f16 100644 --- a/block.go +++ b/block.go @@ -189,6 +189,14 @@ func (p *Markdown) block(data []byte) { } } + // handle math block + if p.extensions&MathJaxSupport != 0 { + if i := p.blockMath(data); i > 0 { + data = data[i:] + continue + } + } + // anything else must look like a normal paragraph // note: this finds underlined headings, too data = data[p.paragraph(data):] @@ -239,7 +247,7 @@ func (p *Markdown) prefixHeading(data []byte) int { } // extract heading id iff found if j < end && k < end { - id = string(data[j+2 : k]) + id = string(data[j+2: k]) end = j skip = k + 1 for end > 0 && data[end-1] == ' ' { @@ -597,7 +605,7 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker if size < 3 { return 0, "" } - marker = string(data[i-size : i]) + marker = string(data[i-size: i]) // if this is the end marker, it must match the beginning marker if oldmarker != "" && marker != oldmarker { @@ -651,7 +659,7 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker } } - *syntax = string(data[syntaxStart : syntaxStart+syn]) + *syntax = string(data[syntaxStart: syntaxStart+syn]) } i = skipChar(data, i, ' ') @@ -1277,7 +1285,7 @@ gatherlines: } } - chunk := data[line+indentIndex : i] + chunk := data[line+indentIndex: i] // evaluate how this line fits in switch { @@ -1301,7 +1309,7 @@ gatherlines: sublist = raw.Len() } - // is this a nested prefix heading? + // is this a nested prefix heading? case p.isPrefixHeading(chunk): // if the heading is not indented, it is not nested in the list // and thus ends the list @@ -1311,9 +1319,9 @@ gatherlines: } *flags |= ListItemContainsBlock - // anything following an empty line is only part - // of this item if it is indented 4 spaces - // (regardless of the indentation of the beginning of the item) + // anything following an empty line is only part + // of this item if it is indented 4 spaces + // (regardless of the indentation of the beginning of the item) case containsBlankLine && indent < 4: if *flags&ListTypeDefinition != 0 && i < len(data)-1 { // is the next item still a part of this list? @@ -1332,7 +1340,7 @@ gatherlines: } break gatherlines - // a blank line means this should be parsed as a block + // a blank line means this should be parsed as a block case containsBlankLine: raw.WriteByte('\n') *flags |= ListItemContainsBlock @@ -1346,7 +1354,7 @@ gatherlines: } // add the line into the working buffer without prefix - raw.Write(data[line+indentIndex : i]) + raw.Write(data[line+indentIndex: i]) line = i } @@ -1408,6 +1416,29 @@ func (p *Markdown) renderParagraph(data []byte) { p.addBlock(Paragraph, data[beg:end]) } +// blockMath handle block surround with $$ +func (p *Markdown) blockMath(data []byte) int { + if len(data) <= 4 || data[0] != '$' || data[1] != '$' || data[2] == '$' { + return 0 + } + + // find next $$ + var end int + for end = 2; end+1 < len(data) && (data[end] != '$' || data[end+1] != '$'); end++ { + } + + // $$ not match + if end+1 == len(data) { + return 0 + } + + // render the display math + container := p.addChild(MathBlock, 0) + container.Literal = data[2:end] + + return end + 2 +} + func (p *Markdown) paragraph(data []byte) int { // prev: index of 1st char of previous line // line: index of 1st char of current line diff --git a/block_test.go b/block_test.go index 0a2a4d84..5c090542 100644 --- a/block_test.go +++ b/block_test.go @@ -1458,6 +1458,16 @@ func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) { doTestsBlock(t, tests, FencedCode|NoEmptyLineBeforeBlock) } +func TestMathBlock(t *testing.T) { + var tests = []string{ + "$y=a+b$$", + "
\\(y=a+b\\)$
\n", + "$$y_2=a_3+b_4$$", + "\\[y_2=a_3+b_4\\]
", + } + doTestsBlock(t, tests, CommonExtensions) +} + func TestTitleBlock_EXTENSION_TITLEBLOCK(t *testing.T) { var tests = []string{ "% Some title\n" + diff --git a/html.go b/html.go index 25fb185e..48283d02 100644 --- a/html.go +++ b/html.go @@ -46,6 +46,7 @@ const ( SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants) TOC // Generate a table of contents + MathJaxFromCDN // Import MathJax js from CDN ) var ( @@ -454,6 +455,10 @@ var ( h5CloseTag = []byte("") h6Tag = []byte("\[`) + blockMathCloseTag = []byte(`\]
`) footnotesDivBytes = []byte("\n\(a_b\)
+`, + }, TestParams{HTMLFlags: SkipHTML, extensions: CommonExtensions}) +} + func BenchmarkSmartDoubleQuotes(b *testing.B) { params := TestParams{HTMLFlags: Smartypants} params.extensions |= Autolink | Strikethrough diff --git a/markdown.go b/markdown.go index ff61cb05..f1e987f7 100644 --- a/markdown.go +++ b/markdown.go @@ -41,19 +41,20 @@ const ( HardLineBreak // Translate newlines into line breaks TabSizeEight // Expand tabs to eight spaces instead of four Footnotes // Pandoc-style footnotes - NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block + NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, math, ordered list, unordered list) block HeadingIDs // specify heading IDs with {#id} Titleblock // Titleblock ala pandoc AutoHeadingIDs // Create the heading ID from the text BackslashLineBreak // Translate trailing backslashes into line breaks DefinitionLists // Render definition lists + MathJaxSupport // Render with MathJax compatible CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants | SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode | Autolink | Strikethrough | SpaceHeadings | HeadingIDs | - BackslashLineBreak | DefinitionLists + BackslashLineBreak | DefinitionLists | NoEmptyLineBeforeBlock | MathJaxSupport ) // ListType contains bitwise or'ed flags for list and list item objects. @@ -63,12 +64,12 @@ type ListType int // Multiple flag values may be ORed together. // These are mostly of interest if you are writing a new output format. const ( - ListTypeOrdered ListType = 1 << iota + ListTypeOrdered ListType = 1 << iota ListTypeDefinition ListTypeTerm ListItemContainsBlock - ListItemBeginningOfList // TODO: figure out if this is of any use now + ListItemBeginningOfList // TODO: figure out if this is of any use now ListItemEndOfList ) @@ -79,7 +80,7 @@ type CellAlignFlags int // Only a single one of these values will be used; they are not ORed together. // These are mostly of interest if you are writing a new output format. const ( - TableAlignmentLeft CellAlignFlags = 1 << iota + TableAlignmentLeft CellAlignFlags = 1 << iota TableAlignmentRight TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight) ) @@ -305,6 +306,9 @@ func New(opts ...Option) *Markdown { p.inlineCallback['M'] = maybeAutoLink p.inlineCallback['F'] = maybeAutoLink } + if p.extensions&MathJaxSupport != 0 { + p.inlineCallback['$'] = math + } if p.extensions&Footnotes != 0 { p.notes = make([]*reference, 0) } @@ -779,7 +783,7 @@ gatherLines: } // get rid of that first tab, write to buffer - raw.Write(data[blockEnd+n : i]) + raw.Write(data[blockEnd+n: i]) hasBlock = true blockEnd = i @@ -936,5 +940,5 @@ func slugify(in []byte) []byte { break } } - return out[a : b+1] + return out[a: b+1] } diff --git a/node.go b/node.go index 51b9e8c1..e1364acf 100644 --- a/node.go +++ b/node.go @@ -12,7 +12,7 @@ type NodeType int // Constants for identifying different types of nodes. See NodeType. const ( - Document NodeType = iota + Document NodeType = iota BlockQuote List Item @@ -36,6 +36,8 @@ const ( TableHead TableBody TableRow + Math + MathBlock ) var nodeTypeNames = []string{ @@ -63,6 +65,8 @@ var nodeTypeNames = []string{ TableHead: "TableHead", TableBody: "TableBody", TableRow: "TableRow", + Math: "Math", + MathBlock: "MathBlock", } func (t NodeType) String() string {