mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-05-18 12:13: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
|
||||
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
|
||||
==============
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ class Character(Entity):
|
||||
_proficiencies_text = list()
|
||||
|
||||
# Appearance
|
||||
# portrait = placeholder not sure how to implement
|
||||
portrait = False
|
||||
age = 0
|
||||
height = ""
|
||||
weight = ""
|
||||
|
||||
Executable → Regular
@@ -2,9 +2,11 @@ import os
|
||||
import subprocess
|
||||
import logging
|
||||
import warnings
|
||||
import io
|
||||
|
||||
import pdfrw
|
||||
from fdfgen import forge_fdf
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
from dungeonsheets.forms import mod_str
|
||||
|
||||
@@ -175,10 +177,10 @@ def create_character_pdf_template(character, basename, flatten=False):
|
||||
# Prepare the actual PDF
|
||||
dirname = os.path.join(os.path.dirname(os.path.abspath(__file__)), "forms/")
|
||||
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
|
||||
fields = {
|
||||
"CharacterName 2": character.name,
|
||||
@@ -199,7 +201,7 @@ def create_personality_pdf_template(character, basename, flatten=False):
|
||||
# Prepare the actual PDF
|
||||
dirname = os.path.join(os.path.dirname(os.path.abspath(__file__)), "forms/")
|
||||
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):
|
||||
@@ -484,10 +486,10 @@ def create_spells_pdf_template(character, basename, flatten=False):
|
||||
# Make the actual pdf
|
||||
dirname = os.path.join(os.path.dirname(os.path.abspath(__file__)), "forms/")
|
||||
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.
|
||||
|
||||
Parameters
|
||||
@@ -506,17 +508,17 @@ def make_pdf(fields: dict, src_pdf: str, basename: str, flatten: bool = False):
|
||||
|
||||
"""
|
||||
try:
|
||||
_make_pdf_pdftk(fields, src_pdf, basename, flatten)
|
||||
_make_pdf_pdftk(fields, src_pdf, basename, flatten, portrait)
|
||||
except FileNotFoundError:
|
||||
# pdftk could not run, so alert the user and use pdfrw
|
||||
warnings.warn(
|
||||
f"Could not run `{PDFTK_CMD}`, using fallback; forcing `--editable`.",
|
||||
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."""
|
||||
template = pdfrw.PdfReader(src_pdf)
|
||||
# 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)
|
||||
|
||||
|
||||
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."""
|
||||
# Create the actual FDF file
|
||||
fdfname = basename + ".fdf"
|
||||
@@ -585,6 +587,11 @@ def _make_pdf_pdftk(fields, src_pdf, basename, flatten=False):
|
||||
fdf_file.write(fdf)
|
||||
fdf_file.close()
|
||||
# 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"
|
||||
popenargs = [
|
||||
PDFTK_CMD,
|
||||
@@ -597,5 +604,37 @@ def _make_pdf_pdftk(fields, src_pdf, basename, flatten=False):
|
||||
if flatten:
|
||||
popenargs.append("flatten")
|
||||
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
|
||||
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:
|
||||
character_props = readers.read_sheet_file(char_file)
|
||||
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
|
||||
basename = char_file.stem
|
||||
char_base = basename + "_char"
|
||||
@@ -440,7 +444,7 @@ def make_character_sheet(
|
||||
)
|
||||
pages.append(char_pdf)
|
||||
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)
|
||||
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
|
||||
character has."""
|
||||
|
||||
portrait = True
|
||||
|
||||
@@ -5,3 +5,4 @@ jinja2
|
||||
sphinx
|
||||
pdfrw
|
||||
EbookLib
|
||||
reportlab
|
||||
|
||||
@@ -26,7 +26,7 @@ setup(name='dungeonsheets',
|
||||
},
|
||||
install_requires=[
|
||||
'fdfgen', 'npyscreen', 'jinja2', 'pdfrw', 'sphinx',
|
||||
'EbookLib',
|
||||
'EbookLib', 'reportlab',
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
||||
Executable → Regular
Reference in New Issue
Block a user