1
1
mirror of https://github.com/Kozea/WeasyPrint.git synced 2024-10-05 00:21:15 +03:00

Merge branch 'master' into float

This commit is contained in:
Simon Sapin 2012-05-30 19:08:48 +02:00
commit 037e4b955c
12 changed files with 134 additions and 73 deletions

View File

@ -17,7 +17,7 @@ import math
from .properties import INITIAL_VALUES, Dimension
from ..urls import get_url_attribute
from ..compat import urlsplit
from ..compat import urlsplit, unquote
ZERO_PIXELS = Dimension(0, 'px')
@ -435,11 +435,9 @@ def line_height(computer, name, value):
@register_computer('anchor')
def anchor(computer, name, values):
"""Compute the ``anchor`` property."""
if values == 'none':
return None
else:
if values != 'none':
_, key = values
return computer.element.get(key)
return computer.element.get(key) or None
@register_computer('link')
@ -459,7 +457,7 @@ def link(computer, name, values):
parsed = urlsplit(url)
# Compare with fragments removed
if parsed[:-1] == document_uri[:-1]:
return 'internal', parsed.fragment
return 'internal', unquote(parsed.fragment)
else:
return 'external', url

View File

@ -8,8 +8,10 @@ web browsers use.
*/
/* http://www.w3.org/TR/html5/Overview#scroll-to-the-fragment-identifier */
*[id] { -weasy-anchor: attr(id); }
*[name] { -weasy-anchor: attr(name); }
a[name] { -weasy-anchor: attr(name); }
*[dir] { unicode-bidi: embed; }
*[hidden] { display: none; }
*[dir=ltr] { direction: ltr; }

View File

@ -20,3 +20,13 @@ col { display: table-column }
colgroup { display: table-column-group }
td, th { display: table-cell }
caption { display: table-caption }
a[href] { -weasy-link: attr(href); }
a[name] { -weasy-anchor: attr(name); }
*[id] { -weasy-anchor: attr(id); }
h1 { -weasy-bookmark-level: 1; -weasy-bookmark-label: contents; }
h2 { -weasy-bookmark-level: 2; -weasy-bookmark-label: contents; }
h3 { -weasy-bookmark-level: 3; -weasy-bookmark-label: contents; }
h4 { -weasy-bookmark-level: 4; -weasy-bookmark-label: contents; }
h5 { -weasy-bookmark-level: 5; -weasy-bookmark-label: contents; }
h6 { -weasy-bookmark-level: 6; -weasy-bookmark-label: contents; }

View File

@ -17,6 +17,7 @@ from .formatting_structure.build import build_formatting_structure
from . import layout
from . import draw
from . import images
from .logger import LOGGER
class Document(object):
@ -81,19 +82,12 @@ class Document(object):
return self._pages
def get_image_from_uri(self, uri, type_=None):
"""
Same as ``weasy.images.get_image_from_uri`` but cache results
"""
missing = object()
surface = self._image_cache.get(uri, missing)
if surface is missing:
surface = images.get_image_from_uri(uri, type_)
self._image_cache[uri] = surface
return surface
return images.get_image_from_uri(self._image_cache, uri, type_)
def write_to(self, target):
backend = self.backend(target)
for page in self.pages:
context = backend.start_page(page.outer_width, page.outer_height)
draw.draw_page(self, page, context)
backend.finish(self)

View File

@ -62,6 +62,7 @@ def draw_page(document, page, context):
draw_box_background(
document, context, stacking_context.page, stacking_context.box)
draw_canvas_background(document, context, page)
draw_border(context, page)
draw_stacking_context(document, context, stacking_context)
@ -556,6 +557,14 @@ def draw_replacedbox(context, box):
box.style.image_rendering])
context.set_source(pattern)
context.paint()
# Make sure `pattern` is garbage collected. If a surface for a SVG image
# is still alive by the time we call show_page(), cairo will rasterize
# the image instead writing vectors.
# Use a unique string that can be traced back here.
# Replaced boxes are atomic, so they should only ever be drawn once.
# Use an object incompatible with the usual 3-tuple to cause an exception
# if this box is used more than that.
box.replacement = 'Removed to work around cairos behavior'
def draw_inline_level(document, context, page, box):

