2525import warnings
2626
2727from datetime import datetime
28- from collections import namedtuple
28+ from collections import namedtuple , defaultdict
2929from collections .abc import Callable
3030from pathlib import Path
3131from typing import Dict , List , Tuple , Optional , Sequence , Any
3232
3333import qrcode
34+ import qrcode .image .svg
3435import fpdf # FPDF, FlexTemplate, FPDF_FONT_DIR
36+ import fpdf .svg
3537
3638from ..api import Account , cryptopaths_parser , create , enumerate_mnemonic , group_parser
3739from ..util import chunker
@@ -207,12 +209,16 @@ def produce_pdf(
207209 produced cards, and the cryptocurrency accounts from the supplied slip39.Details.
208210
209211 Makes available several fonts.
212+
213+ Produces double-sided PDF by default, containing QR codes for SLIP-39 Mnemonics on the back.
210214 """
211215 if paper_format is None :
212216 paper_format = PAPER
213217 if orientations is None :
214218 orientations = ('portrait' , 'landscape' )
215219
220+ double_sided = True if double_sided is None else bool ( double_sided )
221+
216222 # Deduce the card size. All papers sizes are specified in inches.
217223 try :
218224 (card_h ,card_w ),card_margin = CARD_SIZES [card_format .lower ()]
@@ -250,18 +256,19 @@ def produce_pdf(
250256 # Convert all of the first group's account(s) to an address QR code
251257 assert accounts and accounts [0 ], \
252258 "At least one cryptocurrency account must be supplied"
253- qr = {}
259+ qr_acct = {}
254260 for i ,acct in enumerate ( accounts [0 ] ):
255261 qrc = qrcode .QRCode (
256- version = None ,
257- error_correction = qrcode .constants .ERROR_CORRECT_M ,
258- box_size = 10 ,
259- border = 1
262+ version = None ,
263+ error_correction = qrcode .constants .ERROR_CORRECT_M ,
264+ box_size = 10 ,
265+ border = 1 ,
266+ image_factory = qrcode .image .svg .SvgPathImage ,
260267 )
261268 qrc .add_data ( acct .address )
262269 qrc .make ( fit = True )
263270
264- qr [i ] = qrc .make_image ()
271+ qr_acct [i ] = qrc .make_image ()
265272 if log .isEnabledFor ( logging .INFO ):
266273 f = io .StringIO ()
267274 qrc .print_ascii ( out = f )
@@ -337,35 +344,63 @@ def produce_pdf(
337344 if double_sided :
338345 pdf .add_page ()
339346
347+
348+ # Compute the contents of the cards; the keys and their values are the attributes of
349+ # each template. Creates pages of cards (<pos>,<front>,<back>).
350+ page = [] # A sequence of pages [[<card>,..],..]
340351 card_n = 0
341- page_n = None
342352 for g_n ,(g_name ,(g_of ,g_mnems )) in enumerate ( groups .items () ):
343353 for mn_n ,mnem in enumerate ( g_mnems ):
344- p ,(offsetx ,offsety ) = page_xy ( card_n )
345- if p != page_n :
346- pdf .add_page ()
347- page_n = p
348-
349- if double_sided :
350- pdf .add_page ()
351-
354+ p_n ,(p_x ,p_y ) = page_xy ( card_n )
352355 card_n += 1
356+ if p_n == len (page ):
357+ page .append ([])
358+ page [- 1 ].append ( ((p_n ,(p_x ,p_y )), dict (), dict ()) )
353359
354- tpl ['card-title' ] = f"SLIP39 { g_name } ({ mn_n + 1 } /{ len (g_mnems )} ) for: { name } "
355- tpl ['card-requires' ] = requires
356- tpl ['card-crypto1' ] = f"{ accounts [0 ][0 ].crypto } { accounts [0 ][0 ].path } : { accounts [0 ][0 ].address } "
357- tpl ['card-qr1' ] = qr [0 ].get_image ()
360+ _ ,f ,b = page [- 1 ][- 1 ]
361+
362+ f ['card-title' ] = \
363+ b ['card-title' ] = f"SLIP39 { g_name } ({ mn_n + 1 } /{ len (g_mnems )} ) for: { name } "
364+ f ['card-requires' ] = requires
365+ f ['card-crypto1' ] = f"{ accounts [0 ][0 ].crypto } { accounts [0 ][0 ].path } : { accounts [0 ][0 ].address } "
366+ f ['card-qr1' ] = io .BytesIO ( qr_acct [0 ].to_string (encoding = 'unicode' ).encode ('UTF-8' )) # get_image()
358367 if len ( accounts [0 ] ) > 1 :
359- tpl ['card-crypto2' ] = f"{ accounts [0 ][1 ].crypto } { accounts [0 ][1 ].path } : { accounts [0 ][1 ].address } "
360- tpl ['card-qr2' ] = qr [1 ].get_image ()
361- tpl [f'card-g{ g_n } ' ] = f" { g_name :7.7 } .. { mn_n + 1 } " if len ( g_name ) > 8 else f" { g_name } { mn_n + 1 } "
362- tpl [ 'card-link ' ] = 'slip39.com'
368+ f ['card-crypto2' ] = f"{ accounts [0 ][1 ].crypto } { accounts [0 ][1 ].path } : { accounts [0 ][1 ].address } "
369+ f ['card-qr2' ] = io . BytesIO ( qr_acct [1 ].to_string ( encoding = 'unicode' ). encode ( 'UTF-8' )) # get_image()
370+ f [f'card-g{ g_n } ' ] = \
371+ b [ f 'card-g { g_n } ' ] = f" { g_name :7.7 } .. { mn_n + 1 } " if len ( g_name ) > 8 else f" { g_name } { mn_n + 1 } "
363372 if watermark :
364- tpl ['card-watermark' ] = watermark
373+ f ['card-watermark' ] = watermark
365374 for n ,m in enumerate_mnemonic ( mnem ).items ():
366- tpl [f"mnem-{ n } " ] = m
375+ f [f"mnem-{ n } " ] = m
376+
377+ # Back contains QR code of the card's SLIP-39 Mnemonic
378+ qrc = qrcode .QRCode (
379+ version = None ,
380+ error_correction = qrcode .constants .ERROR_CORRECT_M ,
381+ box_size = 10 ,
382+ border = 1 ,
383+ image_factory = qrcode .image .svg .SvgPathImage
384+ )
385+ qrc .add_data ( mnem )
386+ qrc .make ( fit = True )
387+
388+ b ['card-qr-mnem' ] = io .BytesIO ( qrc .make_image ().to_string (encoding = 'unicode' ).encode ('UTF-8' )) # get_image()
367389
368- tpl .render ( offsetx = offsetx , offsety = offsety )
390+ # Now render the front and (optionally) back cards for each page
391+ for p_n ,p in enumerate ( page ):
392+ pdf .add_page ()
393+ for c_n ,((_ ,(p_x ,p_y )),f ,b ) in enumerate ( p ):
394+ for key ,txt in f .items ():
395+ tpl [key ] = txt
396+ tpl .render ( offsetx = p_x , offsety = p_y )
397+ if not double_sided :
398+ continue
399+ pdf .add_page ()
400+ for c_n ,((_ ,(p_x ,p_y )),f ,b ) in enumerate ( p ):
401+ for key ,txt in b .items ():
402+ tpl [key ] = txt
403+ tpl .render ( offsetx = pdf .epw - p_x - card_dim .x , offsety = p_y )
369404
370405 return (paper_format ,orientation ),pdf ,accounts
371406
@@ -392,6 +427,7 @@ def write_pdfs(
392427 wallet_paper = None , # default Wallets to output on Letter format paper,
393428 cover_page = True , # Produce a cover page (including BIP-39 Mnemonic, if using_bip39)
394429 watermark = None , # Any watermark desired on each output
430+ double_sided = None ,
395431):
396432 """Writes a PDF containing a unique SLIP-39 encoded Seed Entropy for each of the names specified.
397433
@@ -494,6 +530,7 @@ def write_pdfs(
494530 orientations = ('portrait' , ) if wallet_pwd else None ,
495531 cover_text = cover_text ,
496532 watermark = watermark ,
533+ double_sided = double_sided ,
497534 )
498535
499536 now = datetime .now ()
0 commit comments