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 `45` +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{})