summaryrefslogtreecommitdiffstats
path: root/libass
diff options
context:
space:
mode:
authorOleg Oshmyan <chortos@inbox.lv>2020-08-17 00:07:33 +0300
committerOleg Oshmyan <chortos@inbox.lv>2020-09-19 17:51:11 +0300
commit236bc18d39c080853d1145c7b978fb8f47e31deb (patch)
treebe1026775f8c4cd073a74b26cd99b38c3fbd51a4 /libass
parent014cbcc3801a498151c7559662d9ce85d41e4948 (diff)
downloadlibass-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')
-rw-r--r--libass/ass_render.c37
-rw-r--r--libass/ass_render.h1
2 files changed, 30 insertions, 8 deletions
diff --git a/libass/ass_render.c b/libass/ass_render.c
index 100df2c..cff7736 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;
diff --git a/libass/ass_render.h b/libass/ass_render.h
index 3cb0e1c..93f661a 100644
--- a/libass/ass_render.h
+++ b/libass/ass_render.h
@@ -124,6 +124,7 @@ typedef struct {
typedef struct glyph_info {
unsigned symbol;
bool skip; // skip glyph when layouting text
+ bool is_trimmed_whitespace;
ASS_Font *font;
int face_index;
int glyph_index;