Something went wrong on our end
Select Git revision
tutorial-02-what-is-monster.html
-
Volker Schukai authoredVolker Schukai authored
pdf.go 8.37 KiB
// Copyright 2023 schukai GmbH
// SPDX-License-Identifier: AGPL-3.0
package document
import (
"fmt"
"gitlab.schukai.com/oss/utilities/documentation-manager/environment"
"gitlab.schukai.com/oss/utilities/documentation-manager/translations"
"gitlab.schukai.com/oss/utilities/documentation-manager/utils"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"text/template"
"time"
)
type BuildPdfEnvironment struct {
SourcePath string
DateFormat string
OutputPath string
Verbose bool
Templates struct {
Latex string
Markdown string
}
}
func (t *BuildPdfEnvironment) GetComponentsTemplates() map[string]string {
return map[string]string{}
}
type PdfDataset struct {
Documents string
CreatedFormat string
}
func NewPdfDataset(env BuildPdfEnvironment) (*PdfDataset, error) {
files, err := getFiles(&env, env.SourcePath)
if err != nil {
return nil, err
}
mapFiles, keys := buildFileMap(files)
d := &PdfDataset{}
docs := []string{}
for _, key := range keys {
text := mapFiles[key].textMeta.text
text, s1Map := utils.MaskCodeBlocks(text, mapFiles[key].relSourcePath, 3)
text, s2Map := utils.MaskCodeBlocks(text, mapFiles[key].relSourcePath, 1)
text = convertHeadlines(text, mapFiles[key].level, mapFiles[key].textMeta.meta.Level)
text = convertAwesomeBoxesToLatex(text)
text = convertImages(text, mapFiles[key].baseDir)
text = convertCircledNumbersToLatex(text)
text = replaceKbdToLatex(text)
text = replaceRelativeLinksToLatex(text, mapFiles[key], mapFiles)
text = utils.InsertCodeBlocks(text, s2Map)
text = utils.InsertCodeBlocks(text, s1Map)
docs = append(docs, "\\newpage{}"+text)
}
d.Documents = strings.Join(docs, "\n")
format := environment.State.GetDocumentDateFormat("")
now := time.Now()
d.CreatedFormat = now.Format(format)
if env.Verbose {
fmt.Println(d.Documents)
}
return d, nil
}
func BuildPDF(env BuildPdfEnvironment) error {
output := env.OutputPath
if env.DateFormat != "" {
dateFormat = env.DateFormat
}
if !filepath.IsAbs(output) {
pwd := os.Getenv("PWD")
output = path.Clean(path.Join(pwd, output))
}
if output == "" {
environment.ExitWithError(2, "the output option must be specified")
}
fileInfo, err := os.Stat(output)
if err != nil {
if !os.IsNotExist(err) {
environment.ExitWithError(2, "cannot stat output file", err.Error())
}
} else {
if fileInfo.IsDir() {
environment.ExitWithError(2, "output file is a directory", output)
}
}
file, err := ioutil.TempFile(os.TempDir(), environment.State.GetInfo().Mnemonic)
checkError(err)
defer func() {
file.Close()
os.Remove(file.Name())
}()
content := environment.ReadTemplate(env.Templates.Markdown)
t, err := template.New("pdf").Funcs(getFuncMap(&env)).Parse(content)
checkError(err)
d, err := NewPdfDataset(env)
checkError(err)
err = t.Execute(file, d)
checkError(err)
luaFilter := createLuaFile()
defer func() {
if luaFilter != nil {
luaFilter.Close()
os.Remove(luaFilter.Name())
}
}()
runPandoc(file.Name(), output, env.Templates.Latex, luaFilter.Name(), env.Verbose)
return nil
}
func replaceRelativeLinksToLatex(content string, f *SourceFile, fileMap SourceFileMap) string {
label := "link_" + f.hash
content = "\\hypertarget{" + label + "}{ } \n" + strings.TrimSpace(content) + "\n"
regEx := regexp.MustCompile(`(?:^|[^!])(?P<match>\[(?P<label>[^]]*)\]\((?P<path>[^)]*)\))`)
matches := regEx.FindAllStringSubmatch(content, -1)
if matches == nil {
return content
}
for _, match := range matches {
result := make(map[string]string)
for i, name := range regEx.SubexpNames() {
if i != 0 && name != "" {
result[name] = match[i]
}
}
if filepath.IsAbs(result["path"]) {
//environment.State.AddWarning(translations.T.Sprintf("Absolute path not found: %s", result["path"]))
continue
}
if utils.IsUrl(result["path"]) {
continue
}
d := filepath.Dir(f.relSourcePath)
p := filepath.Join(d, result["path"])
p = strings.Split(p, "#")[0]
ext := filepath.Ext(p)
if ext == "" {
environment.State.AddWarning(translations.T.Sprintf("No extension, of the link %s, in the file %s is not supported.", p, f.absSourcePath))
continue
}
if ext != ".md" && ext != ".markdown" {
environment.State.AddWarning(translations.T.Sprintf("The extension %s, of the link %s, in the file %s is not supported.", ext, p, f.absSourcePath))
continue
}
s := fileMap.findByRelativePath(p)
if s == nil {
environment.State.AddWarning(translations.T.Sprintf("relative path %s, in file %s, cannot be resolved", result["path"], f.absSourcePath))
continue
}
replace := "\\hyperlink{link_" + s.hash + "}{" + escapeLatexSpecialChars(result["label"]) + "}"
content = strings.Replace(content, result["match"], replace, -1)
}
return content
}
func createLuaFile() *os.File {
tmp, err := ioutil.TempFile(os.TempDir(), "lua-filter")
if err != nil {
environment.ExitWithError(2, "A temporary file cannot be created", err.Error())
}
tmp.WriteString(`
function RawBlock (raw)
return raw.format:match "html"
and pandoc.read(raw.text, "html").blocks
or raw
end
`)
return tmp
}
// https://ftp.gwdg.de/pub/ctan/graphics/awesomebox/awesomebox.pdf
func convertAwesomeBoxesToLatex(content string) string {
regEx := regexp.MustCompile(`(?m)(?P<matches>!!!\s*(?P<type>[^\s]+)\s+?(?P<title>[^\n]*)\n(?P<lines>(?P<lastline>[[:blank:]]+[^\n]+\n)+))`)
matches := regEx.FindAllStringSubmatch(content, -1)
if matches == nil {
return content
}
for _, match := range matches {
result := make(map[string]string)
for i, name := range regEx.SubexpNames() {
if i != 0 && name != "" {
result[name] = match[i]
}
}
boxtype := "note"
switch {
case utils.Contains([]string{"notebox", "note", "info"}, result["type"]):
boxtype = "note"
case utils.Contains([]string{"tipbox", "tip", "hint"}, result["type"]):
boxtype = "tip"
case utils.Contains([]string{"warningbox", "warning", "warn"}, result["type"]):
boxtype = "warning"
case utils.Contains([]string{"cautionbox", "caution", "danger"}, result["type"]):
boxtype = "caution"
case utils.Contains([]string{"importantbox", "important"}, result["type"]):
boxtype = "important"
}
c := ""
t := escapeLatexSpecialChars(result["title"])
if t != "" {
c += "\\textbf{" + utils.TrimQuotes(t) + "}\n"
}
lines := result["lines"]
lines = convertAwesomeBoxesMarkdownWithPandoc(lines)
c += "\n" + lines + "\n" //+escapeLatexSpecialChars(result["lastline"])) + "\n"
awesomebox := `\begin{` + boxtype + `block}` + c + "\n" + `\end{` + boxtype + `block}`
content = strings.Replace(content, result["matches"], "\n"+awesomebox+"\n", 1)
}
return content
}
func convertAwesomeBoxesMarkdownWithPandoc(content string) string {
output := runInlinePandoc(escapeLatexSpecialChars(utils.TrimLines(content)))
return output
}
// The following characters play a special role in LaTeX and are called special printing characters, or simply special characters.
//
// # $ % & ~ _ ^ \ { }
//
// Whenever you put one of these special characters into your file, you are doing something special. If you simply want the character
// to be printed just as any other letter, include a \ in front of the character. For example, \$ will produce $ in your output.
//
// The exception to the rule is the \ itself because \\ has its own special meaning. A \ is produced by typing $\backslash$ in your file.
//
// The meaning of these characters are:
//
// ~ (tilde) unbreakable space, use it whenever you want to leave a space which is unbreakable, and cannot expand or shrink, as e.q. in names: A.~U.~Thor.
// $ (dollar sign) to start an finish math mode.
// _ (underscore) for subscripts in math mode.
// ^ (hat) for superscripts in math mode.
// \ (backslash) starting commands, which extend until the first non-alphanumerical character. The space following the command is swallowed. The following line results in what expected:
//
// The \TeX nician is an expert in \TeX{} language.
//
// {} (curly brackets) to group and separate commands from its surroundings. Must appear in pairs.
func escapeLatexSpecialChars(content string) string {
result := ""
runes := []rune(content)
for i, k := range runes {
if k == '\\' {
result += "\\textbackslash{}"
continue
} else if utils.Contains([]string{"#", "$", "%", "&", "~", "_", "^", "\\", "{", "}"}, string(k)) {
if i == 0 || runes[i-1] != '\\' {
result += "\\"
}
}
result += string(k)
}
return result
}