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

Adding annotations to the PDF to link back its content to its source. #2192

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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: 6 additions & 0 deletions weasyprint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
#: Whether custom HTML metadata should be stored in the generated PDF.
#: :param bool presentational_hints:
#: Whether HTML presentational hints are followed.
#: :param bool srgb:
#: Whether sRGB color profile should be included and set as default for
#: device-dependant RGB colors.
#: :param bool optimize_images:
#: Whether size of embedded images should be optimized, with no quality
#: loss.
Expand All @@ -71,6 +74,7 @@
'uncompressed_pdf': False,
'custom_metadata': False,
'presentational_hints': False,
'srgb': False,
'optimize_images': False,
'jpeg_quality': None,
'dpi': None,
Expand Down Expand Up @@ -210,6 +214,8 @@ def render(self, font_config=None, counter_style=None, **options):
:returns: A :class:`document.Document` object.

"""
for unknown in set(options) - set(DEFAULT_OPTIONS):
LOGGER.warning('Unknown rendering option: %s.', unknown)
new_options = DEFAULT_OPTIONS.copy()
new_options.update(options)
options = new_options
Expand Down
15 changes: 13 additions & 2 deletions weasyprint/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ def docstring(self):
PARSER.add_argument(
'-p', '--presentational-hints', action='store_true',
help='follow HTML presentational hints')
PARSER.add_argument(
'--srgb', action='store_true',
help='include sRGB color profile')
PARSER.add_argument(
'--optimize-images', action='store_true',
help='optimize size of embedded images with no quality loss')
Expand Down Expand Up @@ -165,7 +168,8 @@ def main(argv=None, stdout=None, stdin=None, HTML=HTML): # noqa: N803
if args.timeout is not None:
url_fetcher = partial(default_url_fetcher, timeout=args.timeout)

options = vars(args)
options = {
key: value for key, value in vars(args).items() if key in DEFAULT_OPTIONS}

# Default to logging to stderr.
if args.debug:
Expand All @@ -174,7 +178,14 @@ def main(argv=None, stdout=None, stdin=None, HTML=HTML): # noqa: N803
LOGGER.setLevel(logging.INFO)
if not args.quiet:
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
if args.debug:
# Add extra information when debug logging
handler.setFormatter(
logging.Formatter(
'%(levelname)s: %(filename)s:%(lineno)d '
'(%(funcName)s): %(message)s'))
else:
handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
LOGGER.addHandler(handler)

html = HTML(
Expand Down
10 changes: 7 additions & 3 deletions weasyprint/anchors.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,17 @@ def gather_anchors(box, anchors, links, bookmarks, forms, parent_matrix=None,
links.append((link_type, target, rectangle, box))
if is_input:
forms[parent_form].append((box.element, box.style, rectangle))
if matrix and (has_bookmark or has_anchor):
pos_x, pos_y = matrix.transform_point(pos_x, pos_y)
if has_bookmark:
if matrix:
pos_x, pos_y = matrix.transform_point(pos_x, pos_y)
bookmark = (bookmark_level, bookmark_label, (pos_x, pos_y), state)
bookmarks.append(bookmark)
if has_anchor:
anchors[anchor_name] = pos_x, pos_y
pos_x1, pos_y1, pos_x2, pos_y2 = pos_x, pos_y, pos_x + width, pos_y + height
if matrix:
pos_x1, pos_y1 = matrix.transform_point(pos_x1, pos_y1)
pos_x2, pos_y2 = matrix.transform_point(pos_x2, pos_y2)
anchors[anchor_name] = (pos_x1, pos_y1, pos_x2, pos_y2)

for child in box.all_children():
gather_anchors(child, anchors, links, bookmarks, forms, matrix, parent_form)
Expand Down
8 changes: 4 additions & 4 deletions weasyprint/css/html5_ua.css
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,14 @@ input[type="reset"]:not([value])::before {
}
input[type="checkbox"],
input[type="radio"] {
height: 1.2em;
width: 1.2em;
height: 0.7em;
vertical-align: -0.2em;
width: 0.7em;
}
input[type="checkbox"][checked]:before,
input[type="radio"][checked]:before {
background: black;
content: "";
display: block;
height: 100%;
}
input[type="radio"][checked]:before {
Expand All @@ -179,7 +179,6 @@ input[type="hidden"] {
}
input[type="radio"] {
border-radius: 50%;
margin: 0.2em 0.2em 0 0.4em;
}
input[value]::before {
content: attr(value);
Expand All @@ -191,6 +190,7 @@ input[value=""]::before,
input[type="checkbox"]::before,
input[type="radio"]::before {
content: "";
display: block;
}
select {
background: lightgrey;
Expand Down
10 changes: 10 additions & 0 deletions weasyprint/css/html5_ua_form.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,13 @@
button, input, select, textarea {
appearance: auto;
}

select option,
select:not([multiple])::before,
input:not([type="submit"])::before {
visibility: hidden;
}

textarea {
color: transparent;
}
2 changes: 1 addition & 1 deletion weasyprint/draw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ def draw_background_image(stream, layer, image_rendering):
with stacked(stream):
layer.image.draw(group, image_width, image_height, image_rendering)
pattern.draw_x_object(group.id)
stream.color_space('Pattern')
stream.set_color_space('Pattern')
stream.set_color_special(pattern.id)
if layer.unbounded:
x1, y1, x2, y2 = stream.page_rectangle
Expand Down
5 changes: 4 additions & 1 deletion weasyprint/draw/border.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ def draw_border(stream, box):
with stacked(stream):
rule_width = box.style['column_rule_width']
rule_style = box.style['column_rule_style']
gap = percentage(box.style['column_gap'], box.width)
if box.style['column_gap'] == 'normal':
gap = box.style['font_size'] # normal equals 1em
else:
gap = percentage(box.style['column_gap'], box.width)
position_x = (
child.position_x - (box.style['column_rule_width'] + gap) / 2)
border_box = (position_x, child.position_y, rule_width, child.height)
Expand Down
2 changes: 1 addition & 1 deletion weasyprint/draw/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix):

utf8_text = textbox.pango_layout.text.encode()
previous_utf8_position = 0
stream.text_matrix(*matrix.values)
stream.set_text_matrix(*matrix.values)
last_font = None
string = ''
x_advance = 0
Expand Down
2 changes: 1 addition & 1 deletion weasyprint/formatting_structure/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def element_to_box(element, style_for, get_image_from_uri, base_url,
if not counter_values[name]:
counter_values.pop(name)

box.children = children if style['appearance'] == 'none' else []
box.children = children
process_whitespace(box)
set_content_lists(
element, box, style, counter_values, target_collector, counter_style)
Expand Down
2 changes: 1 addition & 1 deletion weasyprint/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ def draw(self, stream, concrete_width, concrete_height, _image_rendering):
alpha_stream.transform(d=scale_y)
alpha_stream.stream = [f'/{alpha_shading.id} sh']

stream.shading(shading.id)
stream.paint_shading(shading.id)

def layout(self, width, height):
"""Get layout information about the gradient.
Expand Down
29 changes: 26 additions & 3 deletions weasyprint/layout/flex.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from ..css.properties import Dimension
from ..formatting_structure import boxes
from .absolute import AbsolutePlaceholder, absolute_layout
from .percent import resolve_one_percentage, resolve_percentages
from .preferred import max_content_width, min_content_width
from .table import find_in_flow_baseline
Expand All @@ -21,6 +22,10 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
context.create_block_formatting_context()
resume_at = None

