Skip to content

Commit f3d5b54

Browse files
committed
Implemented PDF export with PyPDF instead of PDFTk
1 parent ec9bd28 commit f3d5b54

File tree

5 files changed

+154
-92
lines changed

5 files changed

+154
-92
lines changed

l5r/exporters/fdfexporter.py

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ def set_model(self, model):
3939
def set_form(self, form):
4040
self.form = form
4141

42+
def get_fields(self):
43+
fields = self.build_fields()
44+
for f in fields:
45+
if isinstance(fields[f], bool):
46+
fields[f] = "/Yes" if fields[f] else "/Off"
47+
else:
48+
fields[f] = str(fields[f])
49+
return fields
50+
4251
def export(self, io):
4352
self.export_header(io)
4453
self.export_body(io)
@@ -51,7 +60,11 @@ def export_header(self, io):
5160
io.write(hd.encode('UTF-8'))
5261

5362
def export_body(self, io):
54-
pass
63+
fields = self.build_fields()
64+
65+
# EXPORT FIELDS
66+
for k in fields:
67+
self.export_field(k, fields[k], io)
5568

5669
def export_footer(self, io):
5770
ft = u"""</fields></xfdf>"""
@@ -107,7 +120,7 @@ def __init__(self):
107120
self.skill_offset = 0
108121
self.skills_per_page = 37
109122

110-
def export_body(self, io):
123+
def build_fields(self):
111124
m = self.model
112125
f = self.form
113126

@@ -301,9 +314,7 @@ def export_body(self, io):
301314
fields['SKILL_ROLL.%d' % j] = sk.mod_roll
302315
fields['SKILL_EMPH_MA.%d' % j] = ', '.join(sk.emph)
303316

304-
# EXPORT FIELDS
305-
for k in fields:
306-
self.export_field(k, fields[k], io)
317+
return fields
307318

308319

309320
class FDFExporterShugenja(FDFExporter):
@@ -354,12 +365,11 @@ def export_spells(self, fields, pg=1, ctrl=1, off=0):
354365
# lControlNumber = 1
355366
# lPageNumber += 1
356367

357-
def export_body(self, io):
368+
def build_fields(self):
358369
m = self.model
359370
f = self.form
360371

361372
fields = {}
362-
# self.export_spells(fields)
363373

364374
def get_affinity_text_(element_or_tag):
365375
ring_ = api.data.get_ring(element_or_tag)
@@ -394,9 +404,7 @@ def get_affinity_text_(element_or_tag):
394404
else:
395405
log.app.error('cannot export character school: %s'.format(str(schools[i])))
396406

397-
# EXPORT FIELDS
398-
for k in fields:
399-
self.export_field(k, fields[k], io)
407+
return fields
400408

401409

402410
class FDFExporterSpells(FDFExporterShugenja):
@@ -407,22 +415,18 @@ def __init__(self, offset):
407415
self.spell_offset = offset
408416
self.spell_per_page = 6
409417

410-
def export_body(self, io):
411-
418+
def build_fields(self):
412419
fields = {}
413420
self.export_spells(fields=fields, pg=2, off=self.spell_offset)
414-
415-
# EXPORT FIELDS
416-
for k in fields:
417-
self.export_field(k, fields[k], io)
421+
return fields
418422

419423

420424
class FDFExporterBushi(FDFExporter):
421425

422426
def __init__(self):
423427
super(FDFExporterBushi, self).__init__()
424428

425-
def export_body(self, io):
429+
def build_fields(self):
426430
m = self.model
427431
f = self.form
428432

@@ -459,17 +463,14 @@ def export_body(self, io):
459463
fields['KATA_RING_MA.%d' %
460464
(i + 1)] = '{0} ({1})'.format(kata.element, kata.mastery)
461465

462-
# EXPORT FIELDS
463-
for k in fields:
464-
self.export_field(k, fields[k], io)
465-
466+
return fields
466467

467468
class FDFExporterMonk(FDFExporter):
468469

469470
def __init__(self):
470471
super(FDFExporterMonk, self).__init__()
471472

472-
def export_body(self, io):
473+
def build_fields(self):
473474
m = self.model
474475
f = self.form
475476

@@ -527,9 +528,7 @@ def export_body(self, io):
527528
fields['KIHO_ELEM.%d' % (i + 1)] = ""
528529

529530

530-
# EXPORT FIELDS
531-
for k in fields:
532-
self.export_field(k, fields[k], io)
531+
return fields
533532

534533
def split_in_parts(self, text, max_lines=6):
535534
try:
@@ -572,7 +571,7 @@ def __init__(self, offset=0):
572571
self.weapons_offset = offset
573572
self.weapons_per_page = 10
574573

