mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-10-04 16:07:57 +03:00
Use pixbuf through cffi.
This commit is contained in:
parent
c70b00b5b8
commit
5baee96712
@ -10,85 +10,182 @@
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import division, unicode_literals
|
||||
from __future__ import division
|
||||
# XXX No unicode_literals, cffi likes native strings
|
||||
|
||||
import sys
|
||||
from io import BytesIO
|
||||
from functools import partial
|
||||
|
||||
import cffi
|
||||
import cairocffi as cairo
|
||||
|
||||
from .logger import LOGGER
|
||||
from .compat import xrange
|
||||
|
||||
|
||||
ffi = cffi.FFI()
|
||||
ffi.cdef('''
|
||||
|
||||
typedef unsigned long gsize;
|
||||
typedef unsigned int guint32;
|
||||
typedef unsigned int guint;
|
||||
typedef unsigned char guchar;
|
||||
typedef char gchar;
|
||||
typedef int gint;
|
||||
typedef gint gboolean;
|
||||
typedef guint32 GQuark;
|
||||
typedef void* gpointer;
|
||||
typedef struct {
|
||||
GQuark domain;
|
||||
gint code;
|
||||
gchar *message;
|
||||
} GError;
|
||||
typedef struct {
|
||||
gchar *name;
|
||||
/* ... */
|
||||
} GdkPixbufFormat;
|
||||
typedef enum {
|
||||
GDK_COLORSPACE_RGB
|
||||
} GdkColorspace;
|
||||
typedef ... GdkPixbufLoader;
|
||||
typedef ... GdkPixbuf;
|
||||
|
||||
|
||||
GdkPixbufLoader * gdk_pixbuf_loader_new (void);
|
||||
GdkPixbufFormat * gdk_pixbuf_loader_get_format (GdkPixbufLoader *loader);
|
||||
GdkPixbuf * gdk_pixbuf_loader_get_pixbuf (GdkPixbufLoader *loader);
|
||||
gboolean gdk_pixbuf_loader_write (
|
||||
GdkPixbufLoader *loader, const guchar *buf, gsize count,
|
||||
GError **error);
|
||||
gboolean gdk_pixbuf_loader_close (
|
||||
GdkPixbufLoader *loader, GError **error);
|
||||
|
||||
|
||||
GdkColorspace gdk_pixbuf_get_colorspace (const GdkPixbuf *pixbuf);
|
||||
int gdk_pixbuf_get_n_channels (const GdkPixbuf *pixbuf);
|
||||
gboolean gdk_pixbuf_get_has_alpha (const GdkPixbuf *pixbuf);
|
||||
int gdk_pixbuf_get_bits_per_sample (const GdkPixbuf *pixbuf);
|
||||
int gdk_pixbuf_get_width (const GdkPixbuf *pixbuf);
|
||||
int gdk_pixbuf_get_height (const GdkPixbuf *pixbuf);
|
||||
int gdk_pixbuf_get_rowstride (const GdkPixbuf *pixbuf);
|
||||
guchar * gdk_pixbuf_get_pixels (const GdkPixbuf *pixbuf);
|
||||
gsize gdk_pixbuf_get_byte_length (const GdkPixbuf *pixbuf);
|
||||
GdkPixbuf * gdk_pixbuf_add_alpha (
|
||||
const GdkPixbuf *pixbuf, gboolean substitute_color,
|
||||
guchar r, guchar g, guchar b);
|
||||
|
||||
void g_object_ref (gpointer object);
|
||||
void g_object_unref (gpointer object);
|
||||
void g_error_free (GError *error);
|
||||
|
||||
''')
|
||||
|
||||
gobject = ffi.dlopen('gobject-2.0')
|
||||
gdk_pixbuf = ffi.dlopen('gdk_pixbuf-2.0')
|
||||
|
||||
cairo.install_as_pycairo() # for CairoSVG
|
||||
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
if 1:
|
||||
def gdkpixbuf_loader(file_obj, string):
|
||||
1/0
|
||||
else:
|
||||
def pixbuf_format(loader):
|
||||
format_ = loader.get_format()
|
||||
if format_:
|
||||
return format_.get_name()
|
||||
|
||||
PIXBUF_VERSION = (GdkPixbuf.PIXBUF_MAJOR,
|
||||
GdkPixbuf.PIXBUF_MINOR,
|
||||
GdkPixbuf.PIXBUF_MICRO)
|
||||
if PIXBUF_VERSION < (2, 25, 0):
|
||||
LOGGER.warn('Using gdk-pixbuf %s.%s.%s with introspection. '
|
||||
'Versions before 2.25.0 are known to be buggy. '
|
||||
'Images formats other than PNG may not be supported.',
|
||||
*PIXBUF_VERSION)
|
||||
try:
|
||||
# Unfornately cairo_set_source_pixbuf is not part of Pixbuf itself
|
||||
from gi.repository import Gdk
|
||||
|
||||
def gdkpixbuf_loader(file_obj, string):
|
||||
"""Load raster images with gdk-pixbuf through introspection
|
||||
and Gdk.
|
||||
|
||||
"""
|
||||
pixbuf, jpeg_data = get_pixbuf(file_obj, string)
|
||||
dummy_context = cairo.Context(cairo.ImageSurface(
|
||||
cairo.FORMAT_ARGB32, 1, 1))
|
||||
Gdk.cairo_set_source_pixbuf(dummy_context, pixbuf, 0, 0)
|
||||
# XXX SurfacePattern.get_surface is buggy in py2cairo < 1.10.0
|
||||
# so we’re re-using the same pattern here. This Pattern object
|
||||
# has shared state through set_filter and set_extend.
|
||||
# It is therefore not thread-safe and state must be reset
|
||||
# before any use.
|
||||
get_pattern = dummy_context.get_source
|
||||
if cairo.version_info >= (1, 10, 0):
|
||||
add_jpeg_data(get_pattern().get_surface(), jpeg_data)
|
||||
return get_pattern, pixbuf.get_width(), pixbuf.get_height()
|
||||
|
||||
except ImportError:
|
||||
# Gdk is not available, go through PNG.
|
||||
def gdkpixbuf_loader(file_obj, string):
|
||||
"""Load raster images with gdk-pixbuf through introspection,
|
||||
without Gdk and going through PNG.
|
||||
|
||||
"""
|
||||
pixbuf, jpeg_data = get_pixbuf(file_obj, string)
|
||||
_, png = pixbuf.save_to_bufferv('png', ['compression'], ['0'])
|
||||
return cairo_png_loader(None, png, jpeg_data)
|
||||
def handle_g_error(error):
|
||||
if error != ffi.NULL:
|
||||
error_message = error.message
|
||||
gobject.g_error_free(error)
|
||||
return ValueError(
|
||||
'Pixbuf error: ' + error_message.decode('utf8', 'replace'))
|
||||
|
||||
|
||||
def get_pixbuf(file_obj=None, string=None, chunck_size=16 * 1024):
|
||||
class Pixbuf(object):
|
||||
def __init__(self, handle):
|
||||
self._handle = ffi.gc(handle, gobject.g_object_unref)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return partial(getattr(gdk_pixbuf, 'gdk_pixbuf_' + name), self._handle)
|
||||
|
||||
|
||||
def get_pixbuf(file_obj=None, string=None):
|
||||
"""Create a Pixbuf object."""
|
||||
if file_obj:
|
||||
string = file_obj.read()
|
||||
if not string:
|
||||
raise ValueError('Could not load image: empty content')
|
||||
loader = PixbufLoader()
|
||||
try:
|
||||
loader.write(string)
|
||||
finally:
|
||||
# Pixbuf is really unhappy if we don’t do this:
|
||||
loader.close()
|
||||
jpeg_data = string if pixbuf_format(loader) == 'jpeg' else None
|
||||
return loader.get_pixbuf(), jpeg_data
|
||||
loader = ffi.gc(
|
||||
gdk_pixbuf.gdk_pixbuf_loader_new(), gobject.g_object_unref)
|
||||
error = ffi.new('GError **')
|
||||
|
||||
gdk_pixbuf.gdk_pixbuf_loader_write(
|
||||
loader, ffi.new('guchar[]', string), len(string), error)
|
||||
write_exception = handle_g_error(error[0])
|
||||
gdk_pixbuf.gdk_pixbuf_loader_close(loader, error)
|
||||
close_exception = handle_g_error(error[0])
|
||||
if write_exception is not None:
|
||||
raise write_exception # Only after closing
|
||||
if close_exception is not None:
|
||||
raise close_exception
|
||||
|
||||
pixbuf = gdk_pixbuf.gdk_pixbuf_loader_get_pixbuf(loader)
|
||||
assert pixbuf != ffi.NULL
|
||||
gobject.g_object_ref(ffi.cast('gpointer', pixbuf))
|
||||
pixbuf = Pixbuf(pixbuf)
|
||||
|
||||
format_ = gdk_pixbuf.gdk_pixbuf_loader_get_format(loader)
|
||||
is_jpeg = format_ != ffi.NULL and ffi.string(format_.name) == 'jpeg'
|
||||
jpeg_data = string if is_jpeg else None
|
||||
return pixbuf, jpeg_data
|
||||
|
||||
|
||||
def gdkpixbuf_loader(file_obj, string):
|
||||
"""Load raster images with gdk-pixbuf through introspection
|
||||
and Gdk.
|
||||
|
||||
"""
|
||||
pixbuf, jpeg_data = get_pixbuf(file_obj, string)
|
||||
if not pixbuf.get_has_alpha():
|
||||
# False means: no "substitute" color that becomes transparent.
|
||||
pixbuf = Pixbuf(pixbuf.add_alpha(False, 0, 0, 0))
|
||||
assert pixbuf.get_colorspace() == 'GDK_COLORSPACE_RGB'
|
||||
assert pixbuf.get_n_channels() == 4
|
||||
width = pixbuf.get_width()
|
||||
height = pixbuf.get_height()
|
||||
rowstride = pixbuf.get_width()
|
||||
pixels = ffi.buffer(pixbuf.get_pixels(), pixbuf.get_byte_length())
|
||||
# TODO: remove this when cffi buffers support slicing.
|
||||
pixels = pixels[:]
|
||||
|
||||
# Convert GdkPixbuf’s big-endian RGBA to cairo’s native-endian ARGB
|
||||
cairo_stride = cairo.ImageSurface.format_stride_for_width('ARGB32', width)
|
||||
data = bytearray(cairo_stride * height)
|
||||
big_endian = sys.byteorder == 'big'
|
||||
row_length = width * 4 # stride == row_length + padding
|
||||
for y in xrange(height):
|
||||
offset = rowstride * y
|
||||
end = offset + row_length
|
||||
red = pixels[offset:end:4]
|
||||
green = pixels[offset + 1:end:4]
|
||||
blue = pixels[offset + 2:end:4]
|
||||
alpha = pixels[offset + 3:end:4]
|
||||
offset = cairo_stride * y
|
||||
end = offset + row_length
|
||||
if big_endian:
|
||||
data[offset:end:4] = alpha
|
||||
data[offset + 1:end:4] = red
|
||||
data[offset + 2:end:4] = green
|
||||
data[offset + 3:end:4] = blue
|
||||
else:
|
||||
data[offset + 3:end:4] = alpha
|
||||
data[offset + 2:end:4] = red
|
||||
data[offset + 1:end:4] = green
|
||||
data[offset:end:4] = blue
|
||||
|
||||
surface = cairo.ImageSurface('ARGB32', width, height, data, cairo_stride)
|
||||
add_jpeg_data(surface, jpeg_data)
|
||||
get_pattern = lambda: cairo.SurfacePattern(surface)
|
||||
return get_pattern, width, height
|
||||
|
||||
|
||||
def cairo_png_loader(file_obj, string, jpeg_data=None):
|
||||
@ -101,6 +198,8 @@ def cairo_png_loader(file_obj, string, jpeg_data=None):
|
||||
|
||||
def add_jpeg_data(surface, jpeg_data):
|
||||
if jpeg_data and hasattr(surface, 'set_mime_data'):
|
||||
# TODO: remove this when cffi/cairocffi support byte string as buffers.
|
||||
jpeg_data = bytearray(jpeg_data)
|
||||
surface.set_mime_data('image/jpeg', jpeg_data)
|
||||
|
||||
|
||||
@ -183,6 +282,8 @@ def get_image_from_uri(cache, url_fetcher, uri, type_=None):
|
||||
pass
|
||||
except Exception as exc:
|
||||
LOGGER.warn('Error for image at %s : %r', uri, exc)
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
image = None
|
||||
cache[uri] = image
|
||||
return image
|
||||
|
@ -151,7 +151,7 @@ def image_to_pixels(surface, width, height):
|
||||
assert (surface.get_width(), surface.get_height()) == (width, height)
|
||||
# RGB24 is actually the same as ARGB32, with A unused.
|
||||
assert surface.get_format() in ('ARGB32', 'RGB24')
|
||||
pixels = surface.get_data()
|
||||
pixels = surface.get_data()[:]
|
||||
stride = surface.get_stride()
|
||||
row_bytes = width * 4
|
||||
if stride != row_bytes:
|
||||
|
@ -68,10 +68,9 @@ ffi.cdef('''
|
||||
|
||||
typedef int gint;
|
||||
typedef gint gboolean;
|
||||
typedef void* gpointer;
|
||||
typedef ... cairo_t;
|
||||
typedef ... PangoLayout;
|
||||
/* Actually less specific, but this is our only usage: */
|
||||
typedef PangoLayout *gpointer;
|
||||
typedef ... PangoFontDescription;
|
||||
typedef ... PangoLayoutIter;
|
||||
typedef struct {
|
||||
@ -298,6 +297,9 @@ def split_first_line(text, style, hinting, max_width):
|
||||
if next_word:
|
||||
new_first_line = first_part + next_word
|
||||
layout.set_text(new_first_line)
|
||||
lines = layout.iter_lines()
|
||||
first_line = next(lines, None)
|
||||
second_line = next(lines, None)
|
||||
if second_line is None: # XXX never reached?
|
||||
resume_at = len(new_first_line.encode('utf-8')) + 1
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user