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

Add first-level bookmarks, use floats for XY positions, fix little things

This commit is contained in:
Guillaume Ayoub 2012-05-15 19:29:54 +02:00
parent 1ea79c9e9e
commit 111932edf5
8 changed files with 201 additions and 42 deletions

View File

@ -416,6 +416,16 @@ def line_height(computer, name, value):
return ('PIXELS', pixels)
@register_computer('anchor')
def anchor(computer, name, values):
"""Compute the ``anchor`` property."""
if values == 'none':
return None
else:
_, key = values
return computer.element.get(key)
@register_computer('link')
def link(computer, name, values):
"""Compute the ``link`` property."""
@ -432,16 +442,6 @@ def link(computer, name, values):
return url
@register_computer('label')
def label(computer, name, values):
"""Compute the ``label`` property."""
if values == 'none':
return None
else:
_, key = values
return computer.element.get(key)
@register_computer('transform')
def transform(computer, name, value):
"""Compute the ``transform`` property."""

View File

@ -8,8 +8,8 @@ web browsers use.
*/
*[id] { -weasy-label: attr(id); }
*[name] { -weasy-label: attr(name); }
*[id] { -weasy-anchor: attr(id); }
*[name] { -weasy-anchor: attr(name); }
*[dir] { unicode-bidi: embed; }
*[hidden] { display: none; }
*[dir=ltr] { direction: ltr; }
@ -294,17 +294,17 @@ form { display: block; unicode-bidi: isolate; }
frame { display: block; }
frameset { display: block; }
h1 { display: block; font-size: 2em; font-weight: bold; hyphens: manual; margin-bottom: .67em; margin-top: .67em; page-break-after: avoid; page-break-inside: avoid; unicode-bidi: isolate; }
h1 { display: block; font-size: 2em; font-weight: bold; hyphens: manual; margin-bottom: .67em; margin-top: .67em; page-break-after: avoid; page-break-inside: avoid; unicode-bidi: isolate; -weasy-bookmark-level: 1; -weasy-bookmark-label: contents; }
section h1 { font-size: 1.50em; margin-bottom: .83em; margin-top: .83em; }
section section h1 { font-size: 1.17em; margin-bottom: 1.00em; margin-top: 1.00em; }
section section section h1 { font-size: 1.00em; margin-bottom: 1.33em; margin-top: 1.33em; }
section section section section h1 { font-size: .83em; margin-bottom: 1.67em; margin-top: 1.67em; }
section section section section section h1 { font-size: .67em; margin-bottom: 2.33em; margin-top: 2.33em; }
h2 { display: block; font-size: 1.50em; font-weight: bold; hyphens: manual; margin-bottom: .83em; margin-top: .83em; page-break-after: avoid; page-break-inside: avoid; unicode-bidi: isolate; }
h3 { display: block; font-size: 1.17em; font-weight: bold; hyphens: manual; margin-bottom: 1.00em; margin-top: 1.00em; page-break-after: avoid; page-break-inside: avoid; unicode-bidi: isolate; }
h4 { display: block; font-size: 1.00em; font-weight: bold; hyphens: manual; margin-bottom: 1.33em; margin-top: 1.33em; page-break-after: avoid; page-break-inside: avoid; unicode-bidi: isolate; }
h5 { display: block; font-size: .83em; font-weight: bold; hyphens: manual; margin-bottom: 1.67em; margin-top: 1.67em; page-break-after: avoid; unicode-bidi: isolate; }
h6 { display: block; font-size: .67em; font-weight: bold; hyphens: manual; margin-bottom: 2.33em; margin-top: 2.33em; page-break-after: avoid; unicode-bidi: isolate; }
h2 { display: block; font-size: 1.50em; font-weight: bold; hyphens: manual; margin-bottom: .83em; margin-top: .83em; page-break-after: avoid; page-break-inside: avoid; unicode-bidi: isolate; -weasy-bookmark-level: 2; -weasy-bookmark-label: contents; }
h3 { display: block; font-size: 1.17em; font-weight: bold; hyphens: manual; margin-bottom: 1.00em; margin-top: 1.00em; page-break-after: avoid; page-break-inside: avoid; unicode-bidi: isolate; -weasy-bookmark-level: 3; -weasy-bookmark-label: contents; }
h4 { display: block; font-size: 1.00em; font-weight: bold; hyphens: manual; margin-bottom: 1.33em; margin-top: 1.33em; page-break-after: avoid; page-break-inside: avoid; unicode-bidi: isolate; -weasy-bookmark-level: 4; -weasy-bookmark-label: contents; }
h5 { display: block; font-size: .83em; font-weight: bold; hyphens: manual; margin-bottom: 1.67em; margin-top: 1.67em; page-break-after: avoid; unicode-bidi: isolate; -weasy-bookmark-level: 5; -weasy-bookmark-label: contents; }
h6 { display: block; font-size: .67em; font-weight: bold; hyphens: manual; margin-bottom: 2.33em; margin-top: 2.33em; page-break-after: avoid; unicode-bidi: isolate; -weasy-bookmark-level: 6; -weasy-bookmark-label: contents; }
head { display: none; }
header { display: block; unicode-bidi: isolate; }

