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)
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)
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)
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'})
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
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)
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.
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
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)
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,
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;
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)
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,