
| Current Path : /var/www/wsgi/www/api/venv/lib64/python3.12/site-packages/pyhanko/pdf_utils/font/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : /var/www/wsgi/www/api/venv/lib64/python3.12/site-packages/pyhanko/pdf_utils/font/basic.py |
from dataclasses import dataclass
from io import BytesIO
from typing import List, Optional
from pyhanko.pdf_utils import generic
from ..writer import BasePdfFileWriter
from .api import FontEngine, FontEngineFactory, ShapeResult
pdf_name = generic.NameObject
__all__ = [
'SimpleFontEngineFactory',
'SimpleFontEngine',
'SimpleFontMeta',
'get_courier',
]
@dataclass(frozen=True)
class SimpleFontMeta:
first_char: int
last_char: int
widths: List[int]
descriptor: generic.DictionaryObject
COURIER_META = SimpleFontMeta(
# only define one width, let everything default to MissingWidth
first_char=32,
last_char=32,
widths=[600],
descriptor=generic.DictionaryObject(
{
pdf_name('/Type'): pdf_name('/FontDescriptor'),
pdf_name('/FontName'): pdf_name('/Courier'),
# set fixed pitch, serif and nonsymbolic
pdf_name('/Flags'): generic.NumberObject(0b100011),
pdf_name('/FontBBox'): generic.ArrayObject(
map(generic.NumberObject, [-23, -250, 715, 805])
),
pdf_name('/Ascent'): generic.NumberObject(629),
pdf_name('/Descent'): generic.NumberObject(-157),
pdf_name('/CapHeight'): generic.NumberObject(629),
pdf_name('/ItalicAngle'): generic.NumberObject(0),
pdf_name('/StemV'): generic.NumberObject(51),
pdf_name('/MissingWidth'): generic.NumberObject(600),
pdf_name('/AvgWidth'): generic.NumberObject(600),
pdf_name('/MaxWidth'): generic.NumberObject(600),
}
),
)
class SimpleFontEngine(FontEngine):
"""
Simplistic font engine that effectively only works with PDF standard fonts,
and does not care about font metrics. Best used with monospaced fonts such
as Courier.
"""
@property
def uses_complex_positioning(self):
return False
def __init__(
self,
writer: BasePdfFileWriter,
name: str,
avg_width: float,
meta: Optional[SimpleFontMeta] = None,
):
self.avg_width = avg_width
self.name = name
self.meta = meta
super().__init__(writer, name, embedded_subset=False)
def shape(self, txt) -> ShapeResult:
ops = BytesIO()
generic.TextStringObject(txt).write_to_stream(ops)
ops.write(b" Tj")
total_len = len(txt) * self.avg_width
return ShapeResult(
graphics_ops=ops.getvalue(), x_advance=total_len, y_advance=0
)
def as_resource(self):
font_dict = generic.DictionaryObject(
{
pdf_name('/Type'): pdf_name('/Font'),
pdf_name('/BaseFont'): pdf_name('/' + self.name),
pdf_name('/Subtype'): pdf_name('/Type1'),
pdf_name('/Encoding'): pdf_name('/WinAnsiEncoding'),
}
)
# TODO maybe we could be a bit more clever to avoid duplication
# (cf. how GlyphAccumulator does it)
meta = self.meta
if meta is not None:
font_dict['/FirstChar'] = generic.NumberObject(meta.first_char)
font_dict['/LastChar'] = generic.NumberObject(meta.last_char)
font_dict['/Widths'] = generic.ArrayObject(
map(generic.NumberObject, meta.widths)
)
font_dict['/FontDescriptor'] = self.writer.add_object(
generic.DictionaryObject(meta.descriptor)
)
return font_dict
class SimpleFontEngineFactory(FontEngineFactory):
def __init__(
self, name: str, avg_width: float, meta: Optional[SimpleFontMeta] = None
):
self.avg_width = avg_width
self.name = name
self.meta = meta
def create_font_engine(self, writer: BasePdfFileWriter, obj_stream=None):
return SimpleFontEngine(writer, self.name, self.avg_width, self.meta)
@staticmethod
def default_factory():
"""
:return:
A :class:`.FontEngineFactory` instance representing the Courier
standard font.
"""
return SimpleFontEngineFactory('Courier', 0.6, COURIER_META)
def get_courier(pdf_writer: BasePdfFileWriter):
"""
Quick-and-dirty way to obtain a Courier font resource.
:param pdf_writer:
A PDF writer.
:return:
A resource dictionary representing the standard Courier font
(or one of its metric equivalents).
"""
return (
SimpleFontEngineFactory.default_factory()
.create_font_engine(pdf_writer)
.as_resource()
)