150 lines
4.5 KiB
Python
150 lines
4.5 KiB
Python
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()
|
|
)
|