~reesmichael1/chantpointer

8f27a0cece75090038fc41e6e1a0d46a6cce2d6b — Michael Rees 3 years ago e527ca6
Actually generate a PDF
6 files changed, 196 insertions(+), 11 deletions(-)

M chant.go
M handlers.go
M main.go
R templates/psalm.tem => psalm.tem
M templates/html/index.html
A templates/psalm.sty
M chant.go => chant.go +1 -0
@@ 7,6 7,7 @@ type Chant struct {
	Title     string
	Subtitle  string
	PointSize int
	ScorePath string
	Verses    []Verse
}


M handlers.go => handlers.go +68 -2
@@ 1,8 1,15 @@
package main

import (
	"bytes"
	"fmt"
	htmlTemplate "html/template"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	textTemplate "text/template"
)



@@ 13,23 20,82 @@ func GenerateHandler(w http.ResponseWriter, r *http.Request) {
		return
	}

	t, err := textTemplate.ParseFiles("templates/psalm.tem")
	templatePath := "psalm.tem"
	t, err := textTemplate.New(templatePath).Delims("((", "))").ParseFiles(templatePath)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var buf bytes.Buffer
	file, _, err := r.FormFile("score")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	dir, err := ioutil.TempDir("", "chant")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	stylePath := filepath.Join(dir, "psalm.sty")
	style, err := ioutil.ReadFile("templates/psalm.sty")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	err = ioutil.WriteFile(stylePath, style, 0644)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	io.Copy(&buf, file)
	chantPath := filepath.Join(dir, "chant.png")
	err = ioutil.WriteFile(chantPath, buf.Bytes(), 0644)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	buf.Reset()

	c := Chant{
		Verses:    ParseInput(r.FormValue("chant")),
		Title:     r.FormValue("title"),
		Subtitle:  r.FormValue("subtitle"),
		ScorePath: chantPath,
		PointSize: 12,
	}

	err = t.Execute(w, c)
	err = t.Execute(&buf, c)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	texPath := filepath.Join(dir, "psalm.tex")
	err = ioutil.WriteFile(texPath, buf.Bytes(), 0644)

	buf.Reset()

	pdfPath := filepath.Join(dir, "psalm.pdf")

	cmd := exec.Command("pdflatex", texPath)
	cmd.Stdout = &buf
	cmd.Stderr = &buf
	os.Chdir(dir)
	err = cmd.Run()
	if err != nil {
		fmt.Println(string(buf.Bytes()))
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, pdfPath, http.StatusTemporaryRedirect)
}

// IndexHandler handles incoming requests to the generate endpoint

