Skip to content

Commit

Permalink
Merge pull request #165 from PJBrs/latex-dnd-ified
Browse files Browse the repository at this point in the history
More RPGtex dndtemplate options for the fancy feature pages
  • Loading branch information
canismarko authored Aug 22, 2024
2 parents 2d051d1 + 78ac023 commit 5c4e657
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 32 deletions.
2 changes: 1 addition & 1 deletion dungeonsheets/forms/companions_template.tex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ \section*{Companions}
challenge = {[[ monster.challenge_rating ]] ([[ monster.challenge_rating | challenge_rating_to_xp ]] XP)},
]
%\DndMonsterSection{Actions}
[[ monster.__doc__ | rst_to_latex(top_heading_level=2) ]]
[[ monster.__doc__ | monsterdoc ]]
\end{DndMonster}
[% endfor %]

Expand Down
2 changes: 1 addition & 1 deletion dungeonsheets/forms/druid_shapes_template.tex
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ \section*{Known Beasts}
challenge = {[[ shape.challenge_rating ]]},
]
%\DndMonsterSection{Actions}
[[ shape.__doc__ | rst_to_latex(top_heading_level=2) ]]
[[ shape.__doc__ | monsterdoc ]]
\end{DndMonster}
[% endfor %]

Expand Down
2 changes: 1 addition & 1 deletion dungeonsheets/forms/features_template.tex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ \section*{Features}
[%- for feat in character.features %]
\pdfbookmark[1]{[[ feat.name ]]}{Features - [[ feat.name ]]}
\DndFeatHeader{[[ feat.name ]]}[Source: [[ feat.source ]]]
[[- feat.__doc__|rst_to_latex -]]
[[- feat.__doc__|rst_to_latex(use_dnd_decorations=use_dnd_decorations) -]]
[%- endfor -%]
[% else %]
[%- for feat in character.features %]
Expand Down
2 changes: 1 addition & 1 deletion dungeonsheets/forms/infusions_template.tex
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ \section*{Infusions}
\subsection*{[[ inf.name ]]}
[%- if inf.prerequisite -%]\textit{Prerequisite: [[ inf.prerequisite ]]} \\ [% endif %]
[%- if inf.item -%]\textit{Item: [[ inf.item ]]} \\ [% endif %]
[[- inf.__doc__ | rst_to_latex(top_heading_level=2) -]]
[[- inf.__doc__ | rst_to_latex(top_heading_level=2, use_dnd_decorations=use_dnd_decorations) -]]
[% endfor %]
2 changes: 1 addition & 1 deletion dungeonsheets/forms/magic_items_template.tex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ \section*{Magic Items}
[%- if mitem.needs_implementation %]
\textbf{**Not included in stats on Character Sheet} \\
[%- endif -%]
[[- mitem.__doc__|rst_to_latex -]]
[[- mitem.__doc__|rst_to_latex(use_dnd_decorations=use_dnd_decorations) -]]
[% endfor %]
[% else %]
[% for mitem in character.magic_items %]
Expand Down
13 changes: 7 additions & 6 deletions dungeonsheets/forms/monsters_template.tex
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
\section*{Monsters}

[% if use_dnd_decorations %]
\begin{DndTable}{r c c c c}
Name & HP & Hit Dice & AC & Init. \\
\begin{DndLongTable}[firsthead={\textbf{Name}
& \textbf{HP}
& \textbf{Hit Dice}
& \textbf{AC}
& \textbf{Init.} \\ }]{r c c c c}
[% for monster in monsters|sort(attribute='name') %]
[[ monster.name ]] & [[ monster.hp_max ]] & [[ monster.hit_dice ]] & [[ monster.armor_class ]] & [[ monster.initiative ]] \\
[% endfor %]
\end{DndTable}
\end{DndLongTable}

[% for monster in monsters|sort(attribute='name') %]
\pdfbookmark[1]{[[ monster.name ]]}{Monsters - [[ monster.name ]]}
Expand Down Expand Up @@ -41,11 +44,9 @@ \section*{Monsters}
languages = {[% if monster.languages %][[ monster.languages ]][% else %] --- [% endif %]},
challenge = {[[ monster.challenge_rating ]] ([[ monster.challenge_rating | challenge_rating_to_xp ]] XP)},
]
%\DndMonsterSection{Actions}
[[ monster.__doc__ | rst_to_latex(top_heading_level=2) ]]
[[ monster.__doc__ | monsterdoc ]]
\end{DndMonster}
[% endfor %]

[% else %]
\begin{tabular}{r | c c c c}
Name & HP & Hit Dice & AC & Init. \\
Expand Down
39 changes: 26 additions & 13 deletions dungeonsheets/forms/party_summary_template.tex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
\pdfbookmark[1]{Summary}{Summary}
\section*{Summary}

