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;
52 drawing
->glyph
= glyph
;
56 * \brief Add a single point to a contour.
58 static inline void drawing_add_point(ASS_Drawing
*drawing
,
61 FT_Outline
*ol
= &drawing
->glyph
->outline
;
63 if (ol
->n_points
>= drawing
->max_points
) {
64 drawing
->max_points
*= 2;
65 ol
->points
= realloc(ol
->points
, sizeof(FT_Vector
) *
67 ol
->tags
= realloc(ol
->tags
, drawing
->max_points
);
70 ol
->points
[ol
->n_points
].x
= point
->x
;
71 ol
->points
[ol
->n_points
].y
= point
->y
;
72 ol
->tags
[ol
->n_points
] = 1;
77 * \brief Close a contour and check glyph size overflow.
79 static inline void drawing_close_shape(ASS_Drawing
*drawing
)
81 FT_Outline
*ol
= &drawing
->glyph
->outline
;
83 if (ol
->n_contours
>= drawing
->max_contours
) {
84 drawing
->max_contours
*= 2;
85 ol
->contours
= realloc(ol
->contours
, sizeof(short) *
86 drawing
->max_contours
);
90 ol
->contours
[ol
->n_contours
] = ol
->n_points
- 1;
96 * \brief Prepare drawing for parsing. This just sets a few parameters.
98 static void drawing_prepare(ASS_Drawing
*drawing
)
100 // Scaling parameters
101 drawing
->point_scale_x
= drawing
->scale_x
*
102 64.0 / (1 << (drawing
->scale
- 1));
103 drawing
->point_scale_y
= drawing
->scale_y
*
104 64.0 / (1 << (drawing
->scale
- 1));
108 * \brief Finish a drawing. This only sets the horizontal advance according
109 * to the glyph's bbox at the moment.
111 static void drawing_finish(ASS_Drawing
*drawing
, int raw_mode
)
115 FT_Outline
*ol
= &drawing
->glyph
->outline
;
117 // Close the last contour
118 drawing_close_shape(drawing
);
122 for (i
= 0; i
< ol
->n_points
; i
++) {
123 printf("point (%d, %d)\n", (int) ol
->points
[i
].x
,
124 (int) ol
->points
[i
].y
);
128 for (i
= 0; i
< ol
->n_contours
; i
++)
129 printf("contour %d\n", ol
->contours
[i
]);
132 ass_msg(drawing
->library
, MSGL_V
,
133 "Parsed drawing with %d points and %d contours", ol
->n_points
,
139 FT_Outline_Get_CBox(&drawing
->glyph
->outline
, &bbox
);
140 drawing
->glyph
->root
.advance
.x
= d6_to_d16(bbox
.xMax
- bbox
.xMin
);
142 drawing
->desc
= double_to_d6(-drawing
->pbo
* drawing
->scale_y
);
143 drawing
->asc
= bbox
.yMax
- bbox
.yMin
+ drawing
->desc
;
145 // Place it onto the baseline
146 offset
= (bbox
.yMax
- bbox
.yMin
) + double_to_d6(-drawing
->pbo
*
148 for (i
= 0; i
< ol
->n_points
; i
++)
149 ol
->points
[i
].y
+= offset
;
153 * \brief Check whether a number of items on the list is available
155 static int token_check_values(ASS_DrawingToken
*token
, int i
, int type
)
158 for (j
= 0; j
< i
; j
++) {
159 if (!token
|| token
->type
!= type
) return 0;
167 * \brief Tokenize a drawing string into a list of ASS_DrawingToken
168 * This also expands points for closing b-splines
170 static ASS_DrawingToken
*drawing_tokenize(char *str
)
173 int i
, val
, type
= -1, is_set
= 0;
174 FT_Vector point
= {0, 0};
176 ASS_DrawingToken
*root
= NULL
, *tail
= NULL
, *spline_start
= NULL
;
179 if (*p
== 'c' && spline_start
) {
180 // Close b-splines: add the first three points of the b-spline
182 if (token_check_values(spline_start
->next
, 2, TOKEN_B_SPLINE
)) {
183 for (i
= 0; i
< 3; i
++) {
184 tail
->next
= calloc(1, sizeof(ASS_DrawingToken
));
185 tail
->next
->prev
= tail
;
187 tail
->type
= TOKEN_B_SPLINE
;
188 tail
->point
= spline_start
->point
;
189 spline_start
= spline_start
->next
;
193 } else if (!is_set
&& mystrtoi(&p
, &val
)) {
197 } else if (is_set
== 1 && mystrtoi(&p
, &val
)) {
201 } else if (*p
== 'm')
204 type
= TOKEN_MOVE_NC
;
208 type
= TOKEN_CUBIC_BEZIER
;
210 type
= TOKEN_CONIC_BEZIER
;
212 type
= TOKEN_B_SPLINE
;
213 // We're simply ignoring TOKEN_EXTEND_B_SPLINE here.
214 // This is not harmful at all, since it can be ommitted with
215 // similar result (the spline is extended anyway).
217 if (type
!= -1 && is_set
== 2) {
219 tail
->next
= calloc(1, sizeof(ASS_DrawingToken
));
220 tail
->next
->prev
= tail
;
223 root
= tail
= calloc(1, sizeof(ASS_DrawingToken
));
227 if (type
== TOKEN_B_SPLINE
&& !spline_start
)
228 spline_start
= tail
->prev
;
235 ASS_DrawingToken
*t
= root
;
237 printf("token %d point (%d, %d)\n", t
->type
, t
->point
.x
, t
->point
.y
);
246 * \brief Free a list of tokens
248 static void drawing_free_tokens(ASS_DrawingToken
*token
)
251 ASS_DrawingToken
*at
= token
;
258 * \brief Translate and scale a point coordinate according to baseline
261 static inline void translate_point(ASS_Drawing
*drawing
, FT_Vector
*point
)
263 point
->x
= drawing
->point_scale_x
* point
->x
;
264 point
->y
= drawing
->point_scale_y
* -point
->y
;
268 * \brief Evaluate a curve into lines
269 * This curve evaluator is also used in VSFilter (RTS.cpp); it's a simple
270 * implementation of the De Casteljau algorithm.
272 static void drawing_evaluate_curve(ASS_Drawing
*drawing
,
273 ASS_DrawingToken
*token
, char spline
,
276 double cx3
, cx2
, cx1
, cx0
, cy3
, cy2
, cy1
, cy0
;
277 double t
, h
, max_accel
, max_accel1
, max_accel2
;
278 FT_Vector cur
= {0, 0};
281 translate_point(drawing
, &cur
);
286 translate_point(drawing
, &cur
);
291 translate_point(drawing
, &cur
);
296 translate_point(drawing
, &cur
);
306 double div6
= 1.0/6.0;
308 cx3
= div6
*(- x0
+3*x1
-3*x2
+x3
);
309 cx2
= div6
*( 3*x0
-6*x1
+3*x2
);
310 cx1
= div6
*(-3*x0
+3*x2
);
311 cx0
= div6
*( x0
+4*x1
+1*x2
);
313 cy3
= div6
*(- y0
+3*y1
-3*y2
+y3
);
314 cy2
= div6
*( 3*y0
-6*y1
+3*y2
);
315 cy1
= div6
*(-3*y0
+3*y2
);
316 cy0
= div6
*( y0
+4*y1
+1*y2
);
323 cx3
= - x0
+3*x1
-3*x2
+x3
;
324 cx2
= 3*x0
-6*x1
+3*x2
;
328 cy3
= - y0
+3*y1
-3*y2
+y3
;
329 cy2
= 3*y0
-6*y1
+3*y2
;
334 max_accel1
= fabs(2 * cy2
) + fabs(6 * cy3
);
335 max_accel2
= fabs(2 * cx2
) + fabs(6 * cx3
);
337 max_accel
= FFMAX(max_accel1
, max_accel2
);
340 if (max_accel
> CURVE_ACCURACY
)
341 h
= sqrt(CURVE_ACCURACY
/ max_accel
);
346 drawing_add_point(drawing
, &cur
);
349 for (t
= 0; t
< 1.0; t
+= h
) {
350 cur
.x
= cx0
+ t
* (cx1
+ t
* (cx2
+ t
* cx3
));
351 cur
.y
= cy0
+ t
* (cy1
+ t
* (cy2
+ t
* cy3
));
352 drawing_add_point(drawing
, &cur
);
355 cur
.x
= cx0
+ cx1
+ cx2
+ cx3
;
356 cur
.y
= cy0
+ cy1
+ cy2
+ cy3
;
357 drawing_add_point(drawing
, &cur
);
361 * \brief Create and initialize a new drawing and return it
363 ASS_Drawing
*ass_drawing_new(void *fontconfig_priv
, ASS_Font
*font
,
364 ASS_Hinting hint
, FT_Library lib
)
366 ASS_Drawing
* drawing
;
368 drawing
= calloc(1, sizeof(*drawing
));
369 drawing
->text
= calloc(1, DRAWING_INITIAL_SIZE
);
370 drawing
->size
= DRAWING_INITIAL_SIZE
;
372 drawing
->ftlibrary
= lib
;
373 drawing
->library
= font
->library
;
374 drawing_make_glyph(drawing
, fontconfig_priv
, font
, hint
);
376 drawing
->scale_x
= 1.;
377 drawing
->scale_y
= 1.;
378 drawing
->max_contours
= GLYPH_INITIAL_CONTOURS
;
379 drawing
->max_points
= GLYPH_INITIAL_POINTS
;
385 * \brief Free a drawing
387 void ass_drawing_free(ASS_Drawing
* drawing
)
389 FT_Done_Glyph((FT_Glyph
) drawing
->glyph
);
395 * \brief Add one ASCII character to the drawing text buffer
397 void ass_drawing_add_char(ASS_Drawing
* drawing
, char symbol
)
399 drawing
->text
[drawing
->i
++] = symbol
;
400 drawing
->text
[drawing
->i
] = 0;
402 if (drawing
->i
+ 1 >= drawing
->size
) {
404 drawing
->text
= realloc(drawing
->text
, drawing
->size
);
409 * \brief Create a hashcode for the drawing
410 * XXX: To avoid collisions a better hash algorithm might be useful.
412 void ass_drawing_hash(ASS_Drawing
* drawing
)
414 drawing
->hash
= fnv_32a_str(drawing
->text
, FNV1_32A_INIT
);
418 * \brief Convert token list to outline. Calls the line and curve evaluators.
420 FT_OutlineGlyph
*ass_drawing_parse(ASS_Drawing
*drawing
, int raw_mode
)
423 ASS_DrawingToken
*token
;
424 FT_Vector pen
= {0, 0};
426 drawing
->tokens
= drawing_tokenize(drawing
->text
);
427 drawing_prepare(drawing
);
429 token
= drawing
->tokens
;
431 // Draw something according to current command
432 switch (token
->type
) {
435 translate_point(drawing
, &pen
);
440 translate_point(drawing
, &pen
);
442 drawing_close_shape(drawing
);
450 translate_point(drawing
, &to
);
451 if (!started
) drawing_add_point(drawing
, &pen
);
452 drawing_add_point(drawing
, &to
);
457 case TOKEN_CUBIC_BEZIER
:
458 if (token_check_values(token
, 3, TOKEN_CUBIC_BEZIER
) &&
460 drawing_evaluate_curve(drawing
, token
->prev
, 0, started
);
469 if (token_check_values(token
, 3, TOKEN_B_SPLINE
) &&
471 drawing_evaluate_curve(drawing
, token
->prev
, 1, started
);
483 drawing_finish(drawing
, raw_mode
);
484 drawing_free_tokens(drawing
->tokens
);
485 return &drawing
->glyph
;