Compare commits

...

48 Commits

Author SHA1 Message Date
Jiří Kalvoda 27ff4b7d58 Zabalíčkování KaTeX serveru 2 months ago
Jiří Kalvoda ef18947916 Merge remote-tracking branch 'origin/master' into jk-bakalarka 3 months ago
Jiří Kalvoda 7ee84470e9 Workaround module import 3 months ago
Jiří Kalvoda 9255f77d93 UCWTeX: Optional section eject 3 months ago
Jiří Kalvoda a9f41eb83e UCWTeX: textasciitilde 3 months ago
Jiří Kalvoda 17920077da UCWTeX: Užší tabulka 7 months ago
Jiří Kalvoda 5a61ea5fac UCWTeX: Alternativní figures 7 months ago
Jiří Kalvoda 73af3473c7 UCWTeX: Fix languages 7 months ago
Jiří Kalvoda 6bfb62fcdd UCWTeX bigradical to unicode 7 months ago
Jiří Kalvoda 4e2ea4173e FLineMarkup 8 months ago
Jiří Kalvoda 8bcb087979 UCWTeX drobnosti 8 months ago
Jiří Kalvoda 2a5fd812e3 UCWTeX další chybějící UTF-8 znak. 8 months ago
Jiří Kalvoda c46a1f0302 UCWTeX: \vecoverrightarrow 8 months ago
Jiří Kalvoda 9a993676dc UCWTeX: Reforma figure 8 months ago
Jiří Kalvoda 87961754e8 UCWTeX: Vyčlenění formatitkolib 8 months ago
Jiří Kalvoda 08df4dbd78 UCWTeX: Obrázky vždyu zarovnáváme na střed, popisky nejsou slanted 8 months ago
Jiří Kalvoda 57e69cbee4 TeX: Nadpisy bez stránkových zlomů. 8 months ago
Jiří Kalvoda f86815e8ff TeX: Mezery v tabulce 8 months ago
Jiří Kalvoda bb05a3c913 Floatpage 9 months ago
Jiří Kalvoda 9dcf87915e Merge remote-tracking branch 'origin/master' into jk-bakalarka 9 months ago
Jiří Kalvoda ac2c3475e4 UCWTeX: {sub,super}script pomocí matematiky 9 months ago
Jiří Kalvoda 2fd0efe3a7 UCWTeX: Na výrobu pdf si pustíme luatex a naučíme ho hledat soubory 9 months ago
Jiří Kalvoda 4bd05b098b UCWTex: SmallCaps 9 months ago
Jiří Kalvoda 86839a9269 HTML: SmallCaps 9 months ago
Jiří Kalvoda 774af0fedf UCWTeX: Lepší formátování nadpisů 9 months ago
Jiří Kalvoda 38029e3587 fixup of fixup: context.unset_data 9 months ago
Jiří Kalvoda 6d629137fb Merge remote-tracking branch 'origin/master' into jk-bakalarka 9 months ago
Jiří Kalvoda ed40bc4db8 UCWTeX: Formátování obsahu 9 months ago
Jiří Kalvoda 292fcba574 UCWTeX: Další unicode převod 9 months ago
Jiří Kalvoda d783c7ae9c context: current_figure 9 months ago
Jiří Kalvoda 71e5c5bbdc fixup: context.unset_data 9 months ago
Jiří Kalvoda fb80c61139 UCWTeX seznam obrázků 10 months ago
Jiří Kalvoda 7230d87f6b Fix escape do TeXu 10 months ago
Jiří Kalvoda bf46105db8 Podsložka na TeX 10 months ago
Jiří Kalvoda 41f51bf6fe Default pro highlight 10 months ago
Jiří Kalvoda f1f2f63cb5 Odkazy na interní objekty (obrázky) 10 months ago
Jiří Kalvoda a2468b54e1 Merge remote-tracking branch 'origin/master' into jk-bakalarka 10 months ago
Jiří Kalvoda 945bb760e8 UCWTeX: Figure je float 10 months ago
Jiří Kalvoda 88d653af15 Číslování 10 months ago
Jiří Kalvoda 4dd1eb0314 TeX: Základní implementace citací 10 months ago
Jiří Kalvoda 48e34eaf8e BakalářkoTeX 10 months ago
Jiří Kalvoda 37c8ec4e2e Slanted font 10 months ago
Jiří Kalvoda 62263fbe0f Merge remote-tracking branch 'origin/master' into jk-bakalarka 10 months ago
Jiří Kalvoda f97ee7de52 renamed: formatitko.tex -> src/formatitko/formatitko.tex 10 months ago
Jiří Kalvoda 7783dc25f4 Drobnosti 10 months ago
Jiří Kalvoda 405a4f396c Číslování sekcí 10 months ago
Jiří Kalvoda affc2c4279 Fix chybějících obrázků v testech 10 months ago
Jiří Kalvoda a15a711d0f Přechot na LuaTeX 10 months ago
  1. 2
      .gitmodules
  2. 30
      formatitko.tex
  3. 10
      pyproject.toml
  4. 28
      src/formatitko/context.py
  5. 18
      src/formatitko/elements.py
  6. 9
      src/formatitko/formatitko.py
  7. 7
      src/formatitko/html_generator.py
  8. 8
      src/formatitko/katex.py
  9. 0
      src/formatitko/katex_server
  10. 11
      src/formatitko/nop_processor.py
  11. 14
      src/formatitko/output_generator.py
  12. 189
      src/formatitko/tex/formatitko.tex
  13. 79
      src/formatitko/tex/formatitkolib.tex
  14. 22
      src/formatitko/tex/table_of_contents.tex
  15. 18
      src/formatitko/tex/table_of_contents_pictures.tex
  16. 155
      src/formatitko/tex_generator.py
  17. 34
      src/formatitko/transform_processor.py
  18. 10
      test/test-files/test-partial.md

