mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-05-18 20:23:27 +02:00
Added character portraits from @vincentmalloy.
Merge https://github.com/canismarko/dungeon-sheets/pull/100
This commit is contained in:
@@ -51,6 +51,20 @@ standard 5e rules, and are case-insensitive. Refer to the D&D
|
|||||||
xp = 2190
|
xp = 2190
|
||||||
hp_max = 16
|
hp_max = 16
|
||||||
|
|
||||||
|
Character Portrait
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
portrait = True
|
||||||
|
|
||||||
|
If this is set to True and a corresponding portrait file exists,
|
||||||
|
the portrait will be added to the character personality sheet.
|
||||||
|
For now, the file must have a .jpeg extension and be named exactly
|
||||||
|
the same as the character file. This might not work with every Image size.
|
||||||
|
ca 550 * 700px seems to be the right format. Anything smaller should work, too.
|
||||||
|
See the Bard1 example for a demonstration of this feature.
|
||||||
|
|
||||||
Ability Scores
|
Ability Scores
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class Character(Entity):
|
|||||||
_proficiencies_text = list()
|
_proficiencies_text = list()
|
||||||
|
|
||||||
# Appearance
|
# Appearance
|
||||||
# portrait = placeholder not sure how to implement
|
portrait = False
|
||||||
age = 0
|
age = 0
|
||||||
height = ""
|
height = ""
|
||||||
weight = ""
|
weight = ""
|
||||||
|
|||||||
Executable → Regular
@@ -2,9 +2,11 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
|
import io
|
||||||
|
|
||||||
import pdfrw
|
import pdfrw
|
||||||
from fdfgen import forge_fdf
|
from fdfgen import forge_fdf
|
||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
|
||||||
from dungeonsheets.forms import mod_str
|
from dungeonsheets.forms import mod_str
|
||||||
|
|
||||||
@@ -175,10 +177,10 @@ def create_character_pdf_template(character, basename, flatten=False):
|
|||||||
# Prepare the actual PDF
|
# Prepare the actual PDF
|
||||||
dirname = os.path.join(os.path.dirname(os.path.abspath(__file__)), "forms/")
|
dirname = os.path.join(os.path.dirname(os.path.abspath(__file__)), "forms/")
|
||||||
src_pdf = os.path.join(dirname, "blank-character-sheet-default.pdf")
|
src_pdf = os.path.join(dirname, "blank-character-sheet-default.pdf")
|
||||||
return make_pdf(fields, src_pdf=src_pdf, basename=basename, flatten=flatten)
|
return make_pdf(fields, src_pdf=src_pdf, basename=basename, flatten=flatten, portrait="")
|
||||||
|
|
||||||
|
|
||||||
def create_personality_pdf_template(character, basename, flatten=False):
|
def create_personality_pdf_template(character, basename, portrait_file="", flatten=False):
|
||||||
# Prepare the list of fields
|
# Prepare the list of fields
|
||||||
fields = {
|
fields = {
|
||||||
"CharacterName 2": character.name,
|
"CharacterName 2": character.name,
|
||||||
@@ -199,7 +201,7 @@ def create_personality_pdf_template(character, basename, flatten=False):
|
|||||||
# Prepare the actual PDF
|
# Prepare the actual PDF
|
||||||
dirname = os.path.join(os.path.dirname(os.path.abspath(__file__)), "forms/")
|
dirname = os.path.join(os.path.dirname(os.path.abspath(__file__)), "forms/")
|
||||||
src_pdf = os.path.join(dirname, "blank-personality-sheet-default.pdf")
|
src_pdf = os.path.join(dirname, "blank-personality-sheet-default.pdf")
|
||||||
return make_pdf(fields, src_pdf=src_pdf, basename=basename, flatten=flatten)
|
return make_pdf(fields, src_pdf=src_pdf, basename=basename, flatten=flatten, portrait=portrait_file)
|
||||||
|
|
||||||
|
|
||||||
def create_spells_pdf_template(character, basename, flatten=False):
|
def create_spells_pdf_template(character, basename, flatten=False):
|
||||||
@@ -484,10 +486,10 @@ def create_spells_pdf_template(character, basename, flatten=False):
|
|||||||
# Make the actual pdf
|
# Make the actual pdf
|
||||||
dirname = os.path.join(os.path.dirname(os.path.abspath(__file__)), "forms/")
|
dirname = os.path.join(os.path.dirname(os.path.abspath(__file__)), "forms/")
|
||||||
src_pdf = os.path.join(dirname, "blank-spell-sheet-default.pdf")
|
src_pdf = os.path.join(dirname, "blank-spell-sheet-default.pdf")
|
||||||
make_pdf(fields, src_pdf=src_pdf, basename=basename, flatten=flatten)
|
make_pdf(fields, src_pdf=src_pdf, basename=basename, flatten=flatten, portrait="")
|
||||||
|
|
||||||
|
|
||||||
def make_pdf(fields: dict, src_pdf: str, basename: str, flatten: bool = False):
|
def make_pdf(fields: dict, src_pdf: str, basename: str, flatten: bool = False, portrait = ""):
|
||||||
"""Create a new PDF by applying fields to a src PDF document.
|
"""Create a new PDF by applying fields to a src PDF document.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -506,17 +508,17 @@ def make_pdf(fields: dict, src_pdf: str, basename: str, flatten: bool = False):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
_make_pdf_pdftk(fields, src_pdf, basename, flatten)
|
_make_pdf_pdftk(fields, src_pdf, basename, flatten, portrait)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
# pdftk could not run, so alert the user and use pdfrw
|
# pdftk could not run, so alert the user and use pdfrw
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
f"Could not run `{PDFTK_CMD}`, using fallback; forcing `--editable`.",
|
f"Could not run `{PDFTK_CMD}`, using fallback; forcing `--editable`.",
|
||||||
RuntimeWarning,
|
RuntimeWarning,
|
||||||
)
|
)
|
||||||
_make_pdf_pdfrw(fields, src_pdf, basename, flatten)
|
_make_pdf_pdfrw(fields, src_pdf, basename, flatten, portrait)
|
||||||
|
|
||||||
|
|
||||||
def _make_pdf_pdfrw(fields: dict, src_pdf: str, basename: str, flatten: bool = False):
|
def _make_pdf_pdfrw(fields: dict, src_pdf: str, basename: str, flatten: bool = False, portrait = ""):
|
||||||
"""Backup make_pdf function in case pdftk is not available."""
|
"""Backup make_pdf function in case pdftk is not available."""
|
||||||
template = pdfrw.PdfReader(src_pdf)
|
template = pdfrw.PdfReader(src_pdf)
|
||||||
# Different types of PDF fields
|
# Different types of PDF fields
|
||||||
@@ -575,7 +577,7 @@ def _make_pdf_pdfrw(fields: dict, src_pdf: str, basename: str, flatten: bool = F
|
|||||||
pdfrw.PdfWriter().write(f"{basename}.pdf", template)
|
pdfrw.PdfWriter().write(f"{basename}.pdf", template)
|
||||||
|
|
||||||
|
|
||||||
def _make_pdf_pdftk(fields, src_pdf, basename, flatten=False):
|
def _make_pdf_pdftk(fields, src_pdf, basename, flatten=False, portrait=""):
|
||||||
"""More robust way to make a PDF, but has a hard dependency."""
|
"""More robust way to make a PDF, but has a hard dependency."""
|
||||||
# Create the actual FDF file
|
# Create the actual FDF file
|
||||||
fdfname = basename + ".fdf"
|
fdfname = basename + ".fdf"
|
||||||
@@ -585,6 +587,11 @@ def _make_pdf_pdftk(fields, src_pdf, basename, flatten=False):
|
|||||||
fdf_file.write(fdf)
|
fdf_file.write(fdf)
|
||||||
fdf_file.close()
|
fdf_file.close()
|
||||||
# Build the final flattened PDF documents
|
# Build the final flattened PDF documents
|
||||||
|
if portrait != "":
|
||||||
|
dest_pdf = basename + "-temp.pdf"
|
||||||
|
image_pdf = basename + "_image_tmp.pdf"
|
||||||
|
make_image_pdf(portrait, image_pdf)
|
||||||
|
else:
|
||||||
dest_pdf = basename + ".pdf"
|
dest_pdf = basename + ".pdf"
|
||||||
popenargs = [
|
popenargs = [
|
||||||
PDFTK_CMD,
|
PDFTK_CMD,
|
||||||
@@ -597,5 +604,37 @@ def _make_pdf_pdftk(fields, src_pdf, basename, flatten=False):
|
|||||||
if flatten:
|
if flatten:
|
||||||
popenargs.append("flatten")
|
popenargs.append("flatten")
|
||||||
subprocess.call(popenargs)
|
subprocess.call(popenargs)
|
||||||
|
# stamp with image
|
||||||
|
if portrait != "":
|
||||||
|
src_pdf = dest_pdf
|
||||||
|
stamped_pdf = basename + ".pdf"
|
||||||
|
popenargs = [
|
||||||
|
PDFTK_CMD,
|
||||||
|
src_pdf,
|
||||||
|
"stamp",
|
||||||
|
image_pdf,
|
||||||
|
"output",
|
||||||
|
stamped_pdf,
|
||||||
|
]
|
||||||
|
popenargs.append("flatten")
|
||||||
|
subprocess.call(popenargs)
|
||||||
|
# Clean up
|
||||||
|
os.remove(image_pdf)
|
||||||
|
os.remove(dest_pdf)
|
||||||
# Clean up temporary files
|
# Clean up temporary files
|
||||||
os.remove(fdfname)
|
os.remove(fdfname)
|
||||||
|
|
||||||
|
def make_image_pdf(src_img:str, dest_pdf:str):
|
||||||
|
packet = io.BytesIO()
|
||||||
|
can = canvas.Canvas(packet)
|
||||||
|
x_start = 10
|
||||||
|
y_start = 240
|
||||||
|
can.drawImage(src_img, x_start, y_start, width=175, preserveAspectRatio=True, mask='auto')
|
||||||
|
can.showPage()
|
||||||
|
can.save()
|
||||||
|
|
||||||
|
#move to the beginning of the StringIO buffer
|
||||||
|
packet.seek(0)
|
||||||
|
|
||||||
|
new_pdf = pdfrw.PdfReader(packet)
|
||||||
|
pdfrw.PdfWriter().write(dest_pdf, new_pdf)
|
||||||
|
|||||||
Executable → Regular
+5
-1
@@ -420,6 +420,10 @@ def make_character_sheet(
|
|||||||
if character is None:
|
if character is None:
|
||||||
character_props = readers.read_sheet_file(char_file)
|
character_props = readers.read_sheet_file(char_file)
|
||||||
character = _char.Character.load(character_props)
|
character = _char.Character.load(character_props)
|
||||||
|
# Load image file if present
|
||||||
|
portrait_file=""
|
||||||
|
if character.portrait:
|
||||||
|
portrait_file=char_file.stem + ".jpeg"
|
||||||
# Set the fields in the FDF
|
# Set the fields in the FDF
|
||||||
basename = char_file.stem
|
basename = char_file.stem
|
||||||
char_base = basename + "_char"
|
char_base = basename + "_char"
|
||||||
@@ -440,7 +444,7 @@ def make_character_sheet(
|
|||||||
)
|
)
|
||||||
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, flatten=flatten
|
character=character, basename=person_base, portrait_file=portrait_file, flatten=flatten
|
||||||
)
|
)
|
||||||
pages.append(person_pdf)
|
pages.append(person_pdf)
|
||||||
if character.is_spellcaster:
|
if character.is_spellcaster:
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -107,3 +107,5 @@ flaws = """TODO: Describe your characters interesting flaws.
|
|||||||
|
|
||||||
features_and_traits = """TODO: Describe other features and abilities your
|
features_and_traits = """TODO: Describe other features and abilities your
|
||||||
character has."""
|
character has."""
|
||||||
|
|
||||||
|
portrait = True
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ jinja2
|
|||||||
sphinx
|
sphinx
|
||||||
pdfrw
|
pdfrw
|
||||||
EbookLib
|
EbookLib
|
||||||
|
reportlab
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ setup(name='dungeonsheets',
|
|||||||
},
|
},
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'fdfgen', 'npyscreen', 'jinja2', 'pdfrw', 'sphinx',
|
'fdfgen', 'npyscreen', 'jinja2', 'pdfrw', 'sphinx',
|
||||||
'EbookLib',
|
'EbookLib', 'reportlab',
|
||||||
],
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
|
|||||||
Executable → Regular
Reference in New Issue
Block a user