)), ("y-align: bottom", layout.SimpleBoxLayoutRule( x_align=layout.AxisAlignment.ALIGN_MID, y_align=layout.AxisAlignment.ALIGN_MIN, )), (f""" y-align: bottom x-align: mid margins: left: 10 right: 10 """, layout.SimpleBoxLayoutRule(x_align=layout.AxisAlignment.ALIGN_MID, y_align=layout.AxisAlignment.ALIGN_MIN, margins=layout.Margins(left=10, right=10))), (f""" y-align: bottom x-align: mid margins: [10, 10, 0, 0] """, layout.SimpleBoxLayoutRule(x_align=layout.AxisAlignment.ALIGN_MID, y_align=layout.AxisAlignment.ALIGN_MIN, margins=layout.Margins(left=10, right=10))), (f""" y-align: bottom x-align: mid inner-content-scaling: none """, layout.SimpleBoxLayoutRule( x_align=layout.AxisAlignment.ALIGN_MID,
def test_read_qr_config(): from pyhanko.pdf_utils.font import SimpleFontEngineFactory from pyhanko.pdf_utils.font.opentype import GlyphAccumulatorFactory from pyhanko_tests.test_text import NOTO_SERIF_JP config_string = f""" stamp-styles: default: text-box-style: font: {NOTO_SERIF_JP} type: qr background: __stamp__ qr-position: right inner-content-layout: y-align: bottom x-align: mid margins: left: 10 right: 10 alternative1: text-box-style: font: {NOTO_SERIF_JP} background: pyhanko_tests/data/img/stamp-indexed.png type: qr alternative2: type: qr background: pyhanko_tests/data/pdf/pdf-background-test.pdf alternative3: type: text wrong-position: type: qr qr-position: bleh """ cli_config: config.CLIConfig = config.parse_cli_config(config_string) default_qr_style = cli_config.get_stamp_style() assert isinstance(default_qr_style, QRStampStyle) assert default_qr_style.background is stamp.STAMP_ART_CONTENT assert isinstance(default_qr_style.text_box_style.font, GlyphAccumulatorFactory) assert default_qr_style.qr_position == stamp.QRPosition.RIGHT_OF_TEXT expected_layout = layout.SimpleBoxLayoutRule( x_align=layout.AxisAlignment.ALIGN_MID, y_align=layout.AxisAlignment.ALIGN_MIN, margins=layout.Margins(left=10, right=10)) assert default_qr_style.inner_content_layout == expected_layout alternative1 = cli_config.get_stamp_style('alternative1') assert isinstance(alternative1, QRStampStyle) assert isinstance(alternative1.background, PdfImage) assert isinstance(alternative1.text_box_style.font, GlyphAccumulatorFactory) assert alternative1.qr_position == stamp.QRPosition.LEFT_OF_TEXT alternative2 = cli_config.get_stamp_style('alternative2') assert isinstance(alternative2, QRStampStyle) assert isinstance(alternative2.background, ImportedPdfPage) assert isinstance(alternative2.text_box_style.font, SimpleFontEngineFactory) alternative3 = cli_config.get_stamp_style('alternative3') assert isinstance(alternative3, TextStampStyle) assert alternative3.background is None assert isinstance(alternative3.text_box_style.font, SimpleFontEngineFactory) with pytest.raises(ConfigurationError, match='not a valid QR position'): cli_config.get_stamp_style('wrong-position') with pytest.raises(ConfigurationError): cli_config.get_stamp_style('theresnosuchstyle')
def _inner_layout_natural_size(self): text_commands, (text_width, text_height) \ = super()._inner_layout_natural_size() qr_ref, natural_qr_size = self._qr_xobject() self.set_resource( category=content.ResourceType.XOBJECT, name=pdf_name('/QR'), value=qr_ref ) style = self.style stamp_box = self.box # To size the QR code, we proceed as follows: # - If qr_inner_size is not None, use it # - If the stamp has a fully defined bbox already, # make sure it fits within the innseps, and it's not too much smaller # than the text box # - Else, scale down by DEFAULT_QR_SCALE and use that value # # Note: if qr_inner_size is defined AND the stamp bbox is available # already, scaling might still take effect depending on the inner layout # rule. innsep = style.innsep if style.qr_inner_size is not None: qr_size = style.qr_inner_size elif stamp_box.width_defined and stamp_box.height_defined: # ensure that the QR code doesn't shrink too much if the text # box is too tall. min_dim = min( max(stamp_box.height, text_height), max(stamp_box.width, text_width) ) qr_size = min_dim - 2 * innsep else: qr_size = int(round(DEFAULT_QR_SCALE * natural_qr_size)) qr_innunits_scale = qr_size / natural_qr_size qr_padded = qr_size + 2 * innsep # Next up: put the QR code and the text box together to get the # inner layout bounding box if style.qr_position.horizontal_flow: inn_width = qr_padded + text_width inn_height = max(qr_padded, text_height) else: inn_width = max(qr_padded, text_width) inn_height = qr_padded + text_height # grab the base layout rule from the QR position setting default_layout: layout.SimpleBoxLayoutRule = style.qr_position.value # Fill in the margins qr_layout_rule = layout.SimpleBoxLayoutRule( x_align=default_layout.x_align, y_align=default_layout.y_align, margins=layout.Margins.uniform(innsep), # There's no point in scaling here, the inner content canvas # is always big enough inner_content_scaling=layout.InnerScaling.NO_SCALING ) inner_box = layout.BoxConstraints(inn_width, inn_height) qr_inn_pos = qr_layout_rule.fit(inner_box, qr_size, qr_size) # we still need to take the axis reversal into account # (which also implies an adjustment in the y displacement) draw_qr_command = b'q %g 0 0 %g %g %g cm /QR Do Q' % ( qr_inn_pos.x_scale * qr_innunits_scale, -qr_inn_pos.y_scale * qr_innunits_scale, qr_inn_pos.x_pos, qr_inn_pos.y_pos + qr_size, ) # Time to put in the text box now if style.qr_position == QRPosition.LEFT_OF_TEXT: tb_margins = layout.Margins( left=qr_padded, right=0, top=0, bottom=0 ) elif style.qr_position == QRPosition.RIGHT_OF_TEXT: tb_margins = layout.Margins( right=qr_padded, left=0, top=0, bottom=0 ) elif style.qr_position == QRPosition.BELOW_TEXT: tb_margins = layout.Margins( bottom=qr_padded, right=0, left=0, top=0 ) else: tb_margins = layout.Margins( top=qr_padded, right=0, left=0, bottom=0 ) tb_layout_rule = layout.SimpleBoxLayoutRule( # flip around the alignment conventions of the default layout # to position the text box on the other side x_align=default_layout.x_align.flipped, y_align=default_layout.y_align.flipped, margins=tb_margins, inner_content_scaling=layout.InnerScaling.NO_SCALING ) # position the text box text_inn_pos = tb_layout_rule.fit(inner_box, text_width, text_height) commands = [draw_qr_command, b'q', text_inn_pos.as_cm()] commands.extend(text_commands) commands.append(b'Q') return commands, (inn_width, inn_height)