diff --git a/slip39/layout/components.py b/slip39/layout/components.py index 61f770a..e35a97b 100644 --- a/slip39/layout/components.py +++ b/slip39/layout/components.py @@ -213,6 +213,13 @@ def element( self ): Coordinate = namedtuple( 'Coordinate', ('x', 'y') ) +# A SLIP-39 mnemonic cell is eg. "12 advocate": up to MNEM_CHARS (2 digits, a space, an 8-letter word) +# of monospaced font, each MONO_WIDTH of the font size wide; fit within MNEM_FIT of the column width. +MNEM_CHARS = 11 +MONO_WIDTH = .6 +MNEM_FIT = .9 + + def layout_card( card_size: Coordinate, card_margin: int, @@ -314,6 +321,17 @@ def layout_card( assert num_mnemonics in MNEM_ROWS_COLS, \ f"Invalid SLIP-39 mnemonic word count: {num_mnemonics}" rows,cols = MNEM_ROWS_COLS[num_mnemonics] + + def mnem_size( region, size_ratio ): + """The mnemonic font size (points): from the height of each row, unless that is too wide for + the longest "NN word" (MNEM_CHARS mono characters) to fit in the width of each column.""" + return min( + region.h / rows * PT_IN * size_ratio, + region.w / cols * PT_IN * MNEM_FIT / ( MNEM_CHARS * MONO_WIDTH ), + ) + + mnem_fontsize = mnem_size( card_mnemonics, 9/16 ) + mnem_fontsize_big = mnem_size( card_mnemonics_big, 12/16 ) for r in range( rows ): for c in range( cols ): card_mnemonics.add_region_proportional( @@ -325,7 +343,7 @@ def layout_card( y2 = (r+1)/rows, font = 'mono', bold = True, - size_ratio = 9/16, + size = mnem_fontsize, ) ) card_mnemonics_big.add_region_proportional( @@ -337,7 +355,7 @@ def layout_card( y2 = (r+1)/rows, font = 'mono', bold = True, - size_ratio = 12/16, + size = mnem_fontsize_big, ) ) diff --git a/slip39/output_test.py b/slip39/output_test.py index 903c0b4..68f9065 100644 --- a/slip39/output_test.py +++ b/slip39/output_test.py @@ -4,7 +4,8 @@ from fpdf import FPDF, FlexTemplate from .layout import Region, Text, Image, Box, Coordinate -from .defaults import MM_IN +from .layout.components import layout_card, MNEM_CHARS, MONO_WIDTH +from .defaults import MM_IN, PT_IN, CARD_SIZES, MNEM_ROWS_COLS def test_Region(): @@ -73,3 +74,15 @@ def test_Region(): tpl.render( offsetx = card_size.x * MM_IN, offsety = card_size.y * MM_IN ) #pdf.output( "test.pdf" ) # To view results in test.pdf, uncomment + + +def test_card_mnemonics_fit(): + """The longest mnemonic cell ("12 advocate") must fit within its column, for every card size.""" + for card_size,((card_h,card_w),card_margin) in CARD_SIZES.items(): + for num_mnemonics in MNEM_ROWS_COLS: + card = layout_card( Coordinate( y=card_h, x=card_w ), card_margin, num_mnemonics ) + for e in card.elements(): + if e['name'].startswith( 'mnem-' ): + cell_pt = ( e['x2'] - e['x1'] ) / MM_IN * PT_IN + assert e['size'] * MONO_WIDTH * MNEM_CHARS <= cell_pt, \ + f"{card_size} card, {num_mnemonics} words: {e['name']} {e['size']:.2f}pt too big for a {cell_pt:.1f}pt column"