mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-09-17 15:37:34 +03:00
Move resolution to *.write_png only.
This commit is contained in:
parent
e1f5863bc8
commit
eda4bc604e
@ -103,7 +103,7 @@ class HTML(object):
|
||||
from .html import HTML5_UA_STYLESHEET
|
||||
return [HTML5_UA_STYLESHEET]
|
||||
|
||||
def render(self, stylesheets=None, resolution=96, enable_hinting=False):
|
||||
def render(self, stylesheets=None, enable_hinting=False):
|
||||
"""Lay out and paginate the document, but do not (yet) export it
|
||||
to PDF or another format.
|
||||
|
||||
@ -119,18 +119,11 @@ class HTML(object):
|
||||
Whether text, borders and background should be *hinted* to fall
|
||||
at device pixel boundaries. Should be enabled for pixel-based
|
||||
output (like PNG) but not vector based output (like PDF).
|
||||
:type resolution: float
|
||||
:param resolution:
|
||||
The output resolution in cairo user units per CSS inch. At 96 dpi
|
||||
(the default), user units match the CSS ``px`` unit.
|
||||
For example, :class:`cairo.PDFSurface`’s device units are
|
||||
in PostScript points (72dpi), so ``resolution=72`` will set
|
||||
the right scale for physical units.
|
||||
:returns: A :class:`Document` object.
|
||||
|
||||
"""
|
||||
from .document import Document
|
||||
return Document._render(self, stylesheets, resolution, enable_hinting)
|
||||
return Document._render(self, stylesheets, enable_hinting)
|
||||
|
||||
|
||||
def write_pdf(self, target=None, stylesheets=None):
|
||||
@ -147,7 +140,7 @@ class HTML(object):
|
||||
``None`` (the PDF is written to :obj:`target`.)
|
||||
|
||||
"""
|
||||
return self.render(stylesheets, resolution=72).write_pdf(target)
|
||||
return self.render(stylesheets).write_pdf(target)
|
||||
|
||||
def write_png(self, target=None, stylesheets=None, resolution=96):
|
||||
"""Paint the pages vertically to a single PNG image.
|
||||
@ -171,8 +164,8 @@ class HTML(object):
|
||||
|
||||
"""
|
||||
png_bytes, _width, _height = (
|
||||
self.render(stylesheets, resolution, enable_hinting=True)
|
||||
.write_png(target))
|
||||
self.render(stylesheets, enable_hinting=True)
|
||||
.write_png(target, resolution))
|
||||
return png_bytes
|
||||
|
||||
|
||||
|
@ -49,10 +49,14 @@ def _get_metadata(box, bookmarks, links, anchors, matrix):
|
||||
# In case of duplicate IDs, only the first is an anchor.
|
||||
has_anchor = anchor_name and anchor_name not in anchors
|
||||
|
||||
# TODO: account for CSS transforms
|
||||
# matrix *= …
|
||||
|
||||
if has_bookmark or has_link or has_anchor:
|
||||
pos_x, pos_y, width, height = box.hit_area()
|
||||
pos_x, pos_y = matrix.transform_point(pos_x, pos_y)
|
||||
width, height = matrix.transform_distance(width, height)
|
||||
if matrix:
|
||||
pos_x, pos_y = matrix.transform_point(pos_x, pos_y)
|
||||
width, height = matrix.transform_distance(width, height)
|
||||
if has_bookmark:
|
||||
bookmarks.append((bookmark_level, bookmark_label, (pos_x, pos_y)))
|
||||
if has_link:
|
||||
@ -75,14 +79,12 @@ class Page(object):
|
||||
instantiated directly.
|
||||
|
||||
"""
|
||||
def __init__(self, page, enable_hinting=False, resolution=96):
|
||||
dppx = resolution / 96
|
||||
|
||||
def __init__(self, page_box, enable_hinting=False):
|
||||
#: The page width, including margins, in cairo user units.
|
||||
self.width = page.margin_width() * dppx
|
||||
self.width = page_box.margin_width()
|
||||
|
||||
#: The page height, including margins, in cairo user units.
|
||||
self.height = page.margin_height() * dppx
|
||||
self.height = page_box.margin_height()
|
||||
|
||||
#: A list of ``(bookmark_level, bookmark_label, point)`` tuples.
|
||||
#: A point is ``(x, y)`` in cairo units from the top-left of the page.
|
||||
@ -103,22 +105,24 @@ class Page(object):
|
||||
#: form the top-left of the page.)
|
||||
self.anchors = {}
|
||||
|
||||
_get_metadata(page, self.bookmarks, self.links, self.anchors,
|
||||
cairo.Matrix(xx=dppx, yy=dppx))
|
||||
self._page_box = page
|
||||
_get_metadata(
|
||||
page_box, self.bookmarks, self.links, self.anchors, matrix=None)
|
||||
self._page_box = page_box
|
||||
self._enable_hinting = enable_hinting
|
||||
self._dppx = dppx
|
||||
|
||||
def paint(self, cairo_context, left_x=0, top_y=0, clip=False):
|
||||
def paint(self, cairo_context, left_x=0, top_y=0, scale=1, clip=False):
|
||||
"""Paint the surface in cairo, on any type of surface.
|
||||
|
||||
:param cairo_context: any :class:`cairo.Context` object.
|
||||
:type left_x: float
|
||||
:param left_x:
|
||||
X coordinate of the left of the page, in user units.
|
||||
X coordinate of the left of the page, in cairo user units.
|
||||
:type top_y: float
|
||||
:param top_y:
|
||||
Y coordinate of the top of the page, in user units.
|
||||
Y coordinate of the top of the page, in cairo user units.
|
||||
:type scale: float
|
||||
:param scale:
|
||||
Zoom scale in CSS pixels per cairo user unit.
|
||||
:type clip: bool
|
||||
:param clip:
|
||||
Whether to clip/cut content outside the page. If false or
|
||||
@ -128,26 +132,27 @@ class Page(object):
|
||||
with stacked(cairo_context):
|
||||
if self._enable_hinting:
|
||||
left_x, top_y = cairo_context.user_to_device(left_x, top_y)
|
||||
width, height = cairo_context.user_to_device_distance(
|
||||
self.width, self.height)
|
||||
# Hint in device space
|
||||
left_x = int(left_x)
|
||||
top_y = int(top_y)
|
||||
width = int(math.ceil(width))
|
||||
height = int(math.ceil(height))
|
||||
left_x, top_y = cairo_context.device_to_user(left_x, top_y)
|
||||
width, height = cairo_context.device_to_user_distance(
|
||||
width, height)
|
||||
else:
|
||||
# Make (0, 0) the top-left corner:
|
||||
cairo_context.translate(left_x, top_y)
|
||||
# Make user units CSS pixels:
|
||||
cairo_context.scale(scale, scale)
|
||||
if clip:
|
||||
width = self.width
|
||||
height = self.height
|
||||
cairo_context.translate(left_x, top_y)
|
||||
# The top-left corner is now (0, 0)
|
||||
if clip:
|
||||
if self._enable_hinting:
|
||||
width, height = (
|
||||
cairo_context.user_to_device_distance(width, height))
|
||||
# Hint in device space
|
||||
width = int(math.ceil(width))
|
||||
height = int(math.ceil(height))
|
||||
width, height = (
|
||||
cairo_context.device_to_user_distance(width, height))
|
||||
cairo_context.rectangle(0, 0, width, height)
|
||||
cairo_context.clip()
|
||||
cairo_context.scale(self._dppx, self._dppx)
|
||||
# User units are now CSS pixels
|
||||
draw_page(self._page_box, cairo_context, self._enable_hinting)
|
||||
|
||||
|
||||
@ -160,7 +165,7 @@ class Document(object):
|
||||
|
||||
"""
|
||||
@classmethod
|
||||
def _render(cls, html, stylesheets, resolution, enable_hinting):
|
||||
def _render(cls, html, stylesheets, enable_hinting):
|
||||
style_for = get_all_computed_styles(html, user_stylesheets=[
|
||||
css if hasattr(css, 'rules')
|
||||
else CSS(guess=css, media_type=html.media_type)
|
||||
@ -171,7 +176,7 @@ class Document(object):
|
||||
enable_hinting, style_for, get_image_from_uri,
|
||||
build_formatting_structure(
|
||||
html.root_element, style_for, get_image_from_uri))
|
||||
return cls([Page(p, enable_hinting, resolution) for p in page_boxes])
|
||||
return cls([Page(p, enable_hinting) for p in page_boxes])
|
||||
|
||||
def __init__(self, pages):
|
||||
#: A list of :class:`Page` objects.
|
||||
@ -301,8 +306,10 @@ class Document(object):
|
||||
# (1, 1) is overridden by .set_size() below.
|
||||
surface = cairo.PDFSurface(file_obj, 1, 1)
|
||||
context = cairo.Context(surface)
|
||||
context.scale(0.75, 0.75)
|
||||
for page in self.pages:
|
||||
surface.set_size(page.width, page.height)
|
||||
surface.set_size(page.width * 0.75, page.height * 0.75)
|
||||
# 0.75 = 72 PDF point per inch / 96 CSS pixel per inch
|
||||
page.paint(context)
|
||||
surface.show_page()
|
||||
surface.finish()
|
||||
@ -319,7 +326,7 @@ class Document(object):
|
||||
with open(target, 'wb') as fd:
|
||||
shutil.copyfileobj(file_obj, fd)
|
||||
|
||||
def write_png(self, target=None):
|
||||
def write_png(self, target=None, resolution=96):
|
||||
"""Paint the pages vertically to a single PNG image.
|
||||
|
||||
There is no decoration around pages other than those specified in CSS
|
||||
@ -328,6 +335,10 @@ class Document(object):
|
||||
|
||||
:param target:
|
||||
A filename, file-like object, or ``None``.
|
||||
:type resolution: float
|
||||
:param resolution:
|
||||
The output resolution in PNG pixels per CSS inch. At 96 dpi
|
||||
(the default), PNG pixels match the CSS ``px`` unit.
|
||||
:returns:
|
||||
A ``(png_bytes, png_width, png_height)`` tuple. :obj:`png_bytes`
|
||||
is a byte string if :obj:`target` is ``None``, otherwise ``None``
|
||||
@ -336,23 +347,25 @@ class Document(object):
|
||||
final image, in PNG pixels.
|
||||
|
||||
"""
|
||||
dppx = resolution / 96
|
||||
|
||||
# This duplicates the hinting logic in Page.paint. There is a
|
||||
# dependency cycle otherwise:
|
||||
# this → hinting logic → context → surface → this
|
||||
# But since we do no transform here, cairo_context.user_to_device and
|
||||
# friends are identity functions.
|
||||
widths = [int(math.ceil(p.width)) for p in self.pages]
|
||||
heights = [int(math.ceil(p.height)) for p in self.pages]
|
||||
widths = [int(math.ceil(p.width * dppx)) for p in self.pages]
|
||||
heights = [int(math.ceil(p.height * dppx)) for p in self.pages]
|
||||
|
||||
max_width = max(widths)
|
||||
sum_heights = sum(heights)
|
||||
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, max_width, sum_heights)
|
||||
surface = cairo.ImageSurface(
|
||||
cairo.FORMAT_ARGB32, max_width, sum_heights)
|
||||
context = cairo.Context(surface)
|
||||
|
||||
pos_y = 0
|
||||
for page, width, height in izip(self.pages, widths, heights):
|
||||
pos_x = (max_width - width) / 2
|
||||
with stacked(context):
|
||||
page.paint(context, pos_x, pos_y, clip=True)
|
||||
page.paint(context, pos_x, pos_y, scale=dppx, clip=True)
|
||||
pos_y += height
|
||||
|
||||
if target is None:
|
||||
|
@ -39,7 +39,7 @@ import cairo
|
||||
|
||||
from . import VERSION_STRING
|
||||
from .logger import LOGGER
|
||||
from .compat import xrange, iteritems
|
||||
from .compat import xrange, iteritems, izip
|
||||
from .urls import iri_to_uri
|
||||
from .formatting_structure import boxes
|
||||
|
||||
@ -310,19 +310,23 @@ def prepare_metadata(document, bookmark_root_id):
|
||||
|
||||
"""
|
||||
# X and width unchanged; Y’ = page_height - Y; height’ = -height
|
||||
# Overall * 0.75 for CSS pixels to PDF points:
|
||||
matrices = [cairo.Matrix(xx=0.75, yy=-0.75, y0=page.height * 0.75)
|
||||
for page in document.pages]
|
||||
page_heights = [page.height for page in document.pages]
|
||||
links = []
|
||||
for page_number, page_links in enumerate(document.resolve_links()):
|
||||
for page_links, matrix in izip(document.resolve_links(), matrices):
|
||||
new_page_links = []
|
||||
for link_type, target, rectangle in page_links:
|
||||
if link_type == 'internal':
|
||||
target_page, target_x, target_y = target
|
||||
target = (target_page, target_x,
|
||||
page_heights[target_page] - target_y)
|
||||
# x, y, w, h => x0, y0, x1, y1
|
||||
target = ((target_page,) +
|
||||
matrices[target_page].transform_point(target_x, target_y))
|
||||
rect_x, rect_y, width, height = rectangle
|
||||
pdf_y1 = page_heights[page_number] - rect_y
|
||||
rectangle = rect_x, pdf_y1, rect_x + width, pdf_y1 - height
|
||||
rect_x, rect_y = matrix.transform_point(rect_x, rect_y)
|
||||
width, height = matrix.transform_distance(width, height)
|
||||
# x, y, w, h => x0, y0, x1, y1
|
||||
rectangle = rect_x, rect_y, rect_x + width, rect_y + height
|
||||
new_page_links.append((link_type, target, rectangle))
|
||||
links.append(new_page_links)
|
||||
|
||||
@ -334,7 +338,8 @@ def prepare_metadata(document, bookmark_root_id):
|
||||
flatten_bookmarks(document.make_bookmark_tree()),
|
||||
bookmark_root_id + 1):
|
||||
target_page, target_x, target_y = target
|
||||
target = target_page, target_x, page_heights[target_page] - target_y
|
||||
target = (target_page,) + matrices[target_page].transform_point(
|
||||
target_x, target_y)
|
||||
bookmark = {
|
||||
'Count': 0, 'First': None, 'Last': None, 'Prev': None,
|
||||
'Next': None, 'Parent': last_id_by_depth[depth - 1],
|
||||
@ -377,6 +382,7 @@ def write_pdf_metadata(document, fileobj):
|
||||
pdf.extend_dict(pdf.catalog, pdf_format(
|
||||
'/Outlines {0} 0 R /PageMode /UseOutlines', bookmark_root_id))
|
||||
for bookmark in bookmarks:
|
||||
print(bookmark)
|
||||
content = [pdf_format('<< /Title {0!P}\n', bookmark['label'])]
|
||||
content.append(pdf_format(
|
||||
'/A << /Type /Action /S /GoTo /D [{0} /XYZ {1:f} {2:f} 0] >>',
|
||||
|
@ -460,8 +460,7 @@ def test_low_level_api():
|
||||
''')
|
||||
pdf_bytes = html.write_pdf(stylesheets=[css])
|
||||
assert pdf_bytes.startswith(b'%PDF')
|
||||
assert html.render([css], resolution=72).write_pdf() == pdf_bytes
|
||||
assert html.render([css]).write_pdf() != pdf_bytes # Beware of resolution
|
||||
assert html.render([css]).write_pdf() == pdf_bytes
|
||||
|
||||
png_bytes = html.write_png(stylesheets=[css])
|
||||
document = html.render([css], enable_hinting=True)
|
||||
@ -488,13 +487,25 @@ def test_low_level_api():
|
||||
surface.write_to_png(file_obj)
|
||||
check_png_pattern(file_obj.getvalue(), rotated=True)
|
||||
|
||||
document = html.render([css], enable_hinting=True, resolution=192)
|
||||
document = html.render([css], enable_hinting=True)
|
||||
page, = document.pages
|
||||
assert (page.width, page.height) == (16, 16)
|
||||
png_bytes, width, height = document.write_png()
|
||||
assert (page.width, page.height) == (8, 8)
|
||||
png_bytes, width, height = document.write_png(resolution=192)
|
||||
assert (width, height) == (16, 16)
|
||||
check_png_pattern(png_bytes, x2=True)
|
||||
|
||||
def png_size(result):
|
||||
png_bytes, width, height = result
|
||||
surface = cairo.ImageSurface.create_from_png(io.BytesIO(png_bytes))
|
||||
assert (surface.get_width(), surface.get_height()) == (width, height)
|
||||
return width, height
|
||||
|
||||
document = html.render([css], enable_hinting=True)
|
||||
page, = document.pages
|
||||
assert (page.width, page.height) == (8, 8)
|
||||
# A resolution that is not multiple of 96:
|
||||
assert png_size(document.write_png(resolution=145.2)) == (13, 13)
|
||||
|
||||
document = TestHTML(string='''
|
||||
<style>
|
||||
@page:first { size: 5px 10px } @page { size: 6px 4px }
|
||||
@ -507,12 +518,6 @@ def test_low_level_api():
|
||||
assert (page_1.width, page_1.height) == (5, 10)
|
||||
assert (page_2.width, page_2.height) == (6, 4)
|
||||
|
||||
def png_size(result):
|
||||
png_bytes, width, height = result
|
||||
surface = cairo.ImageSurface.create_from_png(io.BytesIO(png_bytes))
|
||||
assert (surface.get_width(), surface.get_height()) == (width, height)
|
||||
return width, height
|
||||
|
||||
result = document.write_png()
|
||||
# (Max of both widths, Sum of both heights)
|
||||
assert png_size(result) == (6, 14)
|
||||
|
@ -42,9 +42,8 @@ def test_pdf_parser():
|
||||
|
||||
def get_metadata(html, base_url=resource_filename('<inline HTML>')):
|
||||
return pdf.prepare_metadata(
|
||||
TestHTML(string=html, base_url=base_url).render(
|
||||
resolution=72, stylesheets=[
|
||||
CSS(string='@page { size: 500pt 1000pt; margin: 50pt }')]),
|
||||
TestHTML(string=html, base_url=base_url).render(stylesheets=[
|
||||
CSS(string='@page { size: 500pt 1000pt; margin: 50pt }')]),
|
||||
bookmark_root_id=0)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user