]> granicus.if.org Git - libass/blob - libass/ass_font.c
Fix and simplify opaque box border
[libass] / libass / ass_font.c
1 /*
2  * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
3  *
4  * This file is part of libass.
5  *
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.
9  *
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.
17  */
18
19 #include "config.h"
20
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>
29
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"
36
37 #define VERTICAL_LOWER_BOUND 0x02f1
38
39 /**
40  * Select a good charmap, prefer Microsoft Unicode charmaps.
41  * Otherwise, let FreeType decide.
42  */
43 static void charmap_magic(ASS_Library *library, FT_Face face)
44 {
45     int i;
46     int ms_cmap = -1;
47
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;
60     }
61
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;
67     }
68
69     if (!face->charmap) {
70         if (face->num_charmaps == 0) {
71             ass_msg(library, MSGL_WARN, "Font face with no charmaps");
72             return;
73         }
74         ass_msg(library, MSGL_WARN,
75                 "No charmap autodetected, trying the first one");
76         FT_Set_Charmap(face, face->charmaps[0]);
77         return;
78     }
79 }
80
81 /**
82  * \brief find a memory font by name
83  */
84 static int find_font(ASS_Library *library, char *name)
85 {
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;
91 }
92
93 static void buggy_font_workaround(FT_Face face)
94 {
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;
108         }
109     }
110 }
111
112 /**
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
115  */
116 static int add_face(void *fc_priv, ASS_Font *font, uint32_t ch)
117 {
118     char *path;
119     int index;
120     FT_Face face;
121     int error;
122     int mem_idx;
123
124     if (font->n_faces == ASS_FONT_MAX_FACES)
125         return -1;
126
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;
133
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;
147         }
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;
155         }
156     }
157     charmap_magic(font->library, face);
158     buggy_font_workaround(face);
159
160     font->faces[font->n_faces++] = face;
161     ass_face_set_size(face, font->size);
162     free(path);
163     return font->n_faces - 1;
164 }
165
166 /**
167  * \brief Create a new ASS_Font according to "desc" argument
168  */
169 ASS_Font *ass_font_new(Cache *font_cache, ASS_Library *library,
170                        FT_Library ftlibrary, void *fc_priv,
171                        ASS_FontDesc *desc)
172 {
173     int error;
174     ASS_Font *fontp;
175     ASS_Font font;
176
177     fontp = ass_cache_get(font_cache, desc);
178     if (fontp)
179         return fontp;
180
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;
190
191     font.scale_x = font.scale_y = 1.;
192     font.v.x = font.v.y = 0;
193     font.size = 0.;
194
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);
201 }
202
203 /**
204  * \brief Set font transformation matrix and shift vector
205  **/
206 void ass_font_set_transform(ASS_Font *font, double scale_x,
207                             double scale_y, FT_Vector *v)
208 {
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;
214     }
215 }
216
217 void ass_face_set_size(FT_Face face, double size)
218 {
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;
231     }
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;
241 }
242
243 /**
244  * \brief Set font size
245  **/
246 void ass_font_set_size(ASS_Font *font, double size)
247 {
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);
253     }
254 }
255
256 /**
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
260  **/
261 void ass_font_get_asc_desc(ASS_Font *font, uint32_t ch, int *asc,
262                            int *desc)
263 {
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);
276             }
277             return;
278         }
279     }
280
281     *asc = *desc = 0;
282 }
283
284 /*
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.
289  *
290  */
291 static int ass_strike_outline_glyph(FT_Face face, ASS_Font *font,
292                                     FT_Glyph glyph, int under, int through)
293 {
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;
298
299     if (!under && !through)
300         return 0;
301
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));
310
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;
317
318     // Reverse drawing direction for non-truetype fonts
319     dir = FT_Outline_Get_Orientation(ol);
320
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);
327
328         if (pos > 0 || size <= 0)
329             return 1;
330
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},
336         };
337
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;
342             }
343         } else {
344             for (i = 3; i >= 0; i--) {
345                 ol->points[ol->n_points] = points[i];
346                 ol->tags[ol->n_points++] = 1;
347             }
348         }
349
350         ol->contours[ol->n_contours++] = ol->n_points - 1;
351     }
352
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);
357
358         if (pos < 0 || size <= 0)
359             return 1;
360
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},
366         };
367
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;
372             }
373         } else {
374             for (i = 3; i >= 0; i--) {
375                 ol->points[ol->n_points] = points[i];
376                 ol->tags[ol->n_points++] = 1;
377             }
378         }
379
380         ol->contours[ol->n_contours++] = ol->n_points - 1;
381     }
382
383     return 0;
384 }
385
386 void outline_copy(FT_Library lib, FT_Outline *source, FT_Outline **dest)
387 {
388     if (source == NULL) {
389         *dest = NULL;
390         return;
391     }
392     *dest = calloc(1, sizeof(**dest));
393
394     FT_Outline_New(lib, source->n_points, source->n_contours, *dest);
395     FT_Outline_Copy(source, *dest);
396 }
397
398 void outline_free(FT_Library lib, FT_Outline *outline)
399 {
400     if (outline)
401         FT_Outline_Done(lib, outline);
402     free(outline);
403 }
404
405 /**
406  * Slightly embold a glyph without touching its metrics
407  */
408 static void ass_glyph_embolden(FT_GlyphSlot slot)
409 {
410     int str;
411
412     if (slot->format != FT_GLYPH_FORMAT_OUTLINE)
413         return;
414
415     str = FT_MulFix(slot->face->units_per_EM,
416                     slot->face->size->metrics.y_scale) / 64;
417
418     FT_Outline_Embolden(&slot->outline, str);
419 }
420
421 /**
422  * \brief Get glyph and face index
423  * Finds a face that has the requested codepoint and returns both face
424  * and glyph index.
425  */
426 int ass_font_get_index(void *fcpriv, ASS_Font *font, uint32_t symbol,
427                        int *face_index, int *glyph_index)
428 {
429     int index = 0;
430     int i;
431     FT_Face face = 0;
432
433     *glyph_index = 0;
434
435     if (symbol < 0x20) {
436         *face_index = 0;
437         return 0;
438     }
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;
445     }
446
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);
451     }
452
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;
459     }
460
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;
479                 }
480             }
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);
486             }
487         }
488     }
489 #endif
490     // FIXME: make sure we have a valid face_index. this is a HACK.
491     *face_index  = FFMAX(*face_index, 0);
492     *glyph_index = index;
493
494     return 1;
495 }
496
497 /**
498  * \brief Get a glyph
499  * \param ch character code
500  **/
501 FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ASS_Font *font,
502                             uint32_t ch, int face_index, int index,
503                             ASS_Hinting hinting, int deco)
504 {
505     int error;
506     FT_Glyph glyph;
507     FT_Face face = font->faces[face_index];
508     int flags = 0;
509     int vertical = font->desc.vertical;
510
511     flags = FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH
512             | FT_LOAD_IGNORE_TRANSFORM;
513     switch (hinting) {
514     case ASS_HINTING_NONE:
515         flags |= FT_LOAD_NO_HINTING;
516         break;
517     case ASS_HINTING_LIGHT:
518         flags |= FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT;
519         break;
520     case ASS_HINTING_NORMAL:
521         flags |= FT_LOAD_FORCE_AUTOHINT;
522         break;
523     case ASS_HINTING_NATIVE:
524         break;
525     }
526
527     error = FT_Load_Glyph(face, index, flags);
528     if (error) {
529         ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
530                 index);
531         return 0;
532     }
533     if (!(face->style_flags & FT_STYLE_FLAG_ITALIC) &&
534         (font->desc.italic > 55)) {
535         FT_GlyphSlot_Oblique(face->glyph);
536     }
537
538     if (!(face->style_flags & FT_STYLE_FLAG_BOLD) &&
539         (font->desc.bold > 80)) {
540         ass_glyph_embolden(face->glyph);
541     }
542     error = FT_Get_Glyph(face->glyph, &glyph);
543     if (error) {
544         ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
545                 index);
546         return 0;
547     }
548
549     // Rotate glyph, if needed
550     if (vertical && ch >= VERTICAL_LOWER_BOUND) {
551         FT_Matrix m = { 0, double_to_d16(-1.0), double_to_d16(1.0), 0 };
552         FT_Outline_Transform(&((FT_OutlineGlyph) glyph)->outline, &m);
553         FT_Outline_Translate(&((FT_OutlineGlyph) glyph)->outline,
554                              face->glyph->metrics.vertAdvance,
555                              0);
556         glyph->advance.x = face->glyph->linearVertAdvance;
557     }
558
559     // Apply scaling and shift
560     FT_Matrix scale = { double_to_d16(font->scale_x), 0, 0,
561                         double_to_d16(font->scale_y) };
562     FT_Outline *outl = &((FT_OutlineGlyph) glyph)->outline;
563     FT_Outline_Transform(outl, &scale);
564     FT_Outline_Translate(outl, font->v.x, font->v.y);
565     glyph->advance.x *= font->scale_x;
566
567     ass_strike_outline_glyph(face, font, glyph, deco & DECO_UNDERLINE,
568                              deco & DECO_STRIKETHROUGH);
569
570     return glyph;
571 }
572
573 /**
574  * \brief Get kerning for the pair of glyphs.
575  **/
576 FT_Vector ass_font_get_kerning(ASS_Font *font, uint32_t c1, uint32_t c2)
577 {
578     FT_Vector v = { 0, 0 };
579     int i;
580
581     if (font->desc.vertical)
582         return v;
583
584     for (i = 0; i < font->n_faces; ++i) {
585         FT_Face face = font->faces[i];
586         int i1 = FT_Get_Char_Index(face, c1);
587         int i2 = FT_Get_Char_Index(face, c2);
588         if (i1 && i2) {
589             if (FT_HAS_KERNING(face))
590                 FT_Get_Kerning(face, i1, i2, FT_KERNING_DEFAULT, &v);
591             return v;
592         }
593         if (i1 || i2)           // these glyphs are from different font faces, no kerning information
594             return v;
595     }
596     return v;
597 }
598
599 /**
600  * \brief Deallocate ASS_Font
601  **/
602 void ass_font_free(ASS_Font *font)
603 {
604     int i;
605     for (i = 0; i < font->n_faces; ++i)
606         if (font->faces[i])
607             FT_Done_Face(font->faces[i]);
608     if (font->shaper_priv)
609         ass_shaper_font_data_free(font->shaper_priv);
610     free(font->desc.family);
611     free(font);
612 }
613
614 /**
615  * \brief Calculate the cbox of a series of points
616  */
617 static void
618 get_contour_cbox(FT_BBox *box, FT_Vector *points, int start, int end)
619 {
620     box->xMin = box->yMin = INT_MAX;
621     box->xMax = box->yMax = INT_MIN;
622     int i;
623
624     for (i = start; i <= end; i++) {
625         box->xMin = (points[i].x < box->xMin) ? points[i].x : box->xMin;
626         box->xMax = (points[i].x > box->xMax) ? points[i].x : box->xMax;
627         box->yMin = (points[i].y < box->yMin) ? points[i].y : box->yMin;
628         box->yMax = (points[i].y > box->yMax) ? points[i].y : box->yMax;
629     }
630 }
631
632 /**
633  * \brief Determine winding direction of a contour
634  * \return direction; 0 = clockwise
635  */
636 static int get_contour_direction(FT_Vector *points, int start, int end)
637 {
638     int i;
639     long long sum = 0;
640     int x = points[start].x;
641     int y = points[start].y;
642     for (i = start + 1; i <= end; i++) {
643         sum += x * (points[i].y - y) - y * (points[i].x - x);
644         x = points[i].x;
645         y = points[i].y;
646     }
647     sum += x * (points[start].y - y) - y * (points[start].x - x);
648     return sum > 0;
649 }
650
651 /**
652  * \brief Apply fixups to please the FreeType stroker and improve the
653  * rendering result, especially in case the outline has some anomalies.
654  * At the moment, the following fixes are done:
655  *
656  * 1. Reverse contours that have "inside" winding direction but are not
657  *    contained in any other contours' cbox.
658  * 2. Remove "inside" contours depending on border size, so that large
659  *    borders do not reverse the winding direction, which leads to "holes"
660  *    inside the border. The inside will be filled by the border of the
661  *    outside contour anyway in this case.
662  *
663  * \param outline FreeType outline, modified in-place
664  * \param border_x border size, x direction, d6 format
665  * \param border_x border size, y direction, d6 format
666  */
667 void fix_freetype_stroker(FT_Outline *outline, int border_x, int border_y)
668 {
669     int nc = outline->n_contours;
670     int begin, stop;
671     char modified = 0;
672     char *valid_cont = malloc(nc);
673     int start = 0;
674     int end = -1;
675     FT_BBox *boxes = malloc(nc * sizeof(FT_BBox));
676     int i, j;
677     int inside_direction;
678
679     inside_direction = FT_Outline_Get_Orientation(outline) ==
680         FT_ORIENTATION_TRUETYPE;
681
682     // create a list of cboxes of the contours
683     for (i = 0; i < nc; i++) {
684         start = end + 1;
685         end = outline->contours[i];
686         get_contour_cbox(&boxes[i], outline->points, start, end);
687     }
688
689     // for each contour, check direction and whether it's "outside"
690     // or contained in another contour
691     end = -1;
692     for (i = 0; i < nc; i++) {
693         start = end + 1;
694         end = outline->contours[i];
695         int dir = get_contour_direction(outline->points, start, end);
696         valid_cont[i] = 1;
697         if (dir == inside_direction) {
698             for (j = 0; j < nc; j++) {
699                 if (i == j)
700                     continue;
701                 if (boxes[i].xMin >= boxes[j].xMin &&
702                     boxes[i].xMax <= boxes[j].xMax &&
703                     boxes[i].yMin >= boxes[j].yMin &&
704                     boxes[i].yMax <= boxes[j].yMax)
705                     goto check_inside;
706             }
707             /* "inside" contour but we can't find anything it could be
708              * inside of - assume the font is buggy and it should be
709              * an "outside" contour, and reverse it */
710             for (j = 0; j < (end + 1 - start) / 2; j++) {
711                 FT_Vector temp = outline->points[start + j];
712                 char temp2 = outline->tags[start + j];
713                 outline->points[start + j] = outline->points[end - j];
714                 outline->points[end - j] = temp;
715                 outline->tags[start + j] = outline->tags[end - j];
716                 outline->tags[end - j] = temp2;
717             }
718             dir ^= 1;
719         }
720         check_inside:
721         if (dir == inside_direction) {
722             FT_BBox box;
723             get_contour_cbox(&box, outline->points, start, end);
724             int width = box.xMax - box.xMin;
725             int height = box.yMax - box.yMin;
726             if (width < border_x * 2 || height < border_y * 2) {
727                 valid_cont[i] = 0;
728                 modified = 1;
729             }
730         }
731     }
732
733     // if we need to modify the outline, rewrite it and skip
734     // the contours that we determined should be removed.
735     if (modified) {
736         int p = 0, c = 0;
737         for (i = 0; i < nc; i++) {
738             if (!valid_cont[i])
739                 continue;
740             begin = (i == 0) ? 0 : outline->contours[i - 1] + 1;
741             stop = outline->contours[i];
742             for (j = begin; j <= stop; j++) {
743                 outline->points[p].x = outline->points[j].x;
744                 outline->points[p].y = outline->points[j].y;
745                 outline->tags[p] = outline->tags[j];
746                 p++;
747             }
748             outline->contours[c] = p - 1;
749             c++;
750         }
751         outline->n_points = p;
752         outline->n_contours = c;
753     }
754
755     free(boxes);
756     free(valid_cont);
757 }
758