mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-05-19 04:33: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 += "\n\n=================\n\n"
|
||||
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
|
||||
def magic_items_text(self):
|
||||
|
||||
+67
-1
@@ -49,6 +49,7 @@ def create_latex_pdf(
|
||||
basename: str,
|
||||
keep_temp_files: bool = False,
|
||||
use_dnd_decorations: bool = False,
|
||||
comm1: str = "pdflatex"
|
||||
):
|
||||
# Create tex document
|
||||
tex_file = f"{basename}.tex"
|
||||
@@ -59,7 +60,7 @@ def create_latex_pdf(
|
||||
pdf_file = Path(f"{basename}.pdf")
|
||||
output_dir = pdf_file.resolve().parent
|
||||
tex_command_line = [
|
||||
"pdflatex",
|
||||
comm1,
|
||||
"--output-directory",
|
||||
str(output_dir),
|
||||
"-halt-on-error",
|
||||
@@ -200,3 +201,68 @@ def rst_to_latex(rst, top_heading_level=0):
|
||||
tex_parts = latex_parts(rst)
|
||||
tex = tex_parts["body"]
|
||||
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_html"] = epub.rst_to_html
|
||||
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
|
||||
File = Union[Path, str]
|
||||
@@ -137,6 +138,7 @@ def make_sheet(
|
||||
output_format: str = "pdf",
|
||||
fancy_decorations: bool = False,
|
||||
debug: bool = False,
|
||||
use_tex_template: bool = False,
|
||||
):
|
||||
"""Make a character or GM sheet into a PDF.
|
||||
Parameters
|
||||
@@ -173,6 +175,7 @@ def make_sheet(
|
||||
output_format=output_format,
|
||||
fancy_decorations=fancy_decorations,
|
||||
debug=debug,
|
||||
use_tex_template=use_tex_template
|
||||
)
|
||||
return ret
|
||||
|
||||
@@ -406,6 +409,27 @@ def make_character_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(
|
||||
char_file: Union[str, Path],
|
||||
@@ -414,6 +438,7 @@ def make_character_sheet(
|
||||
output_format: str = "pdf",
|
||||
fancy_decorations: bool = False,
|
||||
debug: bool = False,
|
||||
use_tex_template: bool = False,
|
||||
):
|
||||
"""Prepare a PDF character sheet from the given character file.
|
||||
|
||||
@@ -448,7 +473,7 @@ def make_character_sheet(
|
||||
basename = char_file.stem
|
||||
char_base = basename + "_char"
|
||||
person_base = basename + "_person"
|
||||
sheets = [char_base + ".pdf", person_base + ".pdf"]
|
||||
sheets = [char_base + ".pdf"]
|
||||
pages = []
|
||||
# Prepare the tex/html content
|
||||
content_suffix = format_suffixes[output_format]
|
||||
@@ -458,16 +483,24 @@ def make_character_sheet(
|
||||
fancy_decorations=fancy_decorations)
|
||||
# Typeset combined LaTeX file
|
||||
if output_format == "pdf":
|
||||
if use_tex_template:
|
||||
msavage_sheet(
|
||||
character=character, basename=char_base,
|
||||
portrait_file=portrait_file, debug=debug
|
||||
)
|
||||
# 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
|
||||
)
|
||||
pages.append(char_pdf)
|
||||
person_pdf = create_personality_pdf_template(
|
||||
character=character, basename=person_base, portrait_file=portrait_file, flatten=flatten
|
||||
)
|
||||
pages.append(person_pdf)
|
||||
if character.is_spellcaster:
|
||||
)
|
||||
pages.append(char_pdf)
|
||||
person_pdf = create_personality_pdf_template(
|
||||
character=character, basename=person_base,
|
||||
portrait_file=portrait_file, flatten=flatten
|
||||
)
|
||||
pages.append(person_pdf)
|
||||
if character.is_spellcaster and not(use_tex_template):
|
||||
# Create spell sheet
|
||||
spell_base = "{:s}_spells".format(basename)
|
||||
create_spells_pdf_template(
|
||||
@@ -486,7 +519,7 @@ def make_character_sheet(
|
||||
)
|
||||
sheets.append(features_base + ".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:
|
||||
log.warning(
|
||||
f"``pdflatex`` not available. Skipping features for {character.name}"
|
||||
|
||||
Reference in New Issue
Block a user