[[ summary | rst_to_latex ]]
[[ summary | rst_to_latex(use_dnd_decorations=use_dnd_decorations) ]]

[% endif %]

Expand All @@ -11,8 +11,13 @@
\section*{Party}

[% if use_dnd_decorations %]
\begin{DndTable}{r c c c c c c c}
& Str & Dex & Con & Int & Wis & Cha \\
\begin{DndLongTable}[firsthead={
& \textbf{Str}
& \textbf{Dex}
& \textbf{Con}
& \textbf{Int}
& \textbf{Wis}
& \textbf{Cha} \\ }]{r c c c c c c c}
[% for member in party %]
[[ member.name[:18] ]]
& [[ member.strength.modifier | mod_str ]]
Expand All @@ -23,9 +28,11 @@ \section*{Party}
& [[ member.charisma.modifier | mod_str ]]
\\
[% endfor %]
\end{DndTable}
\begin{DndTable}{r c c c}
& AC & Max HP & Spl.\ DC \\
\end{DndLongTable}
\begin{DndLongTable}[firsthead={
& \textbf{AC}
& \textbf{Max HP}
& \textbf{Spl.\ DC} \\ }]{r c c c}
[% for member in party %]
[[ member.name[:28] ]]
& [[ member.armor_class ]]
Expand All @@ -35,26 +42,32 @@ \section*{Party}
[% endfor %]
\\
[% endfor %]
\end{DndTable}
\end{DndLongTable}
% Passive stats
\begin{DndTable}{r c c c}
& Pas. Per.\ & Pas. Ins.\ & Pas. Inv.\ \\
\begin{DndLongTable}[firsthead={
& \textbf{Pas. Per.\ }
& \textbf{Pas. Ins.\ }
& \textbf{Pas. Inv.\ } \\ }]{r c c c}
[% for member in party %]
[[ member.name[:28] ]]
& [[ member.passive_perception ]] % Passive perception
& [[ member.passive_insight ]] % Passive insight
& [[ member.passive_investigation ]] % Passive investigation
\\
[% endfor %]
\end{DndTable}
\end{DndLongTable}
%% XP thresholds for the party
\begin{DndTable}{r c c c c}
& Easy & Medium & Hard & Deadly \\
\begin{DndLongTable}[firsthead={
& \textbf{Easy}
& \textbf{Medium}
& \textbf{Hard}
& \textbf{Deadly} \\ }]{r c c c c}
\textbf{XP Threshold} &
[% for threshold in party | xp_thresholds %]
[[ "{:,}".format(threshold) ]] [% if not loop.last %]&[% endif %]
[% endfor %]
\end{DndTable}
\\
\end{DndLongTable}
[% else %]
\begin{tabular}{r | c c c c c c}
& Str & Dex & Con & Int & Wis & Cha \\
Expand Down
54 changes: 54 additions & 0 deletions dungeonsheets/forms/preamble.tex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
\usepackage{alltt}
\usepackage[bookmarks=true,bookmarksopen=true]{hyperref}
\usepackage{bookmark}
\usepackage{needspace}
\usepackage{supertabular}

% Make lists more compact
Expand All @@ -32,6 +33,59 @@
[% if use_dnd_decorations %]
\usepackage[layout=true]{dnd}
\setlength{\zerosep}{0em}

[% raw %]
% dndlongtable environment
\ExplSyntaxOn

% Table options
\keys_define:nn { dnd / longtable }
{
color .tl_set:N = \l__dnd_table_color_tl,
color .initial:n = tablecolor,
color .value_required:n = true,
header .tl_set:N = \l__dnd_table_header_tl,
header .value_required:n = true,
width .dim_set:N = \l__dnd_table_width_dim,
width .value_required:n = true,
firsthead .tl_set:N = \l__table_firsthead_tl,
firsthead .value_required:n = true,
}

