Skip to content

Commit

Permalink
Reducing fonttools memory usage by 10MiB by passing lazy=True to TTFo…
Browse files Browse the repository at this point in the history
…nt constructor
  • Loading branch information
Lucas-C committed Mar 10, 2023
1 parent f8de21c commit efb668a
Show file tree
Hide file tree
Showing 15 changed files with 28 additions and 23 deletions.
2 changes: 1 addition & 1 deletion fpdf/fpdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1798,7 +1798,7 @@ def add_font(self, family=None, style="", fname=None, uni="DEPRECATED"):
if fontkey in self.fonts or fontkey in self.core_fonts:
warnings.warn(f"Core font or font already added '{fontkey}': doing nothing")
return
font = ttLib.TTFont(font_file_path, fontNumber=0)
font = ttLib.TTFont(font_file_path, fontNumber=0, lazy=True)

scale = 1000 / font["head"].unitsPerEm
default_width = round(scale * font["hmtx"].metrics[".notdef"][0])
Expand Down
2 changes: 1 addition & 1 deletion fpdf/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ def _add_fonts(self):
# recalcTimestamp=False means that it doesn't modify the "modified" timestamp in head table
# if we leave recalcTimestamp=True the tests will break every time
fonttools_font = ttLib.TTFont(
file=font["ttffile"], recalcTimestamp=False, fontNumber=0
file=font["ttffile"], recalcTimestamp=False, fontNumber=0, lazy=True
)

# 1. get all glyphs in PDF
Expand Down
1 change: 0 additions & 1 deletion test/end_to_end_legacy/__init__.py

This file was deleted.

5 changes: 0 additions & 5 deletions test/end_to_end_legacy/charmap/__init__.py

This file was deleted.

Binary file added test/fonts/charmap_first_999_chars-NotoSans.pdf
Binary file not shown.
Binary file not shown.
Binary file added test/fonts/charmap_first_999_chars-NotoSerif.pdf
Binary file not shown.
Binary file not shown.
Binary file added test/fonts/charmap_first_999_chars-Waree.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,45 @@
"""
from pathlib import Path

from fontTools.ttLib import TTFont
import pytest

import fpdf
from fontTools import ttLib
from fpdf import FPDF
from test.conftest import assert_pdf_equal

HERE = Path(__file__).resolve().parent


@pytest.mark.parametrize(
"font_filename",
["DejaVuSans.ttf", "DroidSansFallback.ttf", "Roboto-Regular.ttf", "cmss12.ttf"],
[
font_file.name
for font_file in HERE.glob("*.*tf")
if not any(
exclude in font_file.stem
for exclude in ("Bold", "Italic", "NotoColorEmoji")
)
],
)
def test_first_999_chars(font_filename, tmp_path):
font_path = HERE / ".." / ".." / "fonts" / font_filename
def test_charmap_first_999_chars(font_filename, tmp_path):
"""
Character Map Test
from PyFPDF version 1.7.2: github.com/reingart/pyfpdf/commit/2eab310cfd866ce24947c3a9d850ebda7c6d515d
"""
font_path = HERE / font_filename
font_name = font_path.stem

pdf = fpdf.FPDF()
pdf = FPDF()
pdf.add_page()
pdf.add_font(font_name, fname=font_path)
pdf.add_font(fname=font_path)
pdf.set_font(font_name, size=10)

font = ttLib.TTFont(font_path)
font = TTFont(font_path, lazy=True)
cmap = font.getBestCmap()

# Create a PDF with the first 999 charters defined in the font:
for counter, character in enumerate(cmap, 0):
# Create a PDF with the first 999 characters defined in the font:
for counter, character in enumerate(list(cmap.keys())[:1000]):
pdf.write(8, f"{counter:03}) {character:03x} - {character:c}", print_sh=True)
pdf.ln()
if counter >= 999:
break

assert_pdf_equal(pdf, HERE / f"charmap_first_999_chars-{font_name}.pdf", tmp_path)
8 changes: 5 additions & 3 deletions test/test_perfs.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from pathlib import Path

import memunit, pytest
import pytest

from fpdf import FPDF

HERE = Path(__file__).resolve().parent


@pytest.mark.timeout(40)
# ensure memory usage does not get too high - this value depends on Python version:
@memunit.assert_lt_mb(185)
# Note: this does not combine well with memunit.
# memory_profiler.MemTimer does not terminate properly when a signal is raised by pytest-timeout,
# and as a consequence multiprocessing.util._exit_function becomes blocking at the end of Pytest execution,
# (on line "calling join() for process MemTimer-1")
def test_intense_image_rendering():
png_file_paths = []
for png_file_path in (HERE / "image/png_images/").glob("*.png"):
Expand Down

0 comments on commit efb668a

Please sign in to comment.