2011-12-08 19:31:03 +04:00
|
|
|
|
# coding: utf8
|
2012-03-22 02:19:27 +04:00
|
|
|
|
"""
|
|
|
|
|
weasyprint.images
|
|
|
|
|
-----------------
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
Fetch and decode images in various formats.
|
|
|
|
|
|
|
|
|
|
:copyright: Copyright 2011-2012 Simon Sapin and contributors, see AUTHORS.
|
|
|
|
|
:license: BSD, see LICENSE for details.
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2012-02-17 21:49:58 +04:00
|
|
|
|
from __future__ import division, unicode_literals
|
|
|
|
|
|
|
|
|
|
from io import BytesIO
|
2012-02-22 17:13:14 +04:00
|
|
|
|
import contextlib
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
|
|
|
|
import cairo
|
|
|
|
|
|
|
|
|
|
from .utils import urlopen
|
2012-01-12 22:26:27 +04:00
|
|
|
|
from .css.computed_values import LENGTHS_TO_PIXELS
|
2012-02-22 20:12:40 +04:00
|
|
|
|
from .logger import LOGGER
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
|
|
|
|
|
2011-12-08 21:11:32 +04:00
|
|
|
|
# Map MIME types to functions that take a byte stream and return
|
2012-01-13 21:16:27 +04:00
|
|
|
|
# ``(pattern, width, height)`` a cairo Pattern and its dimension in pixels.
|
2011-12-08 21:11:32 +04:00
|
|
|
|
FORMAT_HANDLERS = {}
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
2011-12-09 13:52:49 +04:00
|
|
|
|
# TODO: currently CairoSVG only support images with an explicit
|
|
|
|
|
# width and height. When it supports images with only an intrinsic ratio
|
|
|
|
|
# this API will need to change.
|
|
|
|
|
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
|
|
|
|
def register_format(mime_type):
|
|
|
|
|
"""Register a handler for a give image MIME type."""
|
2011-12-26 14:48:40 +04:00
|
|
|
|
def _decorator(function):
|
2011-12-08 21:11:32 +04:00
|
|
|
|
FORMAT_HANDLERS[mime_type] = function
|
2011-12-08 19:31:03 +04:00
|
|
|
|
return function
|
2011-12-26 14:48:40 +04:00
|
|
|
|
return _decorator
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@register_format('image/png')
|
2012-01-11 21:43:45 +04:00
|
|
|
|
def png_handler(file_like, _uri):
|
2011-12-08 19:31:03 +04:00
|
|
|
|
"""Return a cairo Surface from a PNG byte stream."""
|
2011-12-08 21:11:32 +04:00
|
|
|
|
surface = cairo.ImageSurface.create_from_png(file_like)
|
2012-01-13 21:16:27 +04:00
|
|
|
|
pattern = cairo.SurfacePattern(surface)
|
|
|
|
|
return pattern, surface.get_width(), surface.get_height()
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
|
|
|
|
|
2011-12-08 21:25:19 +04:00
|
|
|
|
@register_format('image/svg+xml')
|
2012-01-11 21:43:45 +04:00
|
|
|
|
def cairosvg_handler(file_like, uri):
|
2011-12-08 21:25:19 +04:00
|
|
|
|
"""Return a cairo Surface from a SVG byte stream.
|
|
|
|
|
|
|
|
|
|
This handler uses CairoSVG: http://cairosvg.org/
|
|
|
|
|
"""
|
2012-01-16 14:39:02 +04:00
|
|
|
|
from cairosvg.surface import SVGSurface
|
2011-12-19 18:04:17 +04:00
|
|
|
|
from cairosvg.parser import Tree, ParseError
|
2012-01-17 19:27:20 +04:00
|
|
|
|
|
|
|
|
|
class ScaledSVGSurface(SVGSurface):
|
|
|
|
|
"""
|
|
|
|
|
Have the cairo Surface object have intrinsic dimension
|
|
|
|
|
in pixels instead of points.
|
|
|
|
|
"""
|
|
|
|
|
@property
|
|
|
|
|
def device_units_per_user_units(self):
|
|
|
|
|
scale = super(ScaledSVGSurface, self).device_units_per_user_units
|
|
|
|
|
return scale * LENGTHS_TO_PIXELS['pt']
|
|
|
|
|
|
2012-02-21 15:59:06 +04:00
|
|
|
|
if uri.startswith('data:'):
|
|
|
|
|
# Don’t pass data URIs to CairoSVG.
|
|
|
|
|
# They are useless for relative URIs anyway.
|
|
|
|
|
uri = None
|
2012-03-21 17:34:27 +04:00
|
|
|
|
# Draw to a cairo surface but do not write to a file
|
|
|
|
|
tree = Tree(file_obj=file_like, url=uri)
|
|
|
|
|
surface = ScaledSVGSurface(tree, output=None, dpi=96)
|
2012-01-13 21:16:27 +04:00
|
|
|
|
pattern = cairo.SurfacePattern(surface.cairo)
|
2012-01-17 19:27:20 +04:00
|
|
|
|
return pattern, surface.width, surface.height
|
2012-01-12 22:26:27 +04:00
|
|
|
|
|
|
|
|
|
|
2012-01-11 21:43:45 +04:00
|
|
|
|
def fallback_handler(file_like, uri):
|
2011-12-08 19:31:03 +04:00
|
|
|
|
"""
|
|
|
|
|
Parse a byte stream with PIL and return a cairo Surface.
|
|
|
|
|
|
|
|
|
|
PIL supports many raster image formats and does not take a `format`
|
|
|
|
|
parameter, it guesses the format from the content.
|
|
|
|
|
"""
|
2012-02-23 16:18:51 +04:00
|
|
|
|
from pystacia import read_blob
|
2012-02-23 16:26:45 +04:00
|
|
|
|
from pystacia.util import TinyException
|
2012-03-21 17:34:27 +04:00
|
|
|
|
with contextlib.closing(read_blob(file_like.read())) as image:
|
|
|
|
|
png_bytes = image.get_blob('png')
|
|
|
|
|
return png_handler(BytesIO(png_bytes), uri)
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
|
|
|
|
|
2012-02-29 20:38:30 +04:00
|
|
|
|
def get_image_from_uri(uri, type_=None):
|
2011-12-08 19:31:03 +04:00
|
|
|
|
"""Get a :class:`cairo.Surface`` from an image URI."""
|
|
|
|
|
try:
|
|
|
|
|
file_like, mime_type, _charset = urlopen(uri)
|
2012-03-21 17:34:27 +04:00
|
|
|
|
if not type_:
|
|
|
|
|
type_ = mime_type # Use eg. the HTTP header
|
|
|
|
|
#else: the type was forced by eg. a 'type' attribute on <embed>
|
|
|
|
|
handler = FORMAT_HANDLERS.get(type_, fallback_handler)
|
|
|
|
|
return handler(file_like, uri)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
LOGGER.warn('Error for image at %s : %r', uri, exc)
|
2011-12-08 19:31:03 +04:00
|
|
|
|
finally:
|
2012-02-27 19:48:27 +04:00
|
|
|
|
try:
|
|
|
|
|
file_like.close()
|
|
|
|
|
except Exception:
|
2012-03-21 17:34:27 +04:00
|
|
|
|
# May already be closed or something. This is just cleanup anyway.
|
2012-02-27 19:48:27 +04:00
|
|
|
|
pass
|