2
.gitmodules

@ -2,5 +2,5 @@
path = ucwmac
url = git://git.ucw.cz/ucwmac.git
[submodule "src/formatitko/katex-server"]
path = src/formatitko/katex-server
path = src/formatitko/katex_server
url = https://gitea.ks.matfyz.cz:/KSP/formatitko-katex-server

30
formatitko.tex

@ -1,30 +0,0 @@
\input luatex85.sty
\input ucwmac2.tex
\parskip=5pt plus 3pt minus 2pt
\parindent=0sp
\def\strong#1{{%
\def\emph##1{{\bi{}##1}}%
\bf{}#1%
}}
\def\emph#1{{%
\def\strong##1{{\bi{}##1}}%
\it{}#1%
}}
\def\superscript#1{\leavevmode\raise3pt\hbox{\fiverm#1}}
\def\subscript#1{\leavevmode\lower1pt\hbox{\fiverm#1}}
\newcount\fncount
\fncount=1
\def\fnmark{\superscript{\the\fncount}}
\def\fn#1{\footnote\fnmark{#1}\advance\fncount by 1}
\def\section#1{{\parskip1em\settextsize{18}\bf #1}}
\def\subsection#1{{\parskip1em\settextsize{16}\bf #1}}
\def\subsubsection#1{{\parskip1em\settextsize{14}\bf #1}}
\def\subsubsubsection#1{{\parskip1em\settextsize{12}\bf #1}}
\def\subsubsubsubsection#1{{\parskip1em\settextsize{10}\bf #1}}
\def\subsubsubsubsubsection#1{{\parskip1em\settextsize{10}\bi #1}}
\long\def\blockquote#1{\vskip\lineskip\vskip\parskip\hbox{\vrule\hskip5pt\vbox{#1}}}
\def\strikeout#1{FIXME: Strikeout not implemented}
\def\underline#1{FIXME: Underline not implemented}

10
pyproject.toml