if box.style['position'] == 'relative':
# New containing block, use a new absolute list
absolute_boxes = []

# Step 1 is done in formatting_structure.boxes
# Step 2
if box.style['flex_direction'].startswith('row'):
Expand Down Expand Up @@ -111,8 +116,19 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
else:
skip_stack = None
child_skip_stack = skip_stack
for child in children:
for index, child in enumerate(children):
if not child.is_flex_item:
# Absolute child layout: create placeholder.
if child.is_absolutely_positioned():
child.position_x = box.content_box_x()
child.position_y = box.content_box_y()
new_child = placeholder = AbsolutePlaceholder(child)
placeholder.index = index
children[index] = placeholder
if child.style['position'] == 'absolute':
absolute_boxes.append(placeholder)
else:
fixed_boxes.append(placeholder)
continue

# See https://www.w3.org/TR/css-flexbox-1/#min-size-auto
Expand Down Expand Up @@ -839,8 +855,8 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,

# TODO: don't use block_box_layout, see TODOs in Step 14 and
# build.flex_children.
box = box.copy()
box.children = []
box = box.copy_with_children(
[child for child in children if not child.is_flex_item])
child_skip_stack = skip_stack
for line in flex_lines:
for i, child in line:
Expand Down Expand Up @@ -876,6 +892,13 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block,
if resume_at:
break

if box.style['position'] == 'relative':
# New containing block, resolve the layout of the absolute descendants
for absolute_box in absolute_boxes:
absolute_layout(
context, absolute_box, box, fixed_boxes, bottom_space,
skip_stack=None)

# Set box height
# TODO: this is probably useless because of step #15
if axis == 'width' and box.height == 'auto':
Expand Down
71 changes: 30 additions & 41 deletions weasyprint/layout/preferred.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def max_content_width(context, box, outer=True):
if box.is_table_wrapper:
return table_and_columns_preferred_widths(context, box, outer)[1]
elif isinstance(box, boxes.TableCellBox):
return table_cell_max_content_width(context, box, outer)
return table_cell_min_max_content_width(context, box, outer)[1]
elif isinstance(box, (
boxes.BlockContainerBox, boxes.TableColumnBox, boxes.FlexBox)):
return block_max_content_width(context, box, outer)
Expand Down Expand Up @@ -265,22 +265,18 @@ def table_cell_min_content_width(context, box, outer):
return children_min_width