M main.go => main.go +2 -0
@@ 7,7 7,9 @@ import (

func main() {
	static := http.FileServer(http.Dir("static"))
	tmp := http.FileServer(http.Dir("/tmp"))
	http.Handle("/static/", http.StripPrefix("/static", static))
	http.Handle("/tmp/", http.StripPrefix("/tmp", tmp))
	http.HandleFunc("/generate", GenerateHandler)
	http.HandleFunc("/", IndexHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))

R templates/psalm.tem => psalm.tem +8 -8
@@ 1,4 1,4 @@
\documentclass[{{.PointSize}}pt]{article}
\documentclass[((.PointSize))pt]{article}

\usepackage[margin=1in]{geometry}
\usepackage[T1]{fontenc}


@@ 10,9 10,9 @@
\begin{document}

\begin{center}
    {\Large \textbf{ {{ .Title }} }}\\[12pt]
    \textit{ {{.Subtitle }} }\\[30pt]
    \includegraphics[width=\textwidth]{chant.png}
    {\Large \textbf{(( .Title ))}}\\[12pt]
    \textit{((.Subtitle ))}\\[30pt]
    \includegraphics[width=\textwidth]{(( .ScorePath ))}
\end{center}
\vspace{24pt}



@@ 21,11 21,11 @@
\begin{center}
\begin{psalm}

{{ range $v := .Verses }}
\vs{}{ {{ $v.FirstPart}} }
{}{ {{ $v.SecondPart }} }
((range $v := .Verses ))
\vs{}{(( $v.FirstPart ))}
{}{(( $v.SecondPart ))}

{{ end }}
(( end ))
\end{psalm}
\end{center}


M templates/html/index.html => templates/html/index.html +7 -1
@@ 14,7 14,13 @@ html {overflow-y: auto;}
            </div>
        </section>
        <div class="section">
            <form action="/generate" method="post">
            <form action="/generate" method="post" enctype="multipart/form-data">
                <div class="field">
                    <label class="label">Chant Score</label>
                    <div class="control">
                        <input class="input" type="file" name="score" accept="image/*">
                    </div>
                </div>
                <div class="field">
                    <label class="label">Title</label>
                    <div class="control">

A templates/psalm.sty => templates/psalm.sty +110 -0
@@ 0,0 1,110 @@
\usepackage{array}
\usepackage{mathtools}
\usepackage{libertine}

% HORIZONTAL SPACING

\setlength\parindent{0pt}

% amount of space for notes e.g. dynamics and Full
\newlength\notesamount
\setlength\notesamount{4.5em}

% amount of space for verse numbers
\newlength\versenumamount
\setlength\versenumamount{1.5em}

% amount of space for verse text
\newlength\textamount
\setlength\textamount{20cm}

% amount to indent second half of each verse
\newlength\indentamount
\setlength\indentamount{1em}



% VERTICAL SPACING

% vertical space between verses
\newlength\versesepamount
\setlength\versesepamount{0.7em}

% vertical space between sections of psalm
\newlength\stanzagapamount
\setlength\stanzagapamount{1.2em}



% define right-aligned paragraph column for use below
\newcolumntype{R}[1]
    {>{\raggedleft\let\newline\\\arraybackslash\hspace{0pt}}p{#1}}

% verse number
\newcounter{VerseCounter}

% environment for psalm text
\newenvironment{psalm}[1][1]
{
    % number of first verse
    \setcounter{VerseCounter}{#1}
    \begin{tabular}{ R{\notesamount} R{\versenumamount} p{\textamount} }
}
{
    \end{tabular}
}



% below we set some characters as 'active' characters,
% allowing them to be defined as macros

% bar line
\catcode`|=\active
\newcommand{|}{\textbar}

% dot for change of note (use instead of space between words)
\catcode`_=\active
\newcommand{_}
    {\hspace{0.15em}$\vcenter{\hbox{\tiny$\bullet$}}$\hspace{0.15em}}

% pause between words
\newcommand{\ps}
    {*\space}

% dynamics style
\newcommand{\dyn}[1]{\textbf{\textit{#1}}}

% second half of chant
\newcommand{\second}{$\dagger$}

% dot above letter to denote syllable
\newcommand{\syl}[1]{$\dot{\textrm{#1}}$}



% verses use the following columns
% dynamics and full markings | verse numbers | verse text

% normal verse
\newcommand{\vs}[4]{#1 & \theVerseCounter.\stepcounter{VerseCounter} &
    #2\thinspace\\
    #3 & &\hspace{\indentamount}#4\\[\versesepamount]}

% verse without gap in middle
\newcommand{\vscontinue}[4]{#1 & \theVerseCounter.\stepcounter{VerseCounter} &
    #2\thinspace \raisebox{-0.08em}{\large{$\longrightarrow$}}\\
    #3 & &\hspace{\indentamount}#4\\[\versesepamount]}

% verse without verse number e.g. Gloria
\newcommand{\vsnonums}[4]{#1 & & #2\thinspace\\
    #3 & &\hspace{\indentamount}#4\\[\versesepamount]}

% break a long line and indent appropriately
\newcommand{\breaklongline}{\newline\hbox{}\hspace{2\indentamount}}

% gap to signify a new section of the psalm
\newcommand{\stanzagap}{\vspace{\stanzagapamount}\\[-12pt]}

% bracket over grouped syllables
\newcommand{\bracket}[1]{$\overbracket[0.5pt][3pt]{\text{#1}}$}