@ -33,11 +33,19 @@ dependencies = [
[project.scripts]
formatitko = "formatitko.formatitko:main"
[tool.setuptools.package-data]
"formatitko.katex_server" = [
"*",
"node_modules/*",
"node_modules/katex/*",
"node_modules/katex/src/*",
"node_modules/katex/dist/*",
]
[tool.setuptools_scm]
[tool.setuptools.packages.find]
where = ["src"]
exclude = ["src/formatitko/katex-server/node_modules"]
[tool.pyright]
strictParameterNoneValue = false

28
src/formatitko/context.py

@ -1,4 +1,4 @@
from panflute import Doc, Element, Div, Span
from panflute import Doc, Element, Div, Span, Header, Figure
from typing import Union, Callable
from types import ModuleType
@ -19,6 +19,23 @@ CommandCallable = Callable[[Command, 'Context', 'NOPProcessor'], list[Element]]
#
# This class is basically an extension to panflute's doc, this is why metadata
# is read directly from it.
def default_section_number_generator(e: Header, context: 'Context') -> list[Union[str, int]]:
l = e.level
section_counters = context.get_data("section_counters")
section_counters[l-1] += 1
for i in range(l, len(section_counters)):
section_counters[i] = 0
return list(section_counters[:l])
def default_figure_number_generator(e: Figure, context: 'Context') -> Union[str, int]:
figure_type = e.attributes.get("type", "img")
figure_counters = context.get_data("figure_counters")
figure_counters.setdefault(figure_type, 0)
figure_counters[figure_type] += 1
return figure_counters[figure_type]
class Context:
parent: Union["Context", None]
_commands: dict[str, Union[CommandCallable, None]]
@ -32,6 +49,9 @@ class Context:
rel_dir: str # Relative path to the current dir from the root dir
deps: set[str]
section_counters: list[int]
number_generator: Callable[[Header, 'Context'], str]
def __init__(self, doc: Doc, path: str, parent: Union['Context', None]=None, trusted: bool=True):
self.parent = parent
self._commands = {}
@ -47,6 +67,12 @@ class Context:
self.add_dep(path)
if self.get_metadata("flags", immediate=True) is None:
self.set_metadata("flags", {})
if not parent:
self.set_data('section_number_generator', default_section_number_generator)
self.set_data('figure_number_generator', default_figure_number_generator)
self.set_data('section_counters', [0 for i in range(6)])
self.set_data('figure_counters', {})
self.set_data('obj_map', {})
def get_command(self, command: str) -> Union[CommandCallable, None]:
if command in self._commands:

18
src/formatitko/elements.py

@ -1,4 +1,4 @@
from panflute import Quoted, Link
from panflute import Quoted, Emph, Link, Div
from .command import Command, InlineCommand, BlockCommand, CodeCommand
@ -14,6 +14,22 @@ class FQuoted(Quoted):
del kwargs["style"]
super().__init__(*args, **kwargs)
class Slanted(Emph):
pass
class FLink(Link):
obj_map: map
def __init__(self, *args, **kwargs):
self.obj_map = kwargs["obj_map"]
del kwargs["obj_map"]
super().__init__(*args, **kwargs)
class FileLink(Link):
pass
class FLineMarkup(Div):
def __init__(self, *args, **kwargs):
self.color = kwargs["color"]
del kwargs["color"]
super().__init__(*args, **kwargs)

9
src/formatitko/formatitko.py

@ -2,9 +2,11 @@
import argparse
import sys
import os
import tempfile
import subprocess
import shutil
from pathlib import Path
# Import local files
from .util import import_md
@ -112,7 +114,12 @@ def main():
else:
filename = args.output_tex
outdir = tempfile.TemporaryDirectory(prefix="formatitko")
subprocess.run(["pdfcsplain", "-halt-on-error", "-output-directory="+outdir.name, "-jobname=formatitko", filename], check=True)
env = os.environ.copy()
d = Path("/".join(__file__.split("/")[:-1]))
env["TEXINPUTS"]=".:"+str(d/"tex")+":"+env.get("TEXINPUTS", "")
subprocess.run(["luatex", "-halt-on-error", "-output-directory="+outdir.name, "-jobname=formatitko", filename], check=True, env=env)
shutil.move(outdir.name+"/formatitko.pdf", args.output_pdf)
if args.deps is not None:

7
src/formatitko/html_generator.py

@ -111,7 +111,7 @@ class HTMLGenerator(OutputGenerator):
def generate_CodeBlock(self, e: CodeBlock):
lexer = None
if e.classes and len(e.classes) > 0 and (e.attributes["highlight"] == True or e.attributes["highlight"] == 'True'):
if e.classes and len(e.classes) > 0 and (e.attributes.get("highlight", False) in [True, 'True']):
# Syntax highlighting using pygments
for cl in e.classes:
try:
@ -123,7 +123,7 @@ class HTMLGenerator(OutputGenerator):
warnings.warn(f"Syntax highligher does not have lexer for element with these classes: {e.classes}", UserWarning)
if lexer:
formatter = HtmlFormatter(style=e.attributes["style"], noclasses=True)
formatter = HtmlFormatter(style=e.attributes.get("style", self.context.get_metadata("highlight-style")), noclasses=True)
result = highlight(e.text, lexer, formatter)
self.writeraw(result)
else:
@ -344,6 +344,9 @@ class HTMLGenerator(OutputGenerator):
def generate_DefinitionList(self, e: DefinitionList):
self.writeln("<!-- FIXME: DefinitionLists not implemented -->")
def generate_SmallCaps(self, e: SmallCaps):
self.generate_simple_tag(e, attributes=self.common_attributes(e) | {"style": "font-variant: small-caps;"})
class StandaloneHTMLGenerator(HTMLGenerator):
def generate_Doc(self, e: Doc):

8
src/formatitko/katex.py

@ -46,20 +46,20 @@ class KatexClient:
srcdir = os.path.dirname(os.path.realpath(__file__))
# Test if `node_modules` directory exists and if not, run `npm install`
if not os.path.isdir(srcdir + "/katex-server/node_modules"):
if not os.path.isdir(srcdir + "/katex_server/node_modules"):
print("Installing node dependencies for the first time...")
npm = shutil.which("npm") or shutil.which("yarnpkg")
if npm is None:
raise NPMNotFoundError("npm not found. Node.js is required to use KaTeX.")
subprocess.run([npm, "install"], cwd=srcdir+"/katex-server", check=True)
subprocess.run([npm, "install"], cwd=srcdir+"/katex_server", check=True)
self._katex_server_path = srcdir + "/katex-server/index.mjs"
self._katex_server_path = srcdir + "/katex_server/index.mjs"
self._server_process = subprocess.Popen(["node", self._katex_server_path, self._socket_file], stdout=subprocess.PIPE)
ok = self._server_process.stdout.readline()
if ok != b"OK\n":
raise KatexServerError("Failed to connect to katex-server")
raise KatexServerError("Failed to connect to katex_server")
def connect(self):
self._client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

0
src/formatitko/katex-server → src/formatitko/katex_server

11
src/formatitko/nop_processor.py

@ -6,7 +6,7 @@ from panflute import MetaValue
from typing import Union, Callable
from .whitespace import NBSP
from .elements import FQuoted, FileLink
from .elements import FQuoted, Slanted, FLink, FileLink
from .context import Group, InlineGroup, BlockGroup, Context
from .whitespace import Whitespace
from .command import BlockCommand, InlineCommand, CodeCommand, Command
@ -69,6 +69,7 @@ class NOPProcessor:
Cite: self.transform_Cite,
Code: self.transform_Code,
Emph: self.transform_Emph,
Slanted: self.transform_Slanted,
Image: self.transform_Image,
LineBreak: self.transform_LineBreak,
Link: self.transform_Link,
@ -88,6 +89,7 @@ class NOPProcessor:
Underline: self.transform_Underline,
NBSP: self.transform_NBSP,
FQuoted: self.transform_FQuoted,
FLink: self.transform_FLink,
FileLink: self.transform_FileLink,
InlineCommand: self.transform_InlineCommand,
@ -264,6 +266,10 @@ class NOPProcessor:
e.content = self.transform(e.content)
return e
def transform_Slanted(self, e: Slanted) -> Slanted:
e.content = self.transform(e.content)
return e
def transform_Link(self, e: Link) -> Link:
e.content = self.transform(e.content)
return e
@ -300,6 +306,9 @@ class NOPProcessor:
e.content = self.transform(e.content)
return e
def transform_FLink(self, e: FLink) -> FLink:
return self.transform_Link(e)
def transform_FileLink(self, e: FileLink) -> FileLink:
e.content = self.transform(e.content)
return e

14
src/formatitko/output_generator.py

@ -7,7 +7,7 @@ from panflute import stringify
from typing import Union, Callable
from .whitespace import NBSP
from .elements import FQuoted, FileLink
from .elements import FQuoted, Slanted, FLink, FileLink, FLineMarkup
from .context import Group, InlineGroup, BlockGroup, Context
@ -88,6 +88,7 @@ class OutputGenerator:
DefinitionItem: self.generate_DefinitionItem,
DefinitionList: self.generate_DefinitionList,
Div: self.generate_Div,
FLineMarkup: self.generate_FLineMarkup,
Figure: self.generate_Figure,
Header: self.generate_Header,
HorizontalRule: self.generate_HorizontalRule,
@ -108,6 +109,7 @@ class OutputGenerator:
Cite: self.generate_Cite,
Code: self.generate_Code,
Emph: self.generate_Emph,
Slanted: self.generate_Slanted,
Image: self.generate_Image,
LineBreak: self.generate_LineBreak,
Link: self.generate_Link,
@ -127,6 +129,7 @@ class OutputGenerator:
Underline: self.generate_Underline,
NBSP: self.generate_NBSP,
FQuoted: self.generate_FQuoted,
FLink: self.generate_FLink,
FileLink: self.generate_FileLink,
InlineGroup: self.generate_InlineGroup
}
@ -379,11 +382,17 @@ class OutputGenerator:
def generate_Emph(self, e: Emph):
self.generate_simple_tag(e)
def generate_Slanted(self, e: Slanted):
self.generate_Emph(e)
def generate_Image(self, e: Image):
self.generate_simple_tag(e)
def generate_Link(self, e: Link):
self.generate_simple_tag(e)
def generate_FLink(self, e: FLink):
self.generate_Link(e)
def generate_Note(self, e: Note):
self.generate_simple_tag(e)
@ -454,6 +463,9 @@ class OutputGenerator:
def generate_Div(self, e: Div):
self.generate_simple_tag(e)
def generate_FLineMarkup(self, e: FLineMarkup):
self.generate_Div(e)
def generate_Header(self, e: Header):
self.generate_simple_tag(e)

