1package md2man
2
3import (
4	"bytes"
5	"fmt"
6	"html"
7	"strings"
8
9	"github.com/russross/blackfriday"
10)
11
12type roffRenderer struct{}
13
14var listCounter int
15
16func RoffRenderer(flags int) blackfriday.Renderer {
17	return &roffRenderer{}
18}
19
20func (r *roffRenderer) GetFlags() int {
21	return 0
22}
23
24func (r *roffRenderer) TitleBlock(out *bytes.Buffer, text []byte) {
25	out.WriteString(".TH ")
26
27	splitText := bytes.Split(text, []byte("\n"))
28	for i, line := range splitText {
29		line = bytes.TrimPrefix(line, []byte("% "))
30		if i == 0 {
31			line = bytes.Replace(line, []byte("("), []byte("\" \""), 1)
32			line = bytes.Replace(line, []byte(")"), []byte("\" \""), 1)
33		}
34		line = append([]byte("\""), line...)
35		line = append(line, []byte("\" ")...)
36		out.Write(line)
37	}
38	out.WriteString("\n")
39
40	// disable hyphenation
41	out.WriteString(".nh\n")
42	// disable justification (adjust text to left margin only)
43	out.WriteString(".ad l\n")
44}
45
46func (r *roffRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {
47	out.WriteString("\n.PP\n.RS\n\n.nf\n")
48	escapeSpecialChars(out, text)
49	out.WriteString("\n.fi\n.RE\n")
50}
51
52func (r *roffRenderer) BlockQuote(out *bytes.Buffer, text []byte) {
53	out.WriteString("\n.PP\n.RS\n")
54	out.Write(text)
55	out.WriteString("\n.RE\n")
56}
57
58func (r *roffRenderer) BlockHtml(out *bytes.Buffer, text []byte) {
59	out.Write(text)
60}
61
62func (r *roffRenderer) Header(out *bytes.Buffer, text func() bool, level int, id string) {
63	marker := out.Len()
64
65	switch {
66	case marker == 0:
67		// This is the doc header
68		out.WriteString(".TH ")
69	case level == 1:
70		out.WriteString("\n\n.SH ")
71	case level == 2:
72		out.WriteString("\n.SH ")
73	default:
74		out.WriteString("\n.SS ")
75	}
76
77	if !text() {
78		out.Truncate(marker)
79		return
80	}
81}
82
83func (r *roffRenderer) HRule(out *bytes.Buffer) {
84	out.WriteString("\n.ti 0\n\\l'\\n(.lu'\n")
85}
86
87func (r *roffRenderer) List(out *bytes.Buffer, text func() bool, flags int) {
88	marker := out.Len()
89	if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
90		listCounter = 1
91	}
92	if !text() {
93		out.Truncate(marker)
94		return
95	}
96}
97
98func (r *roffRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
99	if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
100		out.WriteString(fmt.Sprintf(".IP \"%3d.\" 5\n", listCounter))
101		listCounter += 1
102	} else {
103		out.WriteString(".IP \\(bu 2\n")
104	}
105	out.Write(text)
106	out.WriteString("\n")
107}
108
109func (r *roffRenderer) Paragraph(out *bytes.Buffer, text func() bool) {
110	marker := out.Len()
111	out.WriteString("\n.PP\n")
112	if !text() {
113		out.Truncate(marker)
114		return
115	}
116	if marker != 0 {
117		out.WriteString("\n")
118	}
119}
120
121func (r *roffRenderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
122	out.WriteString("\n.TS\nallbox;\n")
123
124	max_delims := 0
125	lines := strings.Split(strings.TrimRight(string(header), "\n")+"\n"+strings.TrimRight(string(body), "\n"), "\n")
126	for _, w := range lines {
127		cur_delims := strings.Count(w, "\t")
128		if cur_delims > max_delims {
129			max_delims = cur_delims
130		}
131	}
132	out.Write([]byte(strings.Repeat("l ", max_delims+1) + "\n"))
133	out.Write([]byte(strings.Repeat("l ", max_delims+1) + ".\n"))
134	out.Write(header)
135	if len(header) > 0 {
136		out.Write([]byte("\n"))
137	}
138
139	out.Write(body)
140	out.WriteString("\n.TE\n")
141}
142
143func (r *roffRenderer) TableRow(out *bytes.Buffer, text []byte) {
144	if out.Len() > 0 {
145		out.WriteString("\n")
146	}
147	out.Write(text)
148}
149
150func (r *roffRenderer) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
151	if out.Len() > 0 {
152		out.WriteString("\t")
153	}
154	if len(text) == 0 {
155		text = []byte{' '}
156	}
157	out.Write([]byte("\\fB\\fC" + string(text) + "\\fR"))
158}
159
160func (r *roffRenderer) TableCell(out *bytes.Buffer, text []byte, align int) {
161	if out.Len() > 0 {
162		out.WriteString("\t")
163	}
164	if len(text) > 30 {
165		text = append([]byte("T{\n"), text...)
166		text = append(text, []byte("\nT}")...)
167	}
168	if len(text) == 0 {
169		text = []byte{' '}
170	}
171	out.Write(text)
172}
173
174func (r *roffRenderer) Footnotes(out *bytes.Buffer, text func() bool) {
175
176}
177
178func (r *roffRenderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
179
180}
181
182func (r *roffRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
183	out.WriteString("\n\\[la]")
184	out.Write(link)
185	out.WriteString("\\[ra]")
186}
187
188func (r *roffRenderer) CodeSpan(out *bytes.Buffer, text []byte) {
189	out.WriteString("\\fB\\fC")
190	escapeSpecialChars(out, text)
191	out.WriteString("\\fR")
192}
193
194func (r *roffRenderer) DoubleEmphasis(out *bytes.Buffer, text []byte) {
195	out.WriteString("\\fB")
196	out.Write(text)
197	out.WriteString("\\fP")
198}
199
200func (r *roffRenderer) Emphasis(out *bytes.Buffer, text []byte) {
201	out.WriteString("\\fI")
202	out.Write(text)
203	out.WriteString("\\fP")
204}
205
206func (r *roffRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
207}
208
209func (r *roffRenderer) LineBreak(out *bytes.Buffer) {
210	out.WriteString("\n.br\n")
211}
212
213func (r *roffRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
214	out.Write(content)
215	r.AutoLink(out, link, 0)
216}
217
218func (r *roffRenderer) RawHtmlTag(out *bytes.Buffer, tag []byte) {
219	out.Write(tag)
220}
221
222func (r *roffRenderer) TripleEmphasis(out *bytes.Buffer, text []byte) {
223	out.WriteString("\\s+2")
224	out.Write(text)
225	out.WriteString("\\s-2")
226}
227
228func (r *roffRenderer) StrikeThrough(out *bytes.Buffer, text []byte) {
229}
230
231func (r *roffRenderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
232
233}
234
235func (r *roffRenderer) Entity(out *bytes.Buffer, entity []byte) {
236	out.WriteString(html.UnescapeString(string(entity)))
237}
238
239func processFooterText(text []byte) []byte {
240	text = bytes.TrimPrefix(text, []byte("% "))
241	newText := []byte{}
242	textArr := strings.Split(string(text), ") ")
243
244	for i, w := range textArr {
245		if i == 0 {
246			w = strings.Replace(w, "(", "\" \"", 1)
247			w = fmt.Sprintf("\"%s\"", w)
248		} else {
249			w = fmt.Sprintf(" \"%s\"", w)
250		}
251		newText = append(newText, []byte(w)...)
252	}
253	newText = append(newText, []byte(" \"\"")...)
254
255	return newText
256}
257
258func (r *roffRenderer) NormalText(out *bytes.Buffer, text []byte) {
259	escapeSpecialChars(out, text)
260}
261
262func (r *roffRenderer) DocumentHeader(out *bytes.Buffer) {
263}
264
265func (r *roffRenderer) DocumentFooter(out *bytes.Buffer) {
266}
267
268func needsBackslash(c byte) bool {
269	for _, r := range []byte("-_&\\~") {
270		if c == r {
271			return true
272		}
273	}
274	return false
275}
276
277func escapeSpecialChars(out *bytes.Buffer, text []byte) {
278	for i := 0; i < len(text); i++ {
279		// escape initial apostrophe or period
280		if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
281			out.WriteString("\\&")
282		}
283
284		// directly copy normal characters
285		org := i
286
287		for i < len(text) && !needsBackslash(text[i]) {
288			i++
289		}
290		if i > org {
291			out.Write(text[org:i])
292		}
293
294		// escape a character
295		if i >= len(text) {
296			break
297		}
298		out.WriteByte('\\')
299		out.WriteByte(text[i])
300	}
301}
302