Skip to content

Commit

Permalink
Don't split a line when drawing it
Browse files Browse the repository at this point in the history
To draw a text line, the previous behaviour didn't rely only on the text
actually set on the layout, but also relied on the fact that the line
was cut again when drawn. This change removes the line cutting when
drawing, and thus only relies on the line splitting done during the
layout. This fixes a bug causing some words not being displayed at the
end of a text line drawn with hinting, and the actual drawing size with
hinting was bigger than the size calculated during the layout.

The text included in the drawn layout object was sometimes not cut at
the right position, it was longer but cut when actually drawn. This
commit also fixes this, by always setting the right text in the layout
object.

Fixing this bug enables us to remove a hack introduced to fight against
an "accumulation of floating point errors". I now think that "it wasn't
our war"™. I think that the real reasons of this hack were probably:

- a problem with trailing spaces in the shrink-to-fit functions fixed in
  commit 3a620db, and
- this line-cutting bug while drawing, fixed now.

I've tried hard to reproduce the shink-to-fit problem without this hack,
with no success. I don't see anymore how it can theorically happen with
the current code of the "preferred" module. The only bug fixed by this
hack that I've been able to reproduce is the hinting problem explained
in the first paragraph, and this bug is now really fixed.

Moreover, this hack used to cause another problem: as the maximum size
allowed to an inline block was actually bigger than the real size
available in the line, an inline block whose size was between the real
and the allowed sizes was put on a new line by the split_inline_box
function. This commit fixes #288, the bug reported about this problem.
  • Loading branch information
liZe committed Jan 1, 2016
1 parent d09ba47 commit fac5ee9
Showing 1 changed file with 28 additions and 31 deletions.
59 changes: 28 additions & 31 deletions weasyprint/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,12 @@ def get_ink_position(line):

def first_line_metrics(first_line, text, layout, resume_at, hyphenated=False):
length = first_line.length
if not hyphenated:
first_line_text = utf8_slice(text, slice(length))
if first_line_text.endswith(' ') and resume_at:
# Remove trailing spaces
layout.set_text(first_line_text.rstrip(' '))
first_line = next(layout.iter_lines(), None)
length = first_line.length if first_line is not None else 0
if not hyphenated and resume_at:
# Create layout with final text, remove trailing spaces if needed
first_line_text = utf8_slice(text, slice(length)).rstrip(' ')
layout.set_text(first_line_text)
first_line = next(layout.iter_lines(), None)
length = first_line.length if first_line is not None else 0
width, height = get_size(first_line)
baseline = units_to_double(pango.pango_layout_iter_get_baseline(ffi.gc(
pango.pango_layout_get_iter(layout.layout),
Expand Down Expand Up @@ -418,14 +417,6 @@ def split_first_line(text, style, hinting, max_width, line_width):
``baseline``: baseline in pixels of the first line
"""
# In some cases (shrink-to-fit result being the preferred width)
# this value is coming from Pango itself,
# but floating point errors have accumulated:
# width2 = (width + X) - X # in some cases, width2 < width
# Increase the value a bit to compensate and not introduce
# an unexpected line break.
if max_width is not None:
max_width += style.font_size * 0.2
# Step #1: Get a draft layout with the first line
layout = None
if max_width:
Expand Down Expand Up @@ -467,24 +458,26 @@ def split_first_line(text, style, hinting, max_width, line_width):
# The first word is longer than the line, try to hyphenize it
first_part = ''
second_part = text
next_word = second_part.split(' ', 1)[0]

if not next_word:
next_word = second_part.split(' ', 1)[0]
if next_word:
# next_word might fit without a space afterwards
new_first_line = first_part + next_word
layout.set_text(new_first_line)
lines = layout.iter_lines()
first_line = next(lines, None)
second_line = next(lines, None)
first_line_width, _height = get_size(first_line)
if second_line is None and first_line_width <= max_width:
# The next word fits in the first line, keep the layout
resume_at = len(new_first_line.encode('utf-8')) + 1
if resume_at == len(text.encode('utf-8')):
resume_at = None
return first_line_metrics(first_line, text, layout, resume_at)
else:
# We did not find a word on the next line
return first_line_metrics(first_line, text, layout, resume_at)

# next_word might fit without a space afterwards.
# Pango previously counted that space’s advance width.
new_first_line = first_part + next_word
layout.set_text(new_first_line)
lines = layout.iter_lines()
first_line = next(lines, None)
second_line = next(lines, None)
first_line_width, _height = get_size(first_line)
if second_line is None and first_line_width <= max_width:
# The next word fits in the first line, keep the layout
resume_at = len(new_first_line.encode('utf-8')) + 1
return first_line_metrics(first_line, text, layout, resume_at)
if first_part:
return first_line_metrics(first_line, text, layout, resume_at)

# Step #4: Try to hyphenize
hyphens = style.hyphens
Expand Down Expand Up @@ -581,5 +574,9 @@ def show_first_line(context, pango_layout, hinting):
context = ffi.cast('cairo_t *', context._pointer)
if hinting:
pangocairo.pango_cairo_update_layout(context, pango_layout.layout)
# Set an infinite width as we don't want to break lines when drawing, the
# lines have already been split and the size may differ for example because
# of hinting.
pango.pango_layout_set_width(pango_layout.layout, -1)
pangocairo.pango_cairo_show_layout_line(
context, next(pango_layout.iter_lines()))

0 comments on commit fac5ee9

Please sign in to comment.