mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-06-05 20:38:26 +02:00
Merge pull request #1 from bw-mutley/bw-mutley-patch-1
Adaption to latex character sheet template
This commit is contained in:
@@ -709,6 +709,123 @@ class Character(Creature):
|
|||||||
s = "(See Features Page)\n\n--" + s
|
s = "(See Features Page)\n\n--" + s
|
||||||
s += "\n\n=================\n\n"
|
s += "\n\n=================\n\n"
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@property
|
||||||
|
def features_summary(self):
|
||||||
|
# save space for informed features and traits
|
||||||
|
if hasattr(self, "features_and_traits"):
|
||||||
|
info_list = ["**Other Features**"]
|
||||||
|
info_list += [text.strip() for text in self.features_and_traits.split("\n")
|
||||||
|
if not(text.isspace())]
|
||||||
|
N = len(info_list)
|
||||||
|
for text in info_list:
|
||||||
|
if len(text) > 26: # 26 is just a guess for expected size of lines
|
||||||
|
N += 1
|
||||||
|
if N > 30:
|
||||||
|
return "\n".join(info_list[:30]) + "\n(...)"
|
||||||
|
N = 30 - N
|
||||||
|
else:
|
||||||
|
info_list = []
|
||||||
|
N = 30
|
||||||
|
if len(self.class_list) > 1:
|
||||||
|
featS = ["**Multiclass**:"]
|
||||||
|
for cl in self.class_list:
|
||||||
|
description = cl.name
|
||||||
|
if cl.subclass:
|
||||||
|
description += f"/{cl.subclass}"
|
||||||
|
description += f" {cl.level}"
|
||||||
|
featS.append(description)
|
||||||
|
else:
|
||||||
|
featS = []
|
||||||
|
featS += ["**Features**"]
|
||||||
|
featS += [f.name for f in self.features]
|
||||||
|
if len(featS) > N:
|
||||||
|
featS = featS[:N] + ["(...)"]
|
||||||
|
featS += info_list
|
||||||
|
return "\n\n".join(featS)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def equipment_text(self):
|
||||||
|
eq_list = []
|
||||||
|
if hasattr(self, "magic_items"):
|
||||||
|
eq_list += ["**Magic Items**"]
|
||||||
|
eq_list += [item.name for item in self.magic_items]
|
||||||
|
if hasattr(self, "equipment"):
|
||||||
|
eq_list += ["**Other Equipment**"]
|
||||||
|
eq_list += [text.strip() for text in self.equipment.split("\n")
|
||||||
|
if not(text.isspace())]
|
||||||
|
return "\n\n".join(eq_list)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def proficiencies_by_type(self):
|
||||||
|
prof_dict = {}
|
||||||
|
w_pro = set(self.weapon_proficiencies)
|
||||||
|
if weapons.MartialWeapon in w_pro:
|
||||||
|
prof_dict["Weapons"] = ["All weapons"]
|
||||||
|
elif weapons.SimpleWeapon in w_pro:
|
||||||
|
prof_dict["Weapons"] = ["Simple weapons"]
|
||||||
|
for w in w_pro:
|
||||||
|
if not(issubclass(w, weapons.SimpleWeapon)):
|
||||||
|
prof_dict["Weapons"] += [w.name]
|
||||||
|
else:
|
||||||
|
prof_dict["Weapons"] = [w.name for w in w_pro]
|
||||||
|
if "Weapons" in prof_dict.keys():
|
||||||
|
prof_dict["Weapons"] = ", ".join(prof_dict["Weapons"]) + "."
|
||||||
|
armor_types = ["all armor", "light armor", "medium armor",
|
||||||
|
"heavy armor"]
|
||||||
|
prof_set = set([prof.lower().strip().strip('.')
|
||||||
|
for prof in self.proficiencies_text.split(',')])
|
||||||
|
prof_dict["Armor"] = [ar for ar in armor_types if ar in prof_set]
|
||||||
|
if len(prof_dict["Armor"]) > 2 or 'all armor' in prof_set:
|
||||||
|
prof_dict["Armor"] = ["All armor"]
|
||||||
|
if 'shields' in prof_set:
|
||||||
|
prof_dict["Armor"] += ["shields"]
|
||||||
|
prof_dict["Armor"] = ", ".join(prof_dict["Armor"]) + "."
|
||||||
|
if hasattr(self, 'chosen_tools'):
|
||||||
|
prof_dict["Other"] = self.chosen_tools
|
||||||
|
return prof_dict
|
||||||
|
|
||||||
|
@property
|
||||||
|
def spell_casting_info(self):
|
||||||
|
"""Returns a ready-to-use dictionary for spellsheets."""
|
||||||
|
level_names = ["Cantrip",
|
||||||
|
'FirstLevelSpell',
|
||||||
|
'SecondLevelSpell',
|
||||||
|
'ThirdLevelSpell',
|
||||||
|
'FourthLevelSpell',
|
||||||
|
'FifthLevelSpell',
|
||||||
|
'SixthLevelSpell',
|
||||||
|
'SeventhLevelSpell',
|
||||||
|
'EighthLevelSpell',
|
||||||
|
'NinthLevelSpell']
|
||||||
|
spell_info = {'head':{
|
||||||
|
"classes_and_levels": " / ".join(
|
||||||
|
[c.name + " " + str(c.level) for c in self.spellcasting_classes]
|
||||||
|
),
|
||||||
|
"abilities": " / ".join(
|
||||||
|
[c.spellcasting_ability.upper()[:3]
|
||||||
|
for c in self.spellcasting_classes]
|
||||||
|
),
|
||||||
|
"DCs": " / ".join(
|
||||||
|
[str(self.spell_save_dc(c))
|
||||||
|
for c in self.spellcasting_classes]
|
||||||
|
),
|
||||||
|
"bonuses": " / ".join(
|
||||||
|
["{:+d}".format(self.spell_attack_bonus(c))
|
||||||
|
for c in self.spellcasting_classes]
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
slots = {level_names[k]:self.spell_slots(k) for k in range(1, 10)
|
||||||
|
if self.spell_slots(k) > 0}
|
||||||
|
spell_info["slots"] = slots
|
||||||
|
spell_list = {}
|
||||||
|
for s in self.spells:
|
||||||
|
prepared = s in self.spells_prepared
|
||||||
|
level_info = level_names[s.level]
|
||||||
|
info_there = spell_list.get(level_info, [])
|
||||||
|
spell_list[level_info] = info_there + [(s.name, prepared)]
|
||||||
|
spell_info["list"] = spell_list
|
||||||
|
return spell_info
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def magic_items_text(self):
|
def magic_items_text(self):
|
||||||
|
|||||||
+67
-1
@@ -49,6 +49,7 @@ def create_latex_pdf(
|
|||||||
basename: str,
|
basename: str,
|
||||||
keep_temp_files: bool = False,
|
keep_temp_files: bool = False,
|
||||||
use_dnd_decorations: bool = False,
|
use_dnd_decorations: bool = False,
|
||||||
|
comm1: str = "pdflatex"
|
||||||
):
|
):
|
||||||
# Create tex document
|
# Create tex document
|
||||||
tex_file = f"{basename}.tex"
|
tex_file = f"{basename}.tex"
|
||||||
@@ -59,7 +60,7 @@ def create_latex_pdf(
|
|||||||
pdf_file = Path(f"{basename}.pdf")
|
pdf_file = Path(f"{basename}.pdf")
|
||||||
output_dir = pdf_file.resolve().parent
|
output_dir = pdf_file.resolve().parent
|
||||||
tex_command_line = [
|
tex_command_line = [
|
||||||
"pdflatex",
|
comm1,
|
||||||
"--output-directory",
|
"--output-directory",
|
||||||
str(output_dir),
|
str(output_dir),
|
||||||
"-halt-on-error",
|
"-halt-on-error",
|
||||||
@@ -200,3 +201,68 @@ def rst_to_latex(rst, top_heading_level=0):
|
|||||||
tex_parts = latex_parts(rst)
|
tex_parts = latex_parts(rst)
|
||||||
tex = tex_parts["body"]
|
tex = tex_parts["body"]
|
||||||
return tex
|
return tex
|
||||||
|
|
||||||
|
def rst_to_boxlatex(rst):
|
||||||
|
"""Adapted rst translation from dungeonsheets latex module, removing
|
||||||
|
dice parsing and indentation."""
|
||||||
|
|
||||||
|
if rst is None:
|
||||||
|
return ""
|
||||||
|
tex_parts = latex_parts(rst)
|
||||||
|
tex = tex_parts["body"]
|
||||||
|
tex = tex.replace('\n\n', ' \\\\\n')
|
||||||
|
return tex
|
||||||
|
|
||||||
|
def msavage_spell_info(char):
|
||||||
|
"""Generates the spellsheet for msavage template."""
|
||||||
|
headinfo = char.spell_casting_info["head"]
|
||||||
|
sc_classes = r"\SpellcastingClass{" \
|
||||||
|
+ headinfo["classes_and_levels"] \
|
||||||
|
+ "}"
|
||||||
|
sc_abilities = r"\SpellcastingAbility{" \
|
||||||
|
+ headinfo["abilities"] \
|
||||||
|
+ "}"
|
||||||
|
sc_savedc = r"\SpellSaveDC{" \
|
||||||
|
+ headinfo["DCs"] \
|
||||||
|
+ "}"
|
||||||
|
sc_atk = r"\SpellAttackBonus{" \
|
||||||
|
+ headinfo["bonuses"] \
|
||||||
|
+ "}"
|
||||||
|
tex1 = "\n".join([sc_classes, sc_abilities, sc_savedc, sc_atk]) + "\n"
|
||||||
|
spellslots = char.spell_casting_info["slots"]
|
||||||
|
texT = []
|
||||||
|
for k, v in spellslots.items():
|
||||||
|
texT.append("\\" + k + "SlotsTotal{" + str(v) + "}")
|
||||||
|
tex2 = "\n".join(texT) + "\n"
|
||||||
|
texT = []
|
||||||
|
level_names = level_names = ["Cantrip",
|
||||||
|
'FirstLevelSpell',
|
||||||
|
'SecondLevelSpell',
|
||||||
|
'ThirdLevelSpell',
|
||||||
|
'FourthLevelSpell',
|
||||||
|
'FifthLevelSpell',
|
||||||
|
'SixthLevelSpell',
|
||||||
|
'SeventhLevelSpell',
|
||||||
|
'EighthLevelSpell',
|
||||||
|
'NinthLevelSpell']
|
||||||
|
sheet_spaces = dict(zip(level_names,
|
||||||
|
[8, 13, 13, 13, 13, 9, 9, 9, 7, 7]))
|
||||||
|
comp_letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K",
|
||||||
|
"L", "M"]
|
||||||
|
spellList = char.spell_casting_info["list"]
|
||||||
|
for k, v in spellList.items():
|
||||||
|
slots_max = sheet_spaces[k]
|
||||||
|
if len(v) > slots_max:
|
||||||
|
vsel = sorted(v, key=lambda x: x[1], reverse=True)
|
||||||
|
else:
|
||||||
|
vsel = v[:]
|
||||||
|
for spinfo, slot in zip(vsel[:slots_max], comp_letters):
|
||||||
|
slot_command = "\\"+k+'Slot'+slot
|
||||||
|
slot_command_name = slot_command+"{"+spinfo[0]+"}"
|
||||||
|
if k == "Cantrip":
|
||||||
|
texT = texT + [slot_command_name]
|
||||||
|
continue
|
||||||
|
slot_command_prep = slot_command+"Prepared"+"{"+str(spinfo[1])+"}"
|
||||||
|
texT = texT + [slot_command_name, slot_command_prep]
|
||||||
|
tex3 = "\n".join(texT) + '\n'
|
||||||
|
return "\n".join([tex1, tex2, tex3])
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ jinja_env = forms.jinja_environment()
|
|||||||
jinja_env.filters["rst_to_latex"] = latex.rst_to_latex
|
jinja_env.filters["rst_to_latex"] = latex.rst_to_latex
|
||||||
jinja_env.filters["rst_to_html"] = epub.rst_to_html
|
jinja_env.filters["rst_to_html"] = epub.rst_to_html
|
||||||
jinja_env.filters["to_heading_id"] = epub.to_heading_id
|
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
|
||||||
|
|
||||||
# Custom types
|
# Custom types
|
||||||
File = Union[Path, str]
|
File = Union[Path, str]
|
||||||
@@ -137,6 +138,7 @@ def make_sheet(
|
|||||||
output_format: str = "pdf",
|
output_format: str = "pdf",
|
||||||
fancy_decorations: bool = False,
|
fancy_decorations: bool = False,
|
||||||
debug: bool = False,
|
debug: bool = False,
|
||||||
|
use_tex_template: bool = False,
|
||||||
):
|
):
|
||||||
"""Make a character or GM sheet into a PDF.
|
"""Make a character or GM sheet into a PDF.
|
||||||
Parameters
|
Parameters
|
||||||
@@ -173,6 +175,7 @@ def make_sheet(
|
|||||||
output_format=output_format,
|
output_format=output_format,
|
||||||
fancy_decorations=fancy_decorations,
|
fancy_decorations=fancy_decorations,
|
||||||
debug=debug,
|
debug=debug,
|
||||||
|
use_tex_template=use_tex_template
|
||||||
)
|
)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -406,6 +409,27 @@ def make_character_content(
|
|||||||
)
|
)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
def msavage_sheet(character, basename, portrait_file="", debug=False):
|
||||||
|
"""Another adaption. All changes can be easily included as options
|
||||||
|
in the orignal functions, though."""
|
||||||
|
|
||||||
|
# Load image file if present
|
||||||
|
portrait_command=""
|
||||||
|
if character.portrait and portrait_file:
|
||||||
|
portrait_command = r"\includegraphics[width=5.75cm]{"+ \
|
||||||
|
portrait_file + "}"
|
||||||
|
|
||||||
|
|
||||||
|
tex = jinja_env.get_template("MSavage_template.tex").render(
|
||||||
|
char=character, portrait=portrait_command
|
||||||
|
)
|
||||||
|
latex.create_latex_pdf(
|
||||||
|
tex,
|
||||||
|
basename=basename,
|
||||||
|
keep_temp_files=debug,
|
||||||
|
use_dnd_decorations=True,
|
||||||
|
comm1="xelatex"
|
||||||
|
)
|
||||||
|
|
||||||
def make_character_sheet(
|
def make_character_sheet(
|
||||||
char_file: Union[str, Path],
|
char_file: Union[str, Path],
|
||||||
@@ -414,6 +438,7 @@ def make_character_sheet(
|
|||||||
output_format: str = "pdf",
|
output_format: str = "pdf",
|
||||||
fancy_decorations: bool = False,
|
fancy_decorations: bool = False,
|
||||||
debug: bool = False,
|
debug: bool = False,
|
||||||
|
use_tex_template: bool = False,
|
||||||
):
|
):
|
||||||
"""Prepare a PDF character sheet from the given character file.
|
"""Prepare a PDF character sheet from the given character file.
|
||||||
|
|
||||||
@@ -448,7 +473,7 @@ def make_character_sheet(
|
|||||||
basename = char_file.stem
|
basename = char_file.stem
|
||||||
char_base = basename + "_char"
|
char_base = basename + "_char"
|
||||||
person_base = basename + "_person"
|
person_base = basename + "_person"
|
||||||
sheets = [char_base + ".pdf", person_base + ".pdf"]
|
sheets = [char_base + ".pdf"]
|
||||||
pages = []
|
pages = []
|
||||||
# Prepare the tex/html content
|
# Prepare the tex/html content
|
||||||
content_suffix = format_suffixes[output_format]
|
content_suffix = format_suffixes[output_format]
|
||||||
@@ -458,16 +483,24 @@ def make_character_sheet(
|
|||||||
fancy_decorations=fancy_decorations)
|
fancy_decorations=fancy_decorations)
|
||||||
# Typeset combined LaTeX file
|
# Typeset combined LaTeX file
|
||||||
if output_format == "pdf":
|
if output_format == "pdf":
|
||||||
|
if use_tex_template:
|
||||||
|
msavage_sheet(
|
||||||
|
character=character, basename=char_base,
|
||||||
|
portrait_file=portrait_file, debug=debug
|
||||||
|
)
|
||||||
# Fillable PDF forms
|
# Fillable PDF forms
|
||||||
char_pdf = create_character_pdf_template(
|
else:
|
||||||
|
sheets.append(person_base + ".pdf")
|
||||||
|
char_pdf = create_character_pdf_template(
|
||||||
character=character, basename=char_base, flatten=flatten
|
character=character, basename=char_base, flatten=flatten
|
||||||
)
|
)
|
||||||
pages.append(char_pdf)
|
pages.append(char_pdf)
|
||||||
person_pdf = create_personality_pdf_template(
|
person_pdf = create_personality_pdf_template(
|
||||||
character=character, basename=person_base, portrait_file=portrait_file, flatten=flatten
|
character=character, basename=person_base,
|
||||||
)
|
portrait_file=portrait_file, flatten=flatten
|
||||||
pages.append(person_pdf)
|
)
|
||||||
if character.is_spellcaster:
|
pages.append(person_pdf)
|
||||||
|
if character.is_spellcaster and not(use_tex_template):
|
||||||
# Create spell sheet
|
# Create spell sheet
|
||||||
spell_base = "{:s}_spells".format(basename)
|
spell_base = "{:s}_spells".format(basename)
|
||||||
create_spells_pdf_template(
|
create_spells_pdf_template(
|
||||||
@@ -486,7 +519,7 @@ def make_character_sheet(
|
|||||||
)
|
)
|
||||||
sheets.append(features_base + ".pdf")
|
sheets.append(features_base + ".pdf")
|
||||||
final_pdf = f"{basename}.pdf"
|
final_pdf = f"{basename}.pdf"
|
||||||
merge_pdfs(sheets, final_pdf, clean_up=True)
|
merge_pdfs(sheets, final_pdf, clean_up=not(debug))
|
||||||
except exceptions.LatexNotFoundError:
|
except exceptions.LatexNotFoundError:
|
||||||
log.warning(
|
log.warning(
|
||||||
f"``pdflatex`` not available. Skipping features for {character.name}"
|
f"``pdflatex`` not available. Skipping features for {character.name}"
|
||||||
|
|||||||
Reference in New Issue
Block a user