Skip to content

Commit ad243e8

Browse files
committed
Working implementation of #73
1 parent 459cfcb commit ad243e8

3 files changed

Lines changed: 58 additions & 3 deletions

File tree

cmd/md2pdf/md2pdf.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ var fontName = flag.String("font-name", "", "Font name ID; e.g 'Helvetica-1251'"
2929
var themeArg = flag.String("theme", "light", "[light | dark | /path/to/custom/theme.json]")
3030
var hrAsNewPage = flag.Bool("new-page-on-hr", false, "Interpret HR as a new page; useful for presentations")
3131
var printFooter = flag.Bool("with-footer", false, "Print doc footer (<author> <title> <page number>)")
32+
var generateTOC = flag.Bool("generate-toc", false, "Auto Generate TOC")
3233
var pageSize = flag.String("page-size", "A4", "[A3 | A4 | A5]")
3334
var orientation = flag.String("orientation", "portrait", "[portrait | landscape]")
3435
var logFile = flag.String("log-file", "", "Path to log file")
@@ -174,6 +175,42 @@ func main() {
174175
}
175176

176177
pf := mdtopdf.NewPdfRenderer(params)
178+
179+
if *generateTOC == true {
180+
// we need to generate the TOC for `content`
181+
headers, err := mdtopdf.GetTOCEntries(content)
182+
if err != nil {
183+
log.Fatal(err)
184+
}
185+
headerLinks := make(map[string]*int)
186+
for _, header := range headers {
187+
linkID := pf.Pdf.AddLink()
188+
headerLinks[header.Title] = &linkID
189+
190+
// debug
191+
// log.Printf("Header: '%s' (Level %d) -> Link ID: %d\n",
192+
// header.Title, header.Level, linkID)
193+
}
194+
195+
pf.SetTOCLinks(headerLinks)
196+
pf.Pdf.SetFont("Arial", "B", 24)
197+
198+
// Add a table of contents with clickable links
199+
pf.Pdf.Cell(40, 10, "Table of Contents")
200+
pf.Pdf.Ln(30)
201+
202+
for _, header := range headers {
203+
if linkPtr, exists := headerLinks[header.Title]; exists {
204+
link := *linkPtr
205+
pf.Pdf.SetFont("Arial", "", 12)
206+
tr := pf.Pdf.UnicodeTranslatorFromDescriptor("")
207+
pf.Pdf.WriteLinkID(8, fmt.Sprintf("%s %s", tr("•"), header.Title), link)
208+
pf.Pdf.Ln(15)
209+
}
210+
}
211+
pf.Pdf.AddPage()
212+
}
213+
177214
if inputBaseURL != "" {
178215
pf.InputBaseURL = inputBaseURL
179216
}

mdtopdf.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ type PdfRenderer struct {
129129
documentMatter ast.DocumentMatters // keep track of front/main/back matter.
130130
Extensions parser.Extensions
131131
ColumnWidths map[ast.Node][]float64
132+
133+
tocLinks map[string]*int
132134
}
133135

134136
// TOCEntry represents a table of contents entry
@@ -140,7 +142,7 @@ type TOCEntry struct {
140142

141143
// TOCVisitor implements ast.NodeVisitor to collect headers
142144
type TOCVisitor struct {
143-
entries []TOCEntry
145+
Entries []TOCEntry
144146
}
145147

146148
// Visit implements the ast.NodeVisitor interface
@@ -167,7 +169,7 @@ func (v *TOCVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus {
167169
Title: title,
168170
ID: id,
169171
}
170-
v.entries = append(v.entries, entry)
172+
v.Entries = append(v.Entries, entry)
171173
}
172174
}
173175

@@ -209,7 +211,13 @@ func GetTOCEntries(content []byte) ([]TOCEntry, error) {
209211
// Walk the AST and collect headers
210212
ast.Walk(doc, visitor)
211213

212-
return visitor.entries, nil
214+
return visitor.Entries, nil
215+
}
216+
217+
// SetTOCLinks these will be used in `nodeProcessing.go:processText()` if the header is encoutered
218+
// as we need to call `r.Pdf.SetLink()` if that's the case
219+
func (r *PdfRenderer) SetTOCLinks(tocHeaders map[string]*int) {
220+
r.tocLinks = tocHeaders
213221
}
214222

215223
// SetLightTheme sets theme to 'light'

nodeProcessing.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ func (r *PdfRenderer) processText(node *ast.Text) {
5959
case *ast.Link:
6060
r.writeLink(currentStyle, s, r.cs.peek().destination)
6161
case *ast.Heading:
62+
if len(r.tocLinks) > 0 {
63+
if linkPtr, exists := r.tocLinks[s]; exists {
64+
// Dereference the pointer to get the actual link ID
65+
link := *linkPtr
66+
r.Pdf.SetLink(link, -1, -1)
67+
r.tracer("Text Heading", fmt.Sprintf("Set link for header '%s' with link ID: %d\n", s, link))
68+
} else {
69+
r.tracer("Text Heading", fmt.Sprintf("Header '%s' not found in links map\n", s))
70+
}
71+
}
6272
r.write(currentStyle, s)
6373
case *ast.BlockQuote:
6474
if r.NeedBlockquoteStyleUpdate {

0 commit comments

Comments
 (0)