View File

@ -21,9 +21,13 @@ from .urls import urlopen
from .css.computed_values import LENGTHS_TO_PIXELS
from .logger import LOGGER
#import faulthandler
#faulthandler.enable()
# Map MIME types to functions that take a byte stream and return
# ``(pattern, width, height)`` a cairo Pattern and its dimension in pixels.
# Map MIME types to functions that take a byte stream and return a callable
# that returns ``(pattern, width, height)`` a cairo Pattern and
# its dimension in pixels.
FORMAT_HANDLERS = {}
# TODO: currently CairoSVG only support images with an explicit
@ -44,7 +48,8 @@ def png_handler(file_like, _uri):
"""Return a cairo Surface from a PNG byte stream."""
surface = cairo.ImageSurface.create_from_png(file_like)
pattern = cairo.SurfacePattern(surface)
return pattern, surface.get_width(), surface.get_height()
result = pattern, surface.get_width(), surface.get_height()
return lambda: result
@register_format('image/svg+xml')
@ -70,11 +75,19 @@ def cairosvg_handler(file_like, uri):
# Dont pass data URIs to CairoSVG.
# They are useless for relative URIs anyway.
uri = None
# 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)
pattern = cairo.SurfacePattern(surface.cairo)
return pattern, surface.width, surface.height
bytestring = file_like.read()
# Do not keep a Surface object alive, but regenerate it as needed.
# If a surface for a SVG image is still alive by the time we call
# show_page(), cairo will rasterize the image instead writing vectors.
def draw_svg():
# Draw to a cairo surface but do not write to a file
tree = Tree(bytestring=bytestring, url=uri)
surface = ScaledSVGSurface(tree, output=None, dpi=96)
pattern = cairo.SurfacePattern(surface.cairo)
return pattern, surface.width, surface.height
return draw_svg
def fallback_handler(file_like, uri):
@ -90,20 +103,29 @@ def fallback_handler(file_like, uri):
return png_handler(BytesIO(png_bytes), uri)
def get_image_from_uri(uri, type_=None):
def get_image_from_uri(cache, uri, type_=None):
"""Get a :class:`cairo.Surface`` from an image URI."""
try:
missing = object()
function = cache.get(uri, missing)
if function is not missing:
return function()
file_like, mime_type, _charset = urlopen(uri)
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)
try:
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)
function = handler(file_like, uri)
finally:
try:
file_like.close()
except Exception:
# May already be closed or something.
# This is just cleanup anyway.
pass
cache[uri] = function
return function()
except Exception as exc:
LOGGER.warn('Error for image at %s : %r', uri, exc)
finally:
try:
file_like.close()
except Exception:
# May already be closed or something. This is just cleanup anyway.
pass

View File