View File

@ -129,8 +129,13 @@ INITIAL_VALUES = {
'image_rendering': 'auto',
# Proprietary
'anchor': None, # computed value of 'none'
'link': None, # computed value of 'none'
'label': None, # computed value of 'none'
# CSS3 Generated Content for Paged Media
# http://dev.w3.org/csswg/css3-gcpm/
'bookmark_label': ('keyword', 'none'), # computed value of 'none'
'bookmark_level': 'none',
}

View File

@ -645,7 +645,7 @@ def orphans_widows(token):
"""Validation for the ``orphans`` or ``widows`` properties."""
if token.type == 'INTEGER':
value = token.value
if int(value) == value and value >= 1:
if value >= 1:
return value
@ -819,6 +819,21 @@ def size(tokens):
return width, height
@validator(prefixed=True) # Proprietary
@single_token
def anchor(token):
"""Validation for ``anchor``."""
if get_keyword(token) == 'none':
return 'none'
function = parse_function(token)
if function:
name, args = function
prototype = (name, [a.type for a in args])
args = [a.value for a in args]
if prototype == ('attr', ['IDENT']):
return (name, args[0])
@validator(prefixed=True, wants_base_url=True) # Proprietary
@single_token
def link(token, base_url):
@ -837,19 +852,28 @@ def link(token, base_url):
return token.value
@validator(prefixed=True) # Proprietary
@validator(prefixed=True) # CSS3 GCPM
@single_token
def label(token):
"""Validation for ``label``."""
if get_keyword(token) == 'none':
def bookmark_label(token):
"""Validation for ``bookmark-label``."""
keyword = get_keyword(token)
if keyword in ('none', 'contents', 'content-before',
'content-element', 'content-after'):
return ('keyword', keyword)
elif token.type == 'STRING':
return ('string', token.value)
@validator(prefixed=True) # CSS3 GCPM
@single_token
def bookmark_level(token):
"""Validation for ``bookmark-level``."""
if token.type == 'INTEGER':
value = token.value
if value >= 1:
return value
elif get_keyword(token) == 'none':
return 'none'
function = parse_function(token)
if function:
name, args = function
prototype = (name, [a.type for a in args])
args = [a.value for a in args]
if prototype == ('attr', ['IDENT']):
return (name, args[0])
@validator(prefixed=True) # Not in CR yet

View File

@ -182,12 +182,54 @@ class PDFDocument(Document):
links = [self._get_link_rectangles(page) for page in self.pages]
destinations = dict(self._get_link_destinations())
bookmarks_tree = []
bookmarks_stack = [bookmarks_tree]
bookmark_level = 0
for level, label, destination in self._get_bookmarks():
if not bookmark_level:
bookmark_level = level - 1
if level < bookmark_level:
for i in range(bookmark_level - level + 1):
bookmarks_stack.pop()
elif level > bookmark_level:
missing_levels = level - bookmark_level
for i in range(missing_levels):
children = []
bookmarks_stack[-1].append((label, destination, children))
bookmarks_stack.append(children)
else:
bookmarks_stack.pop()
children = []
bookmarks_stack[-1].append((label, destination, children))
bookmarks_stack.append(children)
bookmark_level = level
if hasattr(target, 'write'):
pdf.write(bytesio, target, links, destinations)
pdf.write(bytesio, target, links, destinations, bookmarks_tree)
else:
with open(target, 'wb') as fd:
pdf.write(bytesio, fd, links, destinations)
pdf.write(bytesio, fd, links, destinations, bookmarks_tree)
def _get_bookmarks(self, page=None, box=None):
if page is None:
for page in self.pages:
for bookmark in self._get_bookmarks(page, page):
yield bookmark
else:
if box.bookmark_label and box.style.bookmark_level != 'none':
position_x = box.position_x
position_y = page.outer_height - box.position_y
yield (
box.style.bookmark_level,
box.bookmark_label,
(self.pages.index(page),
position_x / LENGTHS_TO_PIXELS['pt'],
position_y / LENGTHS_TO_PIXELS['pt']))
if isinstance(box, boxes.ParentBox):
for child in box.children:
for bookmark in self._get_bookmarks(page, child):
yield bookmark
def _get_link_rectangles(self, page, box=None):
if box is None:
@ -216,12 +258,12 @@ class PDFDocument(Document):
page, page, names):
yield destination
else:
if box.style.label and (box.style.label not in names):
names.add(box.style.label)
if box.style.anchor and box.style.anchor not in names:
names.add(box.style.anchor)
position_x = box.position_x
position_y = page.outer_height - box.position_y
yield (
box.style.label,
box.style.anchor,
(self.pages.index(page),
position_x / LENGTHS_TO_PIXELS['pt'],
position_y / LENGTHS_TO_PIXELS['pt']))

