diff options
author | Oleg Oshmyan <chortos@inbox.lv> | 2020-08-17 00:07:33 +0300 |
---|---|---|
committer | Oleg Oshmyan <chortos@inbox.lv> | 2020-09-19 17:51:11 +0300 |
commit | 236bc18d39c080853d1145c7b978fb8f47e31deb (patch) | |
tree | be1026775f8c4cd073a74b26cd99b38c3fbd51a4 /libass/ass_render.c | |
parent | 014cbcc3801a498151c7559662d9ce85d41e4948 (diff) | |
download | libass-236bc18d39c080853d1145c7b978fb8f47e31deb.tar.bz2 libass-236bc18d39c080853d1145c7b978fb8f47e31deb.tar.xz |
Ignore metrics of trimmable whitespace on nonblank lines
Fixes https://github.com/libass/libass/issues/418.
Fixes https://github.com/libass/libass/issues/427.
Fixes https://github.com/mpv-player/mpv/issues/7712.
A longstanding problem we had was that we ignored leading line breaks
in events. We somewhat accidentally assigned line breaks zero height,
and the code in `measure_text` that halved the height of empty lines
explicitly ignored leading empty lines for some reason. #285 fixed both
of these things: the former in e8d98dafb8d4d1fd80e9d398cfbef7db1e2ccb73,
the latter in 5d03af99c6d8f43be973cb6dacb5d6dd0ada33b1.
But life is not so simple!
It turns out that VSFilter [discards metrics of the line break and
leading/trailing spaces for lines that stay nonempty after whitespace
trimming][1]. So we actually handled nonblank (trimmable-space-less)
lines correctly *before* #285 and have been *mishandling* them
since #285 landed, adding the line break's metrics when they should
be ignored. This is not noticeable in plain text, but it affects cases
when a line's contents have a different height from the line breaks:
{\fs96}foo\N{\p1}m 0 0 l 0 1 1 1 1 0{\p0}\Nbar
{\fs96}foo\N{\fs1}bar{\fs96}\Nbaz
(the middle line should be 1px high, not 96px)
[1]: https://github.com/Cyberbeing/xy-VSFilter/blob/3.0.0.306/src/subtitles/RTS.cpp#L1401-L1422
More complicated cases with trimmable spaces have never been
handled correctly, but this is nevertheless a regression.
To fix this:
* move the `trim_whitespace` call before the `measure_text` call
(they seem independent, so this is easy),
* mark trimmed whitespace with a new dedicated flag in addition
to `.skip` so it can be distinguished from invisible glyphs,
* clear accumulated (line-leading-whitespace) metrics
when each line's first non-trimmed-whitespace glyph is found,
* skip trimmed-whitespace glyphs when the line is already proven
nonblank, because they are certainly line-trailing whitespace.
Note: line breaks themselves do have `.skip` and `.is_trimmed_whitespace`.
Note: our `.linebreak` is set on the glyph *after* the line break.
As a result, a nonblank line will include the metrics only of the glyphs
that (would) remain after trimming leading and trailing whitespace
(including the line break), while a blank line includes the metrics of all
glyphs from its first in-line glyph up to and including the terminating
line break (if any).
At the same time, line height is only halved for lines that are truly empty
and don't even contain any whitespace. (Before the halving, the line height
comes from the line break glyph.)
This matches VSFilter.
Diffstat (limited to 'libass/ass_render.c')
-rw-r--r-- | libass/ass_render.c | 37 |
1 files changed, 29 insertions, 8 deletions
diff --git a/libass/ass_render.c b/libass/ass_render.c index 100df2ca..cff7736b 100644 --- a/libass/ass_render.c +++ b/libass/ass_render.c @@ -1495,7 +1495,8 @@ size_t ass_bitmap_construct(void *key, void *value, void *priv) } static void measure_text_on_eol(ASS_Renderer *render_priv, double scale, int cur_line, - int max_asc, int max_desc, double max_border_y) + int max_asc, int max_desc, + double max_border_x, double max_border_y) { render_priv->text_info.lines[cur_line].asc = scale * max_asc; render_priv->text_info.lines[cur_line].desc = scale * max_desc; @@ -1505,6 +1506,9 @@ static void measure_text_on_eol(ASS_Renderer *render_priv, double scale, int cur render_priv->text_info.border_bottom = (int) (render_priv->border_scale * max_border_y + 0.5); if (cur_line == 0) render_priv->text_info.border_top = render_priv->text_info.border_bottom; + // VSFilter takes max \bordx into account for collision, even if far from edge + render_priv->text_info.border_x = FFMAX(render_priv->text_info.border_x, + (int) (render_priv->border_scale * max_border_x + 0.5)); } @@ -1527,15 +1531,29 @@ static void measure_text(ASS_Renderer *render_priv) double scale = 0.5 / 64; int max_asc = 0, max_desc = 0; double max_border_y = 0, max_border_x = 0; + bool empty_trimmed_line = true; for (int i = 0; i < text_info->length; i++) { if (text_info->glyphs[i].linebreak) { - measure_text_on_eol(render_priv, scale, cur_line, max_asc, max_desc, max_border_y); + measure_text_on_eol(render_priv, scale, cur_line, + max_asc, max_desc, max_border_x, max_border_y); + empty_trimmed_line = true; max_asc = max_desc = 0; - max_border_y = 0; + max_border_y = max_border_x = 0; scale = 0.5 / 64; cur_line++; } GlyphInfo *cur = text_info->glyphs + i; + // VSFilter ignores metrics of line-leading/trailing (trimmed) + // whitespace, except when the line becomes empty after trimming + if (empty_trimmed_line && !cur->is_trimmed_whitespace) { + empty_trimmed_line = false; + // Forget metrics of line-leading whitespace + max_asc = max_desc = 0; + max_border_y = max_border_x = 0; + } else if (!empty_trimmed_line && cur->is_trimmed_whitespace) { + // Ignore metrics of line-trailing whitespace + continue; + } max_asc = FFMAX(max_asc, cur->asc); max_desc = FFMAX(max_desc, cur->desc); max_border_y = FFMAX(max_border_y, cur->border_y); @@ -1544,11 +1562,9 @@ static void measure_text(ASS_Renderer *render_priv) scale = 1.0 / 64; } assert(cur_line == text_info->n_lines - 1); - measure_text_on_eol(render_priv, scale, cur_line, max_asc, max_desc, max_border_y); + measure_text_on_eol(render_priv, scale, cur_line, + max_asc, max_desc, max_border_x, max_border_y); text_info->height += cur_line * render_priv->settings.line_spacing; - // VSF takes max \bordx into account for collision, even if far from edge - text_info->border_x = - (int) (render_priv->border_scale * max_border_x + 0.5); } /** @@ -1567,6 +1583,7 @@ static void trim_whitespace(ASS_Renderer *render_priv) cur = ti->glyphs + i; while (i && IS_WHITESPACE(cur)) { cur->skip = true; + cur->is_trimmed_whitespace = true; cur = ti->glyphs + --i; } @@ -1575,6 +1592,7 @@ static void trim_whitespace(ASS_Renderer *render_priv) cur = ti->glyphs; while (i < ti->length && IS_WHITESPACE(cur)) { cur->skip = true; + cur->is_trimmed_whitespace = true; cur = ti->glyphs + ++i; } @@ -1587,17 +1605,20 @@ static void trim_whitespace(ASS_Renderer *render_priv) cur = ti->glyphs + j; while (j && IS_WHITESPACE(cur)) { cur->skip = true; + cur->is_trimmed_whitespace = true; cur = ti->glyphs + --j; } // A break itself can contain a whitespace, too cur = ti->glyphs + i; if (cur->symbol == ' ' || cur->symbol == '\n') { cur->skip = true; + cur->is_trimmed_whitespace = true; // Mark whitespace after j = i + 1; cur = ti->glyphs + j; while (j < ti->length && IS_WHITESPACE(cur)) { cur->skip = true; + cur->is_trimmed_whitespace = true; cur = ti->glyphs + ++j; } i = j - 1; @@ -1735,8 +1756,8 @@ wrap_lines_smart(ASS_Renderer *render_priv, double max_text_width) assert(text_info->n_lines >= 1); #undef DIFF - measure_text(render_priv); trim_whitespace(render_priv); + measure_text(render_priv); cur_line = 1; |