@ -117,7 +117,8 @@ def absolute_block(document, box, containing_block):
box.margin_left = 0
if margin_r == 'auto':
box.margin_right = 0
available_width = cb_width
available_width = cb_width - (
padding_plus_borders_x + box.margin_left + box.margin_right)
box.width = shrink_to_fit(box, available_width)
elif left != 'auto' and right != 'auto' and width != 'auto':
width_for_margins = cb_width - (

View File

@ -21,6 +21,9 @@ from ..text import TextFragment
def shrink_to_fit(box, available_width):
"""Return the shrink-to-fit width of ``box``.
*Warning:* both available_outer_width and the return value are
for width of the *content area*, not margin area.
http://www.w3.org/TR/CSS21/visudet.html#float-width
"""

View File

@ -3521,18 +3521,19 @@ def test_absolute_positioning():
<img src=pattern.png>
<span style="position: relative">
<span style="position: absolute">2</span>
<span>3</span>
<span style="position: absolute">3</span>
<span>4</span>
</span>
''')
html, = page.children
body, = html.children
line, = body.children
img, span1 = line.children
span2, span3 = span1.children
span2, span3, span4 = span1.children
assert span1.position_x == 4
assert span2.position_x == 4
assert span3.position_x == 4
assert span2.position_y == span3.position_y
assert (span2.position_x, span2.position_y) == (4, 0)
assert (span3.position_x, span3.position_y) == (4, 0)
assert span4.position_x == 4
page, = parse('''
<style> img { width: 5px; height: 20px} </style>

View File

@ -16,10 +16,9 @@ import io
import cairo
from .. import HTML
from ..backends import MetadataPDFBackend
from .. import HTML, CSS
from .. import pdf
from .testing_utils import assert_no_logs, resource_filename
from .testing_utils import assert_no_logs, resource_filename, TestPDFDocument
@assert_no_logs
@ -40,10 +39,10 @@ def test_pdf_parser():
assert sizes == [b'0 0 100 100', b'0 0 200 10', b'0 0 3.14 987654321']
def get_metadata(html):
document = HTML(
string=html, base_url=resource_filename('<inline HTML>')
)._get_document(MetadataPDFBackend, [])
def get_metadata(html, base_url=resource_filename('<inline HTML>')):
document = TestPDFDocument(html, base_url=base_url,
user_stylesheets=[CSS(
string='@page { -weasy-size: 500pt 1000pt; margin: 50pt }')])
return pdf.gather_metadata(document)
@ -61,8 +60,8 @@ def get_bookmarks(html, structure_only=False):
return root, bookmarks
def get_links(html):
_bookmarks, links, anchors = get_metadata(html)
def get_links(html, **kwargs):
_bookmarks, links, anchors = get_metadata(html, **kwargs)
links = [
[(uri, tuple(round(v, 6) for v in rect)) for uri, rect in page_links]
for page_links in links]
@ -84,12 +83,11 @@ def test_bookmarks():
root, bookmarks = get_bookmarks('''
<style>
@page { -weasy-size: 1000pt; margin: 0 }
* { height: 90pt; margin: 0 0 10pt 0; page-break-inside: auto }
</style>
<h1>Title 1</h1>
<h1>Title 2</h1>
<div style="margin-left: 50pt"><h2>Title 3</h2></div>
<div style="margin-left: 20pt"><h2>Title 3</h2></div>
<h2>Title 4</h2>
<h3>
Title 5
@ -105,27 +103,27 @@ def test_bookmarks():
assert root == dict(Count=11, First=1, Last=10)
assert bookmarks == [
dict(Count=0, First=None, Last=None, Next=2, Parent=0, Prev=None,
label='Title 1', destination=(0, 0, 1000)),
label='Title 1', destination=(0, 50, 950)),
dict(Count=4, First=3, Last=6, Next=7, Parent=0, Prev=1,
label='Title 2', destination=(0, 0, 900)),
label='Title 2', destination=(0, 50, 850)),
dict(Count=0, First=None, Last=None, Next=4, Parent=2, Prev=None,
label='Title 3', destination=(0, 50, 800)),
label='Title 3', destination=(0, 70, 750)),
dict(Count=1, First=5, Last=5, Next=6, Parent=2, Prev=3,
label='Title 4', destination=(0, 0, 700)),
label='Title 4', destination=(0, 50, 650)),
dict(Count=0, First=None, Last=None, Next=None, Parent=4, Prev=None,
label='Title 5', destination=(0, 0, 600)),
label='Title 5', destination=(0, 50, 550)),
dict(Count=0, First=None, Last=None, Next=None, Parent=2, Prev=4,
label='Title 6', destination=(1, 0, 900)),
label='Title 6', destination=(1, 50, 850)),
dict(Count=2, First=8, Last=8, Next=10, Parent=0, Prev=2,
label='Title 7', destination=(1, 0, 800)),
label='Title 7', destination=(1, 50, 750)),
dict(Count=1, First=9, Last=9, Next=None, Parent=7, Prev=None,
label='Title 8', destination=(1, 0, 700)),
label='Title 8', destination=(1, 50, 650)),
dict(Count=0, First=None, Last=None, Next=None, Parent=8, Prev=None,
label='Title 9', destination=(1, 0, 600)),
label='Title 9', destination=(1, 50, 550)),
dict(Count=1, First=11, Last=11, Next=None, Parent=0, Prev=7,
label='Title 10', destination=(1, 0, 500)),
label='Title 10', destination=(1, 50, 450)),
dict(Count=0, First=None, Last=None, Next=None, Parent=10, Prev=None,
label='Title 11', destination=(1, 0, 400))]
label='Title 11', destination=(1, 50, 350))]
root, bookmarks = get_bookmarks('''
<h2>1</h2> level 1
@ -174,7 +172,6 @@ def test_links():
links, anchors = get_links('''
<style>
@page { -weasy-size: 1000pt; margin: 50pt }
body { margin: 0; font-size: 10pt; line-height: 2 }
p { display: block; height: 90pt; margin: 0 0 10pt 0 }
</style>
@ -184,7 +181,7 @@ def test_links():
src=pattern.png></a></p>
<p id=hello>Hello, World</p>
<p id=lipsum><a style="display: block; page-break-before: always"
href="#hello">Lorem ipsum ...</a></p>
href="#hel%6Co">Lorem ipsum ...</a></p>
''')
assert links == [
[
@ -193,8 +190,20 @@ def test_links():
# 5pt wide (image + 2 * 1pt of border), 20pt high
(('internal', 'lipsum'), (60, 850, 65, 830)),
], [
# 900pt wide (block), 20pt high
(('internal', 'hello'), (50, 950, 950, 930)),
# 400pt wide (block), 20pt high
(('internal', 'hello'), (50, 950, 450, 930)),
]
]
assert anchors == {'hello': (0, 50, 750), 'lipsum': (1, 50, 950)}
links, anchors = get_links(
'<div style="-weasy-link: url(../lipsum)">',
base_url='http://weasyprint.org/foo/bar/')
assert links == [[(('external', 'http://weasyprint.org/foo/lipsum'),
(50, 950, 450, 950))]]
# Relative URI reference without a base URI.
# links, anchors = get_links(
# '<div style="-weasy-link: url(../lipsum)">',
# base_url=None)
# assert links == [[]]

View File

@ -20,7 +20,7 @@ import functools
from .. import HTML, CSS
from ..document import Document
from ..backends import PNGBackend
from ..backends import PNGBackend, MetadataPDFBackend
from ..logger import LOGGER
@ -33,20 +33,31 @@ TEST_UA_STYLESHEET = CSS(os.path.join(
class TestPNGDocument(Document):
"""Like PNGDocument, but with a different user-agent stylesheet.
"""A Document with a PNG backend and a different user-agent stylesheet.
This stylesheet is shorter, which makes tests faster.
"""
backend_class = PNGBackend
def __init__(self, html_source, base_url=None, user_stylesheets=(),
user_agent_stylesheets=(TEST_UA_STYLESHEET,)):
super(TestPNGDocument, self).__init__(
PNGBackend,
self.backend_class,
HTML(string=html_source, base_url=base_url).root_element,
user_stylesheets=user_stylesheets,
user_agent_stylesheets=user_agent_stylesheets)
class TestPDFDocument(TestPNGDocument):
"""A Document with a PDF backend and a different user-agent stylesheet.
This stylesheet is shorter, which makes tests faster.
"""
backend_class = MetadataPDFBackend
def resource_filename(basename):
"""Return the absolute path of the resource called ``basename``."""
return os.path.join(os.path.dirname(__file__), 'resources', basename)

View File

@ -130,8 +130,9 @@ def run(suite_directory):
from pygments.lexers import HtmlLexer
from pygments.formatters import HtmlFormatter
with open(safe_join(suite_directory, test['test_id'] + '.htm')) as fd:
source = fd.read()
filename = safe_join(suite_directory, test['test_id'] + '.htm')
with open(filename, 'rb') as fd:
source = fd.read().decode('utf8')
formatter = HtmlFormatter()
source = highlight(source, HtmlLexer(), formatter)