Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle ::first-letter #407

Merged
merged 7 commits into from
Feb 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions weasyprint/css/html5_ua.css
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ big { font-size: larger; }
blink { text-decoration: blink; }
blockquote { display: block; margin: 1em 40px; unicode-bidi: isolate; }
body { display: block; margin: 8px; }
br:before { content: '\A'; white-space: pre-line; }
br::before { content: '\A'; white-space: pre-line; }
button { display: inline-block; text-align: center; text-indent: 0; }
caption { display: table-caption; unicode-bidi: isolate; }
center { display: block; text-align: center; unicode-bidi: isolate; }
Expand Down Expand Up @@ -419,8 +419,8 @@ p { display: block; margin-bottom: 1em; margin-top: 1em; unicode-bidi: isolate;
param { display: none; }
plaintext { display: block; font-family: monospace; margin-bottom: 1em; margin-top: 1em; unicode-bidi: isolate; white-space: pre; }
pre { display: block; font-family: monospace; margin-bottom: 1em; margin-top: 1em; unicode-bidi: isolate; white-space: pre; }
q:after { content: close-quote; }
q:before { content: open-quote; }
q::after { content: close-quote; }
q::before { content: open-quote; }
rp { display: none; }
rt { display: ruby-text; }
ruby { display: ruby; }
Expand Down
23 changes: 14 additions & 9 deletions weasyprint/formatting_structure/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ def element_to_box(element, style_for, get_image_from_uri, state=None):
# names will be in this new list
counter_scopes.append(set())

children.extend(pseudo_to_box(
box.first_letter_style = style_for(element, 'first-letter')
box.first_line_style = style_for(element, 'first-line')

children.extend(before_after_to_box(
element, 'before', state, style_for, get_image_from_uri))
text = element.text
if text:
Expand All @@ -164,7 +167,7 @@ def element_to_box(element, style_for, get_image_from_uri, state=None):
children[-1].text += text_box.text
else:
children.append(text_box)
children.extend(pseudo_to_box(
children.extend(before_after_to_box(
element, 'after', state, style_for, get_image_from_uri))

# Scopes created by this element’s children stop here.
Expand All @@ -174,14 +177,16 @@ def element_to_box(element, style_for, get_image_from_uri, state=None):
counter_values.pop(name)

box.children = children
box.first_letter_style = style_for(element, 'first-letter')
replace_content_lists(element, box, style, counter_values)

# Specific handling for the element. (eg. replaced element)
return html.handle_element(element, box, get_image_from_uri)


def pseudo_to_box(element, pseudo_type, state, style_for, get_image_from_uri):
"""Yield the box for a :before or :after pseudo-element if there is one."""
def before_after_to_box(element, pseudo_type, state, style_for,
get_image_from_uri):
"""Yield the box for ::before or ::after pseudo-element if there is one."""
style = style_for(element, pseudo_type)
if pseudo_type and style is None:
# Pseudo-elements with no style at all do not get a StyleDict
Expand All @@ -196,7 +201,7 @@ def pseudo_to_box(element, pseudo_type, state, style_for, get_image_from_uri):
return

box = make_box(
'%s:%s' % (element.tag, pseudo_type), element.sourceline, style, [],
'%s::%s' % (element.tag, pseudo_type), element.sourceline, style, [],
get_image_from_uri)

quote_depth, counter_values, _counter_scopes = state
Expand Down Expand Up @@ -1135,8 +1140,8 @@ def box_text(box):
elif isinstance(box, boxes.ParentBox):
return ''.join(
child.text for child in box.descendants()
if not child.element_tag.endswith(':before') and
not child.element_tag.endswith(':after') and
if not child.element_tag.endswith('::before') and
not child.element_tag.endswith('::after') and
isinstance(child, boxes.TextBox))
else:
return ''
Expand All @@ -1146,7 +1151,7 @@ def box_text_before(box):
if isinstance(box, boxes.ParentBox):
return ''.join(
box_text(child) for child in box.descendants()
if child.element_tag.endswith(':before') and
if child.element_tag.endswith('::before') and
not isinstance(child, boxes.ParentBox))
else:
return ''
Expand All @@ -1156,7 +1161,7 @@ def box_text_after(box):
if isinstance(box, boxes.ParentBox):
return ''.join(
box_text(child) for child in box.descendants()
if child.element_tag.endswith(':after') and
if child.element_tag.endswith('::after') and
not isinstance(child, boxes.ParentBox))
else:
return ''
Expand Down
7 changes: 6 additions & 1 deletion weasyprint/layout/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,8 +418,10 @@ def block_container_layout(context, box, max_position_y, skip_stack,

if is_start:
skip = 0
first_letter_style = getattr(box, 'first_letter_style', None)
else:
skip, skip_stack = skip_stack
first_letter_style = None
for index, child in box.enumerate_skip(skip):
child.position_x = position_x
# XXX does not count margins in adjoining_margins:
Expand Down Expand Up @@ -473,7 +475,8 @@ def block_container_layout(context, box, max_position_y, skip_stack,
new_containing_block = box
lines_iterator = iter_line_boxes(
context, child, position_y, skip_stack,
new_containing_block, device_size, absolute_boxes, fixed_boxes)
new_containing_block, device_size, absolute_boxes, fixed_boxes,
first_letter_style)
is_page_break = False
for line, resume_at in lines_iterator:
line.resume_at = resume_at
Expand Down Expand Up @@ -590,6 +593,8 @@ def block_container_layout(context, box, max_position_y, skip_stack,
position_y += collapsed_margin
adjoining_margins = []

if not getattr(child, 'first_letter_style', None):
child.first_letter_style = first_letter_style
(new_child, resume_at, next_page, next_adjoining_margins,
collapsing_through) = block_level_layout(
context, child, max_position_y, skip_stack,
Expand Down
88 changes: 85 additions & 3 deletions weasyprint/layout/inlines.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

from __future__ import division, unicode_literals

import unicodedata

from .absolute import absolute_layout, AbsolutePlaceholder
from .float import avoid_collisions, float_layout
from .replaced import image_marker_layout
Expand All @@ -26,7 +28,8 @@


def iter_line_boxes(context, box, position_y, skip_stack, containing_block,
device_size, absolute_boxes, fixed_boxes):
device_size, absolute_boxes, fixed_boxes,
first_letter_style):
"""Return an iterator of ``(line, resume_at)``.

``line`` is a laid-out LineBox with as much content as possible that
Expand All @@ -45,7 +48,7 @@ def iter_line_boxes(context, box, position_y, skip_stack, containing_block,
while 1:
line, resume_at = get_next_linebox(
context, box, position_y, skip_stack, containing_block,
device_size, absolute_boxes, fixed_boxes)
device_size, absolute_boxes, fixed_boxes, first_letter_style)
if line:
position_y = line.position_y + line.height
if line is None:
Expand All @@ -54,11 +57,12 @@ def iter_line_boxes(context, box, position_y, skip_stack, containing_block,
if resume_at is None:
return
skip_stack = resume_at
first_letter_style = None


def get_next_linebox(context, linebox, position_y, skip_stack,
containing_block, device_size, absolute_boxes,
fixed_boxes):
fixed_boxes, first_letter_style):
"""Return ``(line, resume_at)``."""
resolve_percentages(linebox, containing_block)
if skip_stack is None:
Expand All @@ -67,11 +71,14 @@ def get_next_linebox(context, linebox, position_y, skip_stack,
resolve_one_percentage(linebox, 'text_indent', containing_block.width)
else:
linebox.text_indent = 0
first_letter_style = None

skip_stack = skip_first_whitespace(linebox, skip_stack)
if skip_stack == 'continue':
return None, None

skip_stack = first_letter_to_box(linebox, skip_stack, first_letter_style)

linebox.width = inline_min_content_width(
context, linebox, skip_stack=skip_stack, first_line=True)

Expand Down Expand Up @@ -242,6 +249,81 @@ def remove_last_whitespace(context, box):
# 'white-space' set to 'pre-wrap', UAs may visually collapse them.


def first_letter_to_box(box, skip_stack, first_letter_style):
"""Create a box for the ::first-letter selector."""
if first_letter_style and box.children:
first_letter = ''
child = box.children[0]
if isinstance(child, boxes.TextBox):
if child.element_tag.endswith('::first-letter'):
letter_box = boxes.Inbox(
'%s::first-letter' % box.element_tag,
box.sourceline, first_letter_style.inherit_from(),
[child])
box.children = (
(letter_box,) + tuple(box.children[1:]))
elif child.text:
character_found = False
if skip_stack:
child_skip_stack = skip_stack[1]
if child_skip_stack:
index = child_skip_stack[0]
child.text = child.text[index:]
skip_stack = None
while child.text:
next_letter = child.text[0]
category = unicodedata.category(next_letter)
if category not in ('Ps', 'Pe', 'Pi', 'Pf', 'Po'):
if character_found:
break
character_found = True
first_letter += next_letter
child.text = child.text[1:]
if first_letter.lstrip('\n'):
# "This type of initial letter is similar to an
# inline-level element if its 'float' property is 'none',
# otherwise it is similar to a floated element."
if first_letter_style['float'] == 'none':
letter_box = boxes.InlineBox(
'%s::first-letter' % box.element_tag,
box.sourceline, first_letter_style, [])
text_box = boxes.TextBox(
'%s::first-letter' % box.element_tag,
box.sourceline, letter_box.style.inherit_from(),
first_letter)
letter_box.children = (text_box,)
box.children = (letter_box,) + tuple(box.children)
else:
letter_box = boxes.BlockBox(
'%s::first-letter' % box.element_tag,
box.sourceline, first_letter_style, [])
letter_box.first_letter_style = None
line_box = boxes.LineBox(
'%s::first-letter' % box.element_tag,
box.sourceline, letter_box.style.inherit_from(),
[])
letter_box.children = (line_box,)
text_box = boxes.TextBox(
'%s::first-letter' % box.element_tag,
box.sourceline, letter_box.style.inherit_from(),
first_letter)
line_box.children = (text_box,)
box.children = (letter_box,) + tuple(box.children)
if skip_stack and child_skip_stack:
skip_stack = (skip_stack[0], (
child_skip_stack[0] + 1, child_skip_stack[1]))
elif isinstance(child, boxes.ParentBox):
if skip_stack:
child_skip_stack = skip_stack[1]
else:
child_skip_stack = None
child_skip_stack = first_letter_to_box(
child, child_skip_stack, first_letter_style)
if skip_stack:
skip_stack = (skip_stack[0], child_skip_stack)
return skip_stack


@handle_min_max_width
def replaced_box_width(box, device_size):
"""
Expand Down
Loading