From ee3fe992034b5e391daf418765e97b37fe2b06be Mon Sep 17 00:00:00 2001
From: Russ Ross
Date: Mon, 30 May 2011 11:06:20 -0600
Subject: [PATCH] rudimentary latex backend, additional cleanup
---
Makefile | 4 +-
README.md | 16 ++-
block.go | 2 +-
example/Makefile | 5 +-
example/main.go | 9 +-
html.go | 316 +++++++++++++++++++++++------------------------
latex.go | 305 +++++++++++++++++++++++++++++++++++++++++++++
markdown.go | 2 +-
8 files changed, 493 insertions(+), 166 deletions(-)
create mode 100644 latex.go
diff --git a/Makefile b/Makefile
index 267a713..b5ac93d 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,9 @@ include $(GOROOT)/src/Make.inc
TARG=github.com/russross/blackfriday
-GOFILES=markdown.go block.go inline.go html.go smartypants.go
+GOFILES=markdown.go block.go inline.go html.go smartypants.go latex.go
+
+all: package markdown
include $(GOROOT)/src/Make.pkg
diff --git a/README.md b/README.md
index 3828a0c..e56c47b 100644
--- a/README.md
+++ b/README.md
@@ -79,12 +79,26 @@ implements two additional Smartypants options:
cases). For example, `4/5` becomes `4⁄5`
+LaTeX Output
+------------
+
+A rudimentary LaTeX rendering backend is also included. To see an
+example of its usage, comment out this link in `main.go`:
+
+ renderer := blackfriday.HtmlRenderer(html_flags)
+
+and uncomment this line:
+
+ renderer := blackfriday.LatexRenderer(0)
+
+It renders some basic documents, but is only experimental at this point.
+
+
Todo
----
* Code cleanup
* Better code documentation
-* Implement a LaTeX backend
[1]: http://daringfireball.net/projects/markdown/ "Markdown"
diff --git a/block.go b/block.go
index 5073f65..e78c0ce 100644
--- a/block.go
+++ b/block.go
@@ -514,7 +514,7 @@ func blockTable(out *bytes.Buffer, rndr *render, data []byte) int {
}
if rndr.mk.table != nil {
- rndr.mk.table(out, header_work.Bytes(), body_work.Bytes(), rndr.mk.opaque)
+ rndr.mk.table(out, header_work.Bytes(), body_work.Bytes(), col_data, rndr.mk.opaque)
}
}
diff --git a/example/Makefile b/example/Makefile
index bee4730..4de2ab3 100644
--- a/example/Makefile
+++ b/example/Makefile
@@ -4,8 +4,9 @@ TARG=markdown
GOFILES=main.go
-LIBBF=github.com/russross/blackfriday
+markdown: ../_obj/github.com/russross/blackfriday.a
-PREREQ += ../_obj/$(LIBBF).a
+GC += -I../_obj/
+LD += -L../_obj/
include $(GOROOT)/src/Make.cmd
diff --git a/example/main.go b/example/main.go
index a702a49..3197cb0 100644
--- a/example/main.go
+++ b/example/main.go
@@ -57,8 +57,13 @@ func main() {
html_flags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
html_flags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
- // render the data
- output := blackfriday.Markdown(input, blackfriday.HtmlRenderer(html_flags), extensions)
+ // render the data into HTML (comment this out to deselect HTML)
+ renderer := blackfriday.HtmlRenderer(html_flags)
+
+ // render the data into LaTeX (uncomment to select LaTeX)
+ //renderer := blackfriday.LatexRenderer(0)
+
+ output := blackfriday.Markdown(input, renderer, extensions)
// output the result
if len(os.Args) == 3 {
diff --git a/html.go b/html.go
index 1193ed6..496bd78 100644
--- a/html.go
+++ b/html.go
@@ -65,8 +65,8 @@ func HtmlRenderer(flags int) *Renderer {
r.listitem = htmlListitem
r.paragraph = htmlParagraph
r.table = htmlTable
- r.tableRow = htmlTablerow
- r.tableCell = htmlTablecell
+ r.tableRow = htmlTableRow
+ r.tableCell = htmlTableCell
r.autolink = htmlAutolink
r.codespan = htmlCodespan
@@ -120,7 +120,7 @@ func HtmlTocRenderer(flags int) *Renderer {
return r
}
-func attrEscape(ob *bytes.Buffer, src []byte) {
+func attrEscape(out *bytes.Buffer, src []byte) {
for i := 0; i < len(src); i++ {
// directly copy normal characters
org := i
@@ -128,7 +128,7 @@ func attrEscape(ob *bytes.Buffer, src []byte) {
i++
}
if i > org {
- ob.Write(src[org:i])
+ out.Write(src[org:i])
}
// escape a character
@@ -137,36 +137,36 @@ func attrEscape(ob *bytes.Buffer, src []byte) {
}
switch src[i] {
case '<':
- ob.WriteString("<")
+ out.WriteString("<")
case '>':
- ob.WriteString(">")
+ out.WriteString(">")
case '&':
- ob.WriteString("&")
+ out.WriteString("&")
case '"':
- ob.WriteString(""")
+ out.WriteString(""")
}
}
}
-func htmlHeader(ob *bytes.Buffer, text []byte, level int, opaque interface{}) {
+func htmlHeader(out *bytes.Buffer, text []byte, level int, opaque interface{}) {
options := opaque.(*htmlOptions)
- if ob.Len() > 0 {
- ob.WriteByte('\n')
+ if out.Len() > 0 {
+ out.WriteByte('\n')
}
if options.flags&HTML_TOC != 0 {
- ob.WriteString(fmt.Sprintf("", level, options.toc_data.header_count))
+ out.WriteString(fmt.Sprintf("", level, options.toc_data.header_count))
options.toc_data.header_count++
} else {
- ob.WriteString(fmt.Sprintf("", level))
+ out.WriteString(fmt.Sprintf("", level))
}
- ob.Write(text)
- ob.WriteString(fmt.Sprintf("\n", level))
+ out.Write(text)
+ out.WriteString(fmt.Sprintf("\n", level))
}
-func htmlRawBlock(ob *bytes.Buffer, text []byte, opaque interface{}) {
+func htmlRawBlock(out *bytes.Buffer, text []byte, opaque interface{}) {
sz := len(text)
for sz > 0 && text[sz-1] == '\n' {
sz--
@@ -178,30 +178,30 @@ func htmlRawBlock(ob *bytes.Buffer, text []byte, opaque interface{}) {
if org >= sz {
return
}
- if ob.Len() > 0 {
- ob.WriteByte('\n')
+ if out.Len() > 0 {
+ out.WriteByte('\n')
}
- ob.Write(text[org:sz])
- ob.WriteByte('\n')
+ out.Write(text[org:sz])
+ out.WriteByte('\n')
}
-func htmlHrule(ob *bytes.Buffer, opaque interface{}) {
+func htmlHrule(out *bytes.Buffer, opaque interface{}) {
options := opaque.(*htmlOptions)
- if ob.Len() > 0 {
- ob.WriteByte('\n')
+ if out.Len() > 0 {
+ out.WriteByte('\n')
}
- ob.WriteString("
0 {
- ob.WriteByte('\n')
+func htmlBlockcode(out *bytes.Buffer, text []byte, lang string, opaque interface{}) {
+ if out.Len() > 0 {
+ out.WriteByte('\n')
}
if lang != "" {
- ob.WriteString(" 0 {
- ob.WriteByte(' ')
+ out.WriteByte(' ')
}
- attrEscape(ob, []byte(lang[org:]))
+ attrEscape(out, []byte(lang[org:]))
}
}
- ob.WriteString("\">")
+ out.WriteString("\">")
} else {
- ob.WriteString("")
+ out.WriteString("")
}
if len(text) > 0 {
- attrEscape(ob, text)
+ attrEscape(out, text)
}
- ob.WriteString("
\n")
+ out.WriteString("
\n")
}
/*
@@ -255,13 +255,13 @@ func htmlBlockcode(ob *bytes.Buffer, text []byte, lang string, opaque interface{
* E.g.
* ~~~~ {.python .numbered} =>
*/
-func htmlBlockcodeGithub(ob *bytes.Buffer, text []byte, lang string, opaque interface{}) {
- if ob.Len() > 0 {
- ob.WriteByte('\n')
+func htmlBlockcodeGithub(out *bytes.Buffer, text []byte, lang string, opaque interface{}) {
+ if out.Len() > 0 {
+ out.WriteByte('\n')
}
if len(lang) > 0 {
- ob.WriteString("")
+ out.WriteString("\">")
} else {
- ob.WriteString("")
+ out.WriteString("")
}
if len(text) > 0 {
- attrEscape(ob, text)
+ attrEscape(out, text)
}
- ob.WriteString("
\n")
+ out.WriteString("
\n")
}
-func htmlBlockquote(ob *bytes.Buffer, text []byte, opaque interface{}) {
- ob.WriteString("\n")
- ob.Write(text)
- ob.WriteString("
")
+func htmlBlockquote(out *bytes.Buffer, text []byte, opaque interface{}) {
+ out.WriteString("\n")
+ out.Write(text)
+ out.WriteString("
")
}
-func htmlTable(ob *bytes.Buffer, header []byte, body []byte, opaque interface{}) {
- if ob.Len() > 0 {
- ob.WriteByte('\n')
+func htmlTable(out *bytes.Buffer, header []byte, body []byte, columnData []int, opaque interface{}) {
+ if out.Len() > 0 {
+ out.WriteByte('\n')
}
- ob.WriteString("\n")
- ob.Write(header)
- ob.WriteString("\n\n")
- ob.Write(body)
- ob.WriteString("\n
")
+ out.WriteString("\n")
+ out.Write(header)
+ out.WriteString("\n\n")
+ out.Write(body)
+ out.WriteString("\n
")
}
-func htmlTablerow(ob *bytes.Buffer, text []byte, opaque interface{}) {
- if ob.Len() > 0 {
- ob.WriteByte('\n')
+func htmlTableRow(out *bytes.Buffer, text []byte, opaque interface{}) {
+ if out.Len() > 0 {
+ out.WriteByte('\n')
}
- ob.WriteString("\n")
- ob.Write(text)
- ob.WriteString("\n
")
+ out.WriteString("\n")
+ out.Write(text)
+ out.WriteString("\n
")
}
-func htmlTablecell(ob *bytes.Buffer, text []byte, align int, opaque interface{}) {
- if ob.Len() > 0 {
- ob.WriteByte('\n')
+func htmlTableCell(out *bytes.Buffer, text []byte, align int, opaque interface{}) {
+ if out.Len() > 0 {
+ out.WriteByte('\n')
}
switch align {
case TABLE_ALIGNMENT_LEFT:
- ob.WriteString("")
+ out.WriteString(" | ")
case TABLE_ALIGNMENT_RIGHT:
- ob.WriteString(" | ")
+ out.WriteString(" | ")
case TABLE_ALIGNMENT_CENTER:
- ob.WriteString(" | ")
+ out.WriteString(" | ")
default:
- ob.WriteString(" | ")
+ out.WriteString(" | ")
}
- ob.Write(text)
- ob.WriteString(" | ")
+ out.Write(text)
+ out.WriteString("")
}
-func htmlList(ob *bytes.Buffer, text []byte, flags int, opaque interface{}) {
- if ob.Len() > 0 {
- ob.WriteByte('\n')
+func htmlList(out *bytes.Buffer, text []byte, flags int, opaque interface{}) {
+ if out.Len() > 0 {
+ out.WriteByte('\n')
}
if flags&LIST_TYPE_ORDERED != 0 {
- ob.WriteString("\n")
+ out.WriteString("\n")
} else {
- ob.WriteString("\n")
+ out.WriteString("\n")
}
- ob.Write(text)
+ out.Write(text)
if flags&LIST_TYPE_ORDERED != 0 {
- ob.WriteString("
\n")
+ out.WriteString("
\n")
} else {
- ob.WriteString("\n")
+ out.WriteString("\n")
}
}
-func htmlListitem(ob *bytes.Buffer, text []byte, flags int, opaque interface{}) {
- ob.WriteString("")
+func htmlListitem(out *bytes.Buffer, text []byte, flags int, opaque interface{}) {
+ out.WriteString("")
size := len(text)
for size > 0 && text[size-1] == '\n' {
size--
}
- ob.Write(text[:size])
- ob.WriteString("\n")
+ out.Write(text[:size])
+ out.WriteString("\n")
}
-func htmlParagraph(ob *bytes.Buffer, text []byte, opaque interface{}) {
+func htmlParagraph(out *bytes.Buffer, text []byte, opaque interface{}) {
options := opaque.(*htmlOptions)
i := 0
- if ob.Len() > 0 {
- ob.WriteByte('\n')
+ if out.Len() > 0 {
+ out.WriteByte('\n')
}
if len(text) == 0 {
@@ -379,7 +379,7 @@ func htmlParagraph(ob *bytes.Buffer, text []byte, opaque interface{}) {
return
}
- ob.WriteString("")
+ out.WriteString("
")
if options.flags&HTML_HARD_WRAP != 0 {
for i < len(text) {
org := i
@@ -388,24 +388,24 @@ func htmlParagraph(ob *bytes.Buffer, text []byte, opaque interface{}) {
}
if i > org {
- ob.Write(text[org:i])
+ out.Write(text[org:i])
}
if i >= len(text) {
break
}
- ob.WriteString("
")
- ob.WriteString(options.close_tag)
+ out.WriteString("
")
+ out.WriteString(options.close_tag)
i++
}
} else {
- ob.Write(text[i:])
+ out.Write(text[i:])
}
- ob.WriteString("
\n")
+ out.WriteString("
\n")
}
-func htmlAutolink(ob *bytes.Buffer, link []byte, kind int, opaque interface{}) int {
+func htmlAutolink(out *bytes.Buffer, link []byte, kind int, opaque interface{}) int {
options := opaque.(*htmlOptions)
if len(link) == 0 {
@@ -415,12 +415,12 @@ func htmlAutolink(ob *bytes.Buffer, link []byte, kind int, opaque interface{}) i
return 0
}
- ob.WriteString("")
+ out.Write(link)
+ out.WriteString("\">")
/*
* Pretty print: if we get an email address as
@@ -428,95 +428,95 @@ func htmlAutolink(ob *bytes.Buffer, link []byte, kind int, opaque interface{}) i
* want to print the `mailto:` prefix
*/
if bytes.HasPrefix(link, []byte("mailto:")) {
- attrEscape(ob, link[7:])
+ attrEscape(out, link[7:])
} else {
- attrEscape(ob, link)
+ attrEscape(out, link)
}
- ob.WriteString("")
+ out.WriteString("")
return 1
}
-func htmlCodespan(ob *bytes.Buffer, text []byte, opaque interface{}) int {
- ob.WriteString("")
- attrEscape(ob, text)
- ob.WriteString("
")
+func htmlCodespan(out *bytes.Buffer, text []byte, opaque interface{}) int {
+ out.WriteString("")
+ attrEscape(out, text)
+ out.WriteString("
")
return 1
}
-func htmlDoubleEmphasis(ob *bytes.Buffer, text []byte, opaque interface{}) int {
+func htmlDoubleEmphasis(out *bytes.Buffer, text []byte, opaque interface{}) int {
if len(text) == 0 {
return 0
}
- ob.WriteString("")
- ob.Write(text)
- ob.WriteString("")
+ out.WriteString("")
+ out.Write(text)
+ out.WriteString("")
return 1
}
-func htmlEmphasis(ob *bytes.Buffer, text []byte, opaque interface{}) int {
+func htmlEmphasis(out *bytes.Buffer, text []byte, opaque interface{}) int {
if len(text) == 0 {
return 0
}
- ob.WriteString("")
- ob.Write(text)
- ob.WriteString("")
+ out.WriteString("")
+ out.Write(text)
+ out.WriteString("")
return 1
}
-func htmlImage(ob *bytes.Buffer, link []byte, title []byte, alt []byte, opaque interface{}) int {
+func htmlImage(out *bytes.Buffer, link []byte, title []byte, alt []byte, opaque interface{}) int {
options := opaque.(*htmlOptions)
if len(link) == 0 {
return 0
}
- ob.WriteString(" 0 {
- attrEscape(ob, alt)
+ attrEscape(out, alt)
}
if len(title) > 0 {
- ob.WriteString("\" title=\"")
- attrEscape(ob, title)
+ out.WriteString("\" title=\"")
+ attrEscape(out, title)
}
- ob.WriteByte('"')
- ob.WriteString(options.close_tag)
+ out.WriteByte('"')
+ out.WriteString(options.close_tag)
return 1
}
-func htmlLinebreak(ob *bytes.Buffer, opaque interface{}) int {
+func htmlLinebreak(out *bytes.Buffer, opaque interface{}) int {
options := opaque.(*htmlOptions)
- ob.WriteString("
0 {
- ob.Write(link)
+ out.Write(link)
}
if len(title) > 0 {
- ob.WriteString("\" title=\"")
- attrEscape(ob, title)
+ out.WriteString("\" title=\"")
+ attrEscape(out, title)
}
- ob.WriteString("\">")
+ out.WriteString("\">")
if len(content) > 0 {
- ob.Write(content)
+ out.Write(content)
}
- ob.WriteString("")
+ out.WriteString("")
return 1
}
-func htmlRawTag(ob *bytes.Buffer, text []byte, opaque interface{}) int {
+func htmlRawTag(out *bytes.Buffer, text []byte, opaque interface{}) int {
options := opaque.(*htmlOptions)
if options.flags&HTML_SKIP_HTML != 0 {
return 1
@@ -530,72 +530,72 @@ func htmlRawTag(ob *bytes.Buffer, text []byte, opaque interface{}) int {
if options.flags&HTML_SKIP_IMAGES != 0 && isHtmlTag(text, "img") {
return 1
}
- ob.Write(text)
+ out.Write(text)
return 1
}
-func htmlTripleEmphasis(ob *bytes.Buffer, text []byte, opaque interface{}) int {
+func htmlTripleEmphasis(out *bytes.Buffer, text []byte, opaque interface{}) int {
if len(text) == 0 {
return 0
}
- ob.WriteString("")
- ob.Write(text)
- ob.WriteString("")
+ out.WriteString("")
+ out.Write(text)
+ out.WriteString("")
return 1
}
-func htmlStrikethrough(ob *bytes.Buffer, text []byte, opaque interface{}) int {
+func htmlStrikethrough(out *bytes.Buffer, text []byte, opaque interface{}) int {
if len(text) == 0 {
return 0
}
- ob.WriteString("")
- ob.Write(text)
- ob.WriteString("")
+ out.WriteString("")
+ out.Write(text)
+ out.WriteString("")
return 1
}
-func htmlNormalText(ob *bytes.Buffer, text []byte, opaque interface{}) {
- attrEscape(ob, text)
+func htmlNormalText(out *bytes.Buffer, text []byte, opaque interface{}) {
+ attrEscape(out, text)
}
-func htmlTocHeader(ob *bytes.Buffer, text []byte, level int, opaque interface{}) {
+func htmlTocHeader(out *bytes.Buffer, text []byte, level int, opaque interface{}) {
options := opaque.(*htmlOptions)
for level > options.toc_data.current_level {
if options.toc_data.current_level > 0 {
- ob.WriteString("")
+ out.WriteString("")
}
- ob.WriteString("\n")
+ out.WriteString("\n")
options.toc_data.current_level++
}
for level < options.toc_data.current_level {
- ob.WriteString("
")
+ out.WriteString("
")
if options.toc_data.current_level > 1 {
- ob.WriteString("\n")
+ out.WriteString("\n")
}
options.toc_data.current_level--
}
- ob.WriteString("")
+ out.WriteString("")
options.toc_data.header_count++
if len(text) > 0 {
- ob.Write(text)
+ out.Write(text)
}
- ob.WriteString("\n")
+ out.WriteString("\n")
}
-func htmlTocFinalize(ob *bytes.Buffer, opaque interface{}) {
+func htmlTocFinalize(out *bytes.Buffer, opaque interface{}) {
options := opaque.(*htmlOptions)
for options.toc_data.current_level > 1 {
- ob.WriteString("\n")
+ out.WriteString("\n")
options.toc_data.current_level--
}
if options.toc_data.current_level > 0 {
- ob.WriteString("\n")
+ out.WriteString("\n")
}
}
diff --git a/latex.go b/latex.go
new file mode 100644
index 0000000..67eae70
--- /dev/null
+++ b/latex.go
@@ -0,0 +1,305 @@
+//
+// Black Friday Markdown Processor
+// Originally based on http://github.com/tanoku/upskirt
+// by Russ Ross
+//
+
+//
+//
+// LaTeX rendering backend
+//
+//
+
+package blackfriday
+
+import (
+ "bytes"
+)
+
+func LatexRenderer(flags int) *Renderer {
+ // block-level rendering
+ r := new(Renderer)
+ r.blockcode = latexBlockcode
+ r.blockquote = latexBlockquote
+ //r.blockhtml = ?
+ r.header = latexHeader
+ r.hrule = latexHrule
+ r.list = latexList
+ r.listitem = latexListitem
+ r.paragraph = latexParagraph
+ r.table = latexTable
+ r.tableRow = latexTableRow
+ r.tableCell = latexTableCell
+
+ // inline rendering
+ r.autolink = latexAutolink
+ r.codespan = latexCodespan
+ r.doubleEmphasis = latexDoubleEmphasis
+ r.emphasis = latexEmphasis
+ r.image = latexImage
+ r.linebreak = latexLinebreak
+ r.link = latexLink
+ //r.rawHtmlTag = ?
+ r.strikethrough = latexStrikethrough
+
+ r.normalText = latexNormalText
+
+ r.documentHeader = latexDocumentHeader
+ r.documentFooter = latexDocumentFooter
+
+ r.opaque = nil
+ return r
+}
+
+// render code chunks using verbatim, or listings if we have a language
+func latexBlockcode(out *bytes.Buffer, text []byte, lang string, opaque interface{}) {
+ if lang == "" {
+ out.WriteString("\n\\begin{verbatim}\n")
+ } else {
+ out.WriteString("\n\\begin{lstlisting}[language=")
+ out.WriteString(lang)
+ out.WriteString("]\n")
+ }
+ out.Write(text)
+ if lang == "" {
+ out.WriteString("\n\\end{verbatim}\n")
+ } else {
+ out.WriteString("\n\\end{lstlisting}\n")
+ }
+}
+
+func latexBlockquote(out *bytes.Buffer, text []byte, opaque interface{}) {
+ out.WriteString("\n\\begin{quotation}\n")
+ out.Write(text)
+ out.WriteString("\n\\end{quotation}\n")
+}
+
+//blockhtml func(out *bytes.Buffer, text []byte, opaque interface{})
+
+func latexHeader(out *bytes.Buffer, text []byte, level int, opaque interface{}) {
+ switch level {
+ case 1:
+ out.WriteString("\n\\section{")
+ case 2:
+ out.WriteString("\n\\subsection{")
+ case 3:
+ out.WriteString("\n\\subsubsection{")
+ case 4:
+ out.WriteString("\n\\paragraph{")
+ case 5:
+ out.WriteString("\n\\subparagraph{")
+ case 6:
+ out.WriteString("\n\\textbf{")
+ }
+ out.Write(text)
+ out.WriteString("}\n")
+}
+
+func latexHrule(out *bytes.Buffer, opaque interface{}) {
+ out.WriteString("\n\\HRule\n")
+}
+
+func latexList(out *bytes.Buffer, text []byte, flags int, opaque interface{}) {
+ if flags&LIST_TYPE_ORDERED != 0 {
+ out.WriteString("\n\\begin{enumerate}\n")
+ } else {
+ out.WriteString("\n\\begin{itemize}\n")
+ }
+ out.Write(text)
+ if flags&LIST_TYPE_ORDERED != 0 {
+ out.WriteString("\n\\end{enumerate}\n")
+ } else {
+ out.WriteString("\n\\end{itemize}\n")
+ }
+}
+
+func latexListitem(out *bytes.Buffer, text []byte, flags int, opaque interface{}) {
+ out.WriteString("\n\\item ")
+ out.Write(text)
+}
+
+func latexParagraph(out *bytes.Buffer, text []byte, opaque interface{}) {
+ out.WriteString("\n")
+ out.Write(text)
+ out.WriteString("\n")
+}
+
+func latexTable(out *bytes.Buffer, header []byte, body []byte, columnData []int, opaque interface{}) {
+ out.WriteString("\n\\begin{tabular}{")
+ for _, elt := range columnData {
+ switch elt {
+ case TABLE_ALIGNMENT_LEFT:
+ out.WriteByte('l')
+ case TABLE_ALIGNMENT_RIGHT:
+ out.WriteByte('r')
+ default:
+ out.WriteByte('c')
+ }
+ }
+ out.WriteString("}\n")
+ out.Write(header)
+ out.WriteString(" \\\\\n\\hline\n")
+ out.Write(body)
+ out.WriteString("\n\\end{tabular}\n")
+}
+
+func latexTableRow(out *bytes.Buffer, text []byte, opaque interface{}) {
+ if out.Len() > 0 {
+ out.WriteString(" \\\\\n")
+ }
+ out.Write(text)
+}
+
+func latexTableCell(out *bytes.Buffer, text []byte, align int, opaque interface{}) {
+ if out.Len() > 0 {
+ out.WriteString(" & ")
+ }
+ out.Write(text)
+}
+
+func latexAutolink(out *bytes.Buffer, link []byte, kind int, opaque interface{}) int {
+ out.WriteString("\\href{")
+ if kind == LINK_TYPE_EMAIL {
+ out.WriteString("mailto:")
+ }
+ out.Write(link)
+ out.WriteString("}{")
+ out.Write(link)
+ out.WriteString("}")
+ return 1
+}
+
+func latexCodespan(out *bytes.Buffer, text []byte, opaque interface{}) int {
+ out.WriteString("\\texttt{")
+ escapeSpecialChars(out, text)
+ out.WriteString("}")
+ return 1
+}
+
+func latexDoubleEmphasis(out *bytes.Buffer, text []byte, opaque interface{}) int {
+ out.WriteString("\\textbf{")
+ out.Write(text)
+ out.WriteString("}")
+ return 1
+}
+
+func latexEmphasis(out *bytes.Buffer, text []byte, opaque interface{}) int {
+ out.WriteString("\\textit{")
+ out.Write(text)
+ out.WriteString("}")
+ return 1
+}
+
+func latexImage(out *bytes.Buffer, link []byte, title []byte, alt []byte, opaque interface{}) int {
+ if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) {
+ // treat it like a link
+ out.WriteString("\\href{")
+ out.Write(link)
+ out.WriteString("}{")
+ out.Write(alt)
+ out.WriteString("}")
+ } else {
+ out.WriteString("\\includegraphics{")
+ out.Write(link)
+ out.WriteString("}")
+ }
+ return 1
+}
+
+func latexLinebreak(out *bytes.Buffer, opaque interface{}) int {
+ out.WriteString(" \\\\\n")
+ return 1
+}
+
+func latexLink(out *bytes.Buffer, link []byte, title []byte, content []byte, opaque interface{}) int {
+ out.WriteString("\\href{")
+ out.Write(link)
+ out.WriteString("}{")
+ out.Write(content)
+ out.WriteString("}")
+ return 1
+}
+
+func latexRawHtmlTag(out *bytes.Buffer, tag []byte, opaque interface{}) int {
+ return 0
+}
+
+func latexTripleEmphasis(out *bytes.Buffer, text []byte, opaque interface{}) int {
+ out.WriteString("\\textbf{\\textit{")
+ out.Write(text)
+ out.WriteString("}}")
+ return 1
+}
+
+func latexStrikethrough(out *bytes.Buffer, text []byte, opaque interface{}) int {
+ out.WriteString("\\sout{")
+ out.Write(text)
+ out.WriteString("}")
+ return 1
+}
+
+func needsBackslash(c byte) bool {
+ for _, r := range []byte("_{}%$&\\~") {
+ if c == r {
+ return true
+ }
+ }
+ return false
+}
+
+func escapeSpecialChars(out *bytes.Buffer, text []byte) {
+ for i := 0; i < len(text); i++ {
+ // directly copy normal characters
+ org := i
+
+ for i < len(text) && !needsBackslash(text[i]) {
+ i++
+ }
+ if i > org {
+ out.Write(text[org:i])
+ }
+
+ // escape a character
+ if i >= len(text) {
+ break
+ }
+ out.WriteByte('\\')
+ out.WriteByte(text[i])
+ }
+}
+
+func latexNormalText(out *bytes.Buffer, text []byte, opaque interface{}) {
+ escapeSpecialChars(out, text)
+}
+
+// header and footer
+func latexDocumentHeader(out *bytes.Buffer, opaque interface{}) {
+ out.WriteString("\\documentclass{article}\n")
+ out.WriteString("\n")
+ out.WriteString("\\usepackage{graphicx}\n")
+ out.WriteString("\\usepackage{listings}\n")
+ out.WriteString("\\usepackage[margin=1in]{geometry}\n")
+ out.WriteString("\\usepackage[utf8]{inputenc}\n")
+ out.WriteString("\\usepackage{verbatim}\n")
+ out.WriteString("\\usepackage[normalem]{ulem}\n")
+ out.WriteString("\\usepackage{hyperref}\n")
+ out.WriteString("\n")
+ out.WriteString("\\hypersetup{colorlinks,%\n")
+ out.WriteString(" citecolor=black,%\n")
+ out.WriteString(" filecolor=black,%\n")
+ out.WriteString(" linkcolor=black,%\n")
+ out.WriteString(" urlcolor=black,%\n")
+ out.WriteString(" pdfstartview=FitH,%\n")
+ out.WriteString(" breaklinks=true,%\n")
+ out.WriteString(" pdfauthor={Black Friday Markdown Processor}}\n")
+ out.WriteString("\n")
+ out.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n")
+ out.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n")
+ out.WriteString("\\parindent=0pt\n")
+ out.WriteString("\n")
+ out.WriteString("\\begin{document}\n")
+}
+
+func latexDocumentFooter(out *bytes.Buffer, opaque interface{}) {
+ out.WriteString("\n\\end{document}\n")
+}
diff --git a/markdown.go b/markdown.go
index 7420216..2276ed9 100644
--- a/markdown.go
+++ b/markdown.go
@@ -104,7 +104,7 @@ type Renderer struct {
list func(out *bytes.Buffer, text []byte, flags int, opaque interface{})
listitem func(out *bytes.Buffer, text []byte, flags int, opaque interface{})
paragraph func(out *bytes.Buffer, text []byte, opaque interface{})
- table func(out *bytes.Buffer, header []byte, body []byte, opaque interface{})
+ table func(out *bytes.Buffer, header []byte, body []byte, columnData []int, opaque interface{})
tableRow func(out *bytes.Buffer, text []byte, opaque interface{})
tableCell func(out *bytes.Buffer, text []byte, flags int, opaque interface{})