575-
def export_body(self, io):
574+
def build_fields(self):
576575
m = self.model
577576
f = self.form
578577
fields = {}
@@ -601,16 +600,14 @@ def export_body(self, io):
601600
fields['WEAPON.DMG.%d' % j] = weap.base_dmg
602601
fields['WEAPON.NOTES.%d' % j] = weap.desc
603602

604-
# EXPORT FIELDS
605-
for k in fields:
606-
self.export_field(k, fields[k], io)
603+
return fields
607604

608605
class FDFExporterCourtier(FDFExporter):
609606

610607
def __init__(self):
611608
super(FDFExporterCourtier, self).__init__()
612609

613-
def export_body(self, io):
610+
def build_fields(self):
614611
m = self.model
615612
f = self.form
616613

@@ -636,9 +633,7 @@ def export_body(self, io):
636633
fields['BUSHI_TECH.%d.%d' % (rank, i)] = tech.name
637634
fields['BUSHI_TECH_TEXT.%d.%d' % (rank, i)] = tech.desc
638635

639-
# EXPORT FIELDS
640-
for k in fields:
641-
self.export_field(k, fields[k], io)
636+
return fields
642637

643638

644639
class FDFExporterSkills(FDFExporter):
@@ -649,7 +644,7 @@ def __init__(self, offset=0):
649644
self.skill_offset = offset
650645
self.skills_per_page = 37
651646

652-
def export_body(self, io):
647+
def build_fields(self):
653648
m = self.model
654649
f = self.form
655650

@@ -669,6 +664,5 @@ def export_body(self, io):
669664
fields['SKILL_ROLL.%d' % i] = sk.mod_roll
670665
fields['SKILL_EMPH_MA.%d' % i] = ', '.join(sk.emph)
671666

672-
# EXPORT FIELDS
673-
for k in fields:
674-
self.export_field(k, fields[k], io)
667+
return fields
668+

l5r/l5rcmcore/__init__.py

Lines changed: 40 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
from PyQt5.QtGui import QDesktopServices
4040
from PyQt5.QtCore import QUrl
4141

42+
# PyPDF
43+
from pypdf import PdfReader, PdfWriter
44+
4245
APP_NAME = 'l5rcm'
4346
APP_DESC = 'Legend of the Five Rings: Character Manager'
4447
APP_VERSION = '3.17.0'
@@ -105,18 +108,13 @@ def export_as_text(self, export_file):
105108
# exporter.export(f)
106109
#f.close()
107110

108-
def create_fdf(self, exporter):
109-
111+
def create_form_fields(self, exporter):
110112
exporter.set_form(self)
111113
exporter.set_model(self.pc)
114+
return exporter.get_fields()
112115

113-
fd, fpath = mkstemp(suffix='.fdf', text=False)
114-
with os.fdopen(fd, 'wb') as fobj:
115-
exporter.export(fobj)
116-
117-
return fpath
118116

119-
def flatten_pdf(self, fdf_file, source_pdf, target_pdf, target_suffix=None):
117+
def flatten_pdf(self, form_fields, source_pdf, target_pdf, target_suffix=None):
120118
basen = os.path.splitext(os.path.basename(target_pdf))[0]
121119
based = os.path.dirname(target_pdf)
122120

@@ -125,49 +123,41 @@ def flatten_pdf(self, fdf_file, source_pdf, target_pdf, target_suffix=None):
125123
else:
126124
target_pdf = os.path.join(based, basen) + '.pdf'
127125

128-
# call pdftk
129-
args_ = [self.get_pdftk(), source_pdf, 'fill_form',
130-
fdf_file, 'output', target_pdf, 'flatten']
126+
try:
127+
reader = PdfReader(source_pdf)
128+
writer = PdfWriter()
129+
writer.append(reader)
131130

132-
log.app.debug('call %s', args_)
131+
writer.update_page_form_field_values(
132+
None,
133+
form_fields,
134+
auto_regenerate=False,
135+
flatten=False
136+
)
133137

134-
ret = subprocess.call(args_)
135-
self.try_remove(fdf_file)
138+
with open(target_pdf, "wb") as output_stream:
139+
writer.write(output_stream)
136140

137-
if ret == 0:
138141
log.app.info('created pdf %s', target_pdf)
139-
else:
140-
log.app.warn('could not flatten pdf. pdftk exited with error code: %d', ret)
141-
142-
return ret == 0
142+
return True
143+
finally:
144+
pass
145+
#except Exception as ex:
146+
# log.app.error('could not generate output pdf. exception: %s', ex)
147+
# return False
143148