\NewDocumentEnvironment {DndLongTable} { o m }
{
\group_begin:

\tablefirsthead{\l__table_firsthead_tl}

\dim_set:Nn \l__dnd_table_width_dim { \linewidth }
\tl_if_novalue:nF {#1}
{ \keys_set:nn { dnd / longtable } {#1} }

\par \vspace { 9pt plus 3pt minus 3pt}

\tl_if_empty:NF \l__dnd_table_header_tl
{
\group_begin:
\needspace{5\baselineskip}
\noindent \DndFontTableTitle{ \l__dnd_table_header_tl}
\par \vspace{ 5pt plus 2pt minus 2pt }
\group_end:
}

\DndFontTableBody

\rowcolors {1} {} {\l__dnd_table_color_tl}

\begin{supertabular*} {\l__dnd_table_width_dim} {#2}
}
{
\end{supertabular*} \vspace { 9pt plus 3pt minus 3pt }
\group_end:
}
\ExplSyntaxOff
[% endraw %]

[% else %]
\usepackage[margin=1.5cm]{geometry}
\usepackage[dvipsnames]{color}
Expand Down
2 changes: 1 addition & 1 deletion dungeonsheets/forms/spellbook_template.tex
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ \section*{Spells}
\item [Components:] [[ spl.component_string ]]
\end{description}
[%- endif -%]
[[- spl.__doc__ | rst_to_latex(top_heading_level=1) -]]
[[- spl.__doc__ | rst_to_latex(top_heading_level=1, use_dnd_decorations=use_dnd_decorations) -]]
[% endfor %]
2 changes: 1 addition & 1 deletion dungeonsheets/forms/subclasses_template.tex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ \section*{Subclasses}
[%- for sc in character.subclasses if sc not in ['', None, 'None', 'none'] %]
\pdfbookmark[1]{[[ sc.name ]]}{Subclasses - [[ sc.name ]]}
\DndFeatHeader{[[ sc.name ]]} % Would like to add source here
[[- sc.__doc__ | rst_to_latex(top_heading_level=2) ]]
[[- sc.__doc__ | rst_to_latex(top_heading_level=2, use_dnd_decorations=use_dnd_decorations) ]]
[%- endfor %]
[% else %]
[%- for sc in character.subclasses if sc not in ['', None, 'None', 'none'] %]
Expand Down
128 changes: 123 additions & 5 deletions dungeonsheets/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def rst_to_latex(rst, top_heading_level: int=0, format_dice: bool = True, use_dn
format_dice
If true, dice strings (e.g. "1d4") will be formatted in
monospace font.
Returns
=======
tex : str
Expand All @@ -214,10 +214,41 @@ def rst_to_latex(rst, top_heading_level: int=0, format_dice: bool = True, use_dn
tex = tex_parts["body"]
# Apply fancy D&D decorations
if use_dnd_decorations:
tex = re.sub(r"p{[0-9.]+\\DUtablewidth}", "l ", tex, flags=re.M)
tex = tex.replace(r"\begin{supertabular}[c]", r"\begin{DndTable}")
tex = tex.replace(r"\begin{supertabular}", r"\begin{DndTable}")
tex = tex.replace(r"\end{supertabular}", r"\end{DndTable}")
tex = tex.replace(r"\begin{supertabular}[c]", r"\begin{DndLongTable}[header=]")
tex = tex.replace(r"\begin{supertabular}", r"\begin{DndLongTable}[header=]")
tex = tex.replace(r"\end{supertabular}", r"\end{DndLongTable}")

# Stretch table to the entire width of the text column.
# First, get all table headers in tex and put them in a list. Each header
# is a string.
tableheader = re.findall(r"\\begin{DndLongTable}.*", tex)
for header in tableheader:
# Get all collumn widths, compute initial table width
colwidths = [width for width in re.findall(r"0\.[0-9]+", str(header))]
tablewidth = 0
for width in colwidths:
tablewidth += float(width)
# Prepare the transformed header
transformed_header = header
for width in colwidths:
# Transform the column width by dividing it by the initial table width
transformed_width = round( float(width) / tablewidth, 3)
# Subtract the table column separation spaces from the transformed column width
transformed_width = r"\\dimexpr " + str(transformed_width) + r"\\DUtablewidth -2\\tabcolsep"
# Replace the original width with the transformed width
transformed_header = re.sub(width + r"\\DUtablewidth",
transformed_width,
transformed_header)
# Replace the original table header with the transformed one
tex = tex.replace(header, transformed_header)
# Correct table header to the DndLongTable format.
# First deal with the table caption, if present:
tex = re.sub(r"(begin{DndLongTable}\[header=)\](.*?\n)\\multicolumn.*?\n(.*?)\n.*?\\\\\n",
r"\1\3]\2", tex, flags=re.M|re.DOTALL)
# Next, take the first table row and define it as the first page table header:
tex = re.sub(r"(begin{DndLongTable}\[header=.*?)\](.*?)\n(.*?\\\\)\n\n",
r"\1,firsthead={\3 }]\2\n", tex, flags=re.M|re.DOTALL)

return tex

def rst_to_boxlatex(rst):
Expand Down Expand Up @@ -306,3 +337,90 @@ def msavage_spell_info(char):
texT = texT + [slot_command_name, slot_command_prep]
tex3 = "\n".join(texT) + '\n'
return "\n".join([tex1, tex2, tex3, tex4])

def RPGtex_monster_info(char):
"""Generates the headings for the monster block info in the DND latex style"""
tex = """\n""" # Clean start with a newline
# Counting sections here:
# 0: Feats
# 1: Actions and reactions
# 2: Legendary Actions
# 3: Other types of actions.
# Challenges: Some monsters only have feats, some only have actions.
sectiontype = 0
sectionlist = char.split(" # ") # Four spaces, a hash, and another space
for section in sectionlist:
# First find out what type of section we're dealing with, and
# set sectiontype accordingly;
# The first section is either feats or actions. Set sectiontype to
# actions (and format accordingly) if the first line matches
# actions or reactions
if re.match (r"^[re]*actions\n", section, re.IGNORECASE):
sectiontype = 1
elif re.match (r"^legendary actions\n", section, re.IGNORECASE):
sectiontype = 2
elif sectionlist.index(section) > 0: # Not the first section, nor does
sectiontype = 3 # it have any header we recognize

# Now we latex format each section according to type, applying
# rst_to_latex where it's convenient, and resorting to our own
# means where necessary. Goal is to make use of DND-5e-LaTeX-Template
# style as much as possible.
if sectiontype == 0 or sectiontype == 3:
# Use rst_to_latex first, because of easy itemization
section = rst_to_latex(section) # This somehow adds a newline at the start of the section
# Snip away begin and end description:
section = re.sub (r"\\[a-z]+{description}\n", "", section)
# Sub \item[{}] with \DndMonsterAction{} headers:
section = re.sub (r"\\item\[{(.+)\.}\]", r"\\DndMonsterAction{\1}", section)
if sectiontype == 3: # Add section header
section = re.sub(r"^\n(.*)\n", r"\\DndMonsterSection{\1}", section)
# Remove spurious newlines from the start of the section:
section = re.sub (r"^\n", r"", section)
# Remove spurious newlines from the end of the section:
section = re.sub (r"\n\n$", r"\n", section)
tex += section

if sectiontype == 1:
# Process the section line by line.
subsection = ""
lines = section.splitlines()
for line in lines:
if re.match (r"^\S", line):
line = re.sub(r"(.+)$", r"\n\\DndMonsterSection{\1}\n", line)
subsection += line
else:
# Italicize weapon type and hit, and remove six leading spaces
line = re.sub(r"^ {6}(.+)\:(.+)(Hit)\: (.+)", r"\\textit{\1:} \2\\textit{\3:} \4 ", line)
# Remove leading spaces from other lines
line = re.sub(r"^ {6}(.+)", r"\1\n", line)
# Add DndMonsterAction header for each action, and remove four leading spaces
line = re.sub(r"^ {4}(\S.+)\.$", r"\\DndMonsterAction{\1}\n", line)
subsection += line
subsection = re.sub(r" {6}", "\n", subsection)
# Dice
subsection = re.sub(r"\((\d+)d(\d+)\s*([+-]*)\s*(\d*)\)", r" (\\texttt{\1d\2\3\4}) ", subsection)
tex += subsection

if sectiontype == 2:
# First process the section line by line to only get the legendary actions,
# then add section start, end and header.
subsection = ""
lines = section.splitlines()
for line in lines:
if re.match(r"^ {4}\S", line):
# New legendary action, remove leading spaces
line = re.sub(r"^ {4}(.+)\.$", r"\\DndMonsterLegendaryAction{\1}{}", line)
subsection += line + "\n"
elif re.match(r"^ {6}\S", line):
# Remove leading spaces from other lines
line = re.sub(r"^ {6}(.+)", r"\1", line)
subsection += line + "\n"
# Add section header and DndMonsterLegendaryActions environment
subsection = ("\n\\DndMonsterSection{Legendary Actions}\n\\begin{DndMonsterLegendaryActions}\n" + subsection + "\\end{DndMonsterLegendaryActions}\n")
# Put the curly braces in place
subsection = re.sub(r"}{}\n(.+?)\n\\", r"}\n{\1}\n\\", subsection, re.MULTILINE, re.DOTALL)
# Dice
subsection = re.sub(r"\((\d+)d(\d+)\s*([+-]*)\s*(\d*)\)", r" (\\texttt{\1d\2\3\4})", subsection)
tex += subsection
return tex
1 change: 1 addition & 0 deletions dungeonsheets/make_sheets.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
jinja_env.filters["to_heading_id"] = epub.to_heading_id
jinja_env.filters["boxed"] = latex.rst_to_boxlatex
jinja_env.filters["spellsheetparser"] = latex.msavage_spell_info
jinja_env.filters["monsterdoc"] = latex.RPGtex_monster_info

# Custom types
File = Union[Path, str]
Expand Down
Loading

0 comments on commit 5c4e657

Please sign in to comment.