189
src/formatitko/tex/formatitko.tex

@ -0,0 +1,189 @@
\input ltluatex.tex
\input luatex85.sty
\input ucwmac2.tex
\ucwmodule{luaofs}
\ucwmodule{verb}
\ucwmodule{link}
\clickablefalse
\pdfglyphtounicode{summationtext}{2211}
\pdfglyphtounicode{summationdisplay}{2211}
\pdfglyphtounicode{summation}{2211}
\pdfglyphtounicode{parenleftbig}{0028}
\pdfglyphtounicode{parenrightbig}{0029}
\pdfglyphtounicode{parenleftBig}{0028}
\pdfglyphtounicode{parenrightBig}{0029}
\pdfglyphtounicode{parenleftbigg}{0028}
\pdfglyphtounicode{parenrightbigg}{0029}
\pdfglyphtounicode{parenleftBigg}{0028}
\pdfglyphtounicode{parenrightBigg}{0029}
\pdfglyphtounicode{radicalbig}{221A}
\pdfglyphtounicode{radicalBig}{221A}
\pdfglyphtounicode{radicalbigg}{221A}
\pdfglyphtounicode{radicalBigg}{221A}
\pdfglyphtounicode{hatwidest}{0302}
\input formatitkolib.tex
\input minim-xmp.tex
\startmetadata
pdfaid:part 2
pdfaid:conformance U
stopmetadata
\pdfcompresslevel=0
\pdfobjcompresslevel=0
% \pdfobj{/Alternate /DeviceRGB}
\input glyphtounicode.tex
\pdfgentounicode=1
%Create an OutputIntent in order to correctly specify colours
\immediate\pdfobj stream attr{/N 3} file{sRGB.icc}
\pdfcatalog{%
/OutputIntents [
<<
/Type /OutputIntent
/S /GTS_PDFA1
/DestOutputProfile \the\pdflastobj\space 0 R
/OutputConditionIdentifier (sRGB)
/Info (sRGB)
>>
]
}
\parskip=5pt plus 3pt minus 2pt
\parindent=0sp
% Fonty
\ofsdeclarefamily [Pagella] {%
\loadtextfam qplr;%
qplb;%
qpli;%
qplbi;;%
}
% doporučen je horní, dolní a pravý okraj 25 mm, levý okraj 40 mm.
% Protože doba tisknutí prací je už pryč, my máme stejně velkou stránku uprostřed papíru
\voffset 25mm
\hoffset 32.5mm
\vsize\pdfpageheight
\advance\vsize -2\voffset
\hsize\pdfpagewidth
\advance\hsize -2\hoffset
\advance\voffset -\pdfvorigin
\advance\hoffset -\pdfhorigin
\def\MSfeat#1{:mode=node;script=latn;+tlig}
\registertfm qplr - file:texgyrepagella-regular.otf\MSfeat{}
\registertfm qplb - file:texgyrepagella-bold.otf\MSfeat{}
\registertfm qpli - file:texgyrepagella-italic.otf\MSfeat{}
\registertfm qplbi - file:texgyrepagella-bolditalic.otf\MSfeat{}
%\setfonts[Pagella/10]
%\def\h{\it} % hint
%\def\bh{\bi} % bold hint
\def\mod{\mathrel{\rm mod}}
\settextsize{12}
\long\def\floatinsert#1{\par{
\setbox0=\vbox{\boxmaxdepth=2pt\relax #1}
\dimen0=\dimexpr \ht0 + \dp0 + \baselineskip + \pagetotal - \pageshrink \relax
\ifdim\dimen0 > \pagegoal
\insert\topins{
\penalty 100
\splittopskip=0pt
\splitmaxdepth=\maxdimen
\floatingpenalty=0
\box0
\nobreak\bigskip\medskip
}
\else
\goodbreak\bigskip
\box0
\goodbreak\bigskip
\unparskip
\fi
}}
% Obecny plovouci objekt: \float{objekt}{popisek}{mezera pred}{mezera po}
\long\def\figure#1#2#3#4{
\medskip#3
\hbox to \hsize{\hfil\vtop{
\parindent=0pt
\leftskip=0pt plus 0.2\hsize
\rightskip=0pt plus 0.2\hsize
\parfillskip=0pt
\spaceskip=0.3333em
\settextsize{10}
#1
}\hfil}#4
\medskip
\smallskip
{
\setbox0=\hbox{\settextsize{10}#2}
\ifdim\wd0 < 0.8\hsize
\centerline{\box0}
\else
\centerline{\vtop{
\hsize=0.8\hsize
\parindent=0pt
\leftskip=0pt plus 0.3\hsize
\rightskip=0pt plus 0.3\hsize
\parfillskip=0pt
\spaceskip=0.3333em
\settextsize{10}#2
}}
\fi
}}
\long\def\floatpage#1{
\pageinsert
\vbox to \vsize{#1}
\endinsert
}
% Dva floaty vedle sebe: \float{objekt1}{popisek1}{id1}{objekt2}{popisek2}{id2}
\def\twofloats#1#2#3#4#5#6{\floatinsert{
\medskip
\centerline{\vbox{\halign{\hss##\hss&\qquad\hss##\hss\cr
#1&#4\cr
\noalign{\medskip\smallskip}
#2&#5\cr
}}}
}}
% Obsah a odkazy
\newwrite\tocfile
\immediate\openout\tocfile=toc-new.aux
% Voláme: \addtoc\tocmacro{number}{asterisks}{title}
\long\def\addtoc#1#2#3#4{%
\edef\brum{%
\write\tocfile{\string#1{\noexpand\the\noexpand\count0}{#2}{#3}{#4}}%
}
\brum%
}

