github.com/xyproto/orbiton/v2@v2.65.12-0.20240516144430-e10a419274ec/pandoc.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  
     9  	"github.com/xyproto/env/v2"
    10  	"github.com/xyproto/files"
    11  	"github.com/xyproto/vt100"
    12  )
    13  
    14  var (
    15  	userConfigDir     = env.Dir("XDG_CONFIG_HOME", "~/.config")
    16  	pandocTexFilename = filepath.Join(userConfigDir, "o", "pandoc.tex")
    17  )
    18  
    19  const (
    20  	listingsSetupTex = `% https://tex.stackexchange.com/a/179956/5116
    21  \usepackage{xcolor}
    22  \lstset{
    23      basicstyle=\ttfamily,
    24      keywordstyle=\color[rgb]{0.13,0.29,0.53}\bfseries,
    25      stringstyle=\color[rgb]{0.31,0.60,0.02},
    26      commentstyle=\color[rgb]{0.56,0.35,0.01}\itshape,
    27      stepnumber=1,
    28      numbersep=5pt,
    29      backgroundcolor=\color[RGB]{248,248,248},
    30      showspaces=false,
    31      showstringspaces=false,
    32      showtabs=false,
    33      tabsize=2,
    34      captionpos=b,
    35      breaklines=true,
    36      breakatwhitespace=true,
    37      breakautoindent=true,
    38      escapeinside={\%*}{*)},
    39      linewidth=\textwidth,
    40      basewidth=0.5em,
    41      showlines=true,
    42  }
    43  `
    44  )
    45  
    46  // exportPandocPDF will render PDF from Markdown using pandoc
    47  func (e *Editor) exportPandocPDF(c *vt100.Canvas, tty *vt100.TTY, status *StatusBar, pandocPath, pdfFilename string) error {
    48  	// This function used to be concurrent. There are some leftovers from this that could be refactored away.
    49  
    50  	status.ClearAll(c)
    51  	status.SetMessage("Rendering to PDF using Pandoc...")
    52  	status.ShowNoTimeout(c, e)
    53  
    54  	// The reason for writing to a temporary file is to be able to export without saving
    55  	// the currently edited file.
    56  
    57  	tempFilename := ""
    58  	f, err := os.CreateTemp(tempDir, "_o*.md")
    59  	if err != nil {
    60  		return err
    61  	}
    62  	defer os.Remove(tempFilename)
    63  	tempFilename = f.Name()
    64  
    65  	// TODO: Implement a SaveAs function
    66  
    67  	// Save to tmpfn
    68  	oldFilename := e.filename
    69  	e.filename = tempFilename
    70  	err = e.Save(c, tty)
    71  	if err != nil {
    72  		e.filename = oldFilename
    73  		status.ClearAll(c)
    74  		status.SetError(err)
    75  		status.Show(c, e)
    76  		return err
    77  	}
    78  	e.filename = oldFilename
    79  
    80  	// Check if the PAPERSIZE environment variable is set. Default to "a4".
    81  	papersize := env.Str("PAPERSIZE", "a4")
    82  
    83  	pandocCommand := exec.Command(pandocPath, "-fmarkdown-implicit_figures", "--toc", "-Vgeometry:left=1cm,top=1cm,right=1cm,bottom=2cm", "-Vpapersize:"+papersize, "-Vfontsize=12pt", "--pdf-engine=xelatex", "-o", pdfFilename)
    84  
    85  	expandedTexFilename := env.ExpandUser(pandocTexFilename)
    86  
    87  	// Write the Pandoc Tex style file, for configuring the listings package, if it does not already exist
    88  	if !files.Exists(expandedTexFilename) {
    89  		// First create the folder, if needed, in a best effort attempt
    90  		folderPath := filepath.Dir(expandedTexFilename)
    91  		os.MkdirAll(folderPath, os.ModePerm)
    92  		// Write the Pandoc Tex style file
    93  		err = os.WriteFile(expandedTexFilename, []byte(listingsSetupTex), 0o644)
    94  		if err != nil {
    95  			status.SetErrorMessage("Could not write " + pandocTexFilename + ": " + err.Error())
    96  			status.Show(c, e)
    97  			return err
    98  		}
    99  	}
   100  
   101  	// use the listings package
   102  	pandocCommand.Args = append(pandocCommand.Args, "--listings", "-H"+expandedTexFilename)
   103  
   104  	// add output and input filenames
   105  	pandocCommand.Args = append(pandocCommand.Args, "-o"+pdfFilename, oldFilename)
   106  
   107  	// Save the command in a temporary file, using the current filename
   108  	saveCommand(pandocCommand)
   109  
   110  	// Use the temporary filename for the last argument, now that the command has been saved
   111  	pandocCommand.Args[len(pandocCommand.Args)-1] = tempFilename
   112  
   113  	if output, err := pandocCommand.CombinedOutput(); err != nil {
   114  		status.ClearAll(c)
   115  
   116  		// The program was executed, but failed
   117  		outputByteLines := bytes.Split(bytes.TrimSpace(output), []byte{'\n'})
   118  		errorMessage := string(outputByteLines[len(outputByteLines)-1])
   119  
   120  		if len(errorMessage) == 0 {
   121  			errorMessage = err.Error()
   122  		}
   123  
   124  		status.SetErrorMessage(errorMessage)
   125  		status.Show(c, e)
   126  
   127  		return err
   128  	}
   129  
   130  	status.ClearAll(c)
   131  	status.SetMessage("Saved " + pdfFilename)
   132  	status.ShowNoTimeout(c, e)
   133  	return nil
   134  }