Пример #1
0
    def add_hyperlinks(self, links, anchors, context, scale):
        """Include hyperlinks in current page."""
        if cairo.cairo_version() < 11504:
            return

        # We round floats to avoid locale problems, see
        # https://github.com/Kozea/WeasyPrint/issues/742

        # TODO: Instead of using rects, we could use the drawing rectangles
        # defined by cairo when drawing targets. This would give a feeling
        # similiar to what browsers do with links that span multiple lines.
        for link in links:
            link_type, link_target, rectangle = link
            if link_type == 'external':
                attributes = "rect=[{} {} {} {}] uri='{}'".format(
                    *([int(round(i * scale)) for i in rectangle] +
                      [link_target.replace("'", '%27')]))
            elif link_type == 'internal':
                attributes = "rect=[{} {} {} {}] dest='{}'".format(
                    *([int(round(i * scale)) for i in rectangle] +
                      [link_target.replace("'", '%27')]))
            elif link_type == 'attachment':
                # Attachments are handled in write_pdf_metadata
                continue
            context.tag_begin(cairo.TAG_LINK, attributes)
            context.tag_end(cairo.TAG_LINK)

        for anchor in anchors:
            anchor_name, x, y = anchor
            attributes = "name='{}' x={} y={}".format(
                anchor_name.replace("'", '%27'), int(round(x * scale)),
                int(round(y * scale)))
            context.tag_begin(cairo.TAG_DEST, attributes)
            context.tag_end(cairo.TAG_DEST)
Пример #2
0
    def add_hyperlinks(self, links, anchors, context, scale):
        """Include hyperlinks in current page."""
        if cairo.cairo_version() < 11504:
            return

        # TODO: Instead of using rects, we could use the drawing rectangles
        # defined by cairo when drawing targets. This would give a feeling
        # similiar to what browsers do with links that span multiple lines.
        for link in links:
            link_type, link_target, rectangle = link
            if link_type == 'external':
                attributes = "rect=[{} {} {} {}] uri='{}'".format(
                    *([i * scale for i in rectangle] + [link_target]))
            elif link_type == 'internal':
                attributes = "rect=[{} {} {} {}] dest='{}'".format(
                    *([i * scale for i in rectangle] + [link_target]))
            elif link_type == 'attachment':
                # Attachments are handled in write_pdf_metadata
                continue
            context.tag_begin(cairo.TAG_LINK, attributes)
            context.tag_end(cairo.TAG_LINK)

        for anchor in anchors:
            anchor_name, x, y = anchor
            attributes = "name='{}' x={} y={}".format(
                anchor_name, x * scale, y * scale)
            context.tag_begin(cairo.TAG_DEST, attributes)
            context.tag_end(cairo.TAG_DEST)
Пример #3
0
    def add_hyperlinks(self, links, anchors, context, scale):
        """Include hyperlinks in current page."""
        if cairo.cairo_version() < 11504:
            return

        # TODO: Instead of using rects, we could use the drawing rectangles
        # defined by cairo when drawing targets. This would give a feeling
        # similiar to what browsers do with links that span multiple lines.
        for link in links:
            link_type, link_target, rectangle = link
            if link_type == 'external':
                attributes = "rect=[{} {} {} {}] uri='{}'".format(
                    *([_locale_float(i * scale)
                       for i in rectangle] + [link_target]))
            elif link_type == 'internal':
                attributes = "rect=[{} {} {} {}] dest='{}'".format(
                    *([_locale_float(i * scale)
                       for i in rectangle] + [link_target]))
            elif link_type == 'attachment':
                # Attachments are handled in write_pdf_metadata
                continue
            context.tag_begin(cairo.TAG_LINK, attributes)
            context.tag_end(cairo.TAG_LINK)

        for anchor in anchors:
            anchor_name, x, y = anchor
            attributes = "name='{}' x={} y={}".format(anchor_name,
                                                      _locale_float(x * scale),
                                                      _locale_float(y * scale))
            context.tag_begin(cairo.TAG_DEST, attributes)
            context.tag_end(cairo.TAG_DEST)