79
src/formatitko/tex/formatitkolib.tex

@ -0,0 +1,79 @@
\def\strong#1{{%
\def\emph##1{{\bi{}##1}}%
\bf{}#1%
}}
\def\emph#1{{%
\def\strong##1{{\bi{}##1}}%
\it{}#1%
}}
\def\textasciitilde{$\sim$}
\def\N{{\bb N}}
\def\R{{\bb R}}
\def\E{{\bb E}}
\def\O{{\cal O}}
\def\SYM{{\rm SYM}}
\def\frac#1#2{{{#1} \over {#2}}}
\def\superscript#1{$^{\hbox{\settextsize{0.8\textsize}#1}}$}
\def\subscript#1{$_{\hbox{\settextsize{0.8\textsize}#1}}$}
\newcount\fncount
\fncount=1
\def\fnmark{$^{\the\fncount}$}
\def\fn#1{\footnote\fnmark{#1}\advance\fncount by 1}
\def\sectioneject{\vfil\supereject}
\def\section#1#2{
\sectioneject
\vskip 16pt\vbox{\settextsize{20}\bf #1\kern 1em\relax#2%
\addtoc\tocsection{#1}{}{#2}%
}\nobreak\vskip 12pt
}
\def\subsection#1#2{
\vskip 12pt\vbox{\settextsize{18}\bf #1\kern 1em\relax#2%
\addtoc\tocsubsection{#1}{}{#2}%
}\nobreak\vskip 7pt
}
\def\subsubsection#1#2{
\vskip 10pt\vbox{\settextsize{16}\bf #1\kern 1em\relax#2%
\addtoc\tocsubsubsection{#1}{}{#2}%
}\nobreak\vskip 6pt
}
\def\subsubsubsection#1#2{
\vskip 8pt\vbox{\settextsize{14}\bf #1\kern 1em\relax#2}\nobreak\vskip 5pt
}
\def\subsubsubsubsection#1#2{
\vskip 7pt\vbox{\settextsize{12}\bf #1\kern 1em\relax#2}\nobreak\vskip 5pt
}
\def\subsubsubsubsubsection#1#2{
\vskip 7pt\vbox{\settextsize{12}\bf #1\kern 1em\relax#2}\nobreak\vskip 5pt
}
\long\def\blockquote#1{\vskip\lineskip\vskip\parskip\hbox{\vrule\hskip5pt\vbox{#1}}}
\def\strikeout#1{FIXME: Strikeout not implemented}
\def\underline#1{FIXME: Underline not implemented}
\def\mathbb#1{\hbox{\bb #1}}
\def\unparskip{\vskip-\parskip}
\catcode`@=11
\def\vecoverrightarrow#1{\mathpalette\vecoverrightarrowtmp{#1}}
\def\vecoverrightarrowtmp#1#2{\vbox{\m@th\ialign{##\crcr
\vecrightarrowfill\crcr\noalign{\kern-\p@\kern 0.09em\nointerlineskip}
$\hfil#1{#2\,}\hfil$\crcr}}}
\def\vecrightarrowfill{$\settextsize{5}\m@th\smash-\mkern-7mu%
\cleaders\hbox{$\mkern-2mu\smash-\mkern-2mu$}\hfill
\settextsize{5}\mkern-7mu\mathord\rightarrow$}
\catcode`@=12
\long\def\linemarkup#1#2{%
\par\noindent\hbox{%
\hbox{}\hskip -6pt {\colorlocal{#1}\vrule width 1pt \hskip 5pt}%
\hbox to \hsize{\vbox{#2}\hfil}%
%\hbox{}\hskip 5pt {\colorlocal{#1}\vrule width 1pt \hskip -6pt}%
}\par%
}

22
src/formatitko/tex/table_of_contents.tex

@ -0,0 +1,22 @@
{
\def\pagelink#1{#1}
\def\toclink#1#2{%
#2
}
\def\stdskip{\vskip 3pt}
\def\tocsection#1#2#3#4{
\line{\bf\hbox to 2em{#2\hfil}#4~\hfil\pagelink{#1}}
}
\def\tocsubsection#1#2#3#4{
\line{\hskip 1.5cm \hbox to 3em{#2\hfil}#4~\hfil\pagelink{#1}}
}
\def\tocsubsubsection#1#2#3#4{
\line{\hskip 3cm \hbox to 4em{#2\hfil}#4~\hfil\pagelink{#1}}
}
\def\tocpicture#1#2#3#4{}
\vskip 1cm
\input toc.aux
}

18
src/formatitko/tex/table_of_contents_pictures.tex

@ -0,0 +1,18 @@
{
\def\pagelink#1{#1}
\def\toclink#1#2{%
#2
}
\def\stdskip{\vskip 3pt}
\def\tocsection#1#2#3#4{}
\def\tocsubsection#1#2#3#4{}
\def\tocsubsubsection#1#2#3#4{}
\def\tocpicture#1#2#3#4{
\line{\hbox to 2em{#2\hfil}#4~\hfil\pagelink{#1}}\stdskip
}
\vskip 1cm
\input toc.aux
}

155
src/formatitko/tex_generator.py

@ -11,10 +11,14 @@ from .output_generator import OutputGenerator
from .images import ImageProcessor, ImageProcessorNamespaceSearcher
from .whitespace import NBSP
from .elements import FQuoted
from .elements import FQuoted, FLineMarkup
from .context import Group, InlineGroup, BlockGroup, Context
from .util import inlinify
def color_to_rgb(color):
import matplotlib.colors
return matplotlib.colors.to_rgb(color)
class UCWTexGenerator(OutputGenerator):
imageProcessor: ImageProcessor
_bold: int
@ -24,22 +28,30 @@ class UCWTexGenerator(OutputGenerator):
self.imageProcessor = imageProcessor
self._bold = 0
self._italic = 0
self._floatpages = {}
super().__init__(output_file, *args, **kwargs)
def escape_special_chars(self, text: str) -> str:
text = text.replace("&", r"\&")
text = text.replace("%", r"\%")
text = text.replace("$", r"\$")
text = text.replace("#", r"\#")
text = text.replace("_", r"\_")
text = text.replace("{", r"\{")
text = text.replace("}", r"\}")
text = text.replace("~", r"\textasciitilde{}")
text = text.replace("^", r"\textasciicircum{}")
text = text.replace("\\", r"\textbackslash{}")
text = text.replace(" ", "~") # We use unicode no-break spaces to force nbsp in output
text = text.replace("", "")
return text
if '\\' in text:
print("ESCAPE", text)
out = ""
for char in text:
out += {
'&': r"\&",
'%': r"\%",
'$': r"\$",
'#': r"\#",
'_': r"\_",
'{': r"\{",
'}': r"\}",
'~': r"\textasciitilde{}",
'^': r"\textasciicircum{}",
'\\': r"\textbackslash{}",
' ': r"~",
'': r"",
}.get(char, char)
return out
def generate(self, e: Union[Element, ListContainer]):
if hasattr(e, "attributes") and "only" in e.attributes and e.attributes["only"] != "tex":
@ -67,21 +79,17 @@ class UCWTexGenerator(OutputGenerator):
self.writepar(r"\vskip5pt\hrule\hfil\vskip5pt{}")
def generate_Doc(self, e: Doc):
self.writeln(r"\input ucwmac2.tex")
self.writeln(r"\ucwmodule{ofs}")
self.writeln(r"\ucwmodule{verb}")
self.writeln(r"\ucwmodule{link}")
self.writeln(r"\input formatitko.tex")
self.generate(e.content)
self.writeln(r"\bye")
def get_language_macro(self, lang: str):
if lang == "cs":
return r"\chyph\lefthyphenmin=2\righthyphenmin=2{}"
return r"\uselanguage{czech}\frenchspacing\lefthyphenmin=2\righthyphenmin=2{}"
elif lang == "sk":
return r"\shyph\lefthyphenmin=2\righthyphenmin=2{}"
return r"\uselanguage{slovak}\frenchspacing\lefthyphenmin=2\righthyphenmin=2{}"
elif lang == "en":
return r"\ehyph\lefthyphenmin=2\righthyphenmin=2{}"
return r"\uselanguage{USenglish}\nonfrenchspacing\lefthyphenmin=2\righthyphenmin=2{}"
else:
return ""
@ -101,7 +109,7 @@ class UCWTexGenerator(OutputGenerator):
def generate_Header(self, e: Header):
self.ensure_empty(2)
self.write("\\"+"sub"*(e.level-1)+"section{")
self.write("\\"+"sub"*(e.level-1)+"section{"+".".join(map(str, e.attributes["number"]))+"}{")
self.generate(e.content)
self.write(r"}")
self.ensure_empty(2)
@ -147,31 +155,58 @@ class UCWTexGenerator(OutputGenerator):
width = str(int(e.attributes["width"][:-1])/100) + "\\hsize"
width = "width " + width
if isinstance(e.parent.parent, Figure):
self.writeln(f'\\putimage{{{width}}}{{{url}}}')
else:
self.writepar(f'\\putimage{{{width}}}{{{url}}}')
self.writeln(f'\\centerline{{\\putimage{{{width}}}{{{url}}}}}')
def generate_Code(self, e: Code):
self.write(r"\verb`")
self.write(e.text)
self.write(r"`")
def generate_Figure(self, e: Figure):
self.ensure_empty(2)
self.writeln(r"\vskip5pt")
self.writeln(r"\centerline{")
def generate_nonfloat_Figure(self, e: Figure):
self.writeln(r"\figure{")
self.indent_more()
self.generate(e.content)
self.indent_less()
self.writeln(r"}")
self.writeln(r"\centerline{")
self.writeln(r"}{")
self.indent_more()
if 'number' in e.attributes:
type_text = e.attributes.get("type_text", "Obrázek")
self.writeln(f"{type_text} {e.attributes['number']}:")
self.generate(e.caption)
if 'number' in e.attributes:
tocmac = e.attributes.get("tocmac", "tocpicture")
if tocmac:
self.writeln("\\addtoc\\"+tocmac+r"{"+str(e.attributes['number'])+"}{}{")
self.indent_more()
self.generate(e.caption.content)
self.indent_less()
self.writeln("}")
self.indent_less()
self.writeln(r"}")
self.writeln(r"\vskip5pt{}")
self.ensure_empty(2)
self.writeln(r"}{}{}")
def generate_Figure(self, e: Figure):
if "floatpage" in e.attributes:
fp = e.attributes["floatpage"]
flush = fp[-1]=="!"
if flush:
fp = fp[:-1]
self._floatpages.setdefault(fp, [])
self._floatpages[fp].append(e)
if flush:
self.writeln(r"\floatpage{")
self.writeln(r"\vfill")
for x in self._floatpages[fp]:
self.generate_nonfloat_Figure(x)
self.writeln(r"\vfill")
self.writeln(r"}")
del self._floatpages[fp]
else:
self.ensure_empty(2)
self.writeln(r"\floatinsert{")
self.generate_nonfloat_Figure(e)
self.writeln(r"}")
self.ensure_empty(2)
def generate_Emph(self, e: Emph):
if self._bold > 0:
@ -183,6 +218,11 @@ class UCWTexGenerator(OutputGenerator):
self._italic-=1
self.write(r"}")
def generate_Slanted(self, e: Emph):
self.write(r"{\sl{}")
self.generate(e.content)
self.write(r"}")
def generate_Strong(self, e: Strong):
if self._italic > 0:
self.write(r"{\bi{}")
@ -194,15 +234,15 @@ class UCWTexGenerator(OutputGenerator):
self.write(r"}")
def generate_Caption(self, e: Caption):
self.generate_Emph(e)
self.generate(e.content)
def generate_Math(self, e: Math):
if e.format == "DisplayMath":
self.ensure_empty(2)
self.ensure_empty(1)
self.writeraw("$$")
self.writeraw(e.text.strip())
self.writeraw("$$")
self.ensure_empty(2)
self.ensure_empty(1)
else:
self.write("$")
self.write(e.text)
@ -214,22 +254,23 @@ class UCWTexGenerator(OutputGenerator):
self.write(r"}")
def generate_Table(self, e: Table):
hskip = r"\hskip 0.6em\relax"
aligns = {
"AlignLeft": r"\quad#\quad\hfil",
"AlignRight": r"\quad\hfil#\quad",
"AlignCenter": r"\quad\hfil#\hfil\quad",
"AlignDefault": r"\quad#\quad\hfil"
"AlignLeft": hskip+r"\relax#\hfil"+hskip,
"AlignRight": hskip+r"\hfil#"+hskip,
"AlignCenter": hskip+r"\hfil#\hfil"+hskip,
"AlignDefault": hskip+r"#\hfil"+hskip,
}
self.writeln(r"\vskip1em")
self.writeln(r"\halign{\strut"+"&".join([aligns[col[0]] for col in e.colspec])+r"\cr")
self.writeln(r"\leavevmode\vbox{\halign{\strut"+"&".join([aligns[col[0]] for col in e.colspec])+r"\cr")
self.indent_more()
self.generate(e.head.content)
self.writeln(r"\noalign{\hrule}")
self.writeln(r"\noalign{\vskip 0.3em\hrule\vskip 0.3em}")
self.generate(e.content[0].content)
self.writeln(r"\noalign{\hrule}")
self.writeln(r"\noalign{\vskip 0.3em\hrule\vskip 0.3em}")
self.generate(e.foot.content)
self.indent_less()
self.writeln("}")
self.writeln("}}")
self.writeln(r"\vskip1em")
def generate_TableRow(self, e: TableRow):
@ -263,6 +304,16 @@ class UCWTexGenerator(OutputGenerator):
def generate_Div(self, e: Div):
self.generate(e.content)
def generate_FLineMarkup(self, e: FLineMarkup):
self.ensure_empty(2)
r,g,b = color_to_rgb(e.color)
self.writeln(r"\linemarkup{\rgb{"+f"{r} {g} {b}"+"}}{")
self.indent_more()
self.generate(e.content)
self.indent_less()
self.writeln(r"}")
self.ensure_empty(2)
def generate_LineBlock(self, e: LineBlock):
self.writeln()
@ -323,6 +374,11 @@ class UCWTexGenerator(OutputGenerator):
self.writeln(r"}")
def generate_Link(self, e: Link):
if len(e.content) == 0:
if e.url.startswith('#'):
obj = e.obj_map[e.url[1:]]
self.write(str(obj.attributes["number"]))
return
if len(e.content) == 1 and isinstance(e.content[0], Str) and e.content[0].text == e.url:
self.write(r"\url{")
else:
@ -348,7 +404,7 @@ class UCWTexGenerator(OutputGenerator):
self.writeln("% FIXME: Citations not implemented")
def generate_Cite(self, e: Cite):
self.writeln("% FIXME: Cites not implemented")
self.generate(e.content)
def generate_Definition(self, e: Definition):
self.writeln("% FIXME: Definitions not implemented")
@ -364,3 +420,8 @@ class UCWTexGenerator(OutputGenerator):
def generate_Strikeout(self, e: Strikeout):
self.writeln("% FIXME: Strikeouts not implemented")
def generate_SmallCaps(self, e: Strikeout):
self.write(r"{\csc{}")
self.generate(e.content)
self.write(r"}")

34
src/formatitko/transform_processor.py

@ -14,7 +14,7 @@ import importlib
import json
from .whitespace import NBSP
from .elements import FQuoted
from .elements import FQuoted, Slanted, FLink
from .context import Group, InlineGroup, BlockGroup
from .util import nullify, import_md
from .context import Context, CommandCallable
@ -228,12 +228,12 @@ class TransformProcessor(NOPProcessor):
importedDoc = import_md(open(filename, "r").read())
self.transform(importedDoc.content)
elif e.attributes["type"] == "module":
matches = re.match(r"^(\w+)(?: as (\w+))?$", e.content[0].text[1:])
matches = re.match(r"^([\w\.]+)(?: as (\w+))?$", e.content[0].text[1:])
if not matches:
raise SyntaxError(f"`{e.content[0].text[1:]}`: invalid syntax")
module = importlib.import_module(matches.group(1))
module_name = matches.group(1) if matches.group(2) is None else matches.group(2)
self.context.add_commands_from_module(module, module_name)
self.context.add_commands_from_module(module, "")
elif e.attributes["type"] == "metadata":
filename = self.context.dir + "/" + e.content[0].text[1:]
self.context.add_dep(filename)
@ -260,6 +260,11 @@ class TransformProcessor(NOPProcessor):
raise TypeError(f"Cannot print value of metadatum '{e.content[0].text[1:]}' of type '{type(val)}'")
return e
if "slanted" in e.classes:
# `.slanted` class for Span
# Content of Span is enclosed into Slanted (subclass os Emph)
return self.transform(Slanted(*e.content))
return super().transform_Span(e)
def transform_CodeBlock(self, e: CodeBlock) -> Union[CodeBlock, Div, Null]:
@ -306,3 +311,26 @@ class TransformProcessor(NOPProcessor):
else:
return e
def transform_Header(self, e: Header) -> Header:
if "number" not in e.attributes:
if 'unnumbered' in e.classes:
e.attributes["number"] = ""
else:
e.attributes["number"] = self.context.get_data("section_number_generator")(e, self.context)
return e
def transform_Figure(self, e: Figure) -> Figure:
if "number" not in e.attributes:
if 'unnumbered' in e.classes:
e.attributes["number"] = ""
else:
e.attributes["number"] = self.context.get_data("figure_number_generator")(e, self.context)
self.context.get_data("obj_map")[e.identifier] = e
self.context.set_data("current_figure", e)
r = super().transform_Figure(e)
self.context.unset_data("current_figure")
return r
def transform_Link(self, e: Link) -> Link:
e = FLink(*e.content, url=e.url, identifier=e.identifier, attributes=e.attributes, classes=e.classes, obj_map=self.context.get_data("obj_map"))
return super().transform_Link(e)

10
test/test-files/test-partial.md

@ -66,14 +66,14 @@ raise Exception("Jsem piča")
-->
![This is a figure, go figure...](logo.svg){width=25%}What
![This is a figure, go figure...](logo.pdf){width=50%}
![Fakt epesní reproduktor](reproduktor.jpeg){width=10em}
![Fakt epesní reproduktor](reproduktor.png "Hodně rozpixelovaný obrázek reproduktoru"){width=10em file-width=1000}
![This is a figure, go figure...](logo.jpg){width=50%}
```
![This is a figure, go figure...](logo1.png){width=10em}
![Fakt epesní reproduktor](reproduktor.jpeg){width=10em}
![This is a figure, go figure...](logo.jpg){width=50%}
![Fakt epesní reproduktor](reproduktor.png "Hodně rozpixelovaný obrázek reproduktoru"){width=10em file-width=1000}

Loading…
Cancel
Save