Skip to content

Commit

Permalink
Work through bit-rot (celiagg#128)
Browse files Browse the repository at this point in the history
This should fix recent `pip` source install issues.

* `Cython` version is now unbounded. `celiagg` needed to be
  added to the include directories but that's basically it.
* `numpy` is now used instead of `oldest-supported-numpy`
* `importlib.resources` is now used in place of `pkg_resources`.
  Unfortunately this breaks the API of `celiagg.example_font`.
* Dropped support for Python 3.7
  • Loading branch information
jwiggins authored Jun 17, 2024
1 parent b76a5dc commit 8f1ce91
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 79 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']

runs-on: ${{ matrix.os }}

Expand Down
28 changes: 17 additions & 11 deletions celiagg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#
# Authors: Erik Hvatum <[email protected]>
# John Wiggins
import contextlib
import sys

from . import _celiagg
Expand All @@ -38,21 +39,26 @@
HAS_TEXT = _celiagg.has_text_rendering()


@contextlib.contextmanager
def example_font():
""" Returns the path to a TTF font which is included with the library for
testing purposes.
"""
import pkg_resources

# Windows GDI font selection uses names and not file paths.
# Our included font could be added to the system fonts using
# `AddFontResourceEx`, but that's beyond the scope of this function.
if sys.platform in ('win32', 'cygwin'):
return 'Segoe UI'

return pkg_resources.resource_filename(
'celiagg', 'data/Montserrat-Regular.ttf'
)
try:
# Windows GDI font selection uses names and not file paths.
# Our included font could be added to the system fonts using
# `AddFontResourceEx`, but that's beyond the scope of this function.
if sys.platform in ('win32', 'cygwin'):
yield 'Segoe UI'
else:
import importlib.resources

with importlib.resources.path(
'celiagg.data', 'Montserrat-Regular.ttf'
) as path:
yield str(path)
finally:
pass


# Be explicit
Expand Down
1 change: 1 addition & 0 deletions celiagg/_celiagg.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#
# Authors: Erik Hvatum <[email protected]>

# cython: language_level=3
# distutils: language=c++
from libcpp cimport bool
import cython
Expand Down
Empty file added celiagg/data/__init__.py
Empty file.
17 changes: 9 additions & 8 deletions celiagg/tests/test_canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,15 @@ def test_bad_method_args(self):

if agg.HAS_TEXT:
text = 'Hello!'
font = agg.Font(agg.example_font(), 12.0)
with self.assertRaises(TypeError):
canvas.draw_text(text, None, transform, gs)
with self.assertRaises(TypeError):
canvas.draw_text(text, font, None, gs)
with self.assertRaises(TypeError):
canvas.draw_text(text, font, transform, None)
canvas.draw_text(text, font, transform, gs)
with agg.example_font() as font_path:
font = agg.Font(font_path, 12.0)
with self.assertRaises(TypeError):
canvas.draw_text(text, None, transform, gs)
with self.assertRaises(TypeError):
canvas.draw_text(text, font, None, gs)
with self.assertRaises(TypeError):
canvas.draw_text(text, font, transform, None)
canvas.draw_text(text, font, transform, gs)

def test_stencil_size_mismatch(self):
canvas = agg.CanvasRGB24(np.zeros((4, 5, 3), dtype=np.uint8))
Expand Down
44 changes: 21 additions & 23 deletions celiagg/tests/test_font.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,28 @@
class TestFont(unittest.TestCase):
@unittest.skipIf(is_windows, "Don't test FreeTypeFont on Windows")
def test_freetype_font(self):
path = agg.example_font()
with agg.example_font() as path:
# Test default arguments
font = agg.Font(path, 12.0)
self.assertEqual(font.filepath, path)
self.assertEqual(font.height, 12.0)
self.assertEqual(font.face_index, 0)

# Test default arguments
font = agg.Font(path, 12.0)
self.assertEqual(font.filepath, path)
self.assertEqual(font.height, 12.0)
self.assertEqual(font.face_index, 0)

# Then optional
font = agg.Font(path, 12.0, face_index=42)
self.assertEqual(font.face_index, 42)
# Then optional
font = agg.Font(path, 12.0, face_index=42)
self.assertEqual(font.face_index, 42)

@unittest.skipIf(not is_windows, "Don't test Win32Font on other platforms")
def test_win32_font(self):
face_name = agg.example_font()

# Test default arguments
font = agg.Font(face_name, 12.0)
self.assertEqual(font.face_name, face_name)
self.assertEqual(font.height, 12.0)
self.assertEqual(font.weight, agg.FontWeight.Regular)
self.assertFalse(font.italic)

# Then optional
font = agg.Font(face_name, 12.0, agg.FontWeight.ExtraBold, True)
self.assertEqual(font.weight, agg.FontWeight.ExtraBold)
self.assertTrue(font.italic)
with agg.example_font() as face_name:
# Test default arguments
font = agg.Font(face_name, 12.0)
self.assertEqual(font.face_name, face_name)
self.assertEqual(font.height, 12.0)
self.assertEqual(font.weight, agg.FontWeight.Regular)
self.assertFalse(font.italic)

# Then optional
font = agg.Font(face_name, 12.0, agg.FontWeight.ExtraBold, True)
self.assertEqual(font.weight, agg.FontWeight.ExtraBold)
self.assertTrue(font.italic)
64 changes: 35 additions & 29 deletions celiagg/tests/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,16 @@ def test_font_byte_string(self):
gs = agg.GraphicsState()
transform = agg.Transform()

text_unicode = 'Hello!'
font_unicode = agg.Font(agg.example_font(), 12.0)
text_byte = b'Hello!'
font_byte = agg.Font(agg.example_font().encode('utf8'), 12.0)
with agg.example_font() as font_path:
text_unicode = 'Hello!'
font_unicode = agg.Font(font_path, 12.0)
text_byte = b'Hello!'
font_byte = agg.Font(font_path.encode('utf8'), 12.0)

canvas.draw_text(text_unicode, font_unicode, transform, gs)
canvas.draw_text(text_byte, font_unicode, transform, gs)
canvas.draw_text(text_unicode, font_byte, transform, gs)
canvas.draw_text(text_byte, font_byte, transform, gs)
canvas.draw_text(text_unicode, font_unicode, transform, gs)
canvas.draw_text(text_byte, font_unicode, transform, gs)
canvas.draw_text(text_unicode, font_byte, transform, gs)
canvas.draw_text(text_byte, font_byte, transform, gs)

def test_text_rendering(self):
font_cache = agg.FontCache()
Expand All @@ -52,18 +53,19 @@ def test_text_rendering(self):
)
canvas.clear(1.0, 1.0, 1.0)

