summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDr.Smile <vabnick@gmail.com>2017-07-31 05:33:00 +0300
committerDr.Smile <vabnick@gmail.com>2017-07-31 05:34:03 +0300
commit305463c29a402298911f812b222f54a9f9457920 (patch)
treed072815371260da1c1b9abaa7bd804f59b38b300
parent18967aec888deefcd85eea067050a6b98a7b2ff5 (diff)
downloadlibass-305463c29a402298911f812b222f54a9f9457920.tar.bz2
libass-305463c29a402298911f812b222f54a9f9457920.tar.xz
stroker: implement fast two-outline stroker
-rw-r--r--libass/ass_drawing.c58
-rw-r--r--libass/ass_outline.c1170
-rw-r--r--libass/ass_outline.h7
-rw-r--r--libass/ass_parse.c35
-rw-r--r--libass/ass_parse.h3
-rw-r--r--libass/ass_render.c134
-rw-r--r--libass/ass_render.h3
7 files changed, 1062 insertions, 348 deletions
diff --git a/libass/ass_drawing.c b/libass/ass_drawing.c
index 3428d21..23fe8cd 100644
--- a/libass/ass_drawing.c
+++ b/libass/ass_drawing.c
@@ -35,47 +35,6 @@
#define GLYPH_INITIAL_CONTOURS 5
/*
- * \brief Add a single point to a contour.
- */
-static inline bool drawing_add_point(ASS_Drawing *drawing,
- const FT_Vector *point, char tags)
-{
- ASS_Outline *ol = &drawing->outline;
- if (ol->n_points >= ol->max_points) {
- size_t new_size = 2 * ol->max_points;
- if (!ASS_REALLOC_ARRAY(ol->points, new_size))
- return false;
- if (!ASS_REALLOC_ARRAY(ol->tags, new_size))
- return false;
- ol->max_points = new_size;
- }
-
- ol->points[ol->n_points].x = point->x;
- ol->points[ol->n_points].y = point->y;
- ol->tags[ol->n_points] = tags;
- ol->n_points++;
- return true;
-}
-
-/*
- * \brief Close a contour and check outline size overflow.
- */
-static inline bool drawing_close_shape(ASS_Drawing *drawing)
-{
- ASS_Outline *ol = &drawing->outline;
- if (ol->n_contours >= ol->max_contours) {
- size_t new_size = 2 * ol->max_contours;
- if (!ASS_REALLOC_ARRAY(ol->contours, new_size))
- return false;
- ol->max_contours = new_size;
- }
-
- ol->contours[ol->n_contours] = ol->n_points - 1;
- ol->n_contours++;
- return true;
-}
-
-/*
* \brief Prepare drawing for parsing. This just sets a few parameters.
*/
static void drawing_prepare(ASS_Drawing *drawing)
@@ -278,10 +237,11 @@ static bool drawing_evaluate_curve(ASS_Drawing *drawing,
p[2].y -= y12;
}
- return (started || drawing_add_point(drawing, &p[0], FT_CURVE_TAG_ON)) &&
- drawing_add_point(drawing, &p[1], FT_CURVE_TAG_CUBIC) &&
- drawing_add_point(drawing, &p[2], FT_CURVE_TAG_CUBIC) &&
- drawing_add_point(drawing, &p[3], FT_CURVE_TAG_ON);
+ return (started ||
+ outline_add_point(&drawing->outline, p[0], FT_CURVE_TAG_ON)) &&
+ outline_add_point(&drawing->outline, p[1], FT_CURVE_TAG_CUBIC) &&
+ outline_add_point(&drawing->outline, p[2], FT_CURVE_TAG_CUBIC) &&
+ outline_add_point(&drawing->outline, p[3], FT_CURVE_TAG_ON);
}
/*
@@ -362,7 +322,7 @@ ASS_Outline *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode)
pen = token->point;
translate_point(drawing, &pen);
if (started) {
- if (!drawing_close_shape(drawing))
+ if (!outline_close_contour(&drawing->outline))
goto error;
started = 0;
}
@@ -372,9 +332,9 @@ ASS_Outline *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode)
FT_Vector to;
to = token->point;
translate_point(drawing, &to);
- if (!started && !drawing_add_point(drawing, &pen, FT_CURVE_TAG_ON))
+ if (!started && !outline_add_point(&drawing->outline, pen, FT_CURVE_TAG_ON))
goto error;
- if (!drawing_add_point(drawing, &to, FT_CURVE_TAG_ON))
+ if (!outline_add_point(&drawing->outline, to, FT_CURVE_TAG_ON))
goto error;
started = 1;
token = token->next;
@@ -409,7 +369,7 @@ ASS_Outline *ass_drawing_parse(ASS_Drawing *drawing, int raw_mode)
}
// Close the last contour
- if (started && !drawing_close_shape(drawing))
+ if (started && !outline_close_contour(&drawing->outline))
goto error;
drawing_finish(drawing, raw_mode);
diff --git a/libass/ass_outline.c b/libass/ass_outline.c
index 7c1d784..1facc6d 100644
--- a/libass/ass_outline.c
+++ b/libass/ass_outline.c
@@ -37,21 +37,30 @@ bool outline_alloc(ASS_Outline *outline, size_t n_points, size_t n_contours)
return true;
}
-ASS_Outline *outline_convert(const FT_Outline *source)
+ASS_Outline *outline_create(size_t n_points, size_t n_contours)
{
- if (!source)
- return NULL;
-
ASS_Outline *ol = calloc(1, sizeof(*ol));
if (!ol)
return NULL;
- if (!outline_alloc(ol, source->n_points, source->n_contours)) {
+ if (!outline_alloc(ol, n_points, n_contours)) {
outline_free(ol);
free(ol);
return NULL;
}
+ return ol;
+}
+
+ASS_Outline *outline_convert(const FT_Outline *source)
+{
+ if (!source)
+ return NULL;
+
+ ASS_Outline *ol = outline_create(source->n_points, source->n_contours);
+ if (!ol)
+ return NULL;
+
for (int i = 0; i < source->n_contours; i++)
ol->contours[i] = source->contours[i];
memcpy(ol->points, source->points, sizeof(FT_Vector) * source->n_points);
@@ -66,16 +75,10 @@ ASS_Outline *outline_copy(const ASS_Outline *source)
if (!source)
return NULL;
- ASS_Outline *ol = calloc(1, sizeof(*ol));
+ ASS_Outline *ol = outline_create(source->n_points, source->n_contours);
if (!ol)
return NULL;
- if (!outline_alloc(ol, source->n_points, source->n_contours)) {
- outline_free(ol);
- free(ol);
- return NULL;
- }
-
memcpy(ol->contours, source->contours, sizeof(size_t) * source->n_contours);
memcpy(ol->points, source->points, sizeof(FT_Vector) * source->n_points);
memcpy(ol->tags, source->tags, source->n_points);
@@ -95,6 +98,42 @@ void outline_free(ASS_Outline *outline)
}
+/*
+ * \brief Add a single point to a contour.
+ */
+bool outline_add_point(ASS_Outline *outline, FT_Vector pt, char tag)
+{
+ if (outline->n_points >= outline->max_points) {
+ size_t new_size = 2 * outline->max_points;
+ if (!ASS_REALLOC_ARRAY(outline->points, new_size))
+ return false;
+ if (!ASS_REALLOC_ARRAY(outline->tags, new_size))
+ return false;
+ outline->max_points = new_size;
+ }
+ outline->points[outline->n_points] = pt;
+ outline->tags[outline->n_points] = tag;
+ outline->n_points++;
+ return true;
+}
+
+/*
+ * \brief Close a contour.
+ */
+bool outline_close_contour(ASS_Outline *outline)
+{
+ if (outline->n_contours >= outline->max_contours) {
+ size_t new_size = 2 * outline->max_contours;
+ if (!ASS_REALLOC_ARRAY(outline->contours, new_size))
+ return false;
+ outline->max_contours = new_size;
+ }
+ outline->contours[outline->n_contours] = outline->n_points - 1;
+ outline->n_contours++;
+ return true;
+}
+
+
void outline_translate(const ASS_Outline *outline, FT_Pos dx, FT_Pos dy)
{
for (size_t i = 0; i < outline->n_points; i++) {
@@ -133,143 +172,1008 @@ void outline_get_cbox(const ASS_Outline *outline, FT_BBox *cbox)
}
-/**
- * \brief Calculate the cbox of a series of points
- */
-static void
-get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end)
+
+typedef struct {
+ int32_t x, y;
+} OutlinePoint;
+
+typedef struct {
+ double x, y;
+} Vector;
+
+typedef struct {
+ Vector v;
+ double len;
+} Normal;
+
+typedef struct {
+ ASS_Outline *result[2];
+ double xbord, ybord;
+ double xscale, yscale;
+ int eps;
+
+ bool contour_start;
+ int first_skip, last_skip;
+ Vector first_normal, last_normal;
+ OutlinePoint first_point, last_point;
+
+ double merge_cos, split_cos, min_len;
+ double err_q, err_c, err_a;
+} StrokerState;
+
+static inline double vec_dot(Vector vec1, Vector vec2)
{
- box->xMin = box->yMin = INT_MAX;
- box->xMax = box->yMax = INT_MIN;
- for (int i = start; i <= end; i++) {
- box->xMin = (points[i].x < box->xMin) ? points[i].x : box->xMin;
- box->xMax = (points[i].x > box->xMax) ? points[i].x : box->xMax;
- box->yMin = (points[i].y < box->yMin) ? points[i].y : box->yMin;
- box->yMax = (points[i].y > box->yMax) ? points[i].y : box->yMax;
+ return vec1.x * vec2.x + vec1.y * vec2.y;
+}
+
+static inline double vec_crs(Vector vec1, Vector vec2)
+{
+ return vec1.x * vec2.y - vec1.y * vec2.x;
+}
+
+static inline double vec_len(Vector vec)
+{
+ return sqrt(vec.x * vec.x + vec.y * vec.y);
+}
+
+
+static bool emit_point(StrokerState *str, OutlinePoint pt,
+ Vector offs, char tag, int dir)
+{
+ int32_t dx = (int32_t)(str->xbord * offs.x);
+ int32_t dy = (int32_t)(str->ybord * offs.y);
+
+ if (dir & 1) {
+ FT_Vector res = { pt.x + dx, pt.y + dy };
+ res.y = -res.y;
+ if (!outline_add_point(str->result[0], res, tag))
+ return false;
+ }
+ if (dir & 2) {
+ FT_Vector res = { pt.x - dx, pt.y - dy };
+ res.y = -res.y;
+ if (!outline_add_point(str->result[1], res, tag))
+ return false;
}
+ return true;
}
-/**
- * \brief Determine signed area of a contour
- * \return area doubled
- */
-static long long get_contour_area(FT_Vector *points, int start, int end)
+static void fix_first_point(StrokerState *str, OutlinePoint pt,
+ Vector offs, int dir)
+{
+ int32_t dx = (int32_t)(str->xbord * offs.x);
+ int32_t dy = (int32_t)(str->ybord * offs.y);
+
+ if (dir & 1) {
+ FT_Vector res = { pt.x + dx, pt.y + dy };
+ res.y = -res.y;
+ ASS_Outline *ol = str->result[0];
+ size_t first = ol->n_contours ?
+ ol->contours[ol->n_contours - 1] + 1 : 0;
+ ol->points[first] = res;
+ }
+ if (dir & 2) {
+ FT_Vector res = { pt.x - dx, pt.y - dy };
+ res.y = -res.y;
+ ASS_Outline *ol = str->result[1];
+ size_t first = ol->n_contours ?
+ ol->contours[ol->n_contours - 1] + 1 : 0;
+ ol->points[first] = res;
+ }
+}
+
+static bool process_arc(StrokerState *str, OutlinePoint pt,
+ Vector normal0, Vector normal1,
+ const double *mul, int level, int dir)
{
- long long area = 0;
- int x = points[end].x;
- int y = points[end].y;
- for (int i = start; i <= end; i++) {
- area += (long long)(points[i].x + x) * (points[i].y - y);
- x = points[i].x;
- y = points[i].y;
- }
- return area;
+ Vector center;
+ center.x = (normal0.x + normal1.x) * mul[level];
+ center.y = (normal0.y + normal1.y) * mul[level];
+ if (level)
+ return process_arc(str, pt, normal0, center, mul, level - 1, dir) &&
+ process_arc(str, pt, center, normal1, mul, level - 1, dir);
+ return emit_point(str, pt, normal0, FT_CURVE_TAG_ON, dir) &&
+ emit_point(str, pt, center, FT_CURVE_TAG_CONIC, dir);
}
-/**
- * \brief Apply fixups to please the FreeType stroker and improve the
- * rendering result, especially in case the outline has some anomalies.
- * At the moment, the following fixes are done:
- *
- * 1. Reverse contours that have "inside" winding direction but are not
- * contained in any other contours' cbox.
- * 2. Remove "inside" contours depending on border size, so that large
- * borders do not reverse the winding direction, which leads to "holes"
- * inside the border. The inside will be filled by the border of the
- * outside contour anyway in this case.
- *
- * \param outline FreeType outline, modified in-place
- * \param border_x border size, x direction, d6 format
- * \param border_x border size, y direction, d6 format
- */
-void fix_freetype_stroker(ASS_Outline *outline, int border_x, int border_y)
+static bool draw_arc(StrokerState *str, OutlinePoint pt,
+ Vector normal0, Vector normal1, double c, int dir)
{
- int nc = outline->n_contours;
- int begin, stop;
- char modified = 0;
- char *valid_cont = malloc(nc);
- int start = 0;
- int end = -1;
- FT_BBox *boxes = malloc(nc * sizeof(FT_BBox));
- int i, j;
-
- long long area = 0;
- // create a list of cboxes of the contours
- for (i = 0; i < nc; i++) {
- start = end + 1;
- end = outline->contours[i];
- get_contour_cbox(&boxes[i], outline->points, start, end);
- area += get_contour_area(outline->points, start, end);
- }
- int inside_direction = area < 0;
-
- // for each contour, check direction and whether it's "outside"
- // or contained in another contour
- end = -1;
- for (i = 0; i < nc; i++) {
- start = end + 1;
- end = outline->contours[i];
- int dir = get_contour_area(outline->points, start, end) > 0;
- valid_cont[i] = 1;
- if (dir == inside_direction) {
- for (j = 0; j < nc; j++) {
- if (i == j)
- continue;
- if (boxes[i].xMin >= boxes[j].xMin &&
- boxes[i].xMax <= boxes[j].xMax &&
- boxes[i].yMin >= boxes[j].yMin &&
- boxes[i].yMax <= boxes[j].yMax)
- goto check_inside;
- }
- /* "inside" contour but we can't find anything it could be
- * inside of - assume the font is buggy and it should be
- * an "outside" contour, and reverse it */
- for (j = 0; j < (end - start) / 2; j++) {
- FT_Vector temp = outline->points[start + 1 + j];
- char temp2 = outline->tags[start + 1 + j];
- outline->points[start + 1 + j] = outline->points[end - j];
- outline->points[end - j] = temp;
- outline->tags[start + 1 + j] = outline->tags[end - j];
- outline->tags[end - j] = temp2;
+ const int max_subdiv = 15;
+ double mul[max_subdiv + 1];
+
+ Vector center;
+ bool small_angle = true;
+ if (c < 0) {
+ double mul = dir & 2 ? -sqrt(0.5) : sqrt(0.5);
+ mul /= sqrt(1 - c);
+ center.x = (normal1.y - normal0.y) * mul;
+ center.y = (normal0.x - normal1.x) * mul;
+ c = sqrt(FFMAX(0, 0.5 + 0.5 * c));
+ small_angle = false;
+ }
+
+ int pos = max_subdiv;
+ while (c < str->split_cos && pos) {
+ mul[pos] = sqrt(0.5) / sqrt(1 + c);
+ c = (1 + c) * mul[pos];
+ pos--;
+ }
+ mul[pos] = 1 / (1 + c);
+ return small_angle ?
+ process_arc(str, pt, normal0, normal1, mul + pos, max_subdiv - pos, dir) :
+ process_arc(str, pt, normal0, center, mul + pos, max_subdiv - pos, dir) &&
+ process_arc(str, pt, center, normal1, mul + pos, max_subdiv - pos, dir);
+}
+
+static bool draw_circle(StrokerState *str, OutlinePoint pt, int dir)
+{
+ const int max_subdiv = 15;
+ double mul[max_subdiv + 1], c = 0;
+
+ int pos = max_subdiv;
+ while (c < str->split_cos && pos) {
+ mul[pos] = sqrt(0.5) / sqrt(1 + c);
+ c = (1 + c) * mul[pos];
+ pos--;
+ }
+ mul[pos] = 1 / (1 + c);
+
+ Vector normal[4] = {
+ { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }
+ };
+ return process_arc(str, pt, normal[0], normal[1], mul + pos, max_subdiv - pos, dir) &&
+ process_arc(str, pt, normal[1], normal[2], mul + pos, max_subdiv - pos, dir) &&
+ process_arc(str, pt, normal[2], normal[3], mul + pos, max_subdiv - pos, dir) &&
+ process_arc(str, pt, normal[3], normal[0], mul + pos, max_subdiv - pos, dir);
+}
+
+static bool start_segment(StrokerState *str, OutlinePoint pt,
+ Vector normal, int dir)
+{
+ if (str->contour_start) {
+ str->contour_start = false;
+ str->first_skip = str->last_skip = 0;
+ str->first_normal = str->last_normal = normal;
+ str->first_point = pt;
+ return true;
+ }
+
+ Vector prev = str->last_normal;
+ double c = vec_dot(prev, normal);
+ if (c > str->merge_cos) { // merge without cap
+ double mul = 1 / (1 + c);
+ str->last_normal.x = (str->last_normal.x + normal.x) * mul;
+ str->last_normal.y = (str->last_normal.y + normal.y) * mul;
+ return true;
+ }
+ str->last_normal = normal;
+
+ // check for negative curvative
+ double s = vec_crs(prev, normal);
+ int skip_dir = s < 0 ? 1 : 2;
+ if (dir & skip_dir) {
+ if (!emit_point(str, pt, prev, FT_CURVE_TAG_ON, ~str->last_skip & skip_dir))
+ return false;
+ Vector zero_normal = {0, 0};
+ if (!emit_point(str, pt, zero_normal, FT_CURVE_TAG_ON, skip_dir))
+ return false;
+ }
+ str->last_skip = skip_dir;
+
+ dir &= ~skip_dir;
+ return !dir || draw_arc(str, pt, prev, normal, c, dir);
+}
+
+static bool emit_first_point(StrokerState *str, OutlinePoint pt, int dir)
+{
+ str->last_skip &= ~dir;
+ return emit_point(str, pt, str->last_normal, FT_CURVE_TAG_ON, dir);
+}
+
+static bool prepare_skip(StrokerState *str, OutlinePoint pt, int dir, bool first)
+{
+ if (first)
+ str->first_skip |= dir;
+ else if (!emit_point(str, pt, str->last_normal, FT_CURVE_TAG_ON, ~str->last_skip & dir))
+ return false;
+ str->last_skip |= dir;
+ return true;
+}
+
+static bool add_line(StrokerState *str, OutlinePoint pt, int dir)
+{
+ int32_t dx = pt.x - str->last_point.x;
+ int32_t dy = pt.y - str->last_point.y;
+ if (dx > -str->eps && dx < str->eps && dy > -str->eps && dy < str->eps)
+ return true;
+
+ Vector deriv = { dy * str->yscale, -dx * str->xscale };
+ double scale = 1 / vec_len(deriv);
+ Vector normal = { deriv.x * scale, deriv.y * scale };
+ if (!start_segment(str, str->last_point, normal, dir))
+ return false;
+ if (!emit_first_point(str, str->last_point, dir))
+ return false;
+ str->last_normal = normal;
+ str->last_point = pt;
+ return true;
+}
+
+
+static bool estimate_quadratic_error(StrokerState *str, double c, double s,
+ const Normal *normal, Vector *result)
+{
+ // check radial error
+ if (!((3 + c) * (3 + c) < str->err_q * (1 + c)))
+ return false;
+
+ double mul = 1 / (1 + c);
+ double l0 = 2 * normal[0].len, l1 = 2 * normal[1].len;
+ double dot0 = l0 + normal[1].len * c, crs0 = (l0 * mul - normal[1].len) * s;
+ double dot1 = l1 + normal[0].len * c, crs1 = (l1 * mul - normal[0].len) * s;
+ // check angular error
+ if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1))
+ return false;
+
+ result->x = (normal[0].v.x + normal[1].v.x) * mul;
+ result->y = (normal[0].v.y + normal[1].v.y) * mul;
+ return true;
+}
+
+static bool process_quadratic(StrokerState *str, const OutlinePoint *pt,
+ const Vector *deriv, const Normal *normal,
+ int dir, bool first)
+{
+ double c = vec_dot(normal[0].v, normal[1].v);
+ double s = vec_crs(normal[0].v, normal[1].v);
+ int check_dir = dir, skip_dir = s < 0 ? 1 : 2;
+ if (dir & skip_dir) {
+ double abs_s = fabs(s);
+ double f0 = normal[0].len * c + normal[1].len;
+ double f1 = normal[1].len * c + normal[0].len;
+ double g0 = normal[0].len * abs_s;
+ double g1 = normal[1].len * abs_s;
+ // check for self-intersection
+ if (f0 < abs_s && f1 < abs_s) {
+ double d2 = (f0 * normal[1].len + f1 * normal[0].len) / 2;
+ if (d2 < g0 && d2 < g1) {
+ if (!prepare_skip(str, pt[0], skip_dir, first))
+ return false;
+ if (f0 < 0 || f1 < 0) {
+ Vector zero_normal = {0, 0};
+ if (!emit_point(str, pt[0], zero_normal, FT_CURVE_TAG_ON, skip_dir) ||
+ !emit_point(str, pt[2], zero_normal, FT_CURVE_TAG_ON, skip_dir))
+ return false;
+ } else {
+ double mul = f0 / abs_s;
+ Vector offs = { normal[0].v.x * mul, normal[0].v.y * mul };
+ if (!emit_point(str, pt[0], offs, FT_CURVE_TAG_ON, skip_dir))
+ return false;
+ }
+ dir &= ~skip_dir;
+ if (!dir) {
+ str->last_normal = normal[1].v;
+ return true;
+ }
}
- dir ^= 1;
+ check_dir ^= skip_dir;
+ } else if (c + g0 < 1 && c + g1 < 1)
+ check_dir ^= skip_dir;
+ }
+
+ Vector result;
+ if (check_dir && estimate_quadratic_error(str, c, s, normal, &result)) {
+ if (!emit_first_point(str, pt[0], check_dir))
+ return false;
+ if (!emit_point(str, pt[1], result, FT_CURVE_TAG_CONIC, check_dir))
+ return false;
+ dir &= ~check_dir;
+ if (!dir) {
+ str->last_normal = normal[1].v;
+ return true;
}
- check_inside:
- if (dir == inside_direction) {
- FT_BBox box;
- get_contour_cbox(&box, outline->points, start, end);
- int width = box.xMax - box.xMin;
- int height = box.yMax - box.yMin;
- if (width < border_x * 2 || height < border_y * 2) {
- valid_cont[i] = 0;
- modified = 1;
- }
+ }
+
+ OutlinePoint next[5];
+ next[1].x = pt[0].x + pt[1].x;
+ next[1].y = pt[0].y + pt[1].y;
+ next[3].x = pt[1].x + pt[2].x;
+ next[3].y = pt[1].y + pt[2].y;
+ next[2].x = (next[1].x + next[3].x + 2) >> 2;
+ next[2].y = (next[1].y + next[3].y + 2) >> 2;
+ next[1].x >>= 1;
+ next[1].y >>= 1;
+ next[3].x >>= 1;
+ next[3].y >>= 1;
+ next[0] = pt[0];
+ next[4] = pt[2];
+
+ Vector next_deriv[3];
+ next_deriv[0].x = deriv[0].x / 2;
+ next_deriv[0].y = deriv[0].y / 2;
+ next_deriv[2].x = deriv[1].x / 2;
+ next_deriv[2].y = deriv[1].y / 2;
+ next_deriv[1].x = (next_deriv[0].x + next_deriv[2].x) / 2;
+ next_deriv[1].y = (next_deriv[0].y + next_deriv[2].y) / 2;
+
+ double len = vec_len(next_deriv[1]);
+ if (len < str->min_len) { // check degenerate case
+ if (!emit_first_point(str, next[0], dir))
+ return false;
+ if (!start_segment(str, next[2], normal[1].v, dir))
+ return false;
+ str->last_skip &= ~dir;
+ return emit_point(str, next[2], normal[1].v, FT_CURVE_TAG_ON, dir);
+ }
+
+ double scale = 1 / len;
+ Normal next_normal[3] = {
+ { normal[0].v, normal[0].len / 2 },
+ { { next_deriv[1].x * scale, next_deriv[1].y * scale }, len },
+ { normal[1].v, normal[1].len / 2 }
+ };
+ return process_quadratic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first) &&
+ process_quadratic(str, next + 2, next_deriv + 1, next_normal + 1, dir, false);
+}
+
+static bool add_quadratic(StrokerState *str, const OutlinePoint *pt, int dir)
+{
+ int32_t dx0 = pt[1].x - pt[0].x;
+ int32_t dy0 = pt[1].y - pt[0].y;
+ if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps)
+ return add_line(str, pt[2], dir);
+
+ int32_t dx1 = pt[2].x - pt[1].x;
+ int32_t dy1 = pt[2].y - pt[1].y;
+ if (dx1 > -str->eps && dx1 < str->eps && dy1 > -str->eps && dy1 < str->eps)
+ return add_line(str, pt[2], dir);
+
+ Vector deriv[2] = {
+ { dy0 * str->yscale, -dx0 * str->xscale },
+ { dy1 * str->yscale, -dx1 * str->xscale }
+ };
+ double len0 = vec_len(deriv[0]), scale0 = 1 / len0;
+ double len1 = vec_len(deriv[1]), scale1 = 1 / len1;
+ Normal normal[2] = {
+ { { deriv[0].x * scale0, deriv[0].y * scale0 }, len0 },
+ { { deriv[1].x * scale1, deriv[1].y * scale1 }, len1 }
+ };
+
+ bool first = str->contour_start;
+ if (!start_segment(str, pt[0], normal[0].v, dir))
+ return false;
+ if (!process_quadratic(str, pt, deriv, normal, dir, first))
+ return false;
+ str->last_point = pt[2];
+ return true;
+}
+
+
+enum {
+ FLAG_INTERSECTION = 1,
+ FLAG_ZERO_0 = 2,
+ FLAG_ZERO_1 = 4,
+ FLAG_CLIP_0 = 8,
+ FLAG_CLIP_1 = 16,
+ FLAG_DIR_2 = 32,
+
+ FLAG_COUNT = 6,
+
+ MASK_INTERSECTION = FLAG_INTERSECTION << FLAG_COUNT,
+ MASK_ZERO_0 = FLAG_ZERO_0 << FLAG_COUNT,
+ MASK_ZERO_1 = FLAG_ZERO_1 << FLAG_COUNT,
+ MASK_CLIP_0 = FLAG_CLIP_0 << FLAG_COUNT,
+ MASK_CLIP_1 = FLAG_CLIP_1 << FLAG_COUNT,
+};
+
+static int estimate_cubic_error(StrokerState *str, double c, double s,
+ const double *dc, const double *ds,
+ const Normal *normal, Vector *result,
+ int check_flags, int dir)
+{
+ double t = (ds[0] + ds[1]) / (dc[0] + dc[1]), c1 = 1 + c, ss = s * s;
+ double ts = t * s, tt = t * t, ttc = tt * c1, ttcc = ttc * c1;
+
+ const double w = 0.4;
+ double f0[] = {
+ 10 * w * (c - 1) + 9 * w * tt * c,
+ 2 * (c - 1) + 3 * tt + 2 * ts,
+ 2 * (c - 1) + 3 * tt - 2 * ts,
+ };
+ double f1[] = {
+ 18 * w * (ss - ttc * c),
+ 2 * ss - 6 * ttc - 2 * ts * (c + 4),
+ 2 * ss - 6 * ttc + 2 * ts * (c + 4),
+ };
+ double f2[] = {
+ 9 * w * (ttcc - ss) * c,
+ 3 * ss + 3 * ttcc + 6 * ts * c1,
+ 3 * ss + 3 * ttcc - 6 * ts * c1,
+ };
+
+ double aa = 0, ab = 0;
+ double ch = sqrt(c1 / 2);
+ double inv_ro0 = 1.5 * ch * (ch + 1);
+ for (int i = 0; i < 3; i++) {
+ double a = 2 * f2[i] + f1[i] * inv_ro0;
+ double b = f2[i] - f0[i] * inv_ro0 * inv_ro0;
+ aa += a * a; ab += a * b;
+ }
+ double ro = ab / (aa * inv_ro0 + 1e-9); // best fit
+
+ double err2 = 0;
+ for (int i = 0; i < 3; i++) {
+ double err = f0[i] + ro * (f1[i] + ro * f2[i]);
+ err2 += err * err;
+ }
+ if (!(err2 < str->err_c)) // check radial error
+ return 0;
+
+ double r = ro * c1 - 1;
+ double ro0 = t * r - ro * s;
+ double ro1 = t * r + ro * s;
+
+ int check_dir = check_flags & FLAG_DIR_2 ? 2 : 1;
+ if (dir & check_dir) {
+ double test_s = s, test0 = ro0, test1 = ro1;
+ if (check_flags & FLAG_DIR_2) {
+ test_s = -test_s;
+ test0 = -test0;
+ test1 = -test1;
+ }
+ int flags = 0;
+ if (2 * test_s * r < dc[0] + dc[1]) flags |= FLAG_INTERSECTION;
+ if (normal[0].len - test0 < 0) flags |= FLAG_ZERO_0;
+ if (normal[1].len + test1 < 0) flags |= FLAG_ZERO_1;
+ if (normal[0].len + dc[0] + test_s - test1 * c < 0) flags |= FLAG_CLIP_0;
+ if (normal[1].len + dc[1] + test_s + test0 * c < 0) flags |= FLAG_CLIP_1;
+ if ((flags ^ check_flags) & (check_flags >> FLAG_COUNT)) {
+ dir &= ~check_dir;
+ if (!dir)
+ return 0;
}
}
- // if we need to modify the outline, rewrite it and skip
- // the contours that we determined should be removed.
- if (modified) {
- int p = 0, c = 0;
- for (i = 0; i < nc; i++) {
- if (!valid_cont[i])
- continue;
- begin = (i == 0) ? 0 : outline->contours[i - 1] + 1;
- stop = outline->contours[i];
- for (j = begin; j <= stop; j++) {
- outline->points[p].x = outline->points[j].x;
- outline->points[p].y = outline->points[j].y;
- outline->tags[p] = outline->tags[j];
- p++;
+ double d0c = 2 * dc[0], d0s = 2 * ds[0];
+ double d1c = 2 * dc[1], d1s = 2 * ds[1];
+ double dot0 = d0c + 3 * normal[0].len, crs0 = d0s + 3 * ro0 * normal[0].len;
+ double dot1 = d1c + 3 * normal[1].len, crs1 = d1s + 3 * ro1 * normal[1].len;
+ // check angular error (stage 1)
+ if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1))
+ return 0;
+
+ double cl0 = c * normal[0].len, sl0 = +s * normal[0].len;
+ double cl1 = c * normal[1].len, sl1 = -s * normal[1].len;
+ dot0 = d0c - ro0 * d0s + cl0 + ro1 * sl0 + cl1 / 3;
+ dot1 = d1c - ro1 * d1s + cl1 + ro0 * sl1 + cl0 / 3;
+ crs0 = d0s + ro0 * d0c - sl0 + ro1 * cl0 - sl1 / 3;
+ crs1 = d1s + ro1 * d1c - sl1 + ro0 * cl1 - sl0 / 3;
+ // check angular error (stage 2)
+ if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1))
+ return 0;
+
+ result[0].x = normal[0].v.x + normal[0].v.y * ro0;
+ result[0].y = normal[0].v.y - normal[0].v.x * ro0;
+ result[1].x = normal[1].v.x + normal[1].v.y * ro1;
+ result[1].y = normal[1].v.y - normal[1].v.x * ro1;
+ return dir;
+}
+
+static bool process_cubic(StrokerState *str, const OutlinePoint *pt,
+ const Vector *deriv, const Normal *normal,
+ int dir, bool first)
+{
+ double c = vec_dot(normal[0].v, normal[1].v);
+ double s = vec_crs(normal[0].v, normal[1].v);
+ double dc[] = { vec_dot(normal[0].v, deriv[1]), vec_dot(normal[1].v, deriv[1]) };
+ double ds[] = { vec_crs(normal[0].v, deriv[1]), vec_crs(normal[1].v, deriv[1]) };
+ double f0 = normal[0].len * c + normal[1].len + dc[1];
+ double f1 = normal[1].len * c + normal[0].len + dc[0];
+ double g0 = normal[0].len * s - ds[1];
+ double g1 = normal[1].len * s + ds[0];
+
+ double abs_s = s;
+ int check_dir = dir, skip_dir = 2;
+ int flags = FLAG_INTERSECTION | FLAG_DIR_2;
+ if (s < 0) {
+ abs_s = -s;
+ skip_dir = 1;
+ flags = 0;
+ g0 = -g0;
+ g1 = -g1;
+ }
+
+ if (!(dc[0] + dc[1] > 0))
+ check_dir = 0;
+ else if (dir & skip_dir) {
+ if (f0 < abs_s && f1 < abs_s) { // check for self-intersection
+ double d2 = (f0 + dc[1]) * normal[1].len + (f1 + dc[0]) * normal[0].len;
+ d2 = (d2 + vec_dot(deriv[1], deriv[1])) / 2;
+ if (d2 < g0 && d2 < g1) {
+ double q = sqrt(d2 / (2 - d2));
+ double h0 = (f0 * q + g0) * normal[1].len;
+ double h1 = (f1 * q + g1) * normal[0].len;
+ q *= (4.0 / 3) * d2;
+ if (h0 > q && h1 > q) {
+ if (!prepare_skip(str, pt[0], skip_dir, first))
+ return false;
+ if (f0 < 0 || f1 < 0) {
+ Vector zero_normal = {0, 0};
+ if (!emit_point(str, pt[0], zero_normal, FT_CURVE_TAG_ON, skip_dir) ||
+ !emit_point(str, pt[3], zero_normal, FT_CURVE_TAG_ON, skip_dir))
+ return false;
+ } else {
+ double mul = f0 / abs_s;
+ Vector offs = { normal[0].v.x * mul, normal[0].v.y * mul };
+ if (!emit_point(str, pt[0], offs, FT_CURVE_TAG_ON, skip_dir))
+ return false;
+ }
+ dir &= ~skip_dir;
+ if (!dir) {
+ str->last_normal = normal[1].v;
+ return true;
+ }
+ }
+ }
+ check_dir ^= skip_dir;
+ } else {
+ if (ds[0] < 0)
+ flags ^= MASK_INTERSECTION;
+ if (ds[1] < 0)
+ flags ^= MASK_INTERSECTION | FLAG_INTERSECTION;
+ bool parallel = flags & MASK_INTERSECTION;
+ int badness = parallel ? 0 : 1;
+ if (c + g0 < 1) {
+ if (parallel) {
+ flags ^= MASK_ZERO_0 | FLAG_ZERO_0;
+ if (c < 0)
+ flags ^= MASK_CLIP_0;
+ if (f0 > abs_s)
+ flags ^= FLAG_ZERO_0 | FLAG_CLIP_0;
+ }
+ badness++;
+ } else {
+ flags ^= MASK_INTERSECTION | FLAG_INTERSECTION;
+ if (!parallel) {
+ flags ^= MASK_ZERO_0;
+ if (c > 0)
+ flags ^= MASK_CLIP_0;
+ }
}
- outline->contours[c] = p - 1;
- c++;
+ if (c + g1 < 1) {
+ if (parallel) {
+ flags ^= MASK_ZERO_1 | FLAG_ZERO_1;
+ if (c < 0)
+ flags ^= MASK_CLIP_1;
+ if (f1 > abs_s)
+ flags ^= FLAG_ZERO_1 | FLAG_CLIP_1;
+ }
+ badness++;
+ } else {
+ flags ^= MASK_INTERSECTION;
+ if (!parallel) {
+ flags ^= MASK_ZERO_1;
+ if (c > 0)
+ flags ^= MASK_CLIP_1;
+ }
+ }
+ if (badness > 2)
+ check_dir ^= skip_dir;
+ }
+ }
+
+ Vector result[2];
+ if (check_dir)
+ check_dir = estimate_cubic_error(str, c, s, dc, ds,
+ normal, result, flags, check_dir);
+ if (check_dir) {
+ if (!emit_first_point(str, pt[0], check_dir))
+ return false;
+ if (!emit_point(str, pt[1], result[0], FT_CURVE_TAG_CUBIC, check_dir) ||
+ !emit_point(str, pt[2], result[1], FT_CURVE_TAG_CUBIC, check_dir))
+ return false;
+ dir &= ~check_dir;
+ if (!dir) {
+ str->last_normal = normal[1].v;
+ return true;
}
- outline->n_points = p;
- outline->n_contours = c;
}
- free(boxes);
- free(valid_cont);
+ OutlinePoint next[7], center;
+ next[1].x = pt[0].x + pt[1].x;
+ next[1].y = pt[0].y + pt[1].y;
+ center.x = pt[1].x + pt[2].x + 2;
+ center.y = pt[1].y + pt[2].y + 2;
+ next[5].x = pt[2].x + pt[3].x;
+ next[5].y = pt[2].y + pt[3].y;
+ next[2].x = next[1].x + center.x;
+ next[2].y = next[1].y + center.y;
+ next[4].x = center.x + next[5].x;
+ next[4].y = center.y + next[5].y;
+ next[3].x = (next[2].x + next[4].x - 1) >> 3;
+ next[3].y = (next[2].y + next[4].y - 1) >> 3;
+ next[2].x >>= 2;
+ next[2].y >>= 2;
+ next[4].x >>= 2;
+ next[4].y >>= 2;
+ next[1].x >>= 1;
+ next[1].y >>= 1;
+ next[5].x >>= 1;
+ next[5].y >>= 1;
+ next[0] = pt[0];
+ next[6] = pt[3];
+
+ Vector next_deriv[5], center_deriv;
+ next_deriv[0].x = deriv[0].x / 2;
+ next_deriv[0].y = deriv[0].y / 2;
+ center_deriv.x = deriv[1].x / 2;
+ center_deriv.y = deriv[1].y / 2;
+ next_deriv[4].x = deriv[2].x / 2;
+ next_deriv[4].y = deriv[2].y / 2;
+ next_deriv[1].x = (next_deriv[0].x + center_deriv.x) / 2;
+ next_deriv[1].y = (next_deriv[0].y + center_deriv.y) / 2;
+ next_deriv[3].x = (center_deriv.x + next_deriv[4].x) / 2;
+ next_deriv[3].y = (center_deriv.y + next_deriv[4].y) / 2;
+ next_deriv[2].x = (next_deriv[1].x + next_deriv[3].x) / 2;
+ next_deriv[2].y = (next_deriv[1].y + next_deriv[3].y) / 2;
+
+ double len = vec_len(next_deriv[2]);
+ if (len < str->min_len) { // check degenerate case
+ Normal next_normal[4];
+ next_normal[0].v = normal[0].v;
+ next_normal[0].len = normal[0].len / 2;
+ next_normal[3].v = normal[1].v;
+ next_normal[3].len = normal[1].len / 2;
+
+ next_deriv[1].x += next_deriv[2].x;
+ next_deriv[1].y += next_deriv[2].y;
+ next_deriv[3].x += next_deriv[2].x;
+ next_deriv[3].y += next_deriv[2].y;
+ next_deriv[2].x = next_deriv[2].y = 0;
+
+ double len1 = vec_len(next_deriv[1]);
+ if (len1 < str->min_len) {
+ next_normal[1] = normal[0];
+ } else {
+ double scale = 1 / len1;
+ next_normal[1].v.x = next_deriv[1].x * scale;
+ next_normal[1].v.y = next_deriv[1].y * scale;
+ next_normal[1].len = len1;
+ }
+
+ double len2 = vec_len(next_deriv[3]);
+ if (len2 < str->min_len) {
+ next_normal[2] = normal[1];
+ } else {
+ double scale = 1 / len2;
+ next_normal[2].v.x = next_deriv[3].x * scale;
+ next_normal[2].v.y = next_deriv[3].y * scale;
+ next_normal[2].len = len2;
+ }
+
+ if (len1 < str->min_len) {
+ if (!emit_first_point(str, next[0], dir))
+ return false;
+ } else {
+ if (!process_cubic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first))
+ return false;
+ }
+ if (!start_segment(str, next[2], next_normal[2].v, dir))
+ return false;
+ if (len2 < str->min_len) {
+ if (!emit_first_point(str, next[3], dir))
+ return false;
+ } else {
+ if (!process_cubic(str, next + 3, next_deriv + 2, next_normal + 2, dir, false))
+ return false;
+ }
+ return true;
+ }
+
+ double scale = 1 / len;
+ Normal next_normal[3] = {
+ { normal[0].v, normal[0].len / 2 },
+ { { next_deriv[2].x * scale, next_deriv[2].y * scale }, len },
+ { normal[1].v, normal[1].len / 2 }
+ };
+ return process_cubic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first) &&
+ process_cubic(str, next + 3, next_deriv + 2, next_normal + 1, dir, false);
+}
+
+static bool add_cubic(StrokerState *str, const OutlinePoint *pt, int dir)
+{
+ int flags = 9;
+
+ int32_t dx0 = pt[1].x - pt[0].x;
+ int32_t dy0 = pt[1].y - pt[0].y;
+ if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps) {
+ dx0 = pt[2].x - pt[0].x;
+ dy0 = pt[2].y - pt[0].y;
+ if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps)
+ return add_line(str, pt[3], dir);
+ flags ^= 1;
+ }
+
+ int32_t dx2 = pt[3].x - pt[2].x;
+ int32_t dy2 = pt[3].y - pt[2].y;
+ if (dx2 > -str->eps && dx2 < str->eps && dy2 > -str->eps && dy2 < str->eps) {
+ dx2 = pt[3].x - pt[1].x;
+ dy2 = pt[3].y - pt[1].y;
+ if (dx2 > -str->eps && dx2 < str->eps && dy2 > -str->eps && dy2 < str->eps)
+ return add_line(str, pt[3], dir);
+ flags ^= 4;
+ }
+
+ if (flags == 12)
+ return add_line(str, pt[3], dir);
+
+ int32_t dx1 = pt[flags >> 2].x - pt[flags & 3].x;
+ int32_t dy1 = pt[flags >> 2].y - pt[flags & 3].y;
+
+ Vector deriv[3] = {
+ { dy0 * str->yscale, -dx0 * str->xscale },
+ { dy1 * str->yscale, -dx1 * str->xscale },
+ { dy2 * str->yscale, -dx2 * str->xscale }
+ };
+ double len0 = vec_len(deriv[0]), scale0 = 1 / len0;
+ double len2 = vec_len(deriv[2]), scale2 = 1 / len2;
+ Normal normal[2] = {
+ { { deriv[0].x * scale0, deriv[0].y * scale0 }, len0 },
+ { { deriv[2].x * scale2, deriv[2].y * scale2 }, len2 }
+ };
+
+ bool first = str->contour_start;
+ if (!start_segment(str, pt[0], normal[0].v, dir))
+ return false;
+ if (!process_cubic(str, pt, deriv, normal, dir, first))
+ return false;
+ str->last_point = pt[3];
+ return true;
+}
+
+
+static bool close_contour(StrokerState *str, int dir)
+{
+ if (str->contour_start) {
+ if ((dir & 3) == 3)
+ dir = 1;
+ if (!draw_circle(str, str->last_point, dir))
+ return false;
+ } else {
+ if (!add_line(str, str->first_point, dir))
+ return false;
+ if (!start_segment(str, str->first_point, str->first_normal, dir))
+ return false;
+ if (!emit_point(str, str->first_point, str->first_normal, FT_CURVE_TAG_ON,
+ ~str->last_skip & dir & str->first_skip))
+ return false;
+ if (str->last_normal.x != str->first_normal.x ||
+ str->last_normal.y != str->first_normal.y)
+ fix_first_point(str, str->first_point, str->last_normal,
+ ~str->last_skip & dir & ~str->first_skip);
+ str->contour_start = true;
+ }
+ if ((dir & 1) && !outline_close_contour(str->result[0]))
+ return false;
+ if ((dir & 2) && !outline_close_contour(str->result[1]))
+ return false;
+ return true;
}
+
+/*
+ * Stroke an outline glyph in x/y direction.
+ */
+bool outline_stroke(ASS_Outline *result, ASS_Outline *result1,
+ const ASS_Outline *path, int xbord, int ybord, int eps)
+{
+ if (result1)
+ result1->n_contours = result1->n_points = 0;
+
+ int rad = FFMAX(xbord, ybord);
+ if (rad < eps) {
+ assert(result->max_contours >= path->n_contours);
+ assert(result->max_points >= path->n_points);
+ memcpy(result->contours, path->contours, sizeof(size_t) * path->n_contours);
+ memcpy(result->points, path->points, sizeof(FT_Vector) * path->n_points);
+ memcpy(result->tags, path->tags, path->n_points);
+ result->n_contours = path->n_contours;
+ result->n_points = path->n_points;
+ return true;
+ }
+
+ result->n_contours = result->n_points = 0;
+
+ int dir = result1 ? 3 : 1;
+ StrokerState str;
+ str.result[0] = result;
+ str.result[1] = result1;
+ str.xbord = xbord;
+ str.ybord = ybord;
+ str.xscale = 1.0 / FFMAX(eps, xbord);
+ str.yscale = 1.0 / FFMAX(eps, ybord);
+ str.eps = eps;
+
+ str.contour_start = true;
+ double rel_err = (double)eps / rad;
+ str.merge_cos = 1 - rel_err;
+ double e = sqrt(2 * rel_err);
+ str.split_cos = 1 + 8 * rel_err - 4 * (1 + rel_err) * e;
+ str.min_len = rel_err / 4;
+ str.err_q = 8 * (1 + rel_err) * (1 + rel_err);
+ str.err_c = 390 * rel_err * rel_err;
+ str.err_a = e;
+
+ enum Status {
+ S_ON, S_Q, S_C1, S_C2
+ };
+
+ for (size_t i = 0, j = 0; i < path->n_contours; i++) {
+ OutlinePoint start, p[4];
+ int process_end = 1;
+ enum Status st;
+
+ int last = path->contours[i];
+ if (j > last)
+ return false;
+
+ if (path->points[j].x < -(1 << 28) || path->points[j].x >= (1 << 28))
+ return false;
+ if (path->points[j].y <= -(1 << 28) || path->points[j].y > (1 << 28))
+ return false;
+
+ switch (FT_CURVE_TAG(path->tags[j])) {
+ case FT_CURVE_TAG_ON:
+ p[0].x = path->points[j].x;
+ p[0].y = -path->points[j].y;
+ start = p[0];
+ st = S_ON;
+ break;
+
+ case FT_CURVE_TAG_CONIC:
+ switch (FT_CURVE_TAG(path->tags[last])) {
+ case FT_CURVE_TAG_ON:
+ p[0].x = path->points[last].x;
+ p[0].y = -path->points[last].y;
+ p[1].x = path->points[j].x;
+ p[1].y = -path->points[j].y;
+ process_end = 0;
+ start = p[0];
+ st = S_Q;
+ break;
+
+ case FT_CURVE_TAG_CONIC:
+ p[1].x = path->points[j].x;
+ p[1].y = -path->points[j].y;
+ p[0].x = (p[1].x + path->points[last].x) >> 1;
+ p[0].y = (p[1].y - path->points[last].y) >> 1;
+ start = p[0];
+ st = S_Q;
+ break;
+
+ default:
+ return false;
+ }
+ break;
+
+ default:
+ return false;
+ }
+ str.last_point = start;
+
+ for (j++; j <= last; j++) {
+ if (path->points[j].x < -(1 << 28) || path->points[j].x >= (1 << 28))
+ return false;
+ if (path->points[j].y <= -(1 << 28) || path->points[j].y > (1 << 28))
+ return false;
+
+ switch (FT_CURVE_TAG(path->tags[j])) {
+ case FT_CURVE_TAG_ON:
+ switch (st) {
+ case S_ON:
+ p[1].x = path->points[j].x;
+ p[1].y = -path->points[j].y;
+ if (!add_line(&str, p[1], dir))
+ return false;
+ p[0] = p[1];
+ break;
+
+ case S_Q:
+ p[2].x = path->points[j].x;
+ p[2].y = -path->points[j].y;
+ if (!add_quadratic(&str, p, dir))
+ return false;
+ p[0] = p[2];
+ st = S_ON;
+ break;
+
+ case S_C2:
+ p[3].x = path->points[j].x;
+ p[3].y = -path->points[j].y;
+ if (!add_cubic(&str, p, dir))
+ return false;
+ p[0] = p[3];
+ st = S_ON;
+ break;
+
+ default:
+ return false;
+ }
+ break;
+
+ case FT_CURVE_TAG_CONIC:
+ switch (st) {
+ case S_ON:
+ p[1].x = path->points[j].x;
+ p[1].y = -path->points[j].y;
+ st = S_Q;
+ break;
+
+ case S_Q:
+ p[3].x = path->points[j].x;
+ p[3].y = -path->points[j].y;
+ p[2].x = (p[1].x + p[3].x) >> 1;
+ p[2].y = (p[1].y + p[3].y) >> 1;
+ if (!add_quadratic(&str, p, dir))
+ return false;
+ p[0] = p[2];
+ p[1] = p[3];
+ break;
+
+ default:
+ return false;
+ }
+ break;
+
+ case FT_CURVE_TAG_CUBIC:
+ switch (st) {
+ case S_ON:
+ p[1].x = path->points[j].x;
+ p[1].y = -path->points[j].y;
+ st = S_C1;
+ break;
+
+ case S_C1:
+ p[2].x = path->points[j].x;
+ p[2].y = -path->points[j].y;
+ st = S_C2;
+ break;
+
+ default:
+ return false;
+ }
+ break;
+
+ default:
+ return false;
+ }
+ }
+
+ if (process_end)
+ switch (st) {
+ case S_ON:
+ if (!add_line(&str, start, dir))
+ return false;
+ break;
+
+ case S_Q:
+ p[2] = start;
+ if (!add_quadratic(&str, p, dir))
+ return false;
+ break;
+
+ case S_C2:
+ p[3] = start;
+ if (!add_cubic(&str, p, dir))
+ return false;
+ break;
+
+ default:
+ return false;
+ }
+ if (!close_contour(&str, dir))
+ return false;
+ }
+ return true;
+}
diff --git a/libass/ass_outline.h b/libass/ass_outline.h
index a03f082..86c6b3e 100644
--- a/libass/ass_outline.h
+++ b/libass/ass_outline.h
@@ -35,15 +35,20 @@ typedef struct ass_outline {
#define EFFICIENT_CONTOUR_COUNT 8
bool outline_alloc(ASS_Outline *outline, size_t n_points, size_t n_contours);
+ASS_Outline *outline_create(size_t n_points, size_t n_contours);
ASS_Outline *outline_convert(const FT_Outline *source);
ASS_Outline *outline_copy(const ASS_Outline *source);
void outline_free(ASS_Outline *outline);
+bool outline_add_point(ASS_Outline *outline, FT_Vector pt, char tag);
+bool outline_close_contour(ASS_Outline *outline);
+
void outline_translate(const ASS_Outline *outline, FT_Pos dx, FT_Pos dy);
void outline_transform(const ASS_Outline *outline, const FT_Matrix *matrix);
void outline_get_cbox(const ASS_Outline *outline, FT_BBox *cbox);
-void fix_freetype_stroker(ASS_Outline *outline, int border_x, int border_y);
+bool outline_stroke(ASS_Outline *result, ASS_Outline *result1,
+ const ASS_Outline *path, int xbord, int ybord, int eps);
#endif /* LIBASS_OUTLINE_H */
diff --git a/libass/ass_parse.c b/libass/ass_parse.c
index dc27bf5..80d4bfc 100644
--- a/libass/ass_parse.c
+++ b/libass/ass_parse.c
@@ -138,41 +138,6 @@ void update_font(ASS_Renderer *render_priv)
}
/**
- * \brief Change border width
- *
- * \param render_priv renderer state object
- * \param info glyph state object
- */
-void change_border(ASS_Renderer *render_priv, double border_x, double border_y)
-{
- int bord = 64 * border_x * render_priv->border_scale;
-
- if (bord > 0 && border_x == border_y) {
- if (!render_priv->state.stroker) {
- int error;
- error =
- FT_Stroker_New(render_priv->ftlibrary,
- &render_priv->state.stroker);
- if (error) {
- ass_msg(render_priv->library, MSGL_V,
- "failed to get stroker");
- render_priv->state.stroker = 0;
- }
- render_priv->state.stroker_radius = -1.0;
- }
- if (render_priv->state.stroker && render_priv->state.stroker_radius != bord) {
- FT_Stroker_Set(render_priv->state.stroker, bord,
- FT_STROKER_LINECAP_ROUND,
- FT_STROKER_LINEJOIN_ROUND, 0);
- render_priv->state.stroker_radius = bord;
- }
- } else {
- FT_Stroker_Done(render_priv->state.stroker);
- render_priv->state.stroker = 0;
- }
-}
-
-/**
* \brief Calculate a weighted average of two colors
* calculates c1*(1-a) + c2*a, but separately for each component except alpha
*/
diff --git a/libass/ass_parse.h b/libass/ass_parse.h
index 2b4cee0..947f7ae 100644
--- a/libass/ass_parse.h
+++ b/libass/ass_parse.h
@@ -28,9 +28,6 @@
void update_font(ASS_Renderer *render_priv);
double ensure_font_size(ASS_Renderer *priv, double size);
-void calc_border(ASS_Renderer *priv, double border_x, double border_y);
-void change_border(ASS_Renderer *render_priv, double border_x,
- double border_y);
void apply_transition_effects(ASS_Renderer *render_priv, ASS_Event *event);
void process_karaoke_effects(ASS_Renderer *render_priv);
unsigned get_next_char(ASS_Renderer *render_priv, char **str);
diff --git a/libass/ass_render.c b/libass/ass_render.c
index ac73ec7..4379c09 100644
--- a/libass/ass_render.c
+++ b/libass/ass_render.c
@@ -133,10 +133,6 @@ void ass_renderer_done(ASS_Renderer *render_priv)
rasterizer_done(&render_priv->rasterizer);
#endif
- if (render_priv->state.stroker) {
- FT_Stroker_Done(render_priv->state.stroker);
- render_priv->state.stroker = 0;
- }
if (render_priv->fontselect)
ass_fontselect_free(render_priv->fontselect);
if (render_priv->ftlibrary)
@@ -854,7 +850,6 @@ void reset_render_context(ASS_Renderer *render_priv, ASS_Style *style)
render_priv->state.border_style = style->BorderStyle;
render_priv->state.border_x = style->Outline;
render_priv->state.border_y = style->Outline;
- change_border(render_priv, render_priv->state.border_x, render_priv->state.border_y);
render_priv->state.scale_x = style->ScaleX;
render_priv->state.scale_y = style->ScaleY;
render_priv->state.hspacing = style->Spacing;
@@ -962,120 +957,6 @@ static void draw_opaque_box(ASS_Renderer *render_priv, GlyphInfo *info,
ol->contours[ol->n_contours++] = ol->n_points - 1;
}
-/*
- * Stroke an outline glyph in x/y direction. Applies various fixups to get
- * around limitations of the FreeType stroker.
- */
-static void stroke_outline(ASS_Renderer *render_priv, ASS_Outline *outline,
- int sx, int sy)
-{
- if (sx <= 0 && sy <= 0)
- return;
-
- fix_freetype_stroker(outline, sx, sy);
-
- size_t n_points = outline->n_points;
- if (n_points > SHRT_MAX) {
- ass_msg(render_priv->library, MSGL_WARN, "Too many outline points: %d",
- outline->n_points);
- n_points = SHRT_MAX;
- }
-
- size_t n_contours = FFMIN(outline->n_contours, SHRT_MAX);
- short contours_small[EFFICIENT_CONTOUR_COUNT];
- short *contours = contours_small;
- short *contours_large = NULL;
- if (n_contours > EFFICIENT_CONTOUR_COUNT) {
- contours_large = malloc(n_contours * sizeof(short));
- if (!contours_large)
- return;
- contours = contours_large;
- }
- for (size_t i = 0; i < n_contours; ++i)
- contours[i] = FFMIN(outline->contours[i], n_points - 1);
-
- FT_Outline ftol;
- ftol.n_points = n_points;
- ftol.n_contours = n_contours;
- ftol.points = outline->points;
- ftol.tags = outline->tags;
- ftol.contours = contours;
- ftol.flags = 0;
-
- // Borders are equal; use the regular stroker
- if (sx == sy && render_priv->state.stroker) {
- int error;
- FT_StrokerBorder border = FT_Outline_GetOutsideBorder(&ftol);
- error = FT_Stroker_ParseOutline(render_priv->state.stroker, &ftol, 0);
- if (error) {
- ass_msg(render_priv->library, MSGL_WARN,
- "FT_Stroker_ParseOutline failed, error: %d", error);
- }
- unsigned new_points, new_contours;
- error = FT_Stroker_GetBorderCounts(render_priv->state.stroker, border,
- &new_points, &new_contours);
- if (error) {
- ass_msg(render_priv->library, MSGL_WARN,
- "FT_Stroker_GetBorderCounts failed, error: %d", error);
- }
- outline_free(outline);
- outline->n_points = outline->n_contours = 0;
- if (new_contours > FFMAX(EFFICIENT_CONTOUR_COUNT, n_contours)) {
- if (!ASS_REALLOC_ARRAY(contours_large, new_contours)) {
- free(contours_large);
- return;
- }
- }
- n_points = new_points;
- n_contours = new_contours;
- if (!outline_alloc(outline, n_points, n_contours)) {
- ass_msg(render_priv->library, MSGL_WARN,
- "Not enough memory for border outline");
- free(contours_large);
- return;
- }
- ftol.n_points = ftol.n_contours = 0;
- ftol.points = outline->points;
- ftol.tags = outline->tags;
-
- FT_Stroker_ExportBorder(render_priv->state.stroker, border, &ftol);
-
- outline->n_points = n_points;
- outline->n_contours = n_contours;
- for (size_t i = 0; i < n_contours; ++i)
- outline->contours[i] = (unsigned short) contours[i];
-
- // "Stroke" with the outline emboldener (in two passes if needed).
- // The outlines look uglier, but the emboldening never adds any points
- } else {
-#if (FREETYPE_MAJOR > 2) || \
- ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR > 4)) || \
- ((FREETYPE_MAJOR == 2) && (FREETYPE_MINOR == 4) && (FREETYPE_PATCH >= 10))
- FT_Outline_EmboldenXY(&ftol, sx * 2, sy * 2);
- FT_Outline_Translate(&ftol, -sx, -sy);
-#else
- int i;
- FT_Outline nol;
-
- FT_Outline_New(render_priv->ftlibrary, ftol.n_points,
- ftol.n_contours, &nol);
- FT_Outline_Copy(&ftol, &nol);
-
- FT_Outline_Embolden(&ftol, sx * 2);
- FT_Outline_Translate(&ftol, -sx, -sx);
- FT_Outline_Embolden(&nol, sy * 2);
- FT_Outline_Translate(&nol, -sy, -sy);
-
- for (i = 0; i < ftol.n_points; i++)
- ftol.points[i].y = nol.points[i].y;
-
- FT_Outline_Done(render_priv->ftlibrary, &nol);
-#endif
- }
-
- free(contours_large);
-}
-
/**
* \brief Prepare glyph hash
*/
@@ -1133,7 +1014,7 @@ static void fill_composite_hash(CompositeHashKey *hk, CombinedBitmapInfo *info)
* \brief Get normal and outline (border) glyphs
* \param info out: struct filled with extracted data
* Tries to get both glyphs from cache.
- * If they can't be found, gets a glyph from font face, generates outline with FT_Stroker,
+ * If they can't be found, gets a glyph from font face, generates outline,
* and add them to cache.
* The glyphs are returned in info->glyph and info->outline_glyph
*/
@@ -1218,11 +1099,16 @@ get_outline_glyph(ASS_Renderer *priv, GlyphInfo *info)
} else if ((info->border_x > 0 || info->border_y > 0)
&& double_to_d6(info->scale_x) && double_to_d6(info->scale_y)) {
- change_border(priv, info->border_x, info->border_y);
- val->border = outline_copy(val->outline);
- stroke_outline(priv, val->border,
+ val->border = outline_create(2 * val->outline->n_points,
+ val->outline->n_contours);
+ if (val->border && !outline_stroke(val->border, NULL, val->outline,
double_to_d6(info->border_x * priv->border_scale),
- double_to_d6(info->border_y * priv->border_scale));
+ double_to_d6(info->border_y * priv->border_scale), 16)) {
+ ass_msg(priv->library, MSGL_WARN, "Cannot stoke outline");
+ outline_free(val->border);
+ free(val->border);
+ val->border = NULL;
+ }
}
ass_cache_commit(val, 1);
diff --git a/libass/ass_render.h b/libass/ass_render.h
index 65422e3..e8004e9 100644
--- a/libass/ass_render.h
+++ b/libass/ass_render.h
@@ -23,7 +23,6 @@
#include <inttypes.h>
#include <ft2build.h>
#include FT_FREETYPE_H
-#include FT_STROKER_H
#include FT_GLYPH_H
#include FT_SYNTHESIS_H
#ifdef CONFIG_HARFBUZZ
@@ -218,8 +217,6 @@ typedef struct {
double font_size;
int flags; // decoration flags (underline/strike-through)
- FT_Stroker stroker;
- int stroker_radius; // last stroker radius, for caching stroker objects
int alignment; // alignment overrides go here; if zero, style value will be used
int justify; // justify instructions
double frx, fry, frz;