{"id":17590944,"url":"https://github.com/vindaar/latexdsl","last_synced_at":"2025-04-29T10:38:45.164Z","repository":{"id":45918548,"uuid":"300947404","full_name":"Vindaar/LatexDSL","owner":"Vindaar","description":"A mini DSL to generate LaTeX from Nim","archived":false,"fork":false,"pushed_at":"2024-02-09T10:55:21.000Z","size":273,"stargazers_count":26,"open_issues_count":3,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-22T22:57:03.054Z","etag":null,"topics":["dsl","hacktoberfest","latex","nim-lang","tex","tikz"],"latest_commit_sha":null,"homepage":"https://vindaar.github.io/LatexDSL/latexdsl.html","language":"Nim","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Vindaar.png","metadata":{"files":{"readme":"README.org","changelog":"changelog.org","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-10-03T18:18:35.000Z","updated_at":"2023-12-17T07:05:31.000Z","dependencies_parsed_at":"2024-10-23T01:04:16.561Z","dependency_job_id":null,"html_url":"https://github.com/Vindaar/LatexDSL","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Vindaar%2FLatexDSL","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Vindaar%2FLatexDSL/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Vindaar%2FLatexDSL/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Vindaar%2FLatexDSL/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Vindaar","download_url":"https://codeload.github.com/Vindaar/LatexDSL/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251484874,"owners_count":21596834,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dsl","hacktoberfest","latex","nim-lang","tex","tikz"],"created_at":"2024-10-22T04:43:45.149Z","updated_at":"2025-04-29T10:38:45.132Z","avatar_url":"https://github.com/Vindaar.png","language":"Nim","funding_links":[],"categories":[],"sub_categories":[],"readme":"* LaTeX DSL\n\n=latexdsl= provides a convenient macro (and very soon a bunch of\nhelper templates / macros) to generate LaTeX code from Nim.\n\nThe very short version is, it allows you to write:\n#+begin_src nim\nlet lang = \"english\"\nlet res = latex:\n  \\documentclass{article}\n  \\usepackage[`lang`]{babel}\n  \\usepackage[utf8]{inputenc}\n#+end_src\nto get:\n#+begin_src latex\n\\documentclass{article}\n\\usepackage[english]{babel}\n\\usepackage[utf8]{inputenc}\n#+end_src\n\nNow this might not seem all that useful by itself \"I can just write a\nstring literal with string interpolation\".\n\nYes, _except_: Every TeX command either entered using explicit\nbackslash or opening a statement list will be checked at compile time\nagainst an =enum= of allowed TeX commands! This means we get compile\ntime checks without having to manually write a very large number of\nhelper templates like the following:\n#+begin_src nim\ntemplate bold(arg: untyped): untyped = \"\\\\textbf{\" \u0026 $arg \u0026 \"}\"\n#+end_src\nFor limited TeX application in a project that's certainly enough but\ndefinitely not scalable.\n\nThe same is done for every command that has a statement list as\nits argument. That's also where another convenience feature comes in:\nany Nim block will be understood as a\n#+begin_src latex\n\\begin{command} \nyour TeX\n\\end{command}\n#+end_src\ncommand. So the following:\n#+begin_src nim :results raw\nimport latexdsl\nlet res = latex:\n  center:\n    figure:\n      \\includegraphics[width=r\"0.8\\textwidth\"]{myImage}\necho res\n#+end_src\n(Note the usage of raw string literals to work around stuff that isn't\nallowed as Nim syntax)\n\ndoes generate what you would expect:\n#+begin_src latex\n\\begin{center}\n\\begin{figure}\n\\includegraphics[width=0.8\\textwidth]{myImage}\n\\end{figure}\n\\end{center}\n#+end_src\n\n\nAny optional parameter within =[]= will simply be handed as is into\nthe result. The same is true for arguments within ={}= or any other\ncommand.\n\nThe compile time checks are done on:\n- anything starting with a =\\=: =\\myCommandToBeChecked=\n- anything that opens its own block:\n  #+begin_src nim\n  let res = latex:\n    blockCommand[anOption]{anArgument}{anotherArgument}:\n      \"Some random stuff here\"\n      \\checkMe\n  #+end_src\n  Here =blockCommand= and =\\checkMe= will be checked for validity\n  while the other identifiers won't be.\n\nIn case a command is not part of the =enum= yet, you can omit the CT\ncheck by prepending with two =\\\\= instead of one.\n\n** LaTeX compiler and configuration files\n\nLatexDSL comes with an interface to LaTeX compilers\n(~latexdsl/latex_compiler.nim~) to quickly compile snippets of TeX for\nyou. By default it first tries to use ~lualatex~, then falls back to\n~xelatex~ and finally ~pdflatex~ if either is not found. ~lualatex~\nhas the most sane font handling and can handle large TikZ files\nwithout problem, hence it is the default (despite being a little bit\nslower than ~xelatex~).\n\nLatexDSL uses multiple configuration files to adjust the TeX preamble\nand font settings when the LaTeX compiler is used (for example when using the\nTikZ backend of ~ggplotnim~).\n\n1. a configuration file for the common TeX preamble which should be\n   inserted into each file, ~getConfigDir() / \"latexdsl\" /\n   \"common_preamble.tex\"~. My current configuration for example looks\n   like this:\n   #+begin_src latex\n\\usepackage[utf8]{inputenc}\n\\usepackage{unicode-math} % for unicode support in math environments\n\\usepackage{amsmath}\n\\usepackage{siunitx}\n\\usepackage{booktabs}\n\\sisetup{mode=text,range-phrase = {\\text{~to~}}, range-units=single, print-unity-mantissa=false}\n\\usepackage{mhchem}\n\\usepackage{tikz}\n   #+end_src\n2. Font settings for XeLaTeX can be adjusted by ~getConfigDir() / \"latexdsl\" /\n   \"xelatex_fonts.tex\"~. My current configuration for example looks\n   like this:\n   #+begin_src latex\n\\usepackage{fontspec}\n\\usepackage{ucharclasses}\n\n% Set main font as Latin Modern Roman (vectorized Computer Modern)\n\\setmainfont{CMU Serif}[Ligatures=TeX]\n\n% Fallback font for non-ASCII characters\n\\newfontfamily{\\fallbackfont}{DejaVu Serif}[Ligatures=TeX]\n% And back to default\n\\newfontfamily{\\mainfont}{CMU Serif}[Ligatures=TeX]\n\\setDefaultTransitions{\\fallbackfont}{}\n   #+end_src\n   But note that handling unicode characters in this way is kind of\n   broken in my experience. Hence why I use ~lualatex~ by default.\n3. Font settings for LuaLaTeX can be adjusted by ~getConfigDir() / \"latexdsl\" /\n   \"lualatex_fonts.tex\"~. My current configuration for example looks\n   like this:\n   #+begin_src latex\n\\usepackage{fontspec}\n\n\\directlua{\n  luaotfload.add_fallback(\n  \"FallbackFonts\",\n  {\n        \"DejaVu Serif:mode=harf;\",\n        \"DejaVu Sans Mono:mode=harf;\",\n        % we could add many more fonts here optionally!\n    }\n  )\n}\n\n\\setmainfont{CMU Serif}[RawFeature={fallback=FallbackFonts}]\n\\setmonofont{Inconsolata}[RawFeature={fallback=FallbackFonts}]\n   #+end_src\n\nThese configuration snippets will be inserted into your preamble\nautomatically if you run the ~compile~ command. Defaults similar to\nthe above are used if no configuration files exist.\n\n*NOTE*: Because the font settings are compiler specific they need to\nbe spliced into the TeX body given to the ~compile~ command. It\nreplaces ~\\begin{document}~ by the font settings and\n~\\begin{document}~.\n   \n** An example of available sugar\n\nWithout making this example more complicated than necessary, let's\nconsider an artificial case of performing some data analysis, ending\nup with a plot and the desire to convert both our data and plot into\nsomething directly embeddable in a TeX document.\n\n#+begin_src nim :tangle examples/plotToTex.nim\nimport ggplotnim, latexdsl, strformat\n\n# let's assume we have a complicated proc, which performs our\n# data analysis and returns the result as a ggplotnim `DataFrame`\n\nproc complexCalculation(): DataFrame =\n  # here be code your CPU hates ;)\n  result = seqsToDf({ \"Num\" : @[17, 43, 8, 22],\n                      \"Group\" : @[\"Group 1\", \"Group 2\", \"Group 3\", \"Group 4\"] })\n\n# let's perform our complex calc\nlet df = complexCalculation()\n# and create a fancy plot for it\nlet path = \"examples/dummy_plot.png\"\nggplot(df, aes(Group, Num)) + \n  geom_bar(stat = \"identity\") + \n  xlab(\"Age group\") +\n  ylab(\"Number of participants\") +\n  ggsave(path)\n\n# now we could construct a TeX figure and table for the data manually,\n# but for these use cases two helper procs exist. `figure` and `toTexTable`.\n\n# We want to include the information about the group with the most participants\n# into the caption of the table. So create the correct caption computationally\n# without having to worry about causing code / paper to get out of sync\necho df\nlet maxGroup = df.filter(f{int -\u003e bool: `Num` == max(df[\"Num\"])})\necho maxGroup\n# create two nice labels:\nlet figLab = \"fig:sec:ana:participants\"\nlet tabLab = \"tab:sec:ana:participants\"\n# for simplicity we will use the same caption for figure and table, with different\n# references\nlet cap = \"Number of participants in the experiment by age group. Group \" \u0026\n  \u0026\"{maxGroup[\\\"Group\\\", 0]} had the most participants with {maxGroup[\\\"Num\\\", 0]}\" \u0026\n  \" subjects.\"\n# and add a reference to the table we will create \nlet figCap = latex:\n  \"The data used for the figure is found in tab. \" \\ref{`tabLab`} \".\"\nlet fig = figure(path, caption = cap \u0026 figCap, label = figLab, width = textwidth(0.8),\n                 checkFile = true)\n# NOTE: The `checkFile` argument performs a runtime check on the given path to make\n# sure the file that is supposed to be put into a TeX document actually exists!\n# and finally for the table:\nlet tabCap = latex:\n  \"The data is plotted in fig. \" \\ref{`figLab`} \".\"\nlet tab = toTexTable(df, caption = cap \u0026 tabCap, label = tabLab)\n\n# and from here we could insert the generated TeX code directly into a TeX document.\n# We'll just print it here.\necho fig\necho tab\n#+end_src\nWhich generates the following plot:\n\n[[./examples/dummy_plot.png]]\n\nand outputs the following TeX code to the terminal (this is the\nunformatted output):\n#+begin_src TeX\n\\begin{figure}[htbp]\n\\centering\n\\includegraphics[width=0.8\\textwidth]{examples/dummy_plot.png}\n\\label{fig:sec:ana:participants}\n\\caption{Number of participants in the experiment by age group. Group Group 2 had the most participants with 43 subjects.The data used for the figure is found in tab. \\ref{tab:sec:ana:participants}.\n}\n\n\\end{figure}\n\n\n\n\\begin{table}[htbp]\n\\centering\n\n\\begin{tabular}{l l}\n\\toprule\nNum \u0026 Group\\\\\n\\midrule\n17 \u0026 Group 1\\\\\n43 \u0026 Group 2\\\\\n8 \u0026 Group 3\\\\\n22 \u0026 Group 4\n\\bottomrule\n\\end{tabular}\n\n\\caption{Number of participants in the experiment by age group. Group Group 2 had the most participants with 43 subjects.The data is plotted in fig. \\ref{fig:sec:ana:participants}.\n}\n\\label{tab:sec:ana:participants}\n\n\\end{table}\n#+end_src\n\n*NOTE*: The Dataframe helper functionality is only available on Nim\nversions starting from v1.6!\n\n** Caveats\n\nOf course not every possible LaTeX code can be represented as valid\nNim code. The known caveats and workarounds are listed here:\n\n- value + unit pairs, e.g.\n  #+begin_src TeX\n  margin=2cm\n  #+end_src\n  Use string literal:\n  #+begin_src nim\n  margin=\"2cm\"\n  #+end_src\n- string literals for TeX commands, be sure to use raw literals, due\n  to =\\r, \\n, \\p= etc being interpreted as control\n  characters. E.g. here we need string literals, because =#= is a Nim comment:\n  #+begin_src TeX\n  \\protect\\numberline{\\thesection}#1\n  #+end_src\n  #+begin_src nim\n  r\"\\protect\\numberline{\\thesection}#1\"\n  #+end_src\n- multiline arguments to ={}=:\n  #+begin_src TeX\n  \\newcommand\\invisiblesection[1]{\n    \\refstepcounter{section}\n    \\addcontentsline{toc}{section}{r\"\\protect\\numberline{\\thesection}#1\"}\n    \\sectionmark{\"#1\"}\n  }\n  #+end_src\n  Use Nim Pragma syntax for multiline blocks, ={. multiLine .}=:\n  #+begin_src nim\n  \\newcommand\\invisiblesection[1]{.\n    \\refstepcounter{section}\n    \\addcontentsline{toc}{section}{r\"\\protect\\numberline{\\thesection}#1\"}\n    \\sectionmark{\"#1\"}\n  .}\n  #+end_src\n  NOTE: this still has a downside: you cannot do nested blocks inside\n  the pragma syntax!\n\n** Soon to come\n\nSoon there will be convenience features to e.g. turn a number of same\nlength Nim sequences to a LaTeX table or helper templates to create a\nfigure.\n\nAlso a nice feature would be to generate a full basic TeX file to\nwrite the created TeX code into a document and compile it. \n\nIn addition to that the compile time checking =enum= will be\nextendable at CT using =registerTexCommand=.\n\n** Just why?\n\nWell, I had to generate a bunch of PDFs from a database for the\nmodules / courses in each degree at my department at Uni. At first I\nwrote the code for TeX generation based on pure string\ninterpolation. But that hurt my soul knowing what Nim is capable\nof. \n\nSo that's why I decided to see how far one can push native TeX as\nvalid Nim code. Pretty happy with it. \n\nThe main part of the code that generates the files mentioned above\nthere can be found here:\n\nhttps://gist.github.com/Vindaar/545cf13fb09d75843ea0eef0dec1dae0\n\n(the full code is only hosted on an internal, non public Bitbucket\ninstance unfortunately).\n\nMaybe still not the prettiest Nim code one has ever seen (and that\nfile there is WIP anyway), but the TeX parts aren't gonna change a\nwhole lot. At least I'm happy with this. :)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvindaar%2Flatexdsl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvindaar%2Flatexdsl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvindaar%2Flatexdsl/lists"}