Fix crash with FreeType's updated stroker
[libass.git] / libass / ass_font.c
blob370623d20531b4f2c47f88627768d85adc6dfff1
1 /*
2 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
4 * This file is part of libass.
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #include "config.h"
21 #include <inttypes.h>
22 #include <ft2build.h>
23 #include FT_FREETYPE_H
24 #include FT_SYNTHESIS_H
25 #include FT_GLYPH_H
26 #include FT_TRUETYPE_TABLES_H
27 #include FT_OUTLINE_H
28 #include <strings.h>
30 #include "ass.h"
31 #include "ass_library.h"
32 #include "ass_font.h"
33 #include "ass_fontconfig.h"
34 #include "ass_utils.h"
35 #include "ass_shaper.h"
37 #define VERTICAL_LOWER_BOUND 0x02f1
39 /**
40 * Select a good charmap, prefer Microsoft Unicode charmaps.
41 * Otherwise, let FreeType decide.
43 static void charmap_magic(ASS_Library *library, FT_Face face)
45 int i;
46 int ms_cmap = -1;
48 // Search for a Microsoft Unicode cmap
49 for (i = 0; i < face->num_charmaps; ++i) {
50 FT_CharMap cmap = face->charmaps[i];
51 unsigned pid = cmap->platform_id;
52 unsigned eid = cmap->encoding_id;
53 if (pid == 3 /*microsoft */
54 && (eid == 1 /*unicode bmp */
55 || eid == 10 /*full unicode */ )) {
56 FT_Set_Charmap(face, cmap);
57 return;
58 } else if (pid == 3 && ms_cmap < 0)
59 ms_cmap = i;
62 // Try the first Microsoft cmap if no Microsoft Unicode cmap was found
63 if (ms_cmap >= 0) {
64 FT_CharMap cmap = face->charmaps[ms_cmap];
65 FT_Set_Charmap(face, cmap);
66 return;
69 if (!face->charmap) {
70 if (face->num_charmaps == 0) {
71 ass_msg(library, MSGL_WARN, "Font face with no charmaps");
72 return;
74 ass_msg(library, MSGL_WARN,
75 "No charmap autodetected, trying the first one");
76 FT_Set_Charmap(face, face->charmaps[0]);
77 return;
81 /**
82 * \brief find a memory font by name
84 static int find_font(ASS_Library *library, char *name)
86 int i;
87 for (i = 0; i < library->num_fontdata; ++i)
88 if (strcasecmp(name, library->fontdata[i].name) == 0)
89 return i;
90 return -1;
93 static void buggy_font_workaround(FT_Face face)
95 // Some fonts have zero Ascender/Descender fields in 'hhea' table.
96 // In this case, get the information from 'os2' table or, as
97 // a last resort, from face.bbox.
98 if (face->ascender + face->descender == 0 || face->height == 0) {
99 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
100 if (os2) {
101 face->ascender = os2->sTypoAscender;
102 face->descender = os2->sTypoDescender;
103 face->height = face->ascender - face->descender;
104 } else {
105 face->ascender = face->bbox.yMax;
106 face->descender = face->bbox.yMin;
107 face->height = face->ascender - face->descender;
113 * \brief Select a face with the given charcode and add it to ASS_Font
114 * \return index of the new face in font->faces, -1 if failed
116 static int add_face(void *fc_priv, ASS_Font *font, uint32_t ch)
118 char *path;
119 int index;
120 FT_Face face;
121 int error;
122 int mem_idx;
124 if (font->n_faces == ASS_FONT_MAX_FACES)
125 return -1;
127 path =
128 fontconfig_select(font->library, fc_priv, font->desc.family,
129 font->desc.treat_family_as_pattern,
130 font->desc.bold, font->desc.italic, &index, ch);
131 if (!path)
132 return -1;
134 mem_idx = find_font(font->library, path);
135 if (mem_idx >= 0) {
136 error =
137 FT_New_Memory_Face(font->ftlibrary,
138 (unsigned char *) font->library->
139 fontdata[mem_idx].data,
140 font->library->fontdata[mem_idx].size, index,
141 &face);
142 if (error) {
143 ass_msg(font->library, MSGL_WARN,
144 "Error opening memory font: '%s'", path);
145 free(path);
146 return -1;
148 } else {
149 error = FT_New_Face(font->ftlibrary, path, index, &face);
150 if (error) {
151 ass_msg(font->library, MSGL_WARN,
152 "Error opening font: '%s', %d", path, index);
153 free(path);
154 return -1;
157 charmap_magic(font->library, face);
158 buggy_font_workaround(face);
160 font->faces[font->n_faces++] = face;
161 ass_face_set_size(face, font->size);
162 free(path);
163 return font->n_faces - 1;
167 * \brief Create a new ASS_Font according to "desc" argument
169 ASS_Font *ass_font_new(Cache *font_cache, ASS_Library *library,
170 FT_Library ftlibrary, void *fc_priv,
171 ASS_FontDesc *desc)
173 int error;
174 ASS_Font *fontp;
175 ASS_Font font;
177 fontp = ass_cache_get(font_cache, desc);
178 if (fontp)
179 return fontp;
181 font.library = library;
182 font.ftlibrary = ftlibrary;
183 font.shaper_priv = NULL;
184 font.n_faces = 0;
185 font.desc.family = strdup(desc->family);
186 font.desc.treat_family_as_pattern = desc->treat_family_as_pattern;
187 font.desc.bold = desc->bold;
188 font.desc.italic = desc->italic;
189 font.desc.vertical = desc->vertical;
191 font.scale_x = font.scale_y = 1.;
192 font.v.x = font.v.y = 0;
193 font.size = 0.;
195 error = add_face(fc_priv, &font, 0);
196 if (error == -1) {
197 free(font.desc.family);
198 return 0;
199 } else
200 return ass_cache_put(font_cache, &font.desc, &font);
204 * \brief Set font transformation matrix and shift vector
206 void ass_font_set_transform(ASS_Font *font, double scale_x,
207 double scale_y, FT_Vector *v)
209 font->scale_x = scale_x;
210 font->scale_y = scale_y;
211 if (v) {
212 font->v.x = v->x;
213 font->v.y = v->y;
217 void ass_face_set_size(FT_Face face, double size)
219 TT_HoriHeader *hori = FT_Get_Sfnt_Table(face, ft_sfnt_hhea);
220 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
221 double mscale = 1.;
222 FT_Size_RequestRec rq;
223 FT_Size_Metrics *m = &face->size->metrics;
224 // VSFilter uses metrics from TrueType OS/2 table
225 // The idea was borrowed from asa (http://asa.diac24.net)
226 if (hori && os2) {
227 int hori_height = hori->Ascender - hori->Descender;
228 int os2_height = os2->usWinAscent + os2->usWinDescent;
229 if (hori_height && os2_height)
230 mscale = (double) hori_height / os2_height;
232 memset(&rq, 0, sizeof(rq));
233 rq.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
234 rq.width = 0;
235 rq.height = double_to_d6(size * mscale);
236 rq.horiResolution = rq.vertResolution = 0;
237 FT_Request_Size(face, &rq);
238 m->ascender /= mscale;
239 m->descender /= mscale;
240 m->height /= mscale;
244 * \brief Set font size
246 void ass_font_set_size(ASS_Font *font, double size)
248 int i;
249 if (font->size != size) {
250 font->size = size;
251 for (i = 0; i < font->n_faces; ++i)
252 ass_face_set_size(font->faces[i], size);
257 * \brief Get maximal font ascender and descender.
258 * \param ch character code
259 * The values are extracted from the font face that provides glyphs for the given character
261 void ass_font_get_asc_desc(ASS_Font *font, uint32_t ch, int *asc,
262 int *desc)
264 int i;
265 for (i = 0; i < font->n_faces; ++i) {
266 FT_Face face = font->faces[i];
267 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
268 if (FT_Get_Char_Index(face, ch)) {
269 int y_scale = face->size->metrics.y_scale;
270 if (os2) {
271 *asc = FT_MulFix(os2->usWinAscent, y_scale);
272 *desc = FT_MulFix(os2->usWinDescent, y_scale);
273 } else {
274 *asc = FT_MulFix(face->ascender, y_scale);
275 *desc = FT_MulFix(-face->descender, y_scale);
277 return;
281 *asc = *desc = 0;
285 * Strike a glyph with a horizontal line; it's possible to underline it
286 * and/or strike through it. For the line's position and size, truetype
287 * tables are consulted. Obviously this relies on the data in the tables
288 * being accurate.
291 static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
292 FT_Glyph glyph, int under, int through)
294 TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
295 TT_Postscript *ps = FT_Get_Sfnt_Table(face, ft_sfnt_post);
296 FT_Outline *ol = &((FT_OutlineGlyph) glyph)->outline;
297 int bear, advance, y_scale, i, dir;
299 if (!under && !through)
300 return 0;
302 // Grow outline
303 i = (under ? 4 : 0) + (through ? 4 : 0);
304 ol->points = realloc(ol->points, sizeof(FT_Vector) *
305 (ol->n_points + i));
306 ol->tags = realloc(ol->tags, ol->n_points + i);
307 i = !!under + !!through;
308 ol->contours = realloc(ol->contours, sizeof(short) *
309 (ol->n_contours + i));
311 // If the bearing is negative, the glyph starts left of the current
312 // pen position
313 bear = FFMIN(face->glyph->metrics.horiBearingX, 0);
314 // We're adding half a pixel to avoid small gaps
315 advance = d16_to_d6(glyph->advance.x) + 32;
316 y_scale = face->size->metrics.y_scale;
318 // Reverse drawing direction for non-truetype fonts
319 dir = FT_Outline_Get_Orientation(ol);
321 // Add points to the outline
322 if (under && ps) {
323 int pos, size;
324 pos = FT_MulFix(ps->underlinePosition, y_scale * font->scale_y);
325 size = FT_MulFix(ps->underlineThickness,
326 y_scale * font->scale_y / 2);
328 if (pos > 0 || size <= 0)
329 return 1;
331 FT_Vector points[4] = {
332 {.x = bear, .y = pos + size},
333 {.x = advance, .y = pos + size},
334 {.x = advance, .y = pos - size},
335 {.x = bear, .y = pos - size},
338 if (dir == FT_ORIENTATION_TRUETYPE) {
339 for (i = 0; i < 4; i++) {
340 ol->points[ol->n_points] = points[i];
341 ol->tags[ol->n_points++] = 1;
343 } else {
344 for (i = 3; i >= 0; i--) {
345 ol->points[ol->n_points] = points[i];
346 ol->tags[ol->n_points++] = 1;
350 ol->contours[ol->n_contours++] = ol->n_points - 1;
353 if (through && os2) {
354 int pos, size;
355 pos = FT_MulFix(os2->yStrikeoutPosition, y_scale * font->scale_y);
356 size = FT_MulFix(os2->yStrikeoutSize, y_scale * font->scale_y / 2);
358 if (pos < 0 || size <= 0)
359 return 1;
361 FT_Vector points[4] = {
362 {.x = bear, .y = pos + size},
363 {.x = advance, .y = pos + size},
364 {.x = advance, .y = pos - size},
365 {.x = bear, .y = pos - size},
368 if (dir == FT_ORIENTATION_TRUETYPE) {
369 for (i = 0; i < 4; i++) {
370 ol->points[ol->n_points] = points[i];
371 ol->tags[ol->n_points++] = 1;
373 } else {
374 for (i = 3; i >= 0; i--) {
375 ol->points[ol->n_points] = points[i];
376 ol->tags[ol->n_points++] = 1;
380 ol->contours[ol->n_contours++] = ol->n_points - 1;
383 return 0;
386 void outline_copy(FT_Library lib, FT_Outline *source, FT_Outline **dest)
388 if (source == NULL) {
389 *dest = NULL;
390 return;
392 *dest = calloc(1, sizeof(**dest));
394 FT_Outline_New(lib, source->n_points, source->n_contours, *dest);
395 FT_Outline_Copy(source, *dest);
398 void outline_free(FT_Library lib, FT_Outline *outline)
400 if (outline)
401 FT_Outline_Done(lib, outline);
402 free(outline);
406 * Slightly embold a glyph without touching its metrics
408 static void ass_glyph_embolden(FT_GlyphSlot slot)
410 int str;
412 if (slot->format != FT_GLYPH_FORMAT_OUTLINE)
413 return;
415 str = FT_MulFix(slot->face->units_per_EM,
416 slot->face->size->metrics.y_scale) / 64;
418 FT_Outline_Embolden(&slot->outline, str);
422 * \brief Get glyph and face index
423 * Finds a face that has the requested codepoint and returns both face
424 * and glyph index.
426 int ass_font_get_index(void *fcpriv, ASS_Font *font, uint32_t symbol,
427 int *face_index, int *glyph_index)
429 int index = 0;
430 int i;
431 FT_Face face = 0;
433 *glyph_index = 0;
435 if (symbol < 0x20) {
436 *face_index = 0;
437 return 0;
439 // Handle NBSP like a regular space when rendering the glyph
440 if (symbol == 0xa0)
441 symbol = ' ';
442 if (font->n_faces == 0) {
443 *face_index = 0;
444 return 0;
447 // try with the requested face
448 if (*face_index < font->n_faces) {
449 face = font->faces[*face_index];
450 index = FT_Get_Char_Index(face, symbol);
453 // not found in requested face, try all others
454 for (i = 0; i < font->n_faces && index == 0; ++i) {
455 face = font->faces[i];
456 index = FT_Get_Char_Index(face, symbol);
457 if (index)
458 *face_index = i;
461 #ifdef CONFIG_FONTCONFIG
462 if (index == 0) {
463 int face_idx;
464 ass_msg(font->library, MSGL_INFO,
465 "Glyph 0x%X not found, selecting one more "
466 "font for (%s, %d, %d)", symbol, font->desc.family,
467 font->desc.bold, font->desc.italic);
468 face_idx = *face_index = add_face(fcpriv, font, symbol);
469 if (face_idx >= 0) {
470 face = font->faces[face_idx];
471 index = FT_Get_Char_Index(face, symbol);
472 if (index == 0 && face->num_charmaps > 0) {
473 int i;
474 ass_msg(font->library, MSGL_WARN,
475 "Glyph 0x%X not found, broken font? Trying all charmaps", symbol);
476 for (i = 0; i < face->num_charmaps; i++) {
477 FT_Set_Charmap(face, face->charmaps[i]);
478 if ((index = FT_Get_Char_Index(face, symbol)) != 0) break;
481 if (index == 0) {
482 ass_msg(font->library, MSGL_ERR,
483 "Glyph 0x%X not found in font for (%s, %d, %d)",
484 symbol, font->desc.family, font->desc.bold,
485 font->desc.italic);
489 #endif
490 *glyph_index = index;
492 return 1;
496 * \brief Get a glyph
497 * \param ch character code
499 FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
500 uint32_t ch, int face_index, int index,
501 ASS_Hinting hinting, int deco)
503 int error;
504 FT_Glyph glyph;
505 FT_Face face = font->faces[face_index];
506 int flags = 0;
507 int vertical = font->desc.vertical;
509 flags = FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
510 | FT_LOAD_IGNORE_TRANSFORM;
511 switch (hinting) {
512 case ASS_HINTING_NONE:
513 flags |= FT_LOAD_NO_HINTING;
514 break;
515 case ASS_HINTING_LIGHT:
516 flags |= FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT;
517 break;
518 case ASS_HINTING_NORMAL:
519 flags |= FT_LOAD_FORCE_AUTOHINT;
520 break;
521 case ASS_HINTING_NATIVE:
522 break;
525 error = FT_Load_Glyph(face, index, flags);
526 if (error) {
527 ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
528 index);
529 return 0;
531 if (!(face->style_flags & FT_STYLE_FLAG_ITALIC) &&
532 (font->desc.italic > 55)) {
533 FT_GlyphSlot_Oblique(face->glyph);
536 if (!(face->style_flags & FT_STYLE_FLAG_BOLD) &&
537 (font->desc.bold > 80)) {
538 ass_glyph_embolden(face->glyph);
540 error = FT_Get_Glyph(face->glyph, &glyph);
541 if (error) {
542 ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
543 index);
544 return 0;
547 // Rotate glyph, if needed
548 if (vertical && ch >= VERTICAL_LOWER_BOUND) {
549 FT_Matrix m = { 0, double_to_d16(-1.0), double_to_d16(1.0), 0 };
550 FT_Outline_Transform(&((FT_OutlineGlyph) glyph)->outline, &m);
551 FT_Outline_Translate(&((FT_OutlineGlyph) glyph)->outline,
552 face->glyph->metrics.vertAdvance,
554 glyph->advance.x = face->glyph->linearVertAdvance;
557 // Apply scaling and shift
558 FT_Matrix scale = { double_to_d16(font->scale_x), 0, 0,
559 double_to_d16(font->scale_y) };
560 FT_Outline *outl = &((FT_OutlineGlyph) glyph)->outline;
561 FT_Outline_Transform(outl, &scale);
562 FT_Outline_Translate(outl, font->v.x, font->v.y);
563 glyph->advance.x *= font->scale_x;
565 ass_strike_outline_glyph(face, font, glyph, deco & DECO_UNDERLINE,
566 deco & DECO_STRIKETHROUGH);
568 return glyph;
572 * \brief Get kerning for the pair of glyphs.
574 FT_Vector ass_font_get_kerning(ASS_Font *font, uint32_t c1, uint32_t c2)
576 FT_Vector v = { 0, 0 };
577 int i;
579 if (font->desc.vertical)
580 return v;
582 for (i = 0; i < font->n_faces; ++i) {
583 FT_Face face = font->faces[i];
584 int i1 = FT_Get_Char_Index(face, c1);
585 int i2 = FT_Get_Char_Index(face, c2);
586 if (i1 && i2) {
587 if (FT_HAS_KERNING(face))
588 FT_Get_Kerning(face, i1, i2, FT_KERNING_DEFAULT, &v);
589 return v;
591 if (i1 || i2) // these glyphs are from different font faces, no kerning information
592 return v;
594 return v;
598 * \brief Deallocate ASS_Font
600 void ass_font_free(ASS_Font *font)
602 int i;
603 for (i = 0; i < font->n_faces; ++i)
604 if (font->faces[i])
605 FT_Done_Face(font->faces[i]);
606 if (font->shaper_priv)
607 ass_shaper_font_data_free(font->shaper_priv);
608 free(font->desc.family);
609 free(font);
613 * \brief Calculate the cbox of a series of points
615 static void
616 get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end)
618 box->xMin = box->yMin = INT_MAX;
619 box->xMax = box->yMax = INT_MIN;
620 int i;
622 for (i = start; i <= end; i++) {
623 box->xMin = (points[i].x < box->xMin) ? points[i].x : box->xMin;
624 box->xMax = (points[i].x > box->xMax) ? points[i].x : box->xMax;
625 box->yMin = (points[i].y < box->yMin) ? points[i].y : box->yMin;
626 box->yMax = (points[i].y > box->yMax) ? points[i].y : box->yMax;
631 * \brief Determine winding direction of a contour
632 * \return direction; 0 = clockwise
634 static int get_contour_direction(FT_Vector *points, int start, int end)
636 int i;
637 long long sum = 0;
638 int x = points[start].x;
639 int y = points[start].y;
640 for (i = start + 1; i <= end; i++) {
641 sum += x * (points[i].y - y) - y * (points[i].x - x);
642 x = points[i].x;
643 y = points[i].y;
645 sum += x * (points[start].y - y) - y * (points[start].x - x);
646 return sum > 0;
650 * \brief Apply fixups to please the FreeType stroker and improve the
651 * rendering result, especially in case the outline has some anomalies.
652 * At the moment, the following fixes are done:
654 * 1. Reverse contours that have "inside" winding direction but are not
655 * contained in any other contours' cbox.
656 * 2. Remove "inside" contours depending on border size, so that large
657 * borders do not reverse the winding direction, which leads to "holes"
658 * inside the border. The inside will be filled by the border of the
659 * outside contour anyway in this case.
661 * \param outline FreeType outline, modified in-place
662 * \param border_x border size, x direction, d6 format
663 * \param border_x border size, y direction, d6 format
665 void fix_freetype_stroker(FT_Outline *outline, int border_x, int border_y)
667 int nc = outline->n_contours;
668 int begin, stop;
669 char modified = 0;
670 char *valid_cont = malloc(nc);
671 int start = 0;
672 int end = -1;
673 FT_BBox *boxes = malloc(nc * sizeof(FT_BBox));
674 int i, j;
675 int inside_direction;
677 inside_direction = FT_Outline_Get_Orientation(outline) ==
678 FT_ORIENTATION_TRUETYPE;
680 // create a list of cboxes of the contours
681 for (i = 0; i < nc; i++) {
682 start = end + 1;
683 end = outline->contours[i];
684 get_contour_cbox(&boxes[i], outline->points, start, end);
687 // for each contour, check direction and whether it's "outside"
688 // or contained in another contour
689 end = -1;
690 for (i = 0; i < nc; i++) {
691 start = end + 1;
692 end = outline->contours[i];
693 int dir = get_contour_direction(outline->points, start, end);
694 valid_cont[i] = 1;
695 if (dir == inside_direction) {
696 for (j = 0; j < nc; j++) {
697 if (i == j)
698 continue;
699 if (boxes[i].xMin >= boxes[j].xMin &&
700 boxes[i].xMax <= boxes[j].xMax &&
701 boxes[i].yMin >= boxes[j].yMin &&
702 boxes[i].yMax <= boxes[j].yMax)
703 goto check_inside;
705 /* "inside" contour but we can't find anything it could be
706 * inside of - assume the font is buggy and it should be
707 * an "outside" contour, and reverse it */
708 for (j = 0; j < (end + 1 - start) / 2; j++) {
709 FT_Vector temp = outline->points[start + j];
710 char temp2 = outline->tags[start + j];
711 outline->points[start + j] = outline->points[end - j];
712 outline->points[end - j] = temp;
713 outline->tags[start + j] = outline->tags[end - j];
714 outline->tags[end - j] = temp2;
716 dir ^= 1;
718 check_inside:
719 if (dir == inside_direction) {
720 FT_BBox box;
721 get_contour_cbox(&box, outline->points, start, end);
722 int width = box.xMax - box.xMin;
723 int height = box.yMax - box.yMin;
724 if (width < border_x * 2 || height < border_y * 2) {
725 valid_cont[i] = 0;
726 modified = 1;
731 // if we need to modify the outline, rewrite it and skip
732 // the contours that we determined should be removed.
733 if (modified) {
734 int p = 0, c = 0;
735 for (i = 0; i < nc; i++) {
736 if (!valid_cont[i])
737 continue;
738 begin = (i == 0) ? 0 : outline->contours[i - 1] + 1;
739 stop = outline->contours[i];
740 for (j = begin; j <= stop; j++) {
741 outline->points[p].x = outline->points[j].x;
742 outline->points[p].y = outline->points[j].y;
743 outline->tags[p] = outline->tags[j];
744 p++;
746 outline->contours[c] = p - 1;
747 c++;
749 outline->n_points = p;
750 outline->n_contours = c;
753 free(boxes);
754 free(valid_cont);