Sebastian Walz 860d31cee1
Tohu vaBohu
2023-04-21 00:22:52 +02:00

458 lines
20 KiB
Nix

# TODO: Remove LaTeX-Code, replace with renderer-methods
{ chunks, core, evaluator, renderer, ... }:
let
inherit(core) debug error indentation list number path set string type;
inherit(evaluator) evaluate;
inherit(renderer) putCaption toCaption toDescription render;
evaluateFigure
= { ... } @ document:
{ ... } @ state:
{ dependencies, ... } @ figure:
let
state' = state;
in
state'
// {
dependencies = state'.dependencies ++ dependencies;
figures
= state'.figures
// {
counter = state'.figures.counter + 1;
};
};
getOptions
= config:
( if config ? width then [ "width=${string config.width}" ] else [ ] )
++ ( if config ? height then [ "height=${string config.height}" ] else [ ] )
++ ( getOptions' config );
getOptions'
= config:
( if config ? totalheight then [ "totalheight=${string config.totalheight}" ] else [ ] )
++ ( if config.keepaspectratio or false then [ "keepaspectratio" ] else [ ] )
++ ( if config ? scale then [ "scale=${string config.scale}" ] else [ ] )
++ ( if config ? angle then [ "angle=${string config.angle}" ] else [ ] )
++ ( if config ? origin then [ "origin=${string config.origin}" ] else [ ] )
++ ( if config ? viewport then [ "viewport=${string config.viewport}" ] else [ ] )
++ ( if config ? trim then [ "trim=${string config.trim}" ] else [ ] )
++ ( if config.clip or false then [ "clip" ] else [ ] )
++ ( if config ? page then [ "page=${string config.page}" ] else [ ] )
++ ( if config ? pagebox then [ "pagebox=${string config.pagebox}" ] else [ ] )
++ ( if config.interpolate or false then [ "interpolate" ] else [ ] )
++ ( if config.quiet or false then [ "quiet" ] else [ ] )
++ ( if config.draft or false then [ "draft" ] else [ ] )
++ ( if config ? type then [ "type=${string config.type}" ] else [ ] )
++ ( if config ? ext then [ "ext=${string config.ext}" ] else [ ] )
++ ( if config ? read then [ "read=${string config.read}" ] else [ ] )
++ ( if config ? command then [ "command=${string config.command}" ] else [ ] )
++ ( config.extraOptions or [] );
orDefault
= first:
second:
if first != null
then
first
else
second;
mapSubfigure
= {
align ? "b",
body ? null,
caption ? null,
cite ? null,
description ? null,
file ? null,
height ? null,
label ? null,
plot ? null,
uncover ? null,
only ? null,
width ? null,
...
} @ config:
{
caption = null;
description = toDescription ( orDefault description caption);
inherit align cite label height width uncover only;
}
// (
if file != null
then
{
body = null;
file
= type.matchPrimitiveOrPanic file
{
path = file;
string = file;
};
options = getOptions' config;
plot = null;
}
else if plot != null
then
{
body = null;
file = null;
options = null;
inherit plot;
}
else if body != null
then
{
file = null;
options = null;
plot = null;
inherit body;
}
else
error.unimplemented
);
mapSubfigures
= list.map
(
config:
if config != null
then
mapSubfigure config
else
config
);
# { ... } -> Document::Chunk::Figure -> [ string | Indentation ]
renderFigure
= _:
{ label ? null, subfigures ? null, environment, ... } @ figure:
output:
let
render
= { body, file, options, uncover, only, ... }:
let
uncover'
= text:
if uncover != null
then
[
"\\uncover<${uncover}>{%" indentation.more
"\\alt<${uncover}>{%" indentation.more
"${text}%"
indentation.less "}{%" indentation.more
"\\begin{tikzpicture}%" indentation.more
"\\node[anchor=south west,inner sep=0] (B) at (4,0) {${text}};%"
"\\fill [draw=none, fill=white, fill opacity=0.9] (B.north west) -- (B.north east) -- (B.south east) -- (B.south west) -- (B.north west) -- cycle;%"
indentation.less "\\end{tikzpicture}%"
indentation.less "}%"
indentation.less "}%"
]
else if only != null
then
[
"\\only<${only}>{%" indentation.more
"${text}%"
indentation.less "}%"
]
else
[ text ];
in
if file != null
then
if options != [ ]
then
uncover' "\\includegraphics[${string.concatWith "," options}]{\\source/${file}}"
else
uncover' "\\includegraphics[width=\\linewidth]{\\source/${file}}"
else
body;
in
if output == "LaTeX"
then
[
"\\begin{${environment}}[H]%" indentation.more
"\\centering%"
]
++ (
if subfigures != null
then
let
convert
= { align, label ? null, width, ... } @ figure:
[
"\\begin{subfigure}[${align}]{${width}}" indentation.more
"\\centering"
]
++ ( render figure )
++ ( putCaption figure )
++ ( if label != null then [ "\\label{${label}}" ] else [] )
++ [ indentation.less "\\end{subfigure}" ];
in
list.concatMap
(
figure:
(
if figure != null
then
convert figure
else
[ "\\hfill" ]
)
)
subfigures
else
render figure
)
++ ( putCaption figure )
++ (
if label != null
then
[ "\\labelFigure{${label}}%" ]
else
[ ]
)
++ [ indentation.less "\\end{${environment}}%" ]
else if output == "Markdown"
then
[]
else
debug.panic "render" "Unknown output ${output}";
choose
= list.fold
(
result:
item:
if result == null
then
item
else
result
)
null;
in
{
# set | string -> list -> list -> Document::Chunk::Figure
Figure
= {
body ? null,
caption ? null,
cite ? null,
dependencies ? [],
description ? null,
file ? null,
height ? null,
label ? null,
plot ? null,
uncover ? null,
subfigures ? null,
width ? null,
only ? null,
environment ? "figure",
} @ config:
chunks.Chunk "Figure"
{
render = renderFigure;
evaluate = evaluateFigure;
}
(
{
caption = toCaption ( choose [ caption description "" ] );
description = toDescription ( choose [ description caption "" ] );
inherit cite label height width uncover only environment;
}
// (
if file != null
then
let
file'
= type.matchPrimitiveOrPanic file
{
path = "resources/figures/${path.getBaseName file}";
string = file;
};
in
{
body = null;
file = file';
options = getOptions config;
subfigures = null;
dependencies
= dependencies
++ (
type.matchPrimitiveOrPanic file
{
path = [ { src = "${file}"; dst = "resources/figures/${path.getBaseName file}"; } ];
string = [];
}
);
}
else if plot != null
then
let
toPlot
= data:
type.matchPrimitiveOrPanic data
{
bool = error.throw "Bool in renderFigure?";
path = "\"${data}\" notitle with lines lt rgb \"0x00777777\"";
string = "${data} notitle with lines lt rgb \"0x00777777\"";
set
= (
let
plot
= if data.file or null != null
then
"\"${data.file}\""
else if data.eq or null != null
then
data.eq
else
error.unimplemented;
title
= if data.title or null != null
then
"title \"${data.title}\""
else
"notitle";
lines
= if data.lines or true
then
"with lines lt"
else
"";
colour
= if data.colour or null != null
then
data.colour
else
"0x00777777";
in
"${plot} ${title} ${lines} rgb \"${colour}\""
);
};
plots
= if list.isInstanceOf plot.data
then
string.concatWith ", " ( list.map toPlot plot.data )
else
toPlot plot.data;
genPeaks
= list.map
(
{
x, y,
z ? 0,
colour ? "0x00aaaaaa",
text ? null,
}:
let
x'
= if set.isInstanceOf x
then
( x.from + x.till ) / 2
else
x;
x'' = string (number.round x');
y' = string y;
z' = string (number.round ( x' + z ));
z'' = string (number.round ( x' + z - 60 ));
in
if plot.peaksTop or true
then
''
set arrow from first ${x''}, graph 0.8 to first ${x''}, first ${y'} nohead lc rgb "${colour}"
set arrow from first ${x''}, graph 0.8 to first ${z'}, graph 0.87 nohead lc rgb "${colour}"
set label "\\tiny ${x''}" right rotate by 90 at first ${z'}, graph 0.88
${if text != null then ''set label "\\tiny (${text})" right rotate by 90 at first ${z''}, graph 0.88'' else ""}
''
else
''
set arrow from first ${x''}, graph 0.2 to first ${x''}, first ${y'} nohead lc rgb "${colour}"
set arrow from first ${x''}, graph 0.2 to first ${z'}, graph 0.13 nohead lc rgb "${colour}"
set label "\\tiny ${x''}" right rotate by 90 at first ${z'}, graph 0.12
${if text != null then ''set label "\\tiny (${text})" right rotate by 90 at first ${z''}, graph 0.12'' else ""}
''
);
formatRange = { min, max }: "[${string min}:${string max}]";
body
= string.concatLines
(
[]
++ ( if plot.title or null != null then [ "set title \"{\\\\footnotesize{${plot.title}}}\"" ] else [ "unset title" ] )
++ ( if plot.xLabel or null != null then [ "set xlabel \"{\\\\small{${plot.xLabel}}}\"" ] else [ "unset xlabel" ] )
++ ( if plot.yLabel or null != null then [ "set ylabel \"{\\\\small{${plot.yLabel}}}\"" ] else [ "unset ylabel" ] )
++ ( if plot.xRange or null != null then [ "set xrange ${formatRange plot.xRange}" ] else [] )
++ ( if plot.yRange or null != null then [ "set yrange ${formatRange plot.yRange}" ] else [] )
++ ( if plot.xTics or true then [ "set xtics in" ] else [ "unset xtics" ] )
++ ( if plot.yTics or true then [ "set ytics in" ] else [ "unset ytics" ] )
++ ( if plot.keyPos or null != null then [ "set key ${plot.keyPos}" ] else [] )
++ ( if plot.peaks or null != null then genPeaks plot.peaks else [] )
++ [
"plot ${plots}"
"set output"
]
);
hash = string.hash "sha1" body;
dst = "generated/gnuplot/${hash}-plot";
epsFile = "${dst}.eps";
texFile = "${dst}.tex";
gnuplotSrcFile
= path.toFile "${hash}.gnuplot"
''
set terminal epslatex size ${string width},${string ( if height != null then height else width )}
set output "${epsFile}"
${body}
'';
gnuplotDstFile = "${dst}.gnuplot";
src
= output:
string.concatWith " && "
[
"(mkdir -p \"generated/gnuplot/\""
"gnuplot \"${gnuplotSrcFile}\""
"mv \"${texFile}\" \"${output}.tex\""
"epstopdf \"${epsFile}\" \"${output}.pdf\")"
];
in
{
body = [ "{\\input{\\source/${texFile}}}%" ];
file = null;
options = null;
subfigures = null;
dependencies
= dependencies
++ [
{ src = gnuplotSrcFile; dst = gnuplotDstFile; }
{ inherit dst src; }
];
}
else if body != null
then
{
body = list.expect body;
file = null;
options = null;
subfigures = null;
inherit dependencies;
}
else if subfigures != null
then
{
body = null;
file = null;
options = null;
subfigures = mapSubfigures ( list.expect subfigures );
inherit dependencies;
}
else
error.unimplemented
)
);
}