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
)
116 FT_Outline
*ol
= &drawing
->glyph
->outline
;
118 // Close the last contour
119 drawing_close_shape(drawing
);
123 for (i
= 0; i
< ol
->n_points
; i
++) {
124 printf("point (%d, %d)\n", (int) ol
->points
[i
].x
,
125 (int) ol
->points
[i
].y
);
129 for (i
= 0; i
< ol
->n_contours
; i
++)
130 printf("contour %d\n", ol
->contours
[i
]);
133 ass_msg(drawing
->library
, MSGL_V
,
134 "Parsed drawing with %d points and %d contours", ol
->n_points
,
140 FT_Outline_Get_CBox(&drawing
->glyph
->outline
, &bbox
);
141 drawing
->glyph
->root
.advance
.x
= d6_to_d16(bbox
.xMax
- bbox
.xMin
);
143 drawing
->desc
= double_to_d6(-drawing
->pbo
* drawing
->scale_y
);
144 drawing
->asc
= bbox
.yMax
- bbox
.yMin
+ drawing
->desc
;
146 // Place it onto the baseline
147 offset
= (bbox
.yMax
- bbox
.yMin
) + double_to_d6(-drawing
->pbo
*
149 for (i
= 0; i
< ol
->n_points
; i
++)
150 ol
->points
[i
].y
+= offset
;
154 * \brief Check whether a number of items on the list is available
156 static int token_check_values(ASS_DrawingToken
*token
, int i
, int type
)
159 for (j
= 0; j
< i
; j
++) {
160 if (!token
|| token
->type
!= type
) return 0;
168 * \brief Tokenize a drawing string into a list of ASS_DrawingToken
169 * This also expands points for closing b-splines
171 static ASS_DrawingToken
*drawing_tokenize(char *str
)
174 int i
, val
, type
= -1, is_set
= 0;
175 FT_Vector point
= {0, 0};
177 ASS_DrawingToken
*root
= NULL
, *tail
= NULL
, *spline_start
= NULL
;
180 if (*p
== 'c' && spline_start
) {
181 // Close b-splines: add the first three points of the b-spline
183 if (token_check_values(spline_start
->next
, 2, TOKEN_B_SPLINE
)) {
184 for (i
= 0; i
< 3; i
++) {
185 tail
->next
= calloc(1, sizeof(ASS_DrawingToken
));
186 tail
->next
->prev
= tail
;
188 tail
->type
= TOKEN_B_SPLINE
;
189 tail
->point
= spline_start
->point
;
190 spline_start
= spline_start
->next
;
194 } else if (!is_set
&& mystrtoi(&p
, &val
)) {
198 } else if (is_set
== 1 && mystrtoi(&p
, &val
)) {
202 } else if (*p
== 'm')
205 type
= TOKEN_MOVE_NC
;
209 type
= TOKEN_CUBIC_BEZIER
;
211 type
= TOKEN_CONIC_BEZIER
;
213 type
= TOKEN_B_SPLINE
;
214 // We're simply ignoring TOKEN_EXTEND_B_SPLINE here.
215 // This is not harmful at all, since it can be ommitted with
216 // similar result (the spline is extended anyway).
218 if (type
!= -1 && is_set
== 2) {
220 tail
->next
= calloc(1, sizeof(ASS_DrawingToken
));
221 tail
->next
->prev
= tail
;
224 root
= tail
= calloc(1, sizeof(ASS_DrawingToken
));
228 if (type
== TOKEN_B_SPLINE
&& !spline_start
)
229 spline_start
= tail
->prev
;
236 ASS_DrawingToken
*t
= root
;
238 printf("token %d point (%d, %d)\n", t
->type
, t
->point
.x
, t
->point
.y
);
247 * \brief Free a list of tokens
249 static void drawing_free_tokens(ASS_DrawingToken
*token
)
252 ASS_DrawingToken
*at
= token
;
259 * \brief Translate and scale a point coordinate according to baseline
262 static inline void translate_point(ASS_Drawing
*drawing
, FT_Vector
*point
)
264 point
->x
= drawing
->point_scale_x
* point
->x
;
265 point
->y
= drawing
->point_scale_y
* -point
->y
;
269 * \brief Evaluate a curve into lines
270 * This curve evaluator is also used in VSFilter (RTS.cpp); it's a simple
271 * implementation of the De Casteljau algorithm.
273 static void drawing_evaluate_curve(ASS_Drawing
*drawing
,
274 ASS_DrawingToken
*token
, char spline
,
277 double cx3
, cx2
, cx1
, cx0
, cy3
, cy2
, cy1
, cy0
;
278 double t
, h
, max_accel
, max_accel1
, max_accel2
;
279 FT_Vector cur
= {0, 0};
282 translate_point(drawing
, &cur
);
287 translate_point(drawing
, &cur
);
292 translate_point(drawing
, &cur
);
297 translate_point(drawing
, &cur
);
307 double div6
= 1.0/6.0;
309 cx3
= div6
*(- x0
+3*x1
-3*x2
+x3
);
310 cx2
= div6
*( 3*x0
-6*x1
+3*x2
);
311 cx1
= div6
*(-3*x0
+3*x2
);
312 cx0
= div6
*( x0
+4*x1
+1*x2
);
314 cy3
= div6
*(- y0
+3*y1
-3*y2
+y3
);
315 cy2
= div6
*( 3*y0
-6*y1
+3*y2
);
316 cy1
= div6
*(-3*y0
+3*y2
);
317 cy0
= div6
*( y0
+4*y1
+1*y2
);
324 cx3
= - x0
+3*x1
-3*x2
+x3
;
325 cx2
= 3*x0
-6*x1
+3*x2
;
329 cy3
= - y0
+3*y1
-3*y2
+y3
;
330 cy2
= 3*y0
-6*y1
+3*y2
;
335 max_accel1
= fabs(2 * cy2
) + fabs(6 * cy3
);
336 max_accel2
= fabs(2 * cx2
) + fabs(6 * cx3
);
338 max_accel
= FFMAX(max_accel1
, max_accel2
);
341 if (max_accel
> CURVE_ACCURACY
)
342 h
= sqrt(CURVE_ACCURACY
/ max_accel
);
347 drawing_add_point(drawing
, &cur
);
350 for (t
= 0; t
< 1.0; t
+= h
) {
351 cur
.x
= cx0
+ t
* (cx1
+ t
* (cx2
+ t
* cx3
));
352 cur
.y
= cy0
+ t
* (cy1
+ t
* (cy2
+ t
* cy3
));
353 drawing_add_point(drawing
, &cur
);
356 cur
.x
= cx0
+ cx1
+ cx2
+ cx3
;
357 cur
.y
= cy0
+ cy1
+ cy2
+ cy3
;
358 drawing_add_point(drawing
, &cur
);
362 * \brief Create and initialize a new drawing and return it
364 ASS_Drawing
*ass_drawing_new(void *fontconfig_priv
, ASS_Font
*font
,
365 ASS_Hinting hint
, FT_Library lib
)
367 ASS_Drawing
*drawing
;
369 drawing
= calloc(1, sizeof(*drawing
));
370 drawing
->text
= calloc(1, DRAWING_INITIAL_SIZE
);
371 drawing
->size
= DRAWING_INITIAL_SIZE
;
373 drawing
->ftlibrary
= lib
;
375 drawing
->library
= font
->library
;
376 drawing_make_glyph(drawing
, fontconfig_priv
, font
, hint
);
379 drawing
->scale_x
= 1.;
380 drawing
->scale_y
= 1.;
381 drawing
->max_contours
= GLYPH_INITIAL_CONTOURS
;
382 drawing
->max_points
= GLYPH_INITIAL_POINTS
;
388 * \brief Free a drawing
390 void ass_drawing_free(ASS_Drawing
* drawing
)
394 FT_Done_Glyph((FT_Glyph
) drawing
->glyph
);
401 * \brief Add one ASCII character to the drawing text buffer
403 void ass_drawing_add_char(ASS_Drawing
* drawing
, char symbol
)
405 drawing
->text
[drawing
->i
++] = symbol
;
406 drawing
->text
[drawing
->i
] = 0;
408 if (drawing
->i
+ 1 >= drawing
->size
) {
410 drawing
->text
= realloc(drawing
->text
, drawing
->size
);
415 * \brief Create a hashcode for the drawing
416 * XXX: To avoid collisions a better hash algorithm might be useful.
418 void ass_drawing_hash(ASS_Drawing
* drawing
)
420 drawing
->hash
= fnv_32a_str(drawing
->text
, FNV1_32A_INIT
);
424 * \brief Convert token list to outline. Calls the line and curve evaluators.
426 FT_OutlineGlyph
*ass_drawing_parse(ASS_Drawing
*drawing
, int raw_mode
)
429 ASS_DrawingToken
*token
;
430 FT_Vector pen
= {0, 0};
435 drawing
->tokens
= drawing_tokenize(drawing
->text
);
436 drawing_prepare(drawing
);
438 token
= drawing
->tokens
;
440 // Draw something according to current command
441 switch (token
->type
) {
444 translate_point(drawing
, &pen
);
449 translate_point(drawing
, &pen
);
451 drawing_close_shape(drawing
);
459 translate_point(drawing
, &to
);
460 if (!started
) drawing_add_point(drawing
, &pen
);
461 drawing_add_point(drawing
, &to
);
466 case TOKEN_CUBIC_BEZIER
:
467 if (token_check_values(token
, 3, TOKEN_CUBIC_BEZIER
) &&
469 drawing_evaluate_curve(drawing
, token
->prev
, 0, started
);
478 if (token_check_values(token
, 3, TOKEN_B_SPLINE
) &&
480 drawing_evaluate_curve(drawing
, token
->prev
, 1, started
);
492 drawing_finish(drawing
, raw_mode
);
493 drawing_free_tokens(drawing
->tokens
);
494 return &drawing
->glyph
;