def test_four_qr_stamps(fixed_size): # Share a font subset, the text is the same everywhere gaf = GlyphAccumulatorFactory(NOTO_SANS, font_size=10) w = empty_page() positions = ((10, 700), (10, 500), (10, 10), (260, 10)) for qr_pos, (x, y) in zip(QRPosition, positions): style = QRStampStyle( stamp_text='Test stamp text\nAnother line of text', text_box_style=TextBoxStyle(font=gaf), qr_position=qr_pos, background=STAMP_ART_CONTENT, background_opacity=0.4 ) if fixed_size: if qr_pos.horizontal_flow: box = layout.BoxConstraints(width=300, height=100) else: box = layout.BoxConstraints(width=100, height=300) else: box = None ts = QRStamp( writer=w, style=style, box=box, url='https://example.com' ) ts.apply(0, x=x, y=y) postfix = 'fixed' if fixed_size else 'natural' compare_output(w, f'{EXPECTED_OUTPUT_DIR}/four-stamps-{postfix}.pdf')
def test_stamp_with_fixed_pdf_content(): w = empty_page() style = StaticStampStyle.from_pdf_file( 'pyhanko_tests/data/pdf/pdf-background-test.pdf') stamp = style.create_stamp(w, box=layout.BoxConstraints(200, 50), text_params={}) stamp.apply(0, x=30, y=600) compare_output(w, f'{EXPECTED_OUTPUT_DIR}/stamp-from-static-pdf.pdf')
def test_simple_text_stamp_string_keyed_font(): gaf = GlyphAccumulatorFactory(FREE_SERIF, font_size=10) w = empty_page() style = TextStampStyle( stamp_text=("Hi, this is a string-keyed font test.\n" "This should have a ligature: difficult"), text_box_style=TextBoxStyle(font=gaf), ) ts = TextStamp(w, style, box=layout.BoxConstraints(200, 50)) ts.apply(dest_page=0, x=70, y=50) compare_output(w, f'{EXPECTED_OUTPUT_DIR}/freeserif-test.pdf')
def test_stamp_with_scaled_bitmap_bg(): w = empty_page() text = '\n'.join('Test test test test on a bitmap background!' for _ in range(3)) style = TextStampStyle( stamp_text=text, background=PdfImage('pyhanko_tests/data/img/stamp-indexed.png'), ) ts = TextStamp(w, style, box=layout.BoxConstraints(400, 100)) ts.apply(0, x=30, y=600) compare_output(w, f'{EXPECTED_OUTPUT_DIR}/scaled-bitmap-bg.pdf')
def test_stamp_with_scaled_pdf_bg(): w = empty_page() text = '\n'.join('Test test test test on a PDF background!' for _ in range(3)) style = TextStampStyle( stamp_text=text, background=ImportedPdfPage( 'pyhanko_tests/data/pdf/pdf-background-test.pdf'), ) ts = TextStamp(w, style, box=layout.BoxConstraints(200, 50)) ts.apply(0, x=30, y=600) compare_output(w, f'{EXPECTED_OUTPUT_DIR}/stamp-on-pdf-bg.pdf')
def _arabic_text_page(stream_xrefs): w = empty_page(stream_xrefs=stream_xrefs) style = TextStampStyle( stamp_text='اَلْفُصْحَىٰ', text_box_style=TextBoxStyle( font=GlyphAccumulatorFactory(NOTO_SANS_ARABIC), ), inner_content_layout=layout.SimpleBoxLayoutRule( x_align=layout.AxisAlignment.ALIGN_MID, y_align=layout.AxisAlignment.ALIGN_MID, inner_content_scaling=layout.InnerScaling.STRETCH_TO_FIT, margins=layout.Margins.uniform(5))) ts = TextStamp(writer=w, style=style, box=layout.BoxConstraints(width=300, height=200)) ts.apply(0, x=10, y=60) return w
def test_japanese_vertical_text_stamp(): gaf = GlyphAccumulatorFactory(NOTO_SERIF_JP, font_size=10, writing_direction='ttb') w = empty_page() style = QRStampStyle(stamp_text=('テスト\n縦書きテスト\n改行してみましょう(括弧)\nPDF\n' 'ちょっと長めの文を書いてみた。'), text_box_style=TextBoxStyle(font=gaf, vertical_text=True), qr_position=QRPosition.ABOVE_TEXT, background=STAMP_ART_CONTENT, background_opacity=0.4) box = layout.BoxConstraints(width=100, height=300) ts = QRStamp(writer=w, style=style, box=box, url='https://example.com') ts.apply(0, x=10, y=415) ts = QRStamp(writer=w, style=style, box=None, url='https://example.com') ts.apply(0, x=400, y=415) compare_output(w, f'{EXPECTED_OUTPUT_DIR}/ja-vert-stamps.pdf')
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)
:param y: Vertical position of the stamp's lower left corner on the page. :param url: URL for the QR code to point to. :param text_params: Additional parameters for text template interpolation. """ _stamp_file( input_name, output_name, style, QRStamp, dest_page, x, y, url=url, text_params=text_params ) STAMP_ART_CONTENT = content.RawContent( box=layout.BoxConstraints(width=100, height=100), data=b''' q 1 0 0 -1 0 100 cm 0.603922 0.345098 0.54902 rg 3.699 65.215 m 3.699 65.215 2.375 57.277 7.668 51.984 c 12.957 46.695 27.512 49.34 39.418 41.402 c 39.418 41.402 31.48 40.078 32.801 33.465 c 34.125 26.852 39.418 28.172 39.418 24.203 c 39.418 20.234 30.156 17.59 30.156 14.945 c 30.156 12.297 28.465 1.715 50 1.715 c 71.535 1.715 69.844 12.297 69.844 14.945 c 69.844 17.59 60.582 20.234 60.582 24.203 c 60.582 28.172 65.875 26.852 67.199 33.465 c 68.52 40.078 60.582 41.402 60.582 41.402 c 72.488 49.34 87.043 46.695 92.332 51.984 c 97.625 57.277 96.301 65.215 96.301 65.215 c h f 3.801 68.734 92.398 7.391 re f 3.801 79.512 92.398 7.391 re f 3.801 90.289 92.398 7.391 re f Q
w = empty_page() style = StaticStampStyle.from_pdf_file( 'pyhanko_tests/data/pdf/pdf-background-test.pdf') stamp = style.create_stamp(w, box=layout.BoxConstraints(200, 50), text_params={}) stamp.apply(0, x=30, y=600) compare_output(w, f'{EXPECTED_OUTPUT_DIR}/stamp-from-static-pdf.pdf') @pytest.mark.parametrize('box', [ None, layout.BoxConstraints(width=200), layout.BoxConstraints(height=50), layout.BoxConstraints(aspect_ratio=Fraction(4, 1)), layout.BoxConstraints() ]) def test_static_stamp_enforce_box_defined(box): w = empty_page() style = StaticStampStyle.from_pdf_file( 'pyhanko_tests/data/pdf/pdf-background-test.pdf') with pytest.raises(layout.LayoutError, match="predetermined bounding box"): style.create_stamp(w, box=box, text_params={}) @with_layout_comparison
:param text_params: Additional parameters for text template interpolation. """ _stamp_file(input_name, output_name, style, QRStamp, dest_page, x, y, url=url, text_params=text_params) STAMP_ART_CONTENT = content.RawContent(box=layout.BoxConstraints(width=100, height=100), data=b''' q 1 0 0 -1 0 100 cm 0.603922 0.345098 0.54902 rg 3.699 65.215 m 3.699 65.215 2.375 57.277 7.668 51.984 c 12.957 46.695 27.512 49.34 39.418 41.402 c 39.418 41.402 31.48 40.078 32.801 33.465 c 34.125 26.852 39.418 28.172 39.418 24.203 c 39.418 20.234 30.156 17.59 30.156 14.945 c 30.156 12.297 28.465 1.715 50 1.715 c 71.535 1.715 69.844 12.297 69.844 14.945 c 69.844 17.59 60.582 20.234 60.582 24.203 c 60.582 28.172 65.875 26.852 67.199 33.465 c 68.52 40.078 60.582 41.402 60.582 41.402 c 72.488 49.34 87.043 46.695 92.332 51.984 c 97.625 57.277 96.301 65.215 96.301 65.215 c h f 3.801 68.734 92.398 7.391 re f 3.801 79.512 92.398 7.391 re f 3.801 90.289 92.398 7.391 re f Q