diff --git a/Makefile b/Makefile
index b34d795..0d6f4c9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,5 @@
PROJDIR := src
+TEXER := lualatex
help:
@echo
@@ -11,7 +12,7 @@ help:
@echo ' server - Start lektor server with live change updates'
@echo ' build - Build deployable website into ./bin'
@echo ' deploy - Custom rsync command to sync ./bin to remote server'
- @echo
+ @echo ' pdf - Generate PDF from tex (after build)'
@echo
@echo ' find-links - Search for cross reference between recipes'
@echo
@@ -42,15 +43,14 @@ clean-all: clean plugins
# Build
server:
- @cd '$(PROJDIR)' && lektor server
+ @cd '$(PROJDIR)' && lektor server # -f ENABLE_PDF_EXPORT
build:
@cd '$(PROJDIR)' && \
- lektor build --output-path ../bin --buildstate-path ../build-state -f ENABLE_APPCACHE
+ lektor build --output-path ../bin --buildstate-path ../build-state -f ENABLE_APPCACHE -f ENABLE_PDF_EXPORT
@echo
@echo 'Checking dead links ...'
@python3 extras/find-dead-links.py
- @echo
deploy:
@echo
@@ -59,6 +59,24 @@ deploy:
@echo # --dry-run
rsync -rclzv --exclude=.lektor --exclude=.DS_Store --delete bin/ vps:/srv/http/recipe-lekture
+pdf:
+ @echo
+ @echo 'Generating PDF from tex source ...'
+ @echo 'Check if $(TEXER) exists'
+ @which $(TEXER)
+ @cd extras/pdf-export/ && \
+ SECONDS=0; \
+ for i in 1 2; do \
+ for alt in de en; do \
+ fname="pdf-$${alt}.tex"; \
+ echo "$$ $(TEXER) $${fname} [$${i}]"; \
+ $(TEXER) $${fname} > /dev/null; \
+ done; \
+ done; \
+ echo "done. finished after $${SECONDS}s."
+ rm -rf extras/pdf-export/*.{aux,log,out,toc}
+ mv extras/pdf-export/pdf-*.pdf bin/static
+
# Helper methods on all recipes
find-links:
diff --git a/extras/pdf-export/.gitignore b/extras/pdf-export/.gitignore
index 3e60473..b5b215c 100644
--- a/extras/pdf-export/.gitignore
+++ b/extras/pdf-export/.gitignore
@@ -1,3 +1,7 @@
+/*.aux
+/*.log
+/*.out
+/*.toc
+/dyn-*.tex
/pdf-de.pdf
-/pdf-en.pdf
-/setup-builddir.tex
\ No newline at end of file
+/pdf-en.pdf
\ No newline at end of file
diff --git a/extras/pdf-export/pdf-de.tex b/extras/pdf-export/pdf-de.tex
index 36173ed..bb59a30 100644
--- a/extras/pdf-export/pdf-de.tex
+++ b/extras/pdf-export/pdf-de.tex
@@ -1,7 +1,7 @@
\documentclass[a4paper,12pt,twoside]{article}
\usepackage[ngerman]{babel}
\include{setup}
-\include{setup-builddir} % load \def\builddir{}
+\include{dyn-builddir} % load \def\builddir{}
\def\tIngredients{Zutaten}
\def\tRecipe{Rezept}
@@ -9,6 +9,5 @@
\begin{document}
\makefrontmatter
-\graphicspath{{\builddir/de/}}
-\include{\builddir/de/pdf-export}
+\include{dyn-recipes-de}
\end{document}
\ No newline at end of file
diff --git a/extras/pdf-export/pdf-en.tex b/extras/pdf-export/pdf-en.tex
index 251cae7..17572ad 100644
--- a/extras/pdf-export/pdf-en.tex
+++ b/extras/pdf-export/pdf-en.tex
@@ -1,7 +1,7 @@
\documentclass[letter,12pt,twoside]{article}
\usepackage[english]{babel}
\include{setup}
-\include{setup-builddir} % load \def\builddir{}
+\include{dyn-builddir} % load \def\builddir{}
\def\tIngredients{Ingredients}
\def\tRecipe{recipe}
@@ -9,6 +9,5 @@
\begin{document}
\makefrontmatter
-\graphicspath{{\builddir/en/}}
-\include{\builddir/en/pdf-export}
+\include{dyn-recipes-en}
\end{document}
\ No newline at end of file
diff --git a/extras/pdf-export/setup.tex b/extras/pdf-export/setup.tex
index 6ce927f..5845bef 100644
--- a/extras/pdf-export/setup.tex
+++ b/extras/pdf-export/setup.tex
@@ -9,45 +9,20 @@
\usepackage{tocloft} % \cftbeforesecskip
\usepackage{ragged2e} % \RaggedRight
\usepackage{fontspec} % \setmainfont, \setsansfont, \setmonofont
+\usepackage{microtype}
+\usepackage{xurl}
+\PassOptionsToPackage{hyphens}{url}
+\usepackage[hidelinks]{hyperref}
+
+
+%-------------------------------------------
+% Defines & Geometry
+%-------------------------------------------
+\graphicspath{{\builddir/en/}}
\definecolor{red2}{HTML}{AA203A}
\definecolor{red3}{HTML}{EE6A84}
-\usepackage{xurl}
-\PassOptionsToPackage{hyphens}{url}
-\usepackage{hyperref}
-\renewcommand{\UrlFont}{\normalfont}
-%% Use this if the font is _not_ installed on the system
-%% but it requires the download of the additional fonts package
-%% \setmonofont[Path=fonts/]{FiraSans-Light}
-\setmainfont[
- Path = fonts/,
- BoldFont = Crimson-Bold,
- ItalicFont = Crimson-Italic,
- BoldItalicFont = Crimson-BoldItalic
-]{Crimson-Roman}
-\setsansfont[
- Path = fonts/,
- Scale = 0.95,
- BoldFont = FiraSans-Medium,
- ItalicFont = FiraSans-LightItalic,
- BoldItalicFont = FiraSans-MediumItalic
-]{FiraSans-Regular}
-
-%% Use this if the font is installed on the system
-%% this does not require additional fonts package
-% \setmainfont[
-% BoldFont = {Fira Sans Medium},
-% ItalicFont = {Fira Sans Light Italic},
-% BoldItalicFont = {Fira Sans Medium Italic}
-% ]{Fira Sans Regular}
-% \setmainfont[
-% BoldFont = {Crimson Bold},
-% ItalicFont = {Crimson Italic},
-% BoldItalicFont = {Crimson Bold Italic}
-% ]{Crimson Roman}
-
-% Defines & Geometry
\def\marginwidth{60mm}
\def\marginsep{8.2mm}
\def\marginwoverflow{67mm} % -1.2mm for makebox
@@ -66,56 +41,117 @@
includemp,
% showframe
}
-% \renewcommand{\footnotesep}{2.5ex}
-\renewcommand{\footnoterule}{
- \kern -2pt
- \hrule width \textwidth height .2pt
- \kern 1.8pt}
-% spacing before & after
+
+
+%-------------------------------------------
+% Penalties
+%-------------------------------------------
+\doublehyphendemerits=10000 % No consecutive line hyphens
+\brokenpenalty=10000 % No broken words across columns/pages
+\widowpenalty=9999 % Almost no widows at bottom of page
+\clubpenalty=9999 % Almost no orphans at top of page
+\interfootnotelinepenalty=9999 % Almost never break footnotes
+
+
+%-------------------------------------------
+% Fonts
+%-------------------------------------------
+\renewcommand{\UrlFont}{\normalfont}
+%% Use this if the font is _not_ installed on the system
+%% but it requires the download of the additional fonts package
+%% \setmonofont[Path=fonts/]{FiraSans-Light}
+\setmainfont[
+ Path = fonts/,
+ BoldFont = {Crimson-Bold},
+ ItalicFont = {Crimson-Italic},
+ BoldItalicFont = {Crimson-BoldItalic}
+]{Crimson-Roman}
+\setsansfont[
+ Path = fonts/,
+ Scale = 0.95,
+ BoldFont = {FiraSans-Medium},
+ ItalicFont = {FiraSans-LightItalic},
+ BoldItalicFont = {FiraSans-MediumItalic}
+]{FiraSans-Regular}
+
+%% Use this if the font is installed on the system
+%% this does not require additional fonts package
+% \setmainfont[
+% BoldFont = {Fira Sans Medium},
+% ItalicFont = {Fira Sans Light Italic},
+% BoldItalicFont = {Fira Sans Medium Italic}
+% ]{Fira Sans Regular}
+% \setmainfont[
+% BoldFont = {Crimson Bold},
+% ItalicFont = {Crimson Italic},
+% BoldItalicFont = {Crimson Bold Italic}
+% ]{Crimson Roman}
+
+
+%-------------------------------------------
+% Spacing in sections
+%-------------------------------------------
\makeatletter
-% footer
-\renewcommand\@makefntext[1]{\noindent\makebox[0.5em][l]{\@makefnmark}#1}
-% subsubsection
\renewcommand{\subsubsection}{%
\@startsection{subsubsection}{3}%
{\z@}{-2ex \@plus -1ex \@minus -1ex}{.2ex \@plus 1ex \@minus .2ex}%
{\normalfont\large\bfseries}%
}
-% paragraph
\renewcommand{\paragraph}{%
\@startsection{paragraph}{4}%
- {\z@}{1.5ex \@plus 1ex \@minus .2ex}{-.75em}%
+ {\z@}{1.5ex \@plus 1ex \@minus .2ex}{-.75em \@plus -1em \@minus -.5em}%
{\normalfont\normalsize\bfseries}%
}
+% footer
+\renewcommand\@makefntext[1]{\noindent\makebox[0.5em][l]{\@makefnmark}#1}
\makeatother
-% adjust description environment
-\setlist[description,enumerate,itemize]{%
+% \titleformat*{\section}{\LARGE\bfseries} # titlesec
+\setlength{\cftbeforesecskip}{.5ex} % spacing in TOC
+
+
+%-------------------------------------------
+% Spacing in enumerations
+%-------------------------------------------
+\setlist[description]{%
topsep=0.7\baselineskip,
itemsep=0pt,
+ % labelsep=*,
% itemindent=-2em,
% listparindent=-2em,
- % leftmargin=0em,
font={\sffamily}
}
-\setlist[enumerate,itemize]{leftmargin=1em,topsep=0\baselineskip,parsep=0pt}
-\widowpenalty10000
-\clubpenalty10000
+\setlist[enumerate,itemize]{
+ topsep=0\baselineskip,
+ leftmargin=1em,
+ itemsep=1ex,
+ parsep=0pt
+}
+
+
+%-------------------------------------------
+% Section & footnote numbering
+%-------------------------------------------
% disable section numbering
\setcounter{secnumdepth}{0}
\setcounter{tocdepth}{1}
-% \titleformat*{\section}{\LARGE\bfseries} # titlesec
-\setlength{\cftbeforesecskip}{0.2em} % spacing in TOC
-
+% \renewcommand{\footnotesep}{2.5ex}
+\renewcommand{\footnoterule}{
+ \kern -2pt
+ \hrule width \textwidth height .2pt
+ \kern 1.8pt}
% Footer page numbering
\renewpagestyle{plain}{%
\setfoot[{\makebox[-\marginwoverflow][r]{\thepage}}][][]{}{}{{\makebox[-\marginwoverflow][l]{\thepage}}}
}
\pagestyle{plain}
-% \graphicspath{{../src/content/}}
+%-------------------------------------------
+% Title page
+%-------------------------------------------
\newcommand{\makefrontmatter}{
\begin{titlepage}
+ \setcounter{page}{0} % sets the number 0 on the first page in preview
\newgeometry{top=2.5cm,bottom=2.5cm,left=4cm,right=4cm}
\centering
\vspace*{.06\textheight}
@@ -134,7 +170,10 @@
\setcounter{page}{1}
}
+
+%-------------------------------------------
% Custom commands
+%-------------------------------------------
\newcommand{\meta}[2]{%
\vspace{-0.75em}%
~\includegraphics[height=2ex]{misc/icon-time}\enspace\ifx\relax#1\relax—\else#1\fi%
@@ -146,8 +185,7 @@
\ifx\relax#1\relax\else{%
\enlargethispage{5.75ex}
\let\thefootnote\relax\footnotetext{%
- \hspace{-.6em} Source: %
- \ifx\relax#2\relax#1\else\href{#1}{#2}\fi%
+ \hspace{-.6em} Source: \ifx\relax#2\relax#1\else\href{#1}{#2}\fi%
}\fi%
}
}
@@ -156,7 +194,7 @@
\newcommand{\ingGroup}[1]{\vspace{.2ex}\item{\bfseries\color{red2}\hspace{-1em} #1}}
\newcommand{\ingName}[1]{{\color{red3}#1}}
\newcommand{\ingDetail}[1]{\emph{\footnotesize,#1}}
-\newcommand{\pagelink}[1]{\tRecipe \tPagePrefix\,{\color{red2}\pageref{#1}}}
+\newcommand{\pagelink}[1]{\tRecipe{} \tPagePrefix\,{\color{red2}\pageref{#1}}}
\newcommand{\recipelink}[2]{#2 (\tPagePrefix\,\pageref{#1})}
\newcommand{\external}[2]{#2\footnote{\href{#1}{#1}}}
% \newcommand{\external}[2]{#2\footnote{\url{#1}}}
diff --git a/src/content/pdf-export.tex/contents.lr b/src/content/pdf-export.tex/contents.lr
deleted file mode 100644
index 8fc6658..0000000
--- a/src/content/pdf-export.tex/contents.lr
+++ /dev/null
@@ -1,3 +0,0 @@
-_template: makepdf.tex
----
-_model: none
\ No newline at end of file
diff --git a/src/packages/helper/lektor_helper.py b/src/packages/helper/lektor_helper.py
index 8845e7f..45c3e96 100644
--- a/src/packages/helper/lektor_helper.py
+++ b/src/packages/helper/lektor_helper.py
@@ -210,7 +210,9 @@ class HelperPlugin(Plugin):
repFrac = self.settings[alt]['replFrac']
for line in recipe['ingredients']:
- line = tex.raw_text_to_tex(line).strip()
+ line = line.strip()
+ if mode == 'tex':
+ line = tex.raw_text_to_tex(line)
if not line:
continue
elif line.endswith(':'):
diff --git a/src/packages/html-to-tex/html2latex.sed b/src/packages/html-to-tex/html2latex.sed
index d9832f0..c682de0 100644
--- a/src/packages/html-to-tex/html2latex.sed
+++ b/src/packages/html-to-tex/html2latex.sed
@@ -50,13 +50,12 @@ s?\([^<]*\)?{\\it \1}?g
s?\([^<]*\)?{\\it \1}?g
s?\([^<]*\)?{\\bf \1}?g
s?\([^<]*\)?{\\bf \1}?g
-# old unused, because lektor generated internally other urls then output
-# s?[^<]*?\\recipelink{\1}?g
# recipe specific
-s?\([^<]*\)?\\recipelink{\1}{\2}?g
+s?\([^<]*\)?\\recipelink{\1}{\2}?g
# Get rid of Anchors
s?\([^<]*\)?\\external{\1}{\2}?g
s?]*>??g
s???g
-# after href replace
+# quotes (replace after href)
+s?\([[:space:]]\)"\([^[:space:]]\)?\1``\2?g
s?"?''?g
diff --git a/src/packages/html-to-tex/lektor_html_to_tex.py b/src/packages/html-to-tex/lektor_html_to_tex.py
index df6bd12..c557887 100644
--- a/src/packages/html-to-tex/lektor_html_to_tex.py
+++ b/src/packages/html-to-tex/lektor_html_to_tex.py
@@ -1,21 +1,25 @@
# -*- coding: utf-8 -*-
from lektor.pluginsystem import Plugin # , get_plugin
+import mistune
import os
-import subprocess
+import time
+import subprocess as shell
+import lektor_helper as helper
+import lektor_time_duration as timedur
my_dir = os.path.dirname(os.path.realpath(__file__))
f_sed_a = os.path.join(my_dir, 'html2latex.sed')
-def sed_repl_content(f_sed, content):
- with subprocess.Popen(('echo', content), stdout=subprocess.PIPE) as ps:
- o = subprocess.check_output(['sed', '-f', f_sed], stdin=ps.stdout)
+def sed_repl_tex(f_sed, content):
+ with shell.Popen(('echo', content), stdout=shell.PIPE) as ps:
+ o = shell.check_output(['sed', '-f', f_sed], stdin=ps.stdout)
ps.wait()
return o.decode('utf-8')
def html_to_tex(html):
- return sed_repl_content(f_sed_a, html)
+ return sed_repl_tex(f_sed_a, html)
def raw_text_to_tex(text):
@@ -24,23 +28,104 @@ def raw_text_to_tex(text):
if c in text:
text = text.replace(c, '\\' + c)
return text
- # return sed_repl_content(f_sed_b, text)
+ # return sed_repl_tex(f_sed_b, text)
+
+
+class RecipeToTex(object):
+ def __init__(self, enumerator):
+ super(RecipeToTex, self).__init__()
+ bases = tuple([]) + (mistune.Renderer,)
+ renderer_cls = type('renderer_cls', bases, {})
+ renderer = renderer_cls(escape=False)
+ self.mdparser = mistune.Markdown(renderer, escape=False)
+ self.enumerator = enumerator
+
+ def make(self, recipe, alt):
+ ingredients = ''
+ for x in self.enumerator(recipe, alt, mode='tex'):
+ ingredients += '\n' + self.process_ingredient(x, alt)
+ ingredients = ingredients[1:] or '\\item '
+ self.mdparser.renderer.record = recipe
+ instructions_html = self.mdparser(recipe['directions'].source)
+ instructions = sed_repl_tex(f_sed_a, instructions_html)
+ return self.process_recipe(recipe, alt, ingredients, instructions)
+ pass
+
+ def process_recipe(self, recipe, alt, ingredients, instructions):
+ img = helper.title_image(recipe)
+ if img:
+ img = img.path[:-4] + '@200x150_crop' + img.path[-4:]
+ duration = recipe['time']
+ host = recipe['source'].host
+ time = timedur.to_duration(duration, alt) if duration else ''
+ srcUrl = raw_text_to_tex(str(recipe['source'])) or ''
+ srcHost = raw_text_to_tex(host) if host else ''
+ return f'''
+\\newrecipe{{{recipe['_slug']}}}{{{raw_text_to_tex(recipe['name'])}}}
+\\meta{{{time}}}{{{recipe['yield'] or ''}}}
+\\footer{{{srcUrl}}}{{{srcHost}}}
+
+\\begin{{ingredients}}{{{img or ''}}}
+{ingredients}
+\\end{{ingredients}}
+
+{instructions}
+'''
+
+ def process_ingredient(self, ing, alt):
+ grp = ing.get('group')
+ if grp:
+ return f'\\ingGroup{{{grp}}}'
+
+ ret = ''
+ val = ing['value']
+ meas = ing['measure']
+ note = ing['note']
+ ret += '\\item'
+ if val or meas:
+ sep = '~' if val and meas else ''
+ ret += '[{}{}{}]'.format(val or '', sep, meas or '')
+ ret += f' \\ingName{{{ ing["name"] }}}' # keep space in front
+ if note:
+ ret += '\\ingDetail{'
+ for prt in note.split():
+ if prt.startswith('@../'):
+ ret += f' \\pagelink{{{ prt[4:].rstrip("/") }}}'
+ else:
+ ret += ' ' + prt
+ ret += '}'
+ return ret
class HtmlToTex(Plugin):
name = u'HTML to TEX converter'
description = u'Will convert html formatted text to (la)tex format.'
- def on_before_build_all(self, builder, **extra):
- # export current build dir
- dest_file = my_dir
+ def on_after_prune(self, builder, **extra):
+ maketex = bool(builder.extra_flags.get('ENABLE_PDF_EXPORT'))
+ print('PDF Export: ' + ('ENABLED' if maketex else 'DISABLED'))
+ if not maketex:
+ return
+
+ dest_dir = my_dir
for x in range(3):
- dest_file = os.path.dirname(dest_file)
- dest_file = os.path.join(dest_file, 'extras', 'pdf-export',
- 'setup-builddir.tex')
- with open(dest_file, 'w') as f:
+ dest_dir = os.path.dirname(dest_dir)
+ dest_dir = os.path.join(dest_dir, 'extras', 'pdf-export')
+
+ start_time = time.time()
+ print('PDF Export: generate tex files')
+ with open(os.path.join(dest_dir, 'dyn-builddir.tex'), 'w') as f:
+ # Export current build dir (for image search)
f.write('\\def\\builddir{' + builder.destination_path + '}')
+ parser = RecipeToTex(self.env.jinja_env.filters['enumIngredients'])
+ for alt in self.env.load_config().list_alternatives():
+ tex = ''
+ for recipe in builder.pad.get('/recipes', alt=alt).children:
+ tex += parser.make(recipe, alt)
+ fname = os.path.join(dest_dir, f'dyn-recipes-{alt}.tex')
+ with open(fname, 'w') as f:
+ f.write(tex)
+ print('PDF Export: done in %.2f sec' % (time.time() - start_time))
def on_setup_env(self, **extra):
- self.env.jinja_env.filters['html_to_tex'] = html_to_tex
self.env.jinja_env.filters['raw_text_to_tex'] = raw_text_to_tex
diff --git a/src/templates/makepdf.tex b/src/templates/makepdf.tex
deleted file mode 100644
index 3a752b7..0000000
--- a/src/templates/makepdf.tex
+++ /dev/null
@@ -1,31 +0,0 @@
-{%- set recipe_label = localize(this.alt, 'ingredients.recipeLink') %}
-
-{%- for recipe in site.get('/recipes', this.alt).children %}
-\newrecipe{ {{- recipe._slug }}}{ {{- recipe.name | raw_text_to_tex }}}
-\meta{ {{- recipe.time|duration(this.alt) if recipe.time else '' }}}{ {{- recipe.yield or '' }}}
-\footer{ {{- recipe.source | string | raw_text_to_tex or '' }}}{ {{- recipe.source.host | raw_text_to_tex if recipe.source.host else '' }}}
- {%- set img = recipe | title_image(small=True) %}
-
-\begin{ingredients}{ {%- if img %}{{ img|url }}{% else %}{% endif %}}
- {%- for ing in recipe|enumIngredients(this.alt, mode='tex') %}
- {%- if ing['group'] %}
-\ingGroup{ {{- ing['group'] | raw_text_to_tex }}}
- {%- else %}
-\item
- {%- if ing['value'] or ing['measure'] -%}
- [{%- if ing['value'] %}{{ ing['value'] }}{% endif -%}
- {%- if ing['measure'] %}~{{ ing['measure'] }}{% endif -%}]
- {%- endif %} \ingName{ {{- ing['name'] -}}}
- {%- if ing['note'] %}\ingDetail{
- {%- for prt in ing['note'].split() -%}
- {%- if prt.startswith('@../') %} \pagelink{ {{- prt[4:-1] -}} }
- {%- else %} {{ prt -}}
- {%- endif -%}
- {%- endfor -%}}
- {%- endif -%}
- {%- endif -%}
- {%- endfor %}
-\end{ingredients}
-
-{{ recipe.directions.html | html_to_tex }}
-{%- endfor %}