Skip to content
Merged
2 changes: 2 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
toxenv: py314
- python-version: "3.x"
toxenv: no-flask-caching
- python-version: "3.12"
toxenv: cairo

steps:
- uses: actions/checkout@v6
Expand Down
4 changes: 2 additions & 2 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ Installing Graphite-Render requires:

* Python 3 (3.9 and above).

* Cairo. On debian/ubuntu, install the ``libcairo2`` package.

* Pip, the Python package manager. On debian/ubuntu, install ``python-pip``.

* For image rendering (PNG/SVG/PDF graphs): Cairo. On debian/ubuntu, install the ``libcairo2`` package.

Global installation
-------------------

Expand Down
49 changes: 33 additions & 16 deletions graphite_render/render/glyph.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,27 @@
from urllib.parse import unquote_plus
from zoneinfo import ZoneInfo

import cairocffi as cairo

from .datalib import TimeSeries
from ..utils import to_seconds

# Lazy import for cairocffi - only loaded when actually used in the render pipeline
cairo = None


def _get_cairo():
"""Lazily import cairocffi when needed for rendering."""
global cairo
if cairo is None:
try:
import cairocffi as cairo_module
cairo = cairo_module
except ImportError as e:
raise ImportError(
"cairocffi is required for image rendering but is not installed. "
"Install it with: pip install cairocffi"
) from e
return cairo


INFINITY = float('inf')

Expand Down Expand Up @@ -737,7 +753,7 @@ def __init__(self, **params):
self.loadTemplate(params.get('template', 'default'))

opts = self.ctx.get_font_options()
opts.set_antialias(cairo.ANTIALIAS_NONE)
opts.set_antialias(self.cairo.ANTIALIAS_NONE)
self.ctx.set_font_options(opts)

self.foregroundColor = params.get('fgcolor', self.defaultForeground)
Expand All @@ -755,22 +771,23 @@ def __init__(self, **params):

def setupCairo(self, outputFormat='png'):
self.outputFormat = outputFormat
self.cairo = _get_cairo()
if outputFormat == 'png':
self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
self.width, self.height)
self.surface = self.cairo.ImageSurface(self.cairo.FORMAT_ARGB32,
self.width, self.height)
elif outputFormat == 'svg':
self.surfaceData = BytesIO()
self.surface = cairo.SVGSurface(self.surfaceData,
self.width, self.height)
self.surface = self.cairo.SVGSurface(self.surfaceData,
self.width, self.height)
elif outputFormat == 'pdf':
self.surfaceData = BytesIO()
self.surface = cairo.PDFSurface(self.surfaceData,
self.width, self.height)
self.surface = self.cairo.PDFSurface(self.surfaceData,
self.width, self.height)
res_x, res_y = self.surface.get_fallback_resolution()
self.width = float(self.width / res_x) * 72
self.height = float(self.height / res_y) * 72
self.surface.set_size(self.width, self.height)
self.ctx = cairo.Context(self.surface)
self.ctx = self.cairo.Context(self.surface)

def setColor(self, value, alpha=1.0, forceAlpha=False):
if isinstance(value, tuple) and len(value) == 3:
Expand Down Expand Up @@ -1395,14 +1412,14 @@ def drawLines(self, width=None, dash=None, linecap='butt',
else:
self.ctx.set_dash([], 0)
self.ctx.set_line_cap({
'butt': cairo.LINE_CAP_BUTT,
'round': cairo.LINE_CAP_ROUND,
'square': cairo.LINE_CAP_SQUARE,
'butt': self.cairo.LINE_CAP_BUTT,
'round': self.cairo.LINE_CAP_ROUND,
'square': self.cairo.LINE_CAP_SQUARE,
}[linecap])
self.ctx.set_line_join({
'miter': cairo.LINE_JOIN_MITER,
'round': cairo.LINE_JOIN_ROUND,
'bevel': cairo.LINE_JOIN_BEVEL,
'miter': self.cairo.LINE_JOIN_MITER,
'round': self.cairo.LINE_JOIN_ROUND,
'bevel': self.cairo.LINE_JOIN_BEVEL,
}[linejoin])

# check whether there is an stacked metric
Expand Down
4 changes: 2 additions & 2 deletions tests/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def test_cors(self):
'Access-Control-Allow-Origin' in response.headers)

def test_trailing_slash(self):
response = self.app.get('/render?target=foo')
response = self.app.get('/render?target=foo&format=json')
self.assertEqual(response.status_code, 200)

response = self.app.get('/render/?target=foo')
response = self.app.get('/render/?target=foo&format=json')
self.assertEqual(response.status_code, 200)
Loading