144149
def merge_pdf(self, input_files, output_file):
145-
# call pdftk
146-
args_ = [self.get_pdftk()] + input_files + ['output', output_file]
147-
148-
log.app.debug('call %s', args_)
149-
subprocess.call(args_)
150-
for f in input_files:
151-
self.try_remove(f)
152-
153-
def get_pdftk(self):
154-
155-
sys_path = shutil.which('pdftk')
156-
if sys_path is not None and os.path.exists(sys_path):
157-
return sys_path
158-
159-
if sys.platform == 'win32':
160-
return os.path.join(MY_CWD, 'tools', 'pdftk.exe')
161-
elif sys.platform == 'linux' or sys.platform == 'linux2':
162-
sys_path = '/usr/bin/pdftk'
163-
loc_path = os.path.join(MY_CWD, 'tools', 'pdftk')
164-
if os.path.exists(sys_path):
165-
return sys_path
166-
else:
167-
return loc_path
168-
elif sys.platform == 'darwin':
169-
return os.path.join(MY_CWD, 'tools', 'pdftk')
170-
return 'pdftk'
150+
try:
151+
merger = PdfWriter()
152+
153+
for pdf in input_files:
154+
merger.append(pdf)
155+
156+
merger.write(output_file)
157+
merger.close()
158+
finally:
159+
for f in input_files:
160+
self.try_remove(f)
171161

172162
def try_remove(self, fpath):
173163
try:
@@ -178,10 +168,11 @@ def try_remove(self, fpath):
178168

179169
def write_pdf(self, source, exporter):
180170
source_pdf = get_app_file(source)
181-
source_fdf = self.create_fdf(exporter)
171+
form_fields = self.create_form_fields(exporter)
172+
182173
fd, fpath = mkstemp(suffix='.pdf')
183174
os.fdopen(fd, 'wb').close()
184-
self.flatten_pdf(source_fdf, source_pdf, fpath)
175+
self.flatten_pdf(form_fields, source_pdf, fpath)
185176
self.temp_files.append(fpath)
186177

187178
def commit_pdf_export(self, export_file):
@@ -211,11 +202,11 @@ def export_as_pdf(self, export_file):
211202

212203
# GENERIC SHEET
213204
source_pdf = get_app_file('sheet_all.pdf')
214-
source_fdf = self.create_fdf(exporters.FDFExporterAll())
205+
form_fields = self.create_form_fields(exporters.FDFExporterAll())
215206
fd, fpath = mkstemp(suffix='.pdf')
216207
os.fdopen(fd, 'wb').close()
217208

218-
self.flatten_pdf(source_fdf, source_pdf, fpath)
209+
self.flatten_pdf(form_fields, source_pdf, fpath)
219210
self.temp_files.append(fpath)
220211

221212
# SAMURAI MONKS ALSO FITS IN THE BUSHI CHARACTER SHEET

l5r/sinks/sink_1.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,31 @@
1616
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
1717

1818
from PyQt5 import QtCore, QtGui, QtWidgets
19+
from PyQt5.QtCore import Qt
1920

2021
import l5r.dialogs as dialogs
2122
import l5r.models as models
2223
import os
2324

2425
from l5r.util import log, osutil, names
2526
from l5r.util.settings import L5RCMSettings
27+
from l5r.util.worker import Worker
2628

2729
import l5r.api as api
2830
import l5r.api.character
2931
import l5r.api.character.books
3032

3133
from l5r.l5rcmcore import get_app_file, DB_VERSION, get_icon_path
3234

35+
import pyqtspinner
3336

3437
class Sink1(QtCore.QObject):
3538

3639
def __init__(self, parent=None):
3740
super(Sink1, self).__init__(parent)
3841
self.form = parent
42+
self.thread_pool = QtCore.QThreadPool()
43+
self.thread_pool.setMaxThreadCount(2)
3944

4045
def new_character(self):
4146
form = self.form
@@ -87,11 +92,17 @@ def export_character_as_pdf(self):
8792
file_ = form.select_export_file(".pdf")
8893

8994
if file_:
90-
try:
91-
form.export_as_pdf(file_)
92-
form.open_pdf_file_as_shell(file_)
93-
except Exception as e:
94-
self.form.advise_error(self.tr("Cannot save pdf sheet."))
95+
96+
spinner = pyqtspinner.WaitingSpinner(form, True, True, Qt.ApplicationModal)
97+
98+
worker = Worker(form.export_as_pdf, file_) # Any other args, kwargs are passed to the run function
99+
worker.signals.result.connect(lambda x: form.open_pdf_file_as_shell(file_))
100+
worker.signals.finished.connect(lambda: spinner.stop())
101+
worker.signals.error.connect(lambda x: form.advise_error(self.tr("Cannot save pdf sheet.")))
102+
103+
self.thread_pool.start(worker)
104+
105+
spinner.start() # starts spinning
95106

96107
def switch_to_page_1(self):
97108
self.form.tabs.setCurrentIndex(0)

0 commit comments

Comments
 (0)