Пример #4
0
def test_navigator(tmpdir):
    status, headers, body = wsgi_client(navigator, '/lipsum')
    assert status == '404 Not Found'

    status, headers, body = wsgi_client(navigator, '/')
    body = body.decode('utf8')
    assert status == '200 OK'
    assert headers['Content-Type'].startswith('text/html;')
    assert '<title>WeasyPrint Navigator</title>' in body
    assert '<img' not in body
    assert '></a>' not in body

    test_file = tmpdir.join('test.html')
    test_file.write(b'''
        <h1 id=foo><a href="http://weasyprint.org">Lorem ipsum</a></h1>
        <h2><a href="#foo">bar</a></h2>
    ''')

    url = path2url(test_file.strpath)
    for status, headers, body in [
            wsgi_client(navigator, '/view/' + url),
            wsgi_client(navigator, '/', {'url': url}),
    ]:
        body = body.decode('utf8')
        assert status == '200 OK'
        assert headers['Content-Type'].startswith('text/html;')
        assert '<title>WeasyPrint Navigator</title>' in body
        assert '<img src="data:image/png;base64,' in body
        assert ' name="foo"></a>' in body
        assert ' href="#foo"></a>' in body
        assert ' href="/view/http://weasyprint.org"></a>' in body

    status, headers, body = wsgi_client(navigator, '/pdf/' + url)
    assert status == '200 OK'
    assert headers['Content-Type'] == 'application/pdf'
    assert body.startswith(b'%PDF')
    if cairo.cairo_version() < 11504:  # pragma: no cover
        return
    assert b'/URI (http://weasyprint.org)' in body
    assert b'/Title (Lorem ipsum)' in body

    status, headers, body = wsgi_client(navigator, '/pdf/' + url)
    assert status == '200 OK'
    assert headers['Content-Type'] == 'application/pdf'
    assert body.startswith(b'%PDF')

    with pytest.raises(URLFetchingError):
        wsgi_client(navigator, '/pdf/' + 'test.example')

    with pytest.raises(URLFetchingError):
        wsgi_client(navigator, '/pdf/' + 'test.example', {'test': 'test'})
Пример #5
0
    def export_plot(plot, graph):
        """
        Export a bokeh plot based on Cairo version
        """
        # export graph as a PNG or SVG depending on Cairo version is installed
        # higher quality SVG only works with Cairo version >= 1.15.4
        cairo_version = cairo.cairo_version()
        if cairo_version < 11504:
            filename = f"{graph}_graph.png"
            full_filename = Path(output_path, filename)
            export_png(plot, full_filename)
            print("For a higher quality SVG image file, install Cairo 1.15.4")
        else:
            filename = f"{graph}_graph.svg"
            full_filename = Path(output_path, filename)
            export_svgs(plot, full_filename)

        return filename
Пример #6
0
    def add_hyperlinks(self, links, context, scale):
        """Include hyperlinks in current page."""
        if cairo.cairo_version() < 11504:
            return

        # TODO: Instead of using rects, we could use the drawing rectangles
        # defined by cairo when drawing targets. This would give a feeling
        # similiar to what browsers do with links that span multiple lines.
        for link in links:
            link_type, link_target, rectangle = link
            if link_type == 'external':
                attributes = "rect=[{} {} {} {}] uri='{}'".format(
                    *([i * scale for i in rectangle] + [link_target]))
            elif link_type == 'internal':
                page, x, y = link_target
                attributes = ('rect=[{} {} {} {}] page={} '
                              'pos=[{} {}]'.format(
                                  *([i * scale for i in rectangle] +
                                    [page + 1, x * scale, y * scale])))
            elif link_type == 'attachment':
                # Attachments are handled in write_pdf_metadata
                continue
            context.tag_begin(cairo.TAG_LINK, attributes)
Пример #7
0
import cairosvg.surface

from .urls import fetch, URLFetchingError
from .logger import LOGGER
from .compat import xrange

try:
    from cairocffi import pixbuf
except OSError:
    pixbuf = None

assert cairosvg.surface.cairo is cairocffi, (
    'CairoSVG is using pycairo instead of cairocffi. '
    'Make sure it is not imported before WeasyPrint.')

CAIRO_HAS_MIME_DATA = cairocffi.cairo_version() >= 11000

# Map values of the image-rendering property to cairo FILTER values:
# Values are normalized to lower case.
IMAGE_RENDERING_TO_FILTER = {
    'auto': cairocffi.FILTER_BILINEAR,
    'crisp-edges': cairocffi.FILTER_BEST,
    'pixelated': cairocffi.FILTER_NEAREST,
}


class ImageLoadingError(ValueError):
    """An error occured when loading an image.

    The image data is probably corrupted or in an invalid format.
Пример #8
0
    Fetch and decode images in various formats.

    :copyright: Copyright 2011-2014 Simon Sapin and contributors, see AUTHORS.
    :license: BSD, see LICENSE for details.

"""