font = agg.Font(agg.example_font(), 24.0)
gs = agg.GraphicsState()
paint = agg.SolidPaint(1.0, 0.0, 0.0, 1.0)
transform = agg.Transform()

text = 'Hello!'
width = font_cache.width(font, text)
draw_x, draw_y = 25.0, 50.0
transform.translate(draw_x, draw_y)
cursor = canvas.draw_text(
text, font, transform, gs, stroke=paint, fill=paint
)
with agg.example_font() as font_path:
font = agg.Font(font_path, 24.0)
gs = agg.GraphicsState()
paint = agg.SolidPaint(1.0, 0.0, 0.0, 1.0)
transform = agg.Transform()

text = 'Hello!'
width = font_cache.width(font, text)
draw_x, draw_y = 25.0, 50.0
transform.translate(draw_x, draw_y)
cursor = canvas.draw_text(
text, font, transform, gs, stroke=paint, fill=paint
)
self.assertIsNotNone(cursor)
# Text cursor Y is unchanged, X is `width` pixels away from `draw_x`
self.assertAlmostEqual(cursor[0] - width, draw_x)
Expand All @@ -80,21 +82,25 @@ def test_text_measurement(self):
canvas = agg.CanvasRGB24(
np.zeros((100, 100, 3), dtype=np.uint8), font_cache=font_cache,
)
font = agg.Font(agg.example_font(), 12.0)
gs = agg.GraphicsState()
paint = agg.SolidPaint(1.0, 0.0, 0.0, 1.0)
text = 'Some appropriate string'
transform = agg.Transform()

# Measure before drawing
width_before = font_cache.width(font, text)
with agg.example_font() as font_path:
font = agg.Font(font_path, 12.0)

# Measure before drawing
width_before = font_cache.width(font, text)

# Draw with a transform
transform.translate(25, 75)
transform.rotate(0.75)
canvas.draw_text(text, font, transform, gs, stroke=paint, fill=paint)
# Draw with a transform
transform.translate(25, 75)
transform.rotate(0.75)
canvas.draw_text(
text, font, transform, gs, stroke=paint, fill=paint
)

# Measure after drawing
width_after = font_cache.width(font, text)
# Measure after drawing
width_after = font_cache.width(font, text)

self.assertEqual(width_after, width_before)
4 changes: 2 additions & 2 deletions docs/source/example.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,15 @@ Draw some text
--------------

Next up, some text. We'll use the `Montserrat <https://github.com/JulietaUla/Montserrat>`_ font which is included with
celiagg, conveniently available via the ``example_font`` function. A ``font``
celiagg, conveniently available via the ``example_font`` context manager. A ``font``
object is created with a point size of 96. The ``transform`` gets a
translation of (30, 220) set. This corresponds to a point which is 30 pixels
from the left side of the image and 220 pixels from the *top* of the image.
(celiagg defaults to a top-left origin, but also supports bottom-left origin)
The text will be drawn starting from that point.

.. literalinclude:: simple_ex.py
:lines: 18-20
:lines: 18-21
:linenos:
:lineno-match:

Expand Down
7 changes: 4 additions & 3 deletions docs/source/simple_ex.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
canvas.clear(1.0, 1.0, 1.0)
canvas.draw_shape(path, transform, state, stroke=red_paint)

font = agg.Font(agg.example_font(), 96.0)
transform.translate(30.0, 220.0)
canvas.draw_text("celiagg", font, transform, state, fill=orange_paint)
with agg.example_font() as font_path:
font = agg.Font(font_path, 96.0)
transform.translate(30.0, 220.0)
canvas.draw_text("celiagg", font, transform, state, fill=orange_paint)

image = Image.fromarray(canvas.array, "RGB")
image.save("example.png")
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["cython<3", "oldest-supported-numpy", "setuptools", "wheel"]
requires = ["cython", "numpy", "setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[tool.cibuildwheel]
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ def create_extension():

include_dirs = ['agg-svn/agg-2.4/include',
'agg-svn/agg-2.4',
'celiagg',
numpy.get_include()]
if platform.system() == "Windows":
# Visual studio does not support/need these
Expand Down Expand Up @@ -254,5 +255,5 @@ def create_extension():
package_data={
'celiagg': ['data/*'],
},
python_requires=">=3.7",
python_requires=">=3.8",
)

0 comments on commit 8f1ce91

Please sign in to comment.