View File

@ -76,10 +76,8 @@ class Box(object):
# Default, may be overriden on instances.
is_table_wrapper = False
# Default, may be overriden on instances.
is_for_root_element = False
bookmark_label = None
def __init__(self, element_tag, sourceline, style):
self.element_tag = element_tag

View File

@ -134,6 +134,8 @@ def dom_to_box(document, element, state=None):
box = box.copy_with_children(children)
resolve_bookmark_labels(box)
# Specific handling for the element. (eg. replaced element)
return html.handle_element(document, element, box)
@ -892,3 +894,61 @@ def set_viewport_overflow(root_box):
root_box.viewport_overflow = chosen_box.style.overflow
chosen_box.style = chosen_box.style.updated_copy({'overflow': 'visible'})
return root_box
def box_text_contents(box):
if isinstance(box, boxes.TextBox):
return box.text
elif isinstance(box, boxes.ParentBox):
return ''.join(box_text_contents(child) for child in box.children)
else:
return ''
def box_text_content_element(box):
if isinstance(box, boxes.ParentBox):
return ''.join(
box_text_contents(child) for child in box.children
if not child.element_tag.endswith((':after', ':before')))
else:
return ''
def box_text_content_before(box):
if isinstance(box, boxes.ParentBox):
return ''.join(
box_text_contents(child) for child in box.children
if child.element_tag.endswith(':before'))
else:
return ''
def box_text_content_after(box):
if isinstance(box, boxes.ParentBox):
return ''.join(
box_text_contents(child) for child in box.children
if child.element_tag.endswith(':after'))
else:
return ''
def resolve_bookmark_labels(box):
"""Set the used value of the bookmark-label.
See http://dev.w3.org/csswg/css3-gcpm/#bookmarks
"""
key, value = box.style.bookmark_label
if key == 'keyword':
if value == 'none':
box.bookmark_label = None
elif value == 'content-element':
box.bookmark_label = box_text_content_element(box)
elif value == 'contents':
box.bookmark_label = box_text_contents(box)
elif value == 'content-before':
box.bookmark_label = box_text_content_before(box)
elif value == 'content-before':
box.bookmark_label = box_text_content_after(box)
else:
box.bookmark_label = value

View File

@ -24,7 +24,7 @@ def pdf_encode(unicode_string):
return ('\ufeff' + unicode_string).encode('utf-16-be')
def write(bytesio, target, links, destinations):
def write(bytesio, target, links, destinations, bookmarks):
"""Write PDF from ``bytesio`` to ``target`` adding ``links``."""
bytesio.seek(0)
position = 0
@ -47,6 +47,9 @@ def write(bytesio, target, links, destinations):
if line.endswith(b'/Type /Page\n'):
pages.append(number)
if line.endswith(b'/Type /Catalog\n'):
catalog = number
while lines[0] != b'trailer\n':
lines.pop(0)
@ -78,8 +81,8 @@ def write(bytesio, target, links, destinations):
if link.startswith('#'):
if link[1:] in destinations:
text.append((
'/A << /Type /Action /S /GoTo'
'/D [%d /XYZ %d %d 1]\n'
'/A << /Type /Action /S /GoTo '
'/D [%d /XYZ %f %f 0]\n'
% destinations[link[1:]]
).encode('ascii'))
else:
@ -100,6 +103,33 @@ def write(bytesio, target, links, destinations):
b']\n'
]))
if bookmarks:
parent = number
objects[catalog].insert(
-2, ('/Outlines %d 0 R\n' % parent).encode('ascii'))
objects[parent] = [(
'%d 0 obj\n<< /Type /Outlines '
'/Count %d /First %d 0 R /Last %d 0 R\n>>\nendobj\n' % (
parent, len(bookmarks), parent + 1, parent + len(bookmarks))
).encode('ascii')]
number += 1
for bookmark in bookmarks:
label, destination, children = bookmark
text = ('%d 0 obj\n<< /Title (' % number).encode('ascii')
text += pdf_encode(label)
text += (') /Parent %d 0 R\n' % parent).encode('ascii')
if number > parent + 1:
text += ('/Prev %d 0 R\n' % (number - 1)).encode('ascii')
elif number < parent + len(bookmarks):
text += ('/Next %d 0 R\n' % (number + 1)).encode('ascii')
text += (
'/A << /Type /Action /S /GoTo '
'/D [%d /XYZ %f %f 0]\n>>\n>>\nendobj\n'
% destination).encode('ascii')
objects[number] = [text]
number += 1
for i, line in enumerate(trailer):
if b'/Size' in line:
trailer[i] = (