from __future__ import division, unicode_literals

from io import BytesIO
import math

import cairocffi
cairocffi.install_as_pycairo()  # for CairoSVG
CAIRO_HAS_MIME_DATA = cairocffi.cairo_version() >= 11000

import cairosvg.parser
import cairosvg.surface
assert cairosvg.surface.cairo is cairocffi, (
    'CairoSVG is using pycairo instead of cairocffi. '
    'Make sure it is not imported before WeasyPrint.')

try:
    from cairocffi import pixbuf
except OSError:
    pixbuf = None

from .urls import fetch, URLFetchingError
from .logger import LOGGER
from .compat import xrange
Пример #9
0
    def write_pdf(self, target=None, zoom=1, attachments=None):
        """Paint the pages in a PDF file, with meta-data.

        PDF files written directly by cairo do not have meta-data such as
        bookmarks/outlines and hyperlinks.

        :type target: str, pathlib.Path or file object
        :param target:
            A filename where the PDF file is generated, a file object, or
            :obj:`None`.
        :type zoom: float
        :param zoom:
            The zoom factor in PDF units per CSS units.  **Warning**:
            All CSS units are affected, including physical units like
            ``cm`` and named sizes like ``A4``.  For values other than
            1, the physical CSS units will thus be "wrong".
        :type attachments: list
        :param attachments: A list of additional file attachments for the
            generated PDF document or :obj:`None`. The list's elements are
            :class:`Attachment` objects, filenames, URLs or file-like objects.
        :returns:
            The PDF as :obj:`bytes` if ``target`` is not provided or
            :obj:`None`, otherwise :obj:`None` (the PDF is written to
            ``target``).

        """
        # 0.75 = 72 PDF point (cairo units) per inch / 96 CSS pixel per inch
        scale = zoom * 0.75
        # Use an in-memory buffer. We will need to seek for metadata
        # TODO: avoid this if target can seek? Benchmark first.
        file_obj = io.BytesIO()
        # (1, 1) is overridden by .set_size() below.
        surface = cairo.PDFSurface(file_obj, 1, 1)
        context = cairo.Context(surface)

        PROGRESS_LOGGER.info('Step 6 - Drawing')

        paged_links_and_anchors = list(self.resolve_links())
        for page, links_and_anchors in zip(self.pages,
                                           paged_links_and_anchors):
            links, anchors = links_and_anchors
            surface.set_size(
                math.floor(
                    scale *
                    (page.width + page.bleed['left'] + page.bleed['right'])),
                math.floor(
                    scale *
                    (page.height + page.bleed['top'] + page.bleed['bottom'])))
            with stacked(context):
                context.translate(page.bleed['left'] * scale,
                                  page.bleed['top'] * scale)
                page.paint(context, scale=scale)
                self.add_hyperlinks(links, anchors, context, scale)
                surface.show_page()

        PROGRESS_LOGGER.info('Step 7 - Adding PDF metadata')

        # TODO: overwrite producer when possible in cairo
        if cairo.cairo_version() >= 11504:
            # Set document information
            for attr, key in (('title', cairo.PDF_METADATA_TITLE),
                              ('description', cairo.PDF_METADATA_SUBJECT),
                              ('generator', cairo.PDF_METADATA_CREATOR)):
                value = getattr(self.metadata, attr)
                if value is not None:
                    surface.set_metadata(key, value)
            for attr, key in (('authors', cairo.PDF_METADATA_AUTHOR),
                              ('keywords', cairo.PDF_METADATA_KEYWORDS)):
                value = getattr(self.metadata, attr)
                if value is not None:
                    surface.set_metadata(key, ', '.join(value))
            for attr, key in (('created', cairo.PDF_METADATA_CREATE_DATE),
                              ('modified', cairo.PDF_METADATA_MOD_DATE)):
                value = getattr(self.metadata, attr)
                if value is not None:
                    surface.set_metadata(key, _w3c_date_to_iso(value, attr))

            # Set bookmarks
            bookmarks = self.make_bookmark_tree()
            levels = [cairo.PDF_OUTLINE_ROOT] * len(bookmarks)
            while bookmarks:
                bookmark = bookmarks.pop(0)
                title = bookmark.label
                destination = bookmark.destination
                children = bookmark.children
                state = bookmark.state
                page, x, y = destination

                # We round floats to avoid locale problems, see
                # https://github.com/Kozea/WeasyPrint/issues/742
                link_attribs = 'page={} pos=[{} {}]'.format(
                    page + 1, int(round(x * scale)), int(round(y * scale)))

                outline = surface.add_outline(
                    levels.pop(), title, link_attribs,
                    cairo.PDF_OUTLINE_FLAG_OPEN if state == 'open' else 0)
                levels.extend([outline] * len(children))
                bookmarks = children + bookmarks

        surface.finish()

        # Add extra PDF metadata: attachments, embedded files
        attachment_links = [[
            link for link in page_links if link[0] == 'attachment'
        ] for page_links, page_anchors in paged_links_and_anchors]
        # Write extra PDF metadata only when there is a least one from:
        # - attachments in metadata
        # - attachments as function parameters
        # - attachments as PDF links
        # - bleed boxes
        condition = (self.metadata.attachments or attachments
                     or any(attachment_links)
                     or any(any(page.bleed.values()) for page in self.pages))
        if condition:
            write_pdf_metadata(file_obj, scale, self.url_fetcher,
                               self.metadata.attachments + (attachments or []),
                               attachment_links, self.pages)

        if target is None:
            return file_obj.getvalue()
        else:
            file_obj.seek(0)
            if hasattr(target, 'write'):
                shutil.copyfileobj(file_obj, target)
            else:
                with open(target, 'wb') as fd:
                    shutil.copyfileobj(file_obj, fd)