def table_cell_max_content_width(context, box, outer):
"""Return the max-content width for a ``TableCellBox``."""
return max(
table_cell_min_content_width(context, box, outer),
block_max_content_width(context, box, outer))
def table_cell_min_max_content_width(context, box, outer=True):
"""Return the min- and max-content width for a ``TableCellBox``."""
# This is much faster than calling min and max separately.
min_width = table_cell_min_content_width(context, box, outer)
max_width = max(min_width, block_max_content_width(context, box, outer))
return min_width, max_width


def inline_line_widths(context, box, outer, is_line_start, minimum,
skip_stack=None, first_line=False):
if isinstance(box, boxes.LineBox):
if box.style['text_indent'].unit == '%':
# TODO: this is wrong, text-indent percentages should be resolved
# before calling this function.
text_indent = 0
else:
text_indent = box.style['text_indent'].value
def inline_line_widths(context, box, outer, is_line_start, minimum, skip_stack=None,
first_line=False):
if isinstance(box, boxes.LineBox) and box.style['text_indent'].unit != '%':
text_indent = box.style['text_indent'].value
else:
text_indent = 0

Expand Down Expand Up @@ -459,37 +455,31 @@ def table_and_columns_preferred_widths(context, box, outer=True):
colspan_cells = []

# Define the intermediate content widths
min_content_widths = [0 for i in range(grid_width)]
max_content_widths = [0 for i in range(grid_width)]
intrinsic_percentages = [0 for i in range(grid_width)]
min_content_widths = [0] * grid_width
max_content_widths = [0] * grid_width
intrinsic_percentages = [0] * grid_width

# Intermediate content widths for span 1
for i in range(grid_width):
for groups in (column_groups, columns):
if groups[i]:
if group := groups[i]:
min_content_widths[i] = max(
min_content_widths[i],
min_content_width(context, groups[i]))
min_content_widths[i], min_content_width(context, group))
max_content_widths[i] = max(
max_content_widths[i],
max_content_width(context, groups[i]))
max_content_widths[i], max_content_width(context, group))
intrinsic_percentages[i] = max(
intrinsic_percentages[i],
_percentage_contribution(groups[i]))
intrinsic_percentages[i], _percentage_contribution(group))
for cell in zipped_grid[i]:
if cell:
if cell.colspan == 1:
min_content_widths[i] = max(
min_content_widths[i],
min_content_width(context, cell))
max_content_widths[i] = max(
max_content_widths[i],
max_content_width(context, cell))
intrinsic_percentages[i] = max(
intrinsic_percentages[i],
_percentage_contribution(cell))
else:
colspan_cells.append(cell)
if not cell:
continue
if cell.colspan == 1:
min_width, max_width = table_cell_min_max_content_width(context, cell)
min_content_widths[i] = max(min_content_widths[i], min_width)
max_content_widths[i] = max(max_content_widths[i], max_width)
intrinsic_percentages[i] = max(
intrinsic_percentages[i], _percentage_contribution(cell))
else:
colspan_cells.append(cell)

# Intermediate content widths for span > 1 is wrong in the 4.1 section, as
# explained in its third issue. Min- and max-content widths are handled by
Expand All @@ -501,7 +491,7 @@ def table_and_columns_preferred_widths(context, box, outer=True):
percentage_contributions = []
for i in range(grid_width):
percentage_contribution = intrinsic_percentages[i]
for j, cell in enumerate(zipped_grid[i]):
for j in range(len(zipped_grid[i])):
indexes = [k for k in range(i + 1) if grid[j][k]]
if not indexes:
continue
Expand All @@ -520,8 +510,7 @@ def table_and_columns_preferred_widths(context, box, outer=True):
baseline_percentage)
other_columns_contributions = [
max_content_widths[j]
for j in range(
origin, origin + origin_cell.colspan)
for j in range(origin, origin + origin_cell.colspan)
if intrinsic_percentages[j] == 0]
other_columns_contributions_sum = sum(
other_columns_contributions)
Expand Down
17 changes: 13 additions & 4 deletions weasyprint/layout/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,7 @@ def group_layout(group, position_y, bottom_space, page_is_empty, skip_stack):
else:
cell = new_cell

cell.remove_decoration(
start=cell_skip_stack is not None,
end=cell_resume_at is not None)
cell.remove_decoration(start=cell_skip_stack is not None, end=False)
if cell_resume_at:
if resume_at is None:
resume_at = {index_row: {}}
Expand All @@ -206,12 +204,23 @@ def group_layout(group, position_y, bottom_space, page_is_empty, skip_stack):
new_row_children.append(cell)

if resume_at and not page_is_empty:
if avoid_page_break(row.style['break_inside'], context):
# Avoid break when "break-inside: avoid" is set on row or any
# on its cells.
avoid_break = (
avoid_page_break(row.style['break_inside'], context) or any(
avoid_page_break(cell.style['break_inside'], context)
for cell in row.children))
if avoid_break:
resume_at = {index_row: {}}
remove_placeholders(
context, new_row_children, absolute_boxes, fixed_boxes)
break

if resume_at:
# Remove bottom decoration if row is split.
for cell in new_row_children:
cell.remove_decoration(start=False, end=True)

row = row.copy_with_children(new_row_children)

# Table height algorithm
Expand Down
Loading