2 * Copyright (C) 2009 Grigori Goronzy <greg@geekmind.org>
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.
25 #include "ass_utils.h"
27 #include "ass_drawing.h"
29 #define CURVE_ACCURACY 64.0
30 #define GLYPH_INITIAL_POINTS 100
31 #define GLYPH_INITIAL_CONTOURS 5
34 * \brief Get and prepare a FreeType glyph
36 static void drawing_make_glyph(ASS_Drawing
*drawing
, void *fontconfig_priv
,
37 ASS_Font
*font
, ASS_Hinting hint
)
39 FT_OutlineGlyph glyph
;
42 glyph
= (FT_OutlineGlyph
) ass_font_get_glyph(fontconfig_priv
, font
,
43 (uint32_t) ' ', hint
, 0);
45 FT_Outline_Done(drawing
->ftlibrary
, &glyph
->outline
);
46 FT_Outline_New(drawing
->ftlibrary
, GLYPH_INITIAL_POINTS
,
47 GLYPH_INITIAL_CONTOURS
, &glyph
->outline
);
49 glyph
->outline
.n_contours
= 0;
50 glyph
->outline
.n_points
= 0;
51 glyph
->root
.advance
.x
= glyph
->root
.advance
.y
= 0;
53 drawing
->glyph
= glyph
;
57 * \brief Add a single point to a contour.
59 static inline void drawing_add_point(ASS_Drawing
*drawing
,
62 FT_Outline
*ol
= &drawing
->glyph
->outline
;
64 if (ol
->n_points
>= drawing
->max_points
) {
65 drawing
->max_points
*= 2;
66 ol
->points
= realloc(ol
->points
, sizeof(FT_Vector
) *
68 ol
->tags
= realloc(ol
->tags
, drawing
->max_points
);
71 ol
->points
[ol
->n_points
].x
= point
->x
;
72 ol
->points
[ol
->n_points
].y
= point
->y
;
73 ol
->tags
[ol
->n_points
] = 1;
78 * \brief Close a contour and check glyph size overflow.
80 static inline void drawing_close_shape(ASS_Drawing
*drawing
)
82 FT_Outline
*ol
= &drawing
->glyph
->outline
;
84 if (ol
->n_contours
>= drawing
->max_contours
) {
85 drawing
->max_contours
*= 2;
86 ol
->contours
= realloc(ol
->contours
, sizeof(short) *
87 drawing
->max_contours
);
91 ol
->contours
[ol
->n_contours
] = ol
->n_points
- 1;
97 * \brief Prepare drawing for parsing. This just sets a few parameters.
99 static void drawing_prepare(ASS_Drawing
*drawing
)
101 // Scaling parameters
102 drawing
->point_scale_x
= drawing
->scale_x
*
103 64.0 / (1 << (drawing
->scale
- 1));
104 drawing
->point_scale_y
= drawing
->scale_y
*
105 64.0 / (1 << (drawing
->scale
- 1));
109 * \brief Finish a drawing. This only sets the horizontal advance according
110 * to the glyph's bbox at the moment.
112 static void drawing_finish(ASS_Drawing
*drawing
, int raw_mode
)
115 FT_BBox bbox
= drawing
->cbox
;
116 FT_Outline
*ol
= &drawing
->glyph
->outline
;
118 // Close the last contour
119 drawing_close_shape(drawing
);
121 ass_msg(drawing
->library
, MSGL_V
,
122 "Parsed drawing with %d points and %d contours", ol
->n_points
,
128 drawing
->glyph
->root
.advance
.x
= d6_to_d16(bbox
.xMax
- bbox
.xMin
);
130 drawing
->desc
= double_to_d6(-drawing
->pbo
* drawing
->scale_y
);
131 drawing
->asc
= bbox
.yMax
- bbox
.yMin
+ drawing
->desc
;
133 // Place it onto the baseline
134 offset
= (bbox
.yMax
- bbox
.yMin
) + double_to_d6(-drawing
->pbo
*
136 for (i
= 0; i
< ol
->n_points
; i
++)
137 ol
->points
[i
].y
+= offset
;
141 * \brief Check whether a number of items on the list is available
143 static int token_check_values(ASS_DrawingToken
*token
, int i
, int type
)
146 for (j
= 0; j
< i
; j
++) {
147 if (!token
|| token
->type
!= type
) return 0;
155 * \brief Tokenize a drawing string into a list of ASS_DrawingToken
156 * This also expands points for closing b-splines
158 static ASS_DrawingToken
*drawing_tokenize(char *str
)
161 int i
, val
, type
= -1, is_set
= 0;
162 FT_Vector point
= {0, 0};
164 ASS_DrawingToken
*root
= NULL
, *tail
= NULL
, *spline_start
= NULL
;
167 if (*p
== 'c' && spline_start
) {
168 // Close b-splines: add the first three points of the b-spline
170 if (token_check_values(spline_start
->next
, 2, TOKEN_B_SPLINE
)) {
171 for (i
= 0; i
< 3; i
++) {
172 tail
->next
= calloc(1, sizeof(ASS_DrawingToken
));
173 tail
->next
->prev
= tail
;
175 tail
->type
= TOKEN_B_SPLINE
;
176 tail
->point
= spline_start
->point
;
177 spline_start
= spline_start
->next
;
181 } else if (!is_set
&& mystrtoi(&p
, &val
)) {
185 } else if (is_set
== 1 && mystrtoi(&p
, &val
)) {
189 } else if (*p
== 'm')
192 type
= TOKEN_MOVE_NC
;
196 type
= TOKEN_CUBIC_BEZIER
;
198 type
= TOKEN_CONIC_BEZIER
;
200 type
= TOKEN_B_SPLINE
;
201 // We're simply ignoring TOKEN_EXTEND_B_SPLINE here.
202 // This is not harmful at all, since it can be ommitted with
203 // similar result (the spline is extended anyway).
205 if (type
!= -1 && is_set
== 2) {
207 tail
->next
= calloc(1, sizeof(ASS_DrawingToken
));
208 tail
->next
->prev
= tail
;
211 root
= tail
= calloc(1, sizeof(ASS_DrawingToken
));
215 if (type
== TOKEN_B_SPLINE
&& !spline_start
)
216 spline_start
= tail
->prev
;
225 * \brief Free a list of tokens
227 static void drawing_free_tokens(ASS_DrawingToken
*token
)
230 ASS_DrawingToken
*at
= token
;
237 * \brief Update drawing cbox
239 static inline void update_cbox(ASS_Drawing
*drawing
, FT_Vector
*point
)
241 FT_BBox
*box
= &drawing
->cbox
;
243 box
->xMin
= FFMIN(box
->xMin
, point
->x
);
244 box
->xMax
= FFMAX(box
->xMax
, point
->x
);
245 box
->yMin
= FFMIN(box
->yMin
, point
->y
);
246 box
->yMax
= FFMAX(box
->yMax
, point
->y
);
250 * \brief Translate and scale a point coordinate according to baseline
253 static inline void translate_point(ASS_Drawing
*drawing
, FT_Vector
*point
)
255 point
->x
= drawing
->point_scale_x
* point
->x
;
256 point
->y
= drawing
->point_scale_y
* -point
->y
;
258 update_cbox(drawing
, point
);
262 * \brief Evaluate a curve into lines
263 * This curve evaluator is also used in VSFilter (RTS.cpp); it's a simple
264 * implementation of the De Casteljau algorithm.
266 static void drawing_evaluate_curve(ASS_Drawing
*drawing
,
267 ASS_DrawingToken
*token
, char spline
,
270 double cx3
, cx2
, cx1
, cx0
, cy3
, cy2
, cy1
, cy0
;
271 double t
, h
, max_accel
, max_accel1
, max_accel2
;
272 FT_Vector cur
= {0, 0};
275 translate_point(drawing
, &cur
);
280 translate_point(drawing
, &cur
);
285 translate_point(drawing
, &cur
);
290 translate_point(drawing
, &cur
);
300 double div6
= 1.0/6.0;
302 cx3
= div6
*(- x0
+3*x1
-3*x2
+x3
);
303 cx2
= div6
*( 3*x0
-6*x1
+3*x2
);
304 cx1
= div6
*(-3*x0
+3*x2
);
305 cx0
= div6
*( x0
+4*x1
+1*x2
);
307 cy3
= div6
*(- y0
+3*y1
-3*y2
+y3
);
308 cy2
= div6
*( 3*y0
-6*y1
+3*y2
);
309 cy1
= div6
*(-3*y0
+3*y2
);
310 cy0
= div6
*( y0
+4*y1
+1*y2
);
317 cx3
= - x0
+3*x1
-3*x2
+x3
;
318 cx2
= 3*x0
-6*x1
+3*x2
;
322 cy3
= - y0
+3*y1
-3*y2
+y3
;
323 cy2
= 3*y0
-6*y1
+3*y2
;
328 max_accel1
= fabs(2 * cy2
) + fabs(6 * cy3
);
329 max_accel2
= fabs(2 * cx2
) + fabs(6 * cx3
);
331 max_accel
= FFMAX(max_accel1
, max_accel2
);
334 if (max_accel
> CURVE_ACCURACY
)
335 h
= sqrt(CURVE_ACCURACY
/ max_accel
);
340 drawing_add_point(drawing
, &cur
);
343 for (t
= 0; t
< 1.0; t
+= h
) {
344 cur
.x
= cx0
+ t
* (cx1
+ t
* (cx2
+ t
* cx3
));
345 cur
.y
= cy0
+ t
* (cy1
+ t
* (cy2
+ t
* cy3
));
346 drawing_add_point(drawing
, &cur
);
349 cur
.x
= cx0
+ cx1
+ cx2
+ cx3
;
350 cur
.y
= cy0
+ cy1
+ cy2
+ cy3
;
351 drawing_add_point(drawing
, &cur
);
355 * \brief Create and initialize a new drawing and return it
357 ASS_Drawing
*ass_drawing_new(void *fontconfig_priv
, ASS_Font
*font
,
358 ASS_Hinting hint
, FT_Library lib
)
360 ASS_Drawing
*drawing
;
362 drawing
= calloc(1, sizeof(*drawing
));
363 drawing
->text
= calloc(1, DRAWING_INITIAL_SIZE
);
364 drawing
->size
= DRAWING_INITIAL_SIZE
;
365 drawing
->cbox
.xMin
= drawing
->cbox
.yMin
= INT_MAX
;
366 drawing
->cbox
.xMax
= drawing
->cbox
.yMax
= INT_MIN
;
368 drawing
->ftlibrary
= lib
;
370 drawing
->library
= font
->library
;
371 drawing_make_glyph(drawing
, fontconfig_priv
, font
, hint
);
374 drawing
->scale_x
= 1.;
375 drawing
->scale_y
= 1.;
376 drawing
->max_contours
= GLYPH_INITIAL_CONTOURS
;
377 drawing
->max_points
= GLYPH_INITIAL_POINTS
;
383 * \brief Free a drawing
385 void ass_drawing_free(ASS_Drawing
* drawing
)
394 * \brief Add one ASCII character to the drawing text buffer
396 void ass_drawing_add_char(ASS_Drawing
* drawing
, char symbol
)
398 drawing
->text
[drawing
->i
++] = symbol
;
399 drawing
->text
[drawing
->i
] = 0;
401 if (drawing
->i
+ 1 >= drawing
->size
) {
403 drawing
->text
= realloc(drawing
->text
, drawing
->size
);
408 * \brief Create a hashcode for the drawing
409 * XXX: To avoid collisions a better hash algorithm might be useful.
411 void ass_drawing_hash(ASS_Drawing
* drawing
)
413 drawing
->hash
= fnv_32a_str(drawing
->text
, FNV1_32A_INIT
);
417 * \brief Convert token list to outline. Calls the line and curve evaluators.
419 FT_OutlineGlyph
*ass_drawing_parse(ASS_Drawing
*drawing
, int raw_mode
)
422 ASS_DrawingToken
*token
;
423 FT_Vector pen
= {0, 0};
428 drawing
->tokens
= drawing_tokenize(drawing
->text
);
429 drawing_prepare(drawing
);
431 token
= drawing
->tokens
;
433 // Draw something according to current command
434 switch (token
->type
) {
437 translate_point(drawing
, &pen
);
442 translate_point(drawing
, &pen
);
444 drawing_close_shape(drawing
);
452 translate_point(drawing
, &to
);
453 if (!started
) drawing_add_point(drawing
, &pen
);
454 drawing_add_point(drawing
, &to
);
459 case TOKEN_CUBIC_BEZIER
:
460 if (token_check_values(token
, 3, TOKEN_CUBIC_BEZIER
) &&
462 drawing_evaluate_curve(drawing
, token
->prev
, 0, started
);
471 if (token_check_values(token
, 3, TOKEN_B_SPLINE
) &&
473 drawing_evaluate_curve(drawing
, token
->prev
, 1, started
);
485 drawing_finish(drawing
, raw_mode
);
486 drawing_free_tokens(drawing
->tokens
);
487 return &drawing
->glyph
;