Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions tex2pdf-service/tex2pdf/fonts/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The font and related files in this directory are distributed under the
GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (see the file COPYING), with
the following exemption:

As a special exception, permission is granted to include these font
programs in a Postscript or PDF file that consists of a document that
contains text to be displayed or printed using this font, regardless
of the conditions or license applying to the document itself.

Binary file not shown.
31 changes: 24 additions & 7 deletions tex2pdf-service/tex2pdf/pdf_watermark.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@
# Resolution used to measure the true glyph extent of the rendered watermark.
_INK_DPI = 150

# Fallback font embedded when the caller passes no custom font. A *self-contained*
# face is mandatory for reproducible output (see _build_watermark_overlay): the
# base-14 "Times-Roman" has no font program, so pdf_oxide would rasterize the
# glyph-extent measurement with whatever font it finds on the host, making the
# stamp placement differ from machine to machine. Nimbus Roman is URW's
# metrically-Times-compatible face (the Ghostscript default Times substitute),
# licensed AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817, which permits
# embedding and redistribution. See tex2pdf/fonts/LICENSE.
_BUNDLED_FONT = Path(__file__).resolve().parent / "fonts" / "NimbusRoman-Regular.otf"


class WatermarkError(Exception):
"""Custom exception for watermark errors."""
Expand Down Expand Up @@ -126,14 +136,21 @@ def _build_watermark_overlay(
page_height = fsize * 1.8
baseline = 0.4 * fsize

# Without a caller-supplied font, embed the bundled Nimbus Roman rather than
# the base-14 "Times-Roman": the latter has no font program, so the ink
# measurement below would be rasterized with an arbitrary host font and the
# stamp would land in a slightly different place on every machine. Always
# embedding a self-contained face keeps the output byte-reproducible and
# makes the stamp render identically in every PDF viewer.
if not font_file:
font_file = str(_BUNDLED_FONT)

builder = pdf_oxide.DocumentBuilder()
font_name = "Times-Roman"
if font_file:
# Use the font file stem as the recorded PostScript name (pdf_oxide does
# not reliably parse it from the face) and subset on build().
embedded = pdf_oxide.EmbeddedFont.from_bytes(Path(font_file).read_bytes(), Path(font_file).stem)
font_name = _WATERMARK_FONT_NAME
builder.register_embedded_font(font_name, embedded)
# Use the font file stem as the recorded PostScript name (pdf_oxide does not
# reliably parse it from the face) and subset on build().
embedded = pdf_oxide.EmbeddedFont.from_bytes(Path(font_file).read_bytes(), Path(font_file).stem)
font_name = _WATERMARK_FONT_NAME
builder.register_embedded_font(font_name, embedded)

page = builder.page(page_width, page_height) # type: ignore[call-arg, arg-type]
page.font(font_name, fsize).at(0.0, baseline).inline_color(rgb[0], rgb[1], rgb[2], text)
Expand Down
Loading