You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
7.7 KiB
267 lines
7.7 KiB
from panflute import *
|
|
import os
|
|
|
|
from whitespace import NBSP
|
|
from transform import FQuoted
|
|
from util import inlinify
|
|
from context import Group
|
|
from images import ImageProcessor
|
|
|
|
# Heavily inspired by: git://git.ucw.cz/labsconf2022.git
|
|
def tex(e: Element, i: ImageProcessor, indent_level: int=0, indent_str: str="\t") -> str:
|
|
|
|
# `only` attribute which makes transformed elements appear only in tex
|
|
# output or html output
|
|
if hasattr(e, "attributes") and "only" in e.attributes and e.attributes["only"] != "tex":
|
|
return ""
|
|
|
|
if isinstance(e, ListContainer):
|
|
return ''.join([tex(child, i, indent_level, indent_str) for child in e])
|
|
|
|
# Bits from which the final element output is built at the end of this
|
|
# function. Most elements override this by returning their own output.
|
|
content_foot = ""
|
|
content_head = ""
|
|
|
|
arguments = ""
|
|
open = "{"
|
|
close = "}"
|
|
|
|
tag = e.tag.lower()
|
|
|
|
tags = {
|
|
Header: "h"+chr(64 + e.level) if hasattr(e, "level") else "",
|
|
}
|
|
if type(e) in tags:
|
|
tag = tags[type(e)]
|
|
|
|
# These are also disabled in pandoc so they shouldn't appear in the AST at all.
|
|
not_implemented = {
|
|
Citation: True,
|
|
Cite: True,
|
|
Definition: True,
|
|
DefinitionItem: True,
|
|
DefinitionList: True
|
|
}
|
|
if type(e) in not_implemented:
|
|
return f'% FIXME: {type(e)}s not implemented \n'
|
|
|
|
# Elements which can be represented by a simple string
|
|
simple_string = {
|
|
NBSP: "~",
|
|
Space: " ",
|
|
Null: "",
|
|
LineBreak: f"\\\\",
|
|
SoftBreak: f" ",
|
|
HorizontalRule: "\\hr\n\n"
|
|
}
|
|
if type(e) in simple_string:
|
|
return simple_string[type(e)]
|
|
|
|
# Simplest basic elements
|
|
if isinstance(e, Str):
|
|
return e.text.replace(" ", "~")
|
|
|
|
if isinstance(e, Para):
|
|
return tex(e.content, i, 0, "")+"\n\n"
|
|
|
|
if isinstance(e, Span) or isinstance(e, Plain):
|
|
return tex(e.content, i, 0, "")
|
|
|
|
# Overriding elements with their own returns
|
|
if isinstance(e, Image):
|
|
url = e.url
|
|
|
|
# Attributes → image processor args
|
|
additional_args = {}
|
|
if "file-width" in e.attributes:
|
|
additional_args["width"] = int(e.attributes["file-width"])
|
|
if "file-height" in e.attributes:
|
|
additional_args["height"] = int(e.attributes["file-height"])
|
|
if "file-quality" in e.attributes:
|
|
additional_args["quality"] = int(e.attributes["file-quality"])
|
|
if "file-dpi" in e.attributes:
|
|
additional_args["dpi"] = int(e.attributes["file-dpi"])
|
|
|
|
# The directory of the current file, will also look for images there.
|
|
source_dir = e.attributes["source_dir"]
|
|
|
|
_, ext = os.path.splitext(url)
|
|
ext = ext[1:]
|
|
|
|
# Conversions between various formats.
|
|
if ext in ["pdf", "png", "jpeg"]:
|
|
# Even supported elements have to be 'converted' because the
|
|
# processing contains finding and moving them to the output
|
|
# directory.
|
|
url = i.process_image(url, ext, source_dir, relative=False, **additional_args)
|
|
elif ext in ["svg"]:
|
|
url = i.process_image(url, "pdf", source_dir, relative=False, **additional_args)
|
|
elif ext in ["epdf"]:
|
|
url = i.process_image(url, "pdf", source_dir, relative=False, **additional_args)
|
|
elif ext in ["jpg"]:
|
|
url = i.process_image(url, "jpeg", source_dir, relative=False, **additional_args)
|
|
else:
|
|
url = i.process_image(url, "pdf", source_dir, relative=False, **additional_args)
|
|
|
|
width = ""
|
|
if "width" in e.attributes:
|
|
width = e.attributes["width"]
|
|
# 50% → 0.5\hsize
|
|
if e.attributes["width"][-1] == "%":
|
|
width = str(int(e.attributes["width"][:-1])/100) + "\\hsize"
|
|
width = "width " + width
|
|
return f'\\image{{{width}}}{{{url}}}'
|
|
|
|
if isinstance(e, FQuoted):
|
|
if e.style == "cs":
|
|
if e.quote_type == "SingleQuote":
|
|
return f'‚{tex(e.content, i, 0, "")}‘'
|
|
elif e.quote_type == "DoubleQuote":
|
|
return f'„{tex(e.content, i, 0, "")}“'
|
|
elif e.style == "en":
|
|
if e.quote_type == "SingleQuote":
|
|
return f'‘{tex(e.content, i, 0, "")}’'
|
|
elif e.quote_type == "DoubleQuote":
|
|
return f'“{tex(e.content, i, 0, "")}”'
|
|
else:
|
|
if e.quote_type == "SingleQuote":
|
|
return f'\'{tex(e.content, i, 0, "")}\''
|
|
elif e.quote_type == "DoubleQuote":
|
|
return f'"{tex(e.content, i, 0, "")}"'
|
|
else:
|
|
return f'"{tex(e.content, i, 0, "")}"'
|
|
|
|
if isinstance(e, Code):
|
|
return f'\\verb`{e.text.replace("`", "backtick")}`'
|
|
|
|
if isinstance(e, Figure):
|
|
return f'\\figure{{{tex(e.content, i, indent_level+1, indent_str)}}}{{{tex(e.caption, i, indent_level+1, indent_str)}}}\n\n'
|
|
|
|
# Figure caption
|
|
if isinstance(e, Caption):
|
|
if inlinify(e) is not None:
|
|
return f'\\figcaption{{{tex(e.content, i, 0, "")}}}'
|
|
|
|
if isinstance(e, Math):
|
|
if e.format == "DisplayMath":
|
|
return f'$${e.text}$$\n'
|
|
else:
|
|
return f'${e.text}$'
|
|
|
|
# Footnote
|
|
if isinstance(e, Note):
|
|
tag = "fn"
|
|
if inlinify(e) is not None:
|
|
return f'\\fn{{{tex(inlinify(e), i, 0, "")}}}'
|
|
|
|
if isinstance(e, Table):
|
|
aligns = {
|
|
"AlignLeft": "\\quad#\\quad\\hfil",
|
|
"AlignRight": "\\quad\\hfil#\\quad",
|
|
"AlignCenter": "\\quad\\hfil#\\hfil\\quad",
|
|
"AlignDefault": "\\quad#\\quad\\hfil"
|
|
}
|
|
text = "\strut"+"&".join([aligns[col[0]] for col in e.colspec])+"\cr\n"
|
|
text += tex(e.head.content, i, 0, "")
|
|
text += "\\noalign{\\hrule}\n"
|
|
text += tex(e.content[0].content, i, 0, "")
|
|
text += "\\noalign{\\hrule}\n"
|
|
text += tex(e.foot.content, i, 0, "")
|
|
return "\\vskip1em\n\\halign{"+text+"}\n\\vskip1em\n"
|
|
# FIXME: Implement rowspan
|
|
|
|
if isinstance(e, TableRow):
|
|
return "&".join([("\\multispan"+str(cell.colspan)+" " if cell.colspan > 1 else "")+tex(cell.content, i, 0, "") for cell in e.content])+"\cr\n"
|
|
|
|
if isinstance(e, RawInline):
|
|
if e.format == "tex":
|
|
return e.text
|
|
else:
|
|
return ""
|
|
|
|
if isinstance(e, RawBlock):
|
|
if e.format == "tex":
|
|
return f'{e.text}\n'
|
|
else:
|
|
return ""
|
|
|
|
# See https://pandoc.org/MANUAL.html#line-blocks
|
|
if isinstance(e, LineBlock):
|
|
return f'{tex(e.content, i, indent_level+1, indent_str)}\n'
|
|
|
|
if isinstance(e, LineItem):
|
|
return tex(e.content, i, 0, "") + ("\\\\\n" if e.next else "\n")
|
|
|
|
if type(e) is Div:
|
|
return f'{tex(e.content, i, indent_level+1, indent_str)}'
|
|
|
|
if isinstance(e, Doc):
|
|
return tex(e.content, i, indent_level, indent_str)+"\n\\bye" # Is having the \bye a bad idea here?
|
|
|
|
|
|
# Non-overriding elements, they get generated using the template at the end
|
|
# of this function
|
|
if isinstance(e, BulletList):
|
|
tag = "list"
|
|
open = ""
|
|
arguments = "{o}"
|
|
close = "\\endlist"
|
|
|
|
elif isinstance(e, OrderedList):
|
|
tag = "list"
|
|
open = ""
|
|
styles = {
|
|
"DefaultStyle": "n",
|
|
"Decimal": "n",
|
|
"LowerRoman": "i",
|
|
"UpperRoman:": "I",
|
|
"LowerAlpha": "a",
|
|
"UpperAlpha": "A"
|
|
}
|
|
style = styles[e.style]
|
|
delimiters = {
|
|
"DefaultDelim": f"{style}.",
|
|
"Period": f"{style}.",
|
|
"OneParen": f"{style})",
|
|
"TwoParens": f"({style})"
|
|
}
|
|
style = delimiters[e.delimiter]
|
|
arguments = f"{{{style}}}"
|
|
close = "\\endlist"
|
|
# FIXME: Starting number of list
|
|
|
|
elif isinstance(e, ListItem):
|
|
tag = ":"
|
|
|
|
elif isinstance(e, Link):
|
|
if len(e.content) == 1 and isinstance(e.content[0], Str) and e.content[0].text == e.url:
|
|
tag = "url"
|
|
else:
|
|
tag = "linkurl"
|
|
arguments = f'{{{e.url}}}'
|
|
|
|
elif isinstance(e, Group):
|
|
tag = "begingroup"
|
|
open = ""
|
|
if "language" in e.metadata and e.metadata["language"] is not None:
|
|
open = "\\language"+e.metadata["language"]
|
|
close = "\\endgroup"
|
|
|
|
# The default which all non-overriding elements get generated by. This
|
|
# includes elements, which were not explicitly mentioned in this function,
|
|
# e. g. Strong, Emph...
|
|
|
|
if isinstance(e, Inline):
|
|
return f'\\{tag}{arguments}{open}{content_head}{tex(e.content, i, 0, "") if hasattr(e, "_content") else ""}{e.text if hasattr(e, "text") else ""}{content_foot}{close}'
|
|
|
|
out_str = ""
|
|
out_str = f"\\{tag}{arguments}{open}\n"
|
|
out_str += content_head
|
|
if hasattr(e, "_content"):
|
|
out_str += tex(e.content, i, indent_level+1, indent_str)
|
|
if hasattr(e, "text"):
|
|
out_str += e.text
|
|
out_str += f"{content_foot}\n{close}\n\n"
|
|
|
|
return out_str
|
|
|