mirror of
https://gitlab.com/parroquia-san-leandro/cancionero-web.git
synced 2025-06-02 13:03:12 +02:00
Nuevo generador de cancioneros en línea.
This commit is contained in:
parent
3d9bb9a26b
commit
c622a27e34
13 changed files with 5870 additions and 0 deletions
31
Makefile_generador
Normal file
31
Makefile_generador
Normal file
|
@ -0,0 +1,31 @@
|
|||
.PHONY=static all clean
|
||||
|
||||
ALL_STATIC := $(patsubst res_gen/static/%,generador_public/%,$(wildcard res_gen/static/*))
|
||||
ALL_TEMPLATES := $(wildcard res_gen/templates/*.html)
|
||||
PY_SRC := $(wildcard src/**.py)
|
||||
|
||||
all: generador_public generador_public/index.html static generador_public/res/canciones
|
||||
|
||||
static: $(ALL_STATIC)
|
||||
|
||||
generador_public:
|
||||
mkdir generador_public
|
||||
|
||||
generador_public/res/canciones:
|
||||
rsync -a latex/canciones generador_public/res/.
|
||||
|
||||
generador_public/index.html: $(ALL_TEMPLATES) $(PY_SRC)
|
||||
python3 src/create_generator.py \
|
||||
--latex latex/cancionero.tex \
|
||||
--other-latex latex/canciones/ \
|
||||
--author NONE --title NONE
|
||||
|
||||
generador_public/%: res_gen/static/%
|
||||
cp -ra $< $@
|
||||
|
||||
clean:
|
||||
rm -rf generador_public songs.dat
|
||||
|
||||
.PHONY: upload
|
||||
upload: all
|
||||
rsync -a --delete generador_public/* uberspace:html/cancionero-generador/.
|
209
res_gen/static/generar_pdf.php
Normal file
209
res_gen/static/generar_pdf.php
Normal file
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
$errorlist = array();
|
||||
|
||||
function delTree($dir) {
|
||||
$files = array_diff(scandir($dir), array('.', '..'));
|
||||
foreach ($files as $file) {
|
||||
if (!is_dir("$dir/$file")) {
|
||||
unlink("$dir/$file");
|
||||
}
|
||||
}
|
||||
return rmdir($dir);
|
||||
}
|
||||
|
||||
function param_conversion($name, $type) {
|
||||
global $errorlist;
|
||||
if (validParam($name, "string")) {
|
||||
if (!is_numeric($_POST[$name])) {
|
||||
$errorlist[] = "El parámetro $name debería ser numérico, y es: " . $_POST[$name];
|
||||
} else {
|
||||
if ($type == "integer")
|
||||
$_POST[$name] = intval($_POST[$name]);
|
||||
else if ($type == "double")
|
||||
$_POST[$name] = doubleval($_POST[$name]);
|
||||
else {
|
||||
$errorlist[] = "$type es un tipo inválido al que convertir el parámetro $name";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function validParam($name, $type) {
|
||||
global $errorlist;
|
||||
if (!isset($_POST[$name]))
|
||||
$errorlist[] = "No se ha incluido el parámetro '$name'";
|
||||
else if (($realtype = gettype($_POST[$name])) != $type)
|
||||
$errorlist[] = "El parámetro '$name' debería ser de tipo $type, y es de tipo $realtype";
|
||||
else
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function validEmptyBool($name) {
|
||||
if (!isset($_POST[$name]))
|
||||
$_POST[$name] = "false";
|
||||
return validBool($name);
|
||||
}
|
||||
|
||||
function validBool($name) {
|
||||
if (validParam($name, "string")) {
|
||||
if ($_POST[$name] != "true")
|
||||
$_POST[$name] = "false";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function validInt($name, $min, $max) {
|
||||
global $errorlist;
|
||||
if (param_conversion($name, "integer") && validParam($name, "integer")) {
|
||||
if ($_POST[$name] < $min || $_POST[$name] > $max)
|
||||
$errorlist[] = "El parámetro numérico '$name' tiene un valor fuera de rango. Valor: " . $_POST[$name] . ". Rango válido: [$min, $max].";
|
||||
else
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function validDouble($name, $min, $max) {
|
||||
global $errorlist;
|
||||
if (param_conversion($name, "double") && validParam($name, "double")) {
|
||||
if ($_POST[$name] < $min || $_POST[$name] > $max)
|
||||
$errorlist[] = "El parámetro numérico '$name' tiene un valor fuera de rango. Valor: " . $_POST[$name] . ". Rango válido: [$min, $max].";
|
||||
else
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$TEX_ERROR = "Fatal error occurred, no output PDF file produced!";
|
||||
|
||||
$error = false;
|
||||
// Check fields and their correctness
|
||||
$error |= !validBool("chorded");
|
||||
if (!$error && $_POST["chorded"] != "true") {
|
||||
$error |= !validInt("paperheight", 1, 9999);
|
||||
$error |= !validInt("paperwidth", 1, 9999);
|
||||
$error |= !validDouble("margin", 0.01, 999);
|
||||
$error |= !validInt("fontsize", 8, 100);
|
||||
$error |= !validInt("titlefontsize", 8, 100);
|
||||
$error |= !validInt("subtitlefontsize", 8, 100);
|
||||
}
|
||||
$error |= !validEmptyBool("ifcolumns");
|
||||
if (!$error && $_POST["ifcolumns"] == "true") {
|
||||
$error |= !validInt("columns", 1, 20);
|
||||
}
|
||||
$error |= !validEmptyBool("ifcolumnsep");
|
||||
if (!$error && $_POST["ifcolumnsep"] == "true") {
|
||||
$error |= !validDouble("columnsep", 0.01, 999);
|
||||
}
|
||||
$error |= !validInt("breaksallowed", 0, 3);
|
||||
$error |= !validParam("title", "string");
|
||||
$error |= !validParam("songs", "array");
|
||||
if (!$error) {
|
||||
foreach ($_POST["songs"] as $song) {
|
||||
if (!str_starts_with($song, "canciones/") || str_contains($song, "..")) {
|
||||
$error = true;
|
||||
$errorlist[] = "La canción $song no cumple los requisitos.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create tmp dir and copy/link necessary files
|
||||
if (!$error) {
|
||||
$resdir = getcwd() . "/res";
|
||||
$tmpdir = substr(shell_exec("mktemp -d"), 0, -1); // remove trailing newline
|
||||
symlink("$resdir/songs.sty", "$tmpdir/songs.sty");
|
||||
symlink("$resdir/estilo.sty", "$tmpdir/estilo.sty");
|
||||
symlink("$resdir/acordes.sty", "$tmpdir/acordes.sty");
|
||||
symlink("$resdir/canciones", "$tmpdir/canciones");
|
||||
symlink("$resdir/changepage.sty", "$tmpdir/changepage.sty");
|
||||
symlink("$resdir/tocloft.sty", "$tmpdir/tocloft.sty");
|
||||
symlink("$resdir/uarial.sty", "$tmpdir/uarial.sty");
|
||||
}
|
||||
|
||||
// Generate and fill template
|
||||
if (!$error) {
|
||||
$lines = file("$resdir/template.tex");
|
||||
if ($_POST["chorded"] == "true") {
|
||||
$lines = preg_replace('/%CHORDED%/', '', $lines);
|
||||
} else {
|
||||
$lines = preg_replace('/%LYRICS%/', '', $lines);
|
||||
$lines = preg_replace('/%PAPERHEIGHT%/', $_POST["paperheight"], $lines);
|
||||
$lines = preg_replace('/%PAPERWIDTH%/', $_POST["paperwidth"], $lines);
|
||||
$lines = preg_replace('/%MARGIN%/', $_POST["margin"], $lines);
|
||||
if ($_POST["ifcolumns"] == "true") {
|
||||
$lines = preg_replace('/%SONGCOLUMNS%/', '', $lines);
|
||||
$lines = preg_replace('/%SONGCOLNUM%/', "\\songcolumns{" . $_POST["columns"] . "}", $lines);
|
||||
} else {
|
||||
$lines = preg_replace('/%SONGCOLUMNS%/', 'onesongcolumn', $lines);
|
||||
}
|
||||
$lines = preg_replace('/%FONTSIZE%/', $_POST["fontsize"], $lines);
|
||||
$lines = preg_replace('/%LINESKIP%/', $_POST["fontsize"] + 4, $lines);
|
||||
$lines = preg_replace('/%TITLEFONTSIZE%/', $_POST["titlefontsize"], $lines);
|
||||
$lines = preg_replace('/%TITLELINESKIP%/', $_POST["titlefontsize"] + 4, $lines);
|
||||
$lines = preg_replace('/%SUBTITLEFONTSIZE%/', $_POST["subtitlefontsize"], $lines);
|
||||
}
|
||||
$lines = preg_replace('/%TITLE%/', $_POST["title"], $lines);
|
||||
if ($_POST["ifcolumnsep"] == "true") {
|
||||
$lines = preg_replace('/%IFCOLUMNSEP%/', '', $lines);
|
||||
$lines = preg_replace('/%COLUMNSEP%/', $_POST["columnsep"], $lines);
|
||||
}
|
||||
$songinput = "";
|
||||
foreach ($_POST["songs"] as $song) {
|
||||
$songinput .= '\input{' . $song . "}\n";
|
||||
}
|
||||
$lines = preg_replace('/%SONGLIST%/', $songinput, $lines);
|
||||
$fwrite = file_put_contents("$tmpdir/cancionero.tex", $lines);
|
||||
if (!$fwrite) {
|
||||
$error = true;
|
||||
$errorlist[] = "No se pudo escribir el archivo LaTeX.";
|
||||
}
|
||||
}
|
||||
|
||||
// Compile PDF
|
||||
if (!$error) {
|
||||
chdir($tmpdir);
|
||||
do {
|
||||
$latexlog = shell_exec("/usr/bin/pdflatex -interaction=nonstopmode cancionero.tex");
|
||||
if (str_contains($latexlog, $TEX_ERROR)) {
|
||||
$error = true;
|
||||
$errorlist[] = "Ha sucedido un error en la ejecución, véase el log más abajo.";
|
||||
break;
|
||||
}
|
||||
} while (str_contains($latexlog, "(rerunfilecheck)"));
|
||||
}
|
||||
// Return PDF or error page
|
||||
if (!$error && file_exists($pdffile = "$tmpdir/cancionero.pdf")) {
|
||||
header("Content-Type: application/pdf");
|
||||
header('Content-Disposition: inline;filename=cancionero.pdf');
|
||||
header('Content-Length: ' . filesize($pdffile));
|
||||
readfile($pdffile);
|
||||
} else {
|
||||
echo '<h1>Ha ocurrido un error...</h1>';
|
||||
echo '<p>No se ha podido generar el cancionero.</p>';
|
||||
echo '<p><strong>Por favor,</strong> guarda (Ctrl+S) o imprime (Ctrl+P) esta página y envíasela a Carlos, para que lo pueda solucionar.</p>';
|
||||
echo '<h2>Errores individuales</h2>';
|
||||
echo '<ul>';
|
||||
foreach ($errorlist as $errormsg) {
|
||||
echo "<li>$errormsg</li>";
|
||||
}
|
||||
echo '</ul>';
|
||||
if (isset($tmpdir) && file_exists("$tmpdir/cancionero.log")) {
|
||||
echo '<h2>Detalles del error</h2>';
|
||||
echo '<pre>';
|
||||
readfile("$tmpdir/cancionero.log");
|
||||
echo '</pre>';
|
||||
}
|
||||
echo '<p>Fin de la página.</p>';
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
if (isset($tmpdir) && file_exists($tmpdir))
|
||||
delTree($tmpdir);
|
||||
|
||||
?>
|
3
res_gen/static/img/link-external.svg
Normal file
3
res_gen/static/img/link-external.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="100" width="100">
|
||||
<path fill="#FFF" stroke="#06D" stroke-width="10" d="m43,35H5v60h60V57M45,5v10l10,10-30,30 20,20 30-30 10,10h10V5z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 198 B |
1
res_gen/static/res/.htaccess
Normal file
1
res_gen/static/res/.htaccess
Normal file
|
@ -0,0 +1 @@
|
|||
Deny from all
|
66
res_gen/static/res/acordes.sty
Normal file
66
res_gen/static/res/acordes.sty
Normal file
|
@ -0,0 +1,66 @@
|
|||
% Base del estilo con acordes
|
||||
\ProvidesPackage{acordes}
|
||||
|
||||
% Incluimos el paquete que genera el libro de acordes con la opción chorded
|
||||
\RequirePackage[chorded]{songs}
|
||||
% Formato del papel: A4 (297x210) con todos los márgenes de 15mm
|
||||
\RequirePackage[paperheight=297mm,paperwidth=210mm,margin=15mm,heightrounded]{geometry}
|
||||
% Incluimos el paquete básico de estilo, ver estilo.sty en la misma carpeta
|
||||
\RequirePackage{estilo}
|
||||
% Notas que se deben reconocer como las notas La, Si, Do, Re, Mi, Fa, Sol
|
||||
% Esto sirve para transponer canciones automáticamente y para cambiar los nombres
|
||||
% por los que se quiera: ver siguiente instrucción
|
||||
\notenamesin{A}{B}{C}{D}{E}{F}{G}
|
||||
% Imprime las notas con nombres romances con la primera en mayúscula.
|
||||
% Se puede cambiar a cualquier conjunto de 7 palabras.
|
||||
\notenamesout{La}{Si}{Do}{Re}{Mi}{Fa}{Sol}
|
||||
% Este paquete no sé por qué lo incluí, pero no me atrevo a quitarlo
|
||||
\RequirePackage{etoolbox} % TODO: probar a quitar
|
||||
% Esto permite hacer un índice con múltiples columnas (solo para el índice secuencial)
|
||||
% Los índices alfabéticos y por autor son internos del paquete "songs".
|
||||
\RequirePackage[toc]{multitoc}
|
||||
% Configura el número de columnas de los índices a 2
|
||||
\renewcommand{\multicolumntoc}{2}
|
||||
% Coloca una barra vertical de ancho 1pt entre cada columna del índice.
|
||||
%\setlength{\columnseprule}{1pt}
|
||||
% Incluimos el paquete hyperref, para poder tener enlaces clicables en los índices.
|
||||
% Esto es muy útil a la hora de navegar por el documento en un ordenador.
|
||||
\RequirePackage[bookmarks]{hyperref}
|
||||
|
||||
% Ver último ajuste en letra.sty
|
||||
\songpos{1}
|
||||
|
||||
% Configuración del título (ver letra.sty), similar pero con otras dimensiones
|
||||
\renewcommand\makeprelude{%
|
||||
\resettitles
|
||||
\checkoddpage
|
||||
\ifoddpage
|
||||
\begin{minipage}{70mm}
|
||||
{
|
||||
\raggedright
|
||||
{\fontsize{15pt}{17pt}\sffamily\bfseries\songtitle} \par
|
||||
{\fontsize{9pt}{\baselineskip}\extendprelude}
|
||||
}
|
||||
\end{minipage}
|
||||
\hfill
|
||||
\begin{minipage}{12mm}
|
||||
{\hfill \printsongnum{\thesongnum}}
|
||||
\end{minipage}
|
||||
\else
|
||||
\begin{minipage}{12mm}
|
||||
\printsongnum{\thesongnum}
|
||||
\end{minipage}
|
||||
\hfill
|
||||
\begin{minipage}{70mm}
|
||||
{
|
||||
\raggedleft {\fontsize{15pt}{17pt}\sffamily\bfseries\songtitle} \par
|
||||
{\fontsize{9pt}{\baselineskip}\extendprelude}
|
||||
}
|
||||
\end{minipage}
|
||||
\fi
|
||||
}
|
||||
|
||||
% TODO Para añadir las canciones al índice numérico
|
||||
% https://tex.stackexchange.com/questions/192157/latex-songs-use-tableofcontents/192160?r=SearchResults#192160
|
||||
|
||||
|
142
res_gen/static/res/changepage.sty
Normal file
142
res_gen/static/res/changepage.sty
Normal file
|
@ -0,0 +1,142 @@
|
|||
%% LaTeX2e file `changepage.sty'
|
||||
%% generated by the `filecontents' environment
|
||||
%% from source `changepage' on 2009/10/20.
|
||||
%%
|
||||
\NeedsTeXFormat{LaTeX2e}
|
||||
\ProvidesPackage{changepage}[2009/10/20 v1.0c check page and change page layout]
|
||||
|
||||
% Don't use this with memoir
|
||||
\newcommand*{\cp@whoopsusingmemoir}{}
|
||||
\@ifclassloaded{memoir}{\def\cp@whoopsusingmemoir{\endinput}}
|
||||
\cp@whoopsusingmemoir
|
||||
|
||||
% New \verb|\if| for the strict option
|
||||
\newif\ifstrictpagecheck
|
||||
\strictpagecheckfalse
|
||||
|
||||
% User commands for switching strict page checking on/off
|
||||
\newcommand*{\strictpagecheck}{\strictpagechecktrue}
|
||||
\newcommand*{\easypagecheck}{\strictpagecheckfalse}
|
||||
|
||||
% Declare and process options
|
||||
\DeclareOption{strict}{\strictpagechecktrue}
|
||||
\ProcessOptions\relax
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% New commands for strict checking of odd/even page.
|
||||
%% Works by writing a label and then checking its pageref.
|
||||
|
||||
\newif\ifoddpage
|
||||
\newcounter{cp@cntr}
|
||||
\newcount\cp@tempcnt % instead of \verb|\@memcnta|
|
||||
\newcommand{\cplabel}{^_}
|
||||
|
||||
\gdef\thepmemc@@page{\the\c@page}
|
||||
|
||||
\long\def\pmemprotected@write#1#2#3{% modified \verb|\protected@write|
|
||||
\begingroup
|
||||
\let\thepmemc@@page\relax
|
||||
#2%
|
||||
\let\protect\@unexpandable@protect
|
||||
\edef\reserved@a{\write#1{#3}}%
|
||||
\reserved@a
|
||||
\endgroup
|
||||
\if@nobreak\ifvmode\nobreak\fi\fi}
|
||||
|
||||
\newcommand*{\pmemlabel}[1]{\@bsphack
|
||||
\pmemprotected@write\@auxout{}%
|
||||
{\string\newpmemlabel{#1}{\thepmemc@@page}}%
|
||||
\@esphack}
|
||||
|
||||
\newcommand*{\newpmemlabel}[2]{{\global\@namedef{m@#1}{#2}}}
|
||||
\newcommand*{\pmemlabelref}[1]{%
|
||||
\expandafter\ifx\csname m@#1\endcsname\relax
|
||||
0% % 0 if there is no label yet in the aux file
|
||||
\else
|
||||
\csname m@#1\endcsname
|
||||
\fi}
|
||||
|
||||
\DeclareRobustCommand{\checkoddpage}{%
|
||||
\oddpagefalse%
|
||||
\ifstrictpagecheck%
|
||||
\stepcounter{cp@cntr}\pmemlabel{\cplabel\thecp@cntr}%
|
||||
\cp@tempcnt=\pmemlabelref{\cplabel\thecp@cntr}\relax
|
||||
\ifodd\cp@tempcnt\oddpagetrue\fi
|
||||
\else
|
||||
\ifodd\c@page\oddpagetrue\fi
|
||||
\fi}
|
||||
|
||||
% End newcommands for strict checking of odd/even page.
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
% Copy of some of the code from the ifmtarg package to save requiring ifmtarg
|
||||
\begingroup
|
||||
\catcode`\Q=3
|
||||
\long\gdef\@ifmtarg#1{\@xifmtarg#1QQ\@secondoftwo\@firstoftwo\@nil}
|
||||
\long\gdef\@xifmtarg#1#2Q#3#4#5\@nil{#4}
|
||||
\endgroup
|
||||
|
||||
% Set the page output parameters
|
||||
\DeclareRobustCommand{\ch@ngetext}{%
|
||||
\setlength{\@colht}{\textheight}\setlength{\@colroom}{\textheight}%
|
||||
\setlength{\vsize}{\textheight}\setlength{\columnwidth}{\textwidth}%
|
||||
\if@twocolumn%
|
||||
\advance\columnwidth-\columnsep \divide\columnwidth\tw@%
|
||||
\@firstcolumntrue%
|
||||
\fi%
|
||||
\setlength{\hsize}{\columnwidth}%
|
||||
\setlength{\linewidth}{\hsize}}
|
||||
|
||||
\DeclareRobustCommand{\changetext}[5]{%
|
||||
\@ifmtarg{#1}{}{\addtolength{\textheight}{#1}}%
|
||||
\@ifmtarg{#2}{}{\addtolength{\textwidth}{#2}}%
|
||||
\@ifmtarg{#3}{}{\addtolength{\evensidemargin}{#3}}%
|
||||
\@ifmtarg{#4}{}{\addtolength{\oddsidemargin}{#4}}%
|
||||
\@ifmtarg{#5}{}{\addtolength{\columnsep}{#5}}%
|
||||
\ch@ngetext}
|
||||
|
||||
\DeclareRobustCommand{\changepage}[9]{%
|
||||
\@ifmtarg{#1}{}{\addtolength{\textheight}{#1}}%
|
||||
\@ifmtarg{#2}{}{\addtolength{\textwidth}{#2}}%
|
||||
\@ifmtarg{#3}{}{\addtolength{\evensidemargin}{#3}}%
|
||||
\@ifmtarg{#4}{}{\addtolength{\oddsidemargin}{#4}}%
|
||||
\@ifmtarg{#5}{}{\addtolength{\columnsep}{#5}}%
|
||||
\ch@ngetext%
|
||||
\@ifmtarg{#6}{}{\addtolength{\topmargin}{#6}}%
|
||||
\@ifmtarg{#7}{}{\addtolength{\headheight}{#7}}%
|
||||
\@ifmtarg{#8}{}{\addtolength{\headsep}{#8}}%
|
||||
\@ifmtarg{#9}{}{\addtolength{\footskip}{#9}}}
|
||||
|
||||
\newenvironment{adjustwidth}[2]{%
|
||||
\begin{list}{}{%
|
||||
\topsep\z@%
|
||||
\listparindent\parindent%
|
||||
\parsep\parskip%
|
||||
\@ifmtarg{#1}{\setlength{\leftmargin}{\z@}}%
|
||||
{\setlength{\leftmargin}{#1}}%
|
||||
\@ifmtarg{#2}{\setlength{\rightmargin}{\z@}}%
|
||||
{\setlength{\rightmargin}{#2}}%
|
||||
}
|
||||
\item[]}{\end{list}}
|
||||
|
||||
\newenvironment{adjustwidth*}[2]{%
|
||||
\begin{list}{}{%
|
||||
\topsep\z@%
|
||||
\listparindent\parindent%
|
||||
\parsep\parskip%
|
||||
\checkoddpage
|
||||
\ifoddpage% odd numbered page
|
||||
\@ifmtarg{#1}{\setlength{\leftmargin}{\z@}}%
|
||||
{\setlength{\leftmargin}{#1}}%
|
||||
\@ifmtarg{#2}{\setlength{\rightmargin}{\z@}}%
|
||||
{\setlength{\rightmargin}{#2}}%
|
||||
\else% even numbered page
|
||||
\@ifmtarg{#2}{\setlength{\leftmargin}{\z@}}%
|
||||
{\setlength{\leftmargin}{#2}}%
|
||||
\@ifmtarg{#1}{\setlength{\rightmargin}{\z@}}%
|
||||
{\setlength{\rightmargin}{#1}}%
|
||||
\fi}
|
||||
\item[]}{\end{list}}
|
||||
|
||||
\endinput
|
||||
|
65
res_gen/static/res/estilo.sty
Normal file
65
res_gen/static/res/estilo.sty
Normal file
|
@ -0,0 +1,65 @@
|
|||
% Base del estilo general del cancionero: aquí ajusto cosas generales para ambos cancioneros
|
||||
% y cargo los paquetes necesarios para diversas funciones
|
||||
\ProvidesPackage{estilo}
|
||||
|
||||
% Para poder poner acentos y letras como la Ñ
|
||||
\RequirePackage[utf8]{inputenc}
|
||||
% Para que los títulos de los índices sean en español
|
||||
\RequirePackage[spanish]{babel}
|
||||
% Cargar la fuente Arial 030 para usarla en los números
|
||||
\RequirePackage[scaled]{uarial}
|
||||
% Ajustamos la codificación de las fuentes a T1 (necesario para Arial)
|
||||
\RequirePackage[T1]{fontenc}
|
||||
% Cargar la fuente Helvetica para usarla por defecto en todo el texto
|
||||
% sans serif
|
||||
\RequirePackage[scaled]{helvet}
|
||||
% Este paquete nos da el comando \includepdf que sirve para insertar
|
||||
% cualquier PDF como una página más
|
||||
\RequirePackage[final]{pdfpages}
|
||||
% Para la nota musical
|
||||
\RequirePackage{textcomp}
|
||||
% Para el indice general (secuencial)
|
||||
\RequirePackage{tocloft}
|
||||
|
||||
% Para que las páginas no tengan cabecera ni pie de página (numeración y capítulo actual)
|
||||
\pagestyle{empty}
|
||||
% Para que el paquete de canciones no numere las estrofas (queda demasiado formal).
|
||||
% Se podría reactivar para hacer diapositivas o hojas cortas de canciones
|
||||
\noversenumbers
|
||||
% Hacemos que las letras de las canciones sean sans serif
|
||||
\renewcommand{\lyricfont}{\sffamily}
|
||||
% Número con la fuente URW Arial 030 (similar a los números magnéticos que hay en la parroquia)
|
||||
\renewcommand{\printsongnum}[1]{\fontfamily{ua1}\selectfont\bfseries\LARGE#1}
|
||||
% Esto está comentado, si se descomenta cambiaría los estribillos a cursiva
|
||||
%\renewcommand{\chorusfont}{\slshape}
|
||||
% Esto está comentado, si se descomenta cambiaría los estribillos a negrita
|
||||
%\renewcommand{\chorusfont}{\bfseries}
|
||||
|
||||
% Necesitamos este paquete y la opción para saber si estamos en página par o impar (alineación de números)
|
||||
\RequirePackage{changepage}
|
||||
\strictpagecheck
|
||||
|
||||
% Añade un nuevo campo a las canciones para mostrar en qué melodía están basadas
|
||||
\newsongkey{m}{\def\melody{}}{\def\melody{\textmusicalnote #1\par}}
|
||||
|
||||
% Añade un nuevo campo para definir el índice (toc={...})
|
||||
\newcommand\toctitle{}
|
||||
\newcommand\toclink{}
|
||||
{\makeatletter\gdef\toclink{\@ifundefined{href}{}{{song\theSB@songsnum-\thesongnum
|
||||
.\ifnum\value{section}=0 1\else2\fi}}}}
|
||||
\newcommand\addtotoc[1]{\addtocontents{toc}{\protect\contentsline
|
||||
{\ifnum\value{section}>0sub\fi section}{\numberline\thesongnum#1}{\thepage}\toclink}}
|
||||
\newsongkey{toc}
|
||||
{\def\toctitle{\resettitles\addtotoc\songtitle}}
|
||||
{\def\toctitle{\addtotoc{#1}}}
|
||||
|
||||
% Modifica el preludio (cabecera) de cada canción para incluir melody y toctitle
|
||||
\renewcommand{\extendprelude}{
|
||||
\showrefs\showauthors
|
||||
\melody\toctitle
|
||||
}
|
||||
|
||||
% En la tabla de contenidos, evita que aparezca el número de página (innecesario)
|
||||
\cftpagenumbersoff{chapter}
|
||||
\cftpagenumbersoff{section}
|
||||
\cftpagenumbersoff{subsection}
|
3808
res_gen/static/res/songs.sty
Normal file
3808
res_gen/static/res/songs.sty
Normal file
File diff suppressed because it is too large
Load diff
37
res_gen/static/res/template.tex
Normal file
37
res_gen/static/res/template.tex
Normal file
|
@ -0,0 +1,37 @@
|
|||
\documentclass{book}
|
||||
|
||||
% \ifchorded
|
||||
%CHORDED% \usepackage{acordes}
|
||||
% \else
|
||||
%LYRICS% \usepackage[lyric,noshading,%SONGCOLUMNS%]{songs}
|
||||
%LYRICS% \usepackage{estilo}
|
||||
%LYRICS% \usepackage[paperwidth=%PAPERWIDTH%mm,paperheight=%PAPERHEIGHT%mm,margin=%MARGIN%cm]{geometry}
|
||||
%LYRICS% \renewcommand{\lyricfont}{\fontsize{%FONTSIZE%}{%LINESKIP%}\sffamily}
|
||||
|
||||
\usepackage{hyperref}
|
||||
\songpos{0} % 0 a 3, cuanto mas alto menos se cortan las canciones
|
||||
|
||||
\ifchorded
|
||||
\else
|
||||
\renewcommand\makeprelude{%
|
||||
\resettitles%
|
||||
{%
|
||||
\raggedright%
|
||||
%LYRICS% {\fontsize{%TITLEFONTSIZE%}{%TITLELINESKIP%}\sffamily\bfseries\songtitle} \par
|
||||
%LYRICS% {\fontsize{%SUBTITLEFONTSIZE%}{\baselineskip}\extendprelude}
|
||||
}%
|
||||
}%
|
||||
\fi
|
||||
|
||||
|
||||
\begin{document}
|
||||
|
||||
\songsection{\normalfont \textsc{\huge %TITLE%}}
|
||||
\begin{songs}{}
|
||||
%SONGCOLNUM%
|
||||
%IFCOLUMNSEP%\columnsep=%COLUMNSEP%cm
|
||||
%SONGLIST%
|
||||
\end{songs}
|
||||
|
||||
\end{document}
|
||||
|
1160
res_gen/static/res/tocloft.sty
Normal file
1160
res_gen/static/res/tocloft.sty
Normal file
File diff suppressed because it is too large
Load diff
35
res_gen/static/res/uarial.sty
Normal file
35
res_gen/static/res/uarial.sty
Normal file
|
@ -0,0 +1,35 @@
|
|||
\ProvidesPackage{uarial}%
|
||||
[2006/03/21 1.0 (WaS)]
|
||||
\RequirePackage{keyval}
|
||||
\define@key{UaI}{scaled}[.95]{%
|
||||
\expandafter\def\csname ua1@Scale\endcsname{#1}}
|
||||
\def\ProcessOptionsWithKV#1{%
|
||||
\let\@tempc\relax
|
||||
\let\UaI@tempa\@empty
|
||||
\ifx\@classoptionslist\relax\else
|
||||
\@for\CurrentOption:=\@classoptionslist\do{%
|
||||
\@ifundefined{KV@#1@\CurrentOption}%
|
||||
{}%
|
||||
{%
|
||||
\edef\UaI@tempa{\UaI@tempa,\CurrentOption,}%
|
||||
\@expandtwoargs\@removeelement\CurrentOption
|
||||
\@unusedoptionlist\@unusedoptionlist
|
||||
}%
|
||||
}%
|
||||
\fi
|
||||
\edef\UaI@tempa{%
|
||||
\noexpand\setkeys{#1}{%
|
||||
\UaI@tempa\@ptionlist{\@currname.\@currext}%
|
||||
}%
|
||||
}%
|
||||
\UaI@tempa
|
||||
\let\CurrentOption\@empty
|
||||
}
|
||||
\ProcessOptionsWithKV{UaI}
|
||||
\AtEndOfPackage{%
|
||||
\let\@unprocessedoptions\relax
|
||||
}
|
||||
\renewcommand{\sfdefault}{ua1}
|
||||
\endinput
|
||||
%%
|
||||
%% End of file `ua1.sty'.
|
258
res_gen/templates/generador.html
Normal file
258
res_gen/templates/generador.html
Normal file
|
@ -0,0 +1,258 @@
|
|||
<!doctype html>
|
||||
<html lang="es_ES">
|
||||
<head>
|
||||
<title>Generador de cancioneros</title>
|
||||
<meta charset='utf-8' />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
.chorded .hideChorded {
|
||||
display: none;
|
||||
}
|
||||
.lyrics .hideLyrics {
|
||||
display: none;
|
||||
}
|
||||
a.external {
|
||||
background-image: url(img/link-external.svg);
|
||||
background-position: center right;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 0.857em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
a {
|
||||
color: #36c;
|
||||
}
|
||||
a:hover {
|
||||
color: #3056a9;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Generador de cancioneros</h1>
|
||||
<form id="form" action="generar_pdf.php" method="post" target="_blank">
|
||||
<h2>Ajustes generales</h2>
|
||||
<p>
|
||||
Selecciona el tipo de cancionero:
|
||||
<label><input type="radio" name="chorded" value="true" onchange="setChorded(event)" required>Con acordes</label>
|
||||
<label><input type="radio" name="chorded" value="false" onchange="setChorded(event)" required>Sin acordes</label>
|
||||
</p>
|
||||
<fieldset>
|
||||
<legend>Propiedades de página</legend>
|
||||
<p class="hideChorded">
|
||||
Tamaño del papel:
|
||||
<label><input class="lyricsonly" type="number" size="4" name="paperheight" id="paperheight" required> mm (alto)</label> ×
|
||||
<label><input class="lyricsonly" type="number" size="4" name="paperwidth" id="paperwidth" required> mm (ancho)</label>
|
||||
<strong>o</strong> elige un tamaño estándar
|
||||
<select class="lyricsonly" onchange="setPaperSize(event)">
|
||||
<option value=""></option>
|
||||
<option value="paperheight=297,paperwidth=210">A4 (vertical)</option>
|
||||
<option value="paperheight=210,paperwidth=297">A4 (horizontal)</option>
|
||||
<option value="paperheight=210,paperwidth=148">A5 (vertical)</option>
|
||||
<option value="paperheight=148,paperwidth=210">A5 (horizontal)</option>
|
||||
<option value="paperheight=210,paperwidth=99">Cancionero San Leandro</option>
|
||||
</select>
|
||||
</p><p>
|
||||
Columnas: <label>
|
||||
<input type="checkbox" name="ifcolumns" value="true" checked onclick="toggleCols(event)"> Habilitar</label> | <label><input type="number" name="columns" id="columns" value="2" size="3" min="1" max="10" required> columnas</label>
|
||||
</p><p>
|
||||
Separación extra entre columnas: <label>
|
||||
<input type="checkbox" name="ifcolumnsep" onclick="toggleColSep(event)" value="true"> Habilitar</label> | <label><input type="number" size="4" min="0.1" step="0.1" name="columnsep" id="columnsep" disabled="true" required> cm.</label>
|
||||
</p>
|
||||
<p class="hideChorded">
|
||||
<label>Margen en los bordes: <input class="lyricsonly" type="number" min="0.1" step="0.1" size="4" name="margin" value="1.5" required> cm.</label>
|
||||
</p>
|
||||
</fieldset>
|
||||
<fieldset class="hideChorded">
|
||||
<legend>Tamaño de letra</legend>
|
||||
<p class="hideChorded">
|
||||
Tamaño general: <label><input class="lyricsonly" type="number" min="8" size="4" name="fontsize" value="14" required> pt.</label>
|
||||
</p>
|
||||
<p class="hideChorded">
|
||||
Tamaño de títulos de canción: <label><input class="lyricsonly" type="number" min="8" size="4" name="titlefontsize" value="20" required> pt.</label>
|
||||
</p>
|
||||
<p class="hideChorded">
|
||||
Tamaño de subtítulos de canción: <label><input class="lyricsonly" type="number" min="8" size="4" name="subtitlefontsize" value="10" required> pt.</label>
|
||||
</p>
|
||||
</fieldset>
|
||||
<p>
|
||||
<label>¿Cuando debe cortarse una canción? <select name="breaksallowed">
|
||||
<option value="0">Sin restricción</option>
|
||||
<option value="1">Entre columnas o de una página par a impar</option>
|
||||
<option value="2">Solo entre columnas</option>
|
||||
<option value="3">Nunca</option>
|
||||
</select></label>
|
||||
</p><p>
|
||||
<label>Título del cancionero: <input type="text" name="title" size="40" value="Cancionero" required></label>
|
||||
</p>
|
||||
<h2>Lista de canciones</h2>
|
||||
<p>
|
||||
<input type="button" value="Borrar todas" onclick="clearSongs()">
|
||||
<input type="button" value="Añadir canción" onclick="addSong()">
|
||||
<input type="button" value="Añadir canción aleatoria" onclick="addRandomSong()">
|
||||
<input type="button" value="Añadir la primera canción de cada sección" onclick="addFirstSongs()">
|
||||
<input type="button" value="Añadir una canción por sección (aleatorias)" onclick="addRandomSongs()">
|
||||
</p>
|
||||
<ol id='songlist'></ol>
|
||||
<template id='songtemplate'>
|
||||
<li><select name="songs[]" onchange="setSong(event)" required>
|
||||
<option value="">Selecciona una canción</option>{% for category, songs in categorized_songs %}
|
||||
<optgroup label="— {{ category }}">{% for s in songs %}
|
||||
<option value="{{ s.latex_file }}">{{ s.number }}. {{ s.name }}</option>{% endfor %}
|
||||
</optgroup>{% endfor %}
|
||||
</select>
|
||||
<input type="button" value="Subir" onclick="songUp(event)">
|
||||
<input type="button" value="Bajar" onclick="songDown(event)">
|
||||
<input type="button" value="Quitar" onclick="songDelete(event)">
|
||||
<a target="_blank"></a>
|
||||
</li>
|
||||
</template>
|
||||
<h2>Generar el cancionero</h2>
|
||||
<p>
|
||||
<input type="submit" value="Generar cancionero en PDF">
|
||||
<input type="reset" value="Restablecer formulario">
|
||||
</p>
|
||||
</form>
|
||||
<script>
|
||||
function addSong() {
|
||||
let ol = document.getElementById("songlist");
|
||||
let node = document.getElementById("songtemplate").content.firstElementChild.cloneNode(true);
|
||||
ol.appendChild(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
function songUp(event) {
|
||||
const li = event.target.parentElement;
|
||||
const prev = li.previousElementSibling;
|
||||
const ol = li.parentElement;
|
||||
if (prev)
|
||||
ol.insertBefore(li, prev);
|
||||
}
|
||||
|
||||
function songDown(event) {
|
||||
const li = event.target.parentElement;
|
||||
const next = li.nextElementSibling;
|
||||
const after = next?.nextElementSibling;
|
||||
const ol = li.parentElement;
|
||||
if (after)
|
||||
ol.insertBefore(li, after);
|
||||
else if (next)
|
||||
ol.appendChild(li);
|
||||
}
|
||||
|
||||
function songDelete(event) {
|
||||
const li = event.target.parentElement;
|
||||
const ol = li.parentElement;
|
||||
ol.removeChild(li);
|
||||
}
|
||||
|
||||
function clearSongs() {
|
||||
const ol = document.getElementById("songlist");
|
||||
ol.replaceChildren();
|
||||
}
|
||||
|
||||
function selectRandomizeOptions(select, optionParent) {
|
||||
const options = optionParent.getElementsByTagName("option");
|
||||
do {
|
||||
const i = Math.floor(Math.random() * options.length);
|
||||
select.value = options[i].value;
|
||||
} while (!select.value);
|
||||
select.dispatchEvent(new Event("change"));
|
||||
}
|
||||
|
||||
function addRandomSong() {
|
||||
const li = addSong();
|
||||
const select = li.firstElementChild;
|
||||
selectRandomizeOptions(select, select);
|
||||
}
|
||||
|
||||
function addFirstSongs() {
|
||||
addSongs(false);
|
||||
}
|
||||
|
||||
function addRandomSongs() {
|
||||
addSongs(true);
|
||||
}
|
||||
|
||||
function addSongs(random) {
|
||||
const li = addSong();
|
||||
const groups = li.firstElementChild.getElementsByTagName("optgroup");
|
||||
for (let g of groups) {
|
||||
const select = addSong().firstElementChild;
|
||||
if (random) {
|
||||
selectRandomizeOptions(select, g);
|
||||
} else {
|
||||
select.value = g.firstElementChild.value;
|
||||
select.dispatchEvent(new Event("change"));
|
||||
}
|
||||
}
|
||||
li.parentElement.removeChild(li);
|
||||
}
|
||||
|
||||
function setChorded(event) {
|
||||
const radio = event.target;
|
||||
const chorded = radio.checked && radio.value === "true";
|
||||
const elems = document.getElementsByClassName("lyricsonly");
|
||||
for (const e of elems)
|
||||
e.disabled = chorded;
|
||||
const form = document.getElementById("form");
|
||||
if (chorded) {
|
||||
form.classList.remove("lyrics");
|
||||
form.classList.add("chorded");
|
||||
} else if (radio.checked) {
|
||||
form.classList.remove("chorded");
|
||||
form.classList.add("lyrics");
|
||||
}
|
||||
}
|
||||
|
||||
function setPaperSize(event) {
|
||||
const select = event.target;
|
||||
for (let x of select.value.split(',')) {
|
||||
const pair = x.split('=');
|
||||
document.getElementById(pair[0]).value = pair[1];
|
||||
}
|
||||
}
|
||||
|
||||
function setSong(event) {
|
||||
const select = event.target;
|
||||
const a = select.parentElement.lastElementChild;
|
||||
let validSong = false;
|
||||
if (select.value) {
|
||||
const title = select.selectedOptions[0].textContent;
|
||||
let num = title.substring(0, title.indexOf('.'));
|
||||
while (num.length < 3)
|
||||
num = '0' + num;
|
||||
if (num != '000') {
|
||||
validSong = true;
|
||||
a.textContent = "Ver la letra";
|
||||
a.classList = ["external"]
|
||||
a.href = `https://canciones.sanleandrovalencia.es/${num}`;
|
||||
}
|
||||
}
|
||||
if (!validSong) {
|
||||
a.textContent = "";
|
||||
a.href = ""
|
||||
a.classList = [];
|
||||
}
|
||||
}
|
||||
|
||||
function toggleCols(event) {
|
||||
const input = event.target;
|
||||
const cols = document.getElementById("columns");
|
||||
if (input.checked)
|
||||
cols.value = 2;
|
||||
else
|
||||
cols.value = 1;
|
||||
cols.disabled = !input.checked;
|
||||
}
|
||||
|
||||
function toggleColSep(event) {
|
||||
const input = event.target;
|
||||
const colsep = document.getElementById("columnsep");
|
||||
if (input.checked)
|
||||
colsep.value = 0.5;
|
||||
else
|
||||
colsep.value = "";
|
||||
colsep.disabled = !input.checked;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
55
src/create_generator.py
Normal file
55
src/create_generator.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
import latex_scanner
|
||||
from os.path import join
|
||||
from django.template import Engine, Context
|
||||
from django.conf import settings
|
||||
import pickle
|
||||
|
||||
|
||||
def create_generator(songs, out_dir, dj_engine):
|
||||
'''Generate an HTML file with the list of all known songs for the generator.'''
|
||||
# Remove songs which share the same .tex file
|
||||
files = set()
|
||||
remove = set()
|
||||
for song in songs:
|
||||
if song.latex_file not in files:
|
||||
files.add(song.latex_file)
|
||||
else:
|
||||
remove.add(song.latex_file)
|
||||
selected_songs = [s for s in songs if s.latex_file not in remove]
|
||||
category_set = set(s.category for s in selected_songs)
|
||||
categories = sorted(category_set, key=lambda c:min([s.number for s in songs if s.category == c]))
|
||||
dictionary = [(c, [s for s in selected_songs if s.category == c]) for c in categories]
|
||||
|
||||
|
||||
# Export to html
|
||||
context = Context({'categorized_songs': dictionary})
|
||||
with open(join(out_dir, "index.html"), 'w') as f:
|
||||
f.write(dj_engine.get_template("generador.html").render(context))
|
||||
|
||||
|
||||
def create_argparser():
|
||||
'''Parse main's arguments.'''
|
||||
parser = latex_scanner.create_argparser()
|
||||
parser.add_argument("--generator-dir", required=False, nargs=1, default=["generador_public"],
|
||||
help="The path where the generated files should be placed.")
|
||||
return parser
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = create_argparser().parse_args()
|
||||
try:
|
||||
with open("songs.dat", "rb") as f:
|
||||
songs = pickle.load(f)
|
||||
except OSError:
|
||||
loader = latex_scanner.SongLoader(args.latex[0])
|
||||
if args.other_latex:
|
||||
loader.scan_others(args.other_latex[0], args.other_index[0])
|
||||
songs = loader.songs
|
||||
try:
|
||||
with open("songs.dat", "wb") as f:
|
||||
pickle.dump(songs, f)
|
||||
except OSError:
|
||||
pass
|
||||
settings.configure(USE_TZ=False, USE_I18N=False)
|
||||
e = Engine(dirs=["res_gen/templates/"])
|
||||
create_generator(songs, args.generator_dir[0], e)
|
Loading…
Add table
Add a link
Reference in a new issue