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_simple_qr_noto_stamp(tmp_path): outfile: Path = tmp_path / "test-out.pdf" ga_factory = GlyphAccumulatorFactory(NOTO_SERIF_JP) qr_stamp_file( MINIMAL_PATH, str(outfile), QRStampStyle(stamp_text="Hi, it's\n%(ts)s", text_box_style=TextBoxStyle(font=ga_factory)), dest_page=0, x=70, y=50, url='https://example.com', )
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 _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
class TextStampStyle(BaseStampStyle): """ Style for text-based stamps. Roughly speaking, this stamp type renders some predefined (but parametrised) piece of text inside a text box, and possibly applies a background to it. """ text_box_style: TextBoxStyle = TextBoxStyle() """ The text box style for the internal text box used. """ inner_content_layout: layout.SimpleBoxLayoutRule = None """ Rule determining the position and alignment of the inner text box within the stamp. .. warning:: This only affects the position of the box, not the alignment of the text within. """ stamp_text: str = '%(ts)s' """ Text template for the stamp. The template can contain an interpolation parameter ``ts`` that will be replaced by the stamping time. Additional parameters may be added if necessary. Values for these must be passed to the :meth:`~.TextStamp.__init__` method of the :class:`.TextStamp` class in the ``text_params`` argument. """ timestamp_format: str = '%Y-%m-%d %H:%M:%S %Z' """ Datetime format used to render the timestamp. """ def create_stamp(self, writer: BasePdfFileWriter, box: layout.BoxConstraints, text_params: dict) \ -> 'TextStamp': return TextStamp(writer=writer, style=self, box=box, text_params=text_params)
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 process_entries(cls, config_dict): """ The implementation of :meth:`process_entries` calls :meth:`.TextBoxStyle.from_config` to parse the ``text_box_style`` configuration entry, if present. Then, it processes the background specified. This can either be a path to an image file, in which case it will be turned into an instance of :class:`~.pdf_utils.images.PdfImage`, or the special value ``__stamp__``, which is an alias for :const:`~pyhanko.stamp.STAMP_ART_CONTENT`. See :meth:`.ConfigurableMixin.process_entries` for general documentation about this method. """ super().process_entries(config_dict) try: tbs = config_dict['text_box_style'] config_dict['text_box_style'] \ = TextBoxStyle.from_config(tbs) except KeyError: pass try: bg_spec = config_dict['background'] # 'special' value to use the stamp vector image baked into # the module if bg_spec == '__stamp__': config_dict['background'] = STAMP_ART_CONTENT elif isinstance(bg_spec, str): from pyhanko.pdf_utils.images import PdfImage from PIL import Image img = Image.open(bg_spec) # Setting the writer can be delayed config_dict['background'] = PdfImage(img, writer=None) except KeyError: pass
class TextStampStyle(ConfigurableMixin): """ Style for text-based stamps. Roughly speaking, this stamp type renders some predefined (but parametrised) piece of text inside a text box, and possibly applies a background to it. """ text_box_style: TextBoxStyle = TextBoxStyle() """ The text box style for the internal text box used. """ border_width: int = 3 """ Border width in user units (for the stamp, not the text box). """ stamp_text: str = '%(ts)s' """ Text template for the stamp. The template can contain an interpolation parameter ``ts`` that will be replaced by the stamping time. Additional parameters may be added if necessary. Values for these must be passed to the :meth:`~.TextStamp.__init__` method of the :class:`.TextStamp` class in the ``text_params`` argument. """ timestamp_format: str = '%Y-%m-%d %H:%M:%S %Z' """ Datetime format used to render the timestamp. """ background: PdfContent = None """ :class:`~.pdf_utils.content.PdfContent` instance that will be used to render the stamp's background. """ background_opacity: float = 0.6 """ Opacity value to render the background at. This should be a floating-point number between `0` and `1`. """ @classmethod def process_entries(cls, config_dict): """ The implementation of :meth:`process_entries` calls :meth:`.TextBoxStyle.from_config` to parse the ``text_box_style`` configuration entry, if present. Then, it processes the background specified. This can either be a path to an image file, in which case it will be turned into an instance of :class:`~.pdf_utils.images.PdfImage`, or the special value ``__stamp__``, which is an alias for :const:`~pyhanko.stamp.STAMP_ART_CONTENT`. See :meth:`.ConfigurableMixin.process_entries` for general documentation about this method. """ super().process_entries(config_dict) try: tbs = config_dict['text_box_style'] config_dict['text_box_style'] \ = TextBoxStyle.from_config(tbs) except KeyError: pass try: bg_spec = config_dict['background'] # 'special' value to use the stamp vector image baked into # the module if bg_spec == '__stamp__': config_dict['background'] = STAMP_ART_CONTENT elif isinstance(bg_spec, str): from pyhanko.pdf_utils.images import PdfImage from PIL import Image img = Image.open(bg_spec) # Setting the writer can be delayed config_dict['background'] = PdfImage(img, writer=None) except KeyError: pass