Пример #10
0
from . import CSS
from .css import get_all_computed_styles
from .css.targets import TargetCollector
from .draw import draw_page, stacked
from .fonts import FontConfiguration
from .formatting_structure import boxes
from .formatting_structure.build import build_formatting_structure
from .html import W3C_DATE_RE
from .images import get_image_from_uri as original_get_image_from_uri
from .layout import layout_document
from .layout.backgrounds import percentage
from .logger import LOGGER, PROGRESS_LOGGER
from .pdf import write_pdf_metadata

if cairo.cairo_version() < 11504:
    warnings.warn(
        'There are known rendering problems and missing features with '
        'cairo < 1.15.4. WeasyPrint may work with older versions, but please '
        'read the note about the needed cairo version on the "Install" page '
        'of the documentation before reporting bugs. '
        'http://weasyprint.readthedocs.io/en/latest/install.html')


def _get_matrix(box):
    """Return the matrix for the CSS transforms on this box.

    :returns: a :class:`cairocffi.Matrix` object or :obj:`None`.

    """
    # "Transforms apply to block-level and atomic inline-level elements,
Пример #11
0
from __future__ import division

import re
import warnings

import cairocffi as cairo
import cffi
import pyphen

from .compat import basestring
from .logger import LOGGER

# XXX No unicode_literals, cffi likes native strings

if cairo.cairo_version() <= 11400:
    warnings.warn('There are known rendering problems with Cairo <= 1.14.0')

PANGO_ATTR_FONT_FEATURES_CACHE = {}

ffi = cffi.FFI()
ffi.cdef('''
    // Cairo

    typedef enum {
        CAIRO_FONT_TYPE_TOY,
        CAIRO_FONT_TYPE_FT,
        CAIRO_FONT_TYPE_WIN32,
        CAIRO_FONT_TYPE_QUARTZ,
        CAIRO_FONT_TYPE_USER
    } cairo_font_type_t;
Пример #12
0
    def write_pdf(self, target=None, zoom=1, attachments=None):
        """Paint the pages in a PDF file, with meta-data.

        PDF files written directly by cairo do not have meta-data such as
        bookmarks/outlines and hyperlinks.

        :param target:
            A filename, file-like object, or :obj:`None`.
        :type zoom: float
        :param zoom:
            The zoom factor in PDF units per CSS units.  **Warning**:
            All CSS units are affected, including physical units like
            ``cm`` and named sizes like ``A4``.  For values other than
            1, the physical CSS units will thus be “wrong”.
        :param attachments: A list of additional file attachments for the
            generated PDF document or :obj:`None`. The list's elements are
            :class:`Attachment` objects, filenames, URLs, or file-like objects.
        :returns:
            The PDF as byte string if :obj:`target` is :obj:`None`, otherwise
            :obj:`None` (the PDF is written to :obj:`target`).

        """
        # 0.75 = 72 PDF point (cairo units) per inch / 96 CSS pixel per inch
        scale = zoom * 0.75
        # Use an in-memory buffer. We will need to seek for metadata
        # TODO: avoid this if target can seek? Benchmark first.
        file_obj = io.BytesIO()
        # (1, 1) is overridden by .set_size() below.
        surface = cairo.PDFSurface(file_obj, 1, 1)
        context = cairo.Context(surface)

        LOGGER.info('Step 6 - Drawing')

        paged_links_and_anchors = list(self.resolve_links())
        for page, links_and_anchors in zip(
                self.pages, paged_links_and_anchors):
            links, anchors = links_and_anchors
            surface.set_size(
                math.floor(scale * (
                    page.width + page.bleed['left'] + page.bleed['right'])),
                math.floor(scale * (
                    page.height + page.bleed['top'] + page.bleed['bottom'])))
            with stacked(context):
                context.translate(
                    page.bleed['left'] * scale, page.bleed['top'] * scale)
                page.paint(context, scale=scale)
                self.add_hyperlinks(links, anchors, context, scale)
                surface.show_page()

        LOGGER.info('Step 7 - Adding PDF metadata')

        # TODO: overwrite producer when possible in cairo
        if cairo.cairo_version() >= 11504:
            # Set document information
            for attr, key in (
                    ('title', cairo.PDF_METADATA_TITLE),
                    ('description', cairo.PDF_METADATA_SUBJECT),
                    ('generator', cairo.PDF_METADATA_CREATOR)):
                value = getattr(self.metadata, attr)
                if value is not None:
                    surface.set_metadata(key, value)
            for attr, key in (
                    ('authors', cairo.PDF_METADATA_AUTHOR),
                    ('keywords', cairo.PDF_METADATA_KEYWORDS)):
                value = getattr(self.metadata, attr)
                if value is not None:
                    surface.set_metadata(key, ', '.join(value))
            for attr, key in (
                    ('created', cairo.PDF_METADATA_CREATE_DATE),
                    ('modified', cairo.PDF_METADATA_MOD_DATE)):
                value = getattr(self.metadata, attr)
                if value is not None:
                    surface.set_metadata(key, _w3c_date_to_iso(value, attr))

            # Set bookmarks
            bookmarks = self.make_bookmark_tree()
            levels = [cairo.PDF_OUTLINE_ROOT] * len(bookmarks)
            while bookmarks:
                title, destination, children = bookmarks.pop(0)
                page, x, y = destination
                link_attribs = 'page={} pos=[{} {}]'.format(
                    page + 1, x * scale, y * scale)
                outline = surface.add_outline(
                    levels.pop(), title, link_attribs, 0)
                levels.extend([outline] * len(children))
                bookmarks = children + bookmarks

        surface.finish()

        # Add extra PDF metadata: attachments, embedded files
        attachment_links = [
            [link for link in page_links if link[0] == 'attachment']
            for page_links, page_anchors in paged_links_and_anchors]
        # Write extra PDF metadata only when there is a least one from:
        # - attachments in metadata
        # - attachments as function parameters
        # - attachments as PDF links
        # - bleed boxes
        condition = (
            self.metadata.attachments or
            attachments or
            any(attachment_links) or
            any(any(page.bleed.values()) for page in self.pages))
        if condition:
            write_pdf_metadata(
                file_obj, scale, self.url_fetcher,
                self.metadata.attachments + (attachments or []),
                attachment_links, self.pages)

        if target is None:
            return file_obj.getvalue()
        else:
            file_obj.seek(0)
            if hasattr(target, 'write'):
                shutil.copyfileobj(file_obj, target)
            else:
                with open(target, 'wb') as fd:
                    shutil.copyfileobj(file_obj, fd)
Пример #13
0
from . import CSS
from .css import get_all_computed_styles
from .css.targets import TargetCollector
from .draw import draw_page, stacked
from .fonts import FontConfiguration
from .formatting_structure import boxes
from .formatting_structure.build import build_formatting_structure
from .html import W3C_DATE_RE
from .images import get_image_from_uri as original_get_image_from_uri
from .layout import layout_document
from .layout.backgrounds import percentage
from .logger import LOGGER
from .pdf import write_pdf_metadata

if cairo.cairo_version() < 11504:
    warnings.warn(
        'There are known rendering problems and missing features with '
        'cairo < 1.15.4. WeasyPrint may work with older versions, but please '
        'read the note about the needed cairo version on the "Install" page '
        'of the documentation before reporting bugs. '
        'http://weasyprint.readthedocs.io/en/latest/install.html')


def _get_matrix(box):
    """Return the matrix for the CSS transforms on this box.

    :returns: a :class:`cairocffi.Matrix` object or :obj:`None`.

    """
    # "Transforms apply to block-level and atomic inline-level elements,