1 | #include "mupdf/fitz.h" |
2 | #include "xps-imp.h" |
3 | #include "../fitz/fitz-imp.h" |
4 | |
5 | #include <ft2build.h> |
6 | #include FT_FREETYPE_H |
7 | #include FT_ADVANCES_H |
8 | |
9 | static inline int ishex(int a) |
10 | { |
11 | return (a >= 'A' && a <= 'F') || |
12 | (a >= 'a' && a <= 'f') || |
13 | (a >= '0' && a <= '9'); |
14 | } |
15 | |
16 | static inline int unhex(int a) |
17 | { |
18 | if (a >= 'A' && a <= 'F') return a - 'A' + 0xA; |
19 | if (a >= 'a' && a <= 'f') return a - 'a' + 0xA; |
20 | if (a >= '0' && a <= '9') return a - '0'; |
21 | return 0; |
22 | } |
23 | |
24 | int |
25 | xps_count_font_encodings(fz_context *ctx, fz_font *font) |
26 | { |
27 | FT_Face face = fz_font_ft_face(ctx, font); |
28 | return face->num_charmaps; |
29 | } |
30 | |
31 | void |
32 | xps_identify_font_encoding(fz_context *ctx, fz_font *font, int idx, int *pid, int *eid) |
33 | { |
34 | FT_Face face = fz_font_ft_face(ctx, font); |
35 | *pid = face->charmaps[idx]->platform_id; |
36 | *eid = face->charmaps[idx]->encoding_id; |
37 | } |
38 | |
39 | void |
40 | xps_select_font_encoding(fz_context *ctx, fz_font *font, int idx) |
41 | { |
42 | FT_Face face = fz_font_ft_face(ctx, font); |
43 | FT_Set_Charmap(face, face->charmaps[idx]); |
44 | } |
45 | |
46 | int |
47 | xps_encode_font_char(fz_context *ctx, fz_font *font, int code) |
48 | { |
49 | FT_Face face = fz_font_ft_face(ctx, font); |
50 | int gid = FT_Get_Char_Index(face, code); |
51 | if (gid == 0 && face->charmap && face->charmap->platform_id == 3 && face->charmap->encoding_id == 0) |
52 | gid = FT_Get_Char_Index(face, 0xF000 | code); |
53 | return gid; |
54 | } |
55 | |
56 | void |
57 | xps_measure_font_glyph(fz_context *ctx, xps_document *doc, fz_font *font, int gid, xps_glyph_metrics *mtx) |
58 | { |
59 | int mask = FT_LOAD_NO_SCALE | FT_LOAD_IGNORE_TRANSFORM; |
60 | FT_Face face = fz_font_ft_face(ctx, font); |
61 | FT_Fixed hadv = 0, vadv = 0; |
62 | |
63 | fz_lock(ctx, FZ_LOCK_FREETYPE); |
64 | FT_Get_Advance(face, gid, mask, &hadv); |
65 | FT_Get_Advance(face, gid, mask | FT_LOAD_VERTICAL_LAYOUT, &vadv); |
66 | fz_unlock(ctx, FZ_LOCK_FREETYPE); |
67 | |
68 | mtx->hadv = (float) hadv / face->units_per_EM; |
69 | mtx->vadv = (float) vadv / face->units_per_EM; |
70 | mtx->vorg = (float) face->ascender / face->units_per_EM; |
71 | } |
72 | |
73 | static fz_font * |
74 | xps_lookup_font_imp(fz_context *ctx, xps_document *doc, char *name) |
75 | { |
76 | xps_font_cache *cache; |
77 | for (cache = doc->font_table; cache; cache = cache->next) |
78 | if (!xps_strcasecmp(cache->name, name)) |
79 | return fz_keep_font(ctx, cache->font); |
80 | return NULL; |
81 | } |
82 | |
83 | static void |
84 | xps_insert_font(fz_context *ctx, xps_document *doc, char *name, fz_font *font) |
85 | { |
86 | xps_font_cache *cache = fz_malloc_struct(ctx, xps_font_cache); |
87 | cache->font = NULL; |
88 | cache->name = NULL; |
89 | |
90 | fz_try(ctx) |
91 | { |
92 | cache->font = fz_keep_font(ctx, font); |
93 | cache->name = fz_strdup(ctx, name); |
94 | cache->next = doc->font_table; |
95 | } |
96 | fz_catch(ctx) |
97 | { |
98 | fz_drop_font(ctx, cache->font); |
99 | fz_free(ctx, cache->name); |
100 | fz_free(ctx, cache); |
101 | fz_rethrow(ctx); |
102 | } |
103 | |
104 | doc->font_table = cache; |
105 | } |
106 | |
107 | /* |
108 | * Some fonts in XPS are obfuscated by XOR:ing the first 32 bytes of the |
109 | * data with the GUID in the fontname. |
110 | */ |
111 | static void |
112 | xps_deobfuscate_font_resource(fz_context *ctx, xps_document *doc, xps_part *part) |
113 | { |
114 | unsigned char buf[33]; |
115 | unsigned char key[16]; |
116 | unsigned char *data; |
117 | size_t size; |
118 | char *p; |
119 | int i; |
120 | |
121 | size = fz_buffer_storage(ctx, part->data, &data); |
122 | if (size < 32) |
123 | { |
124 | fz_warn(ctx, "insufficient data for font deobfuscation" ); |
125 | return; |
126 | } |
127 | |
128 | p = strrchr(part->name, '/'); |
129 | if (!p) |
130 | p = part->name; |
131 | |
132 | for (i = 0; i < 32 && *p; p++) |
133 | { |
134 | if (ishex(*p)) |
135 | buf[i++] = *p; |
136 | } |
137 | buf[i] = 0; |
138 | |
139 | if (i != 32) |
140 | { |
141 | fz_warn(ctx, "cannot extract GUID from obfuscated font part name" ); |
142 | return; |
143 | } |
144 | |
145 | for (i = 0; i < 16; i++) |
146 | key[i] = unhex(buf[i*2+0]) * 16 + unhex(buf[i*2+1]); |
147 | |
148 | for (i = 0; i < 16; i++) |
149 | { |
150 | data[i] ^= key[15-i]; |
151 | data[i+16] ^= key[15-i]; |
152 | } |
153 | } |
154 | |
155 | static void |
156 | xps_select_best_font_encoding(fz_context *ctx, xps_document *doc, fz_font *font) |
157 | { |
158 | static struct { int pid, eid; } xps_cmap_list[] = |
159 | { |
160 | { 3, 10 }, /* Unicode with surrogates */ |
161 | { 3, 1 }, /* Unicode without surrogates */ |
162 | { 3, 5 }, /* Wansung */ |
163 | { 3, 4 }, /* Big5 */ |
164 | { 3, 3 }, /* Prc */ |
165 | { 3, 2 }, /* ShiftJis */ |
166 | { 3, 0 }, /* Symbol */ |
167 | { 1, 0 }, |
168 | { -1, -1 }, |
169 | }; |
170 | |
171 | int i, k, n, pid, eid; |
172 | |
173 | n = xps_count_font_encodings(ctx, font); |
174 | for (k = 0; xps_cmap_list[k].pid != -1; k++) |
175 | { |
176 | for (i = 0; i < n; i++) |
177 | { |
178 | xps_identify_font_encoding(ctx, font, i, &pid, &eid); |
179 | if (pid == xps_cmap_list[k].pid && eid == xps_cmap_list[k].eid) |
180 | { |
181 | xps_select_font_encoding(ctx, font, i); |
182 | return; |
183 | } |
184 | } |
185 | } |
186 | |
187 | fz_warn(ctx, "cannot find a suitable cmap" ); |
188 | } |
189 | |
190 | fz_font * |
191 | xps_lookup_font(fz_context *ctx, xps_document *doc, char *base_uri, char *font_uri, char *style_att) |
192 | { |
193 | char partname[1024]; |
194 | char fakename[1024]; |
195 | char *subfont; |
196 | int subfontid = 0; |
197 | xps_part *part; |
198 | fz_font *font; |
199 | |
200 | xps_resolve_url(ctx, doc, partname, base_uri, font_uri, sizeof partname); |
201 | subfont = strrchr(partname, '#'); |
202 | if (subfont) |
203 | { |
204 | subfontid = atoi(subfont + 1); |
205 | *subfont = 0; |
206 | } |
207 | |
208 | /* Make a new part name for font with style simulation applied */ |
209 | fz_strlcpy(fakename, partname, sizeof fakename); |
210 | if (style_att) |
211 | { |
212 | if (!strcmp(style_att, "BoldSimulation" )) |
213 | fz_strlcat(fakename, "#Bold" , sizeof fakename); |
214 | else if (!strcmp(style_att, "ItalicSimulation" )) |
215 | fz_strlcat(fakename, "#Italic" , sizeof fakename); |
216 | else if (!strcmp(style_att, "BoldItalicSimulation" )) |
217 | fz_strlcat(fakename, "#BoldItalic" , sizeof fakename); |
218 | } |
219 | |
220 | font = xps_lookup_font_imp(ctx, doc, fakename); |
221 | if (!font) |
222 | { |
223 | fz_buffer *buf = NULL; |
224 | fz_var(buf); |
225 | |
226 | fz_try(ctx) |
227 | { |
228 | part = xps_read_part(ctx, doc, partname); |
229 | } |
230 | fz_catch(ctx) |
231 | { |
232 | if (fz_caught(ctx) == FZ_ERROR_TRYLATER) |
233 | { |
234 | if (doc->cookie) |
235 | doc->cookie->incomplete = 1; |
236 | } |
237 | else |
238 | fz_warn(ctx, "cannot find font resource part '%s'" , partname); |
239 | return NULL; |
240 | } |
241 | |
242 | /* deobfuscate if necessary */ |
243 | if (strstr(part->name, ".odttf" )) |
244 | xps_deobfuscate_font_resource(ctx, doc, part); |
245 | if (strstr(part->name, ".ODTTF" )) |
246 | xps_deobfuscate_font_resource(ctx, doc, part); |
247 | |
248 | fz_var(font); |
249 | fz_try(ctx) |
250 | { |
251 | font = fz_new_font_from_buffer(ctx, NULL, part->data, subfontid, 1); |
252 | xps_select_best_font_encoding(ctx, doc, font); |
253 | xps_insert_font(ctx, doc, fakename, font); |
254 | } |
255 | fz_always(ctx) |
256 | { |
257 | xps_drop_part(ctx, doc, part); |
258 | } |
259 | fz_catch(ctx) |
260 | { |
261 | fz_drop_font(ctx, font); |
262 | fz_warn(ctx, "cannot load font resource '%s'" , partname); |
263 | return NULL; |
264 | } |
265 | |
266 | if (style_att) |
267 | { |
268 | fz_font_flags_t *flags = fz_font_flags(font); |
269 | int bold = !!strstr(style_att, "Bold" ); |
270 | int italic = !!strstr(style_att, "Italic" ); |
271 | flags->fake_bold = bold; |
272 | flags->is_bold = bold; |
273 | flags->fake_italic = italic; |
274 | flags->is_italic = italic; |
275 | } |
276 | } |
277 | return font; |
278 | } |
279 | |
280 | /* |
281 | * Parse and draw an XPS <Glyphs> element. |
282 | * |
283 | * Indices syntax: |
284 | |
285 | GlyphIndices = GlyphMapping ( ";" GlyphMapping ) |
286 | GlyphMapping = ( [ClusterMapping] GlyphIndex ) [GlyphMetrics] |
287 | ClusterMapping = "(" ClusterCodeUnitCount [":" ClusterGlyphCount] ")" |
288 | ClusterCodeUnitCount = * DIGIT |
289 | ClusterGlyphCount = * DIGIT |
290 | GlyphIndex = * DIGIT |
291 | GlyphMetrics = "," AdvanceWidth ["," uOffset ["," vOffset]] |
292 | AdvanceWidth = ["+"] RealNum |
293 | uOffset = ["+" | "-"] RealNum |
294 | vOffset = ["+" | "-"] RealNum |
295 | RealNum = ((DIGIT ["." DIGIT]) | ("." DIGIT)) [Exponent] |
296 | Exponent = ( ("E"|"e") ("+"|"-") DIGIT ) |
297 | |
298 | */ |
299 | |
300 | static char * |
301 | xps_parse_digits(char *s, int *digit) |
302 | { |
303 | *digit = 0; |
304 | while (*s >= '0' && *s <= '9') |
305 | { |
306 | *digit = *digit * 10 + (*s - '0'); |
307 | s ++; |
308 | } |
309 | return s; |
310 | } |
311 | |
312 | static char * |
313 | xps_parse_real_num(char *s, float *number, int *override) |
314 | { |
315 | char *tail; |
316 | float v; |
317 | v = fz_strtof(s, &tail); |
318 | *override = tail != s; |
319 | if (*override) |
320 | *number = v; |
321 | return tail; |
322 | } |
323 | |
324 | static char * |
325 | xps_parse_cluster_mapping(char *s, int *code_count, int *glyph_count) |
326 | { |
327 | if (*s == '(') |
328 | s = xps_parse_digits(s + 1, code_count); |
329 | if (*s == ':') |
330 | s = xps_parse_digits(s + 1, glyph_count); |
331 | if (*s == ')') |
332 | s ++; |
333 | return s; |
334 | } |
335 | |
336 | static char * |
337 | xps_parse_glyph_index(char *s, int *glyph_index) |
338 | { |
339 | if (*s >= '0' && *s <= '9') |
340 | s = xps_parse_digits(s, glyph_index); |
341 | return s; |
342 | } |
343 | |
344 | static char * |
345 | xps_parse_glyph_metrics(char *s, float *advance, float *uofs, float *vofs, int bidi_level) |
346 | { |
347 | int override; |
348 | if (*s == ',') |
349 | { |
350 | s = xps_parse_real_num(s + 1, advance, &override); |
351 | if (override && (bidi_level & 1)) |
352 | *advance = -*advance; |
353 | } |
354 | if (*s == ',') |
355 | s = xps_parse_real_num(s + 1, uofs, &override); |
356 | if (*s == ',') |
357 | s = xps_parse_real_num(s + 1, vofs, &override); |
358 | return s; |
359 | } |
360 | |
361 | /* |
362 | * Parse unicode and indices strings and encode glyphs. |
363 | * Calculate metrics for positioning. |
364 | */ |
365 | fz_text * |
366 | xps_parse_glyphs_imp(fz_context *ctx, xps_document *doc, fz_matrix ctm, |
367 | fz_font *font, float size, float originx, float originy, |
368 | int is_sideways, int bidi_level, |
369 | char *indices, char *unicode) |
370 | { |
371 | xps_glyph_metrics mtx; |
372 | fz_text *text; |
373 | fz_matrix tm; |
374 | float x = originx; |
375 | float y = originy; |
376 | char *us = unicode; |
377 | char *is = indices; |
378 | size_t un = 0; |
379 | |
380 | if (!unicode && !indices) |
381 | fz_warn(ctx, "glyphs element with neither characters nor indices" ); |
382 | |
383 | if (us) |
384 | { |
385 | if (us[0] == '{' && us[1] == '}') |
386 | us = us + 2; |
387 | un = strlen(us); |
388 | } |
389 | |
390 | if (is_sideways) |
391 | tm = fz_pre_scale(fz_rotate(90), -size, size); |
392 | else |
393 | tm = fz_scale(size, -size); |
394 | |
395 | text = fz_new_text(ctx); |
396 | |
397 | fz_try(ctx) |
398 | { |
399 | while ((us && un > 0) || (is && *is)) |
400 | { |
401 | int char_code = FZ_REPLACEMENT_CHARACTER; |
402 | int code_count = 1; |
403 | int glyph_count = 1; |
404 | |
405 | if (is && *is) |
406 | { |
407 | is = xps_parse_cluster_mapping(is, &code_count, &glyph_count); |
408 | } |
409 | |
410 | if (code_count < 1) |
411 | code_count = 1; |
412 | if (glyph_count < 1) |
413 | glyph_count = 1; |
414 | |
415 | /* TODO: add code chars with cluster mappings for text extraction */ |
416 | |
417 | while (code_count--) |
418 | { |
419 | if (us && un > 0) |
420 | { |
421 | int t = fz_chartorune(&char_code, us); |
422 | us += t; un -= t; |
423 | } |
424 | } |
425 | |
426 | while (glyph_count--) |
427 | { |
428 | int glyph_index = -1; |
429 | float u_offset = 0; |
430 | float v_offset = 0; |
431 | float advance; |
432 | int dir; |
433 | |
434 | if (is && *is) |
435 | is = xps_parse_glyph_index(is, &glyph_index); |
436 | |
437 | if (glyph_index == -1) |
438 | glyph_index = xps_encode_font_char(ctx, font, char_code); |
439 | |
440 | xps_measure_font_glyph(ctx, doc, font, glyph_index, &mtx); |
441 | if (is_sideways) |
442 | advance = mtx.vadv * 100; |
443 | else if (bidi_level & 1) |
444 | advance = -mtx.hadv * 100; |
445 | else |
446 | advance = mtx.hadv * 100; |
447 | |
448 | if (fz_font_flags(font)->fake_bold) |
449 | advance *= 1.02f; |
450 | |
451 | if (is && *is) |
452 | { |
453 | is = xps_parse_glyph_metrics(is, &advance, &u_offset, &v_offset, bidi_level); |
454 | if (*is == ';') |
455 | is ++; |
456 | } |
457 | |
458 | if (bidi_level & 1) |
459 | u_offset = -mtx.hadv * 100 - u_offset; |
460 | |
461 | u_offset = u_offset * 0.01f * size; |
462 | v_offset = v_offset * 0.01f * size; |
463 | |
464 | if (is_sideways) |
465 | { |
466 | tm.e = x + u_offset + (mtx.vorg * size); |
467 | tm.f = y - v_offset + (mtx.hadv * 0.5f * size); |
468 | } |
469 | else |
470 | { |
471 | tm.e = x + u_offset; |
472 | tm.f = y - v_offset; |
473 | } |
474 | |
475 | dir = bidi_level & 1 ? FZ_BIDI_RTL : FZ_BIDI_LTR; |
476 | fz_show_glyph(ctx, text, font, tm, glyph_index, char_code, is_sideways, bidi_level, dir, FZ_LANG_UNSET); |
477 | |
478 | x += advance * 0.01f * size; |
479 | } |
480 | } |
481 | } |
482 | fz_catch(ctx) |
483 | { |
484 | fz_drop_text(ctx, text); |
485 | fz_rethrow(ctx); |
486 | } |
487 | |
488 | return text; |
489 | } |
490 | |
491 | void |
492 | xps_parse_glyphs(fz_context *ctx, xps_document *doc, fz_matrix ctm, |
493 | char *base_uri, xps_resource *dict, fz_xml *root) |
494 | { |
495 | fz_device *dev = doc->dev; |
496 | |
497 | fz_xml *node; |
498 | |
499 | char *fill_uri; |
500 | char *opacity_mask_uri; |
501 | |
502 | char *bidi_level_att; |
503 | char *fill_att; |
504 | char *font_size_att; |
505 | char *font_uri_att; |
506 | char *origin_x_att; |
507 | char *origin_y_att; |
508 | char *is_sideways_att; |
509 | char *indices_att; |
510 | char *unicode_att; |
511 | char *style_att; |
512 | char *transform_att; |
513 | char *clip_att; |
514 | char *opacity_att; |
515 | char *opacity_mask_att; |
516 | |
517 | fz_xml *transform_tag = NULL; |
518 | fz_xml *clip_tag = NULL; |
519 | fz_xml *fill_tag = NULL; |
520 | fz_xml *opacity_mask_tag = NULL; |
521 | |
522 | char *fill_opacity_att = NULL; |
523 | |
524 | fz_font *font; |
525 | |
526 | float font_size = 10; |
527 | int is_sideways = 0; |
528 | int bidi_level = 0; |
529 | |
530 | fz_text *text; |
531 | fz_rect area; |
532 | |
533 | /* |
534 | * Extract attributes and extended attributes. |
535 | */ |
536 | |
537 | bidi_level_att = fz_xml_att(root, "BidiLevel" ); |
538 | fill_att = fz_xml_att(root, "Fill" ); |
539 | font_size_att = fz_xml_att(root, "FontRenderingEmSize" ); |
540 | font_uri_att = fz_xml_att(root, "FontUri" ); |
541 | origin_x_att = fz_xml_att(root, "OriginX" ); |
542 | origin_y_att = fz_xml_att(root, "OriginY" ); |
543 | is_sideways_att = fz_xml_att(root, "IsSideways" ); |
544 | indices_att = fz_xml_att(root, "Indices" ); |
545 | unicode_att = fz_xml_att(root, "UnicodeString" ); |
546 | style_att = fz_xml_att(root, "StyleSimulations" ); |
547 | transform_att = fz_xml_att(root, "RenderTransform" ); |
548 | clip_att = fz_xml_att(root, "Clip" ); |
549 | opacity_att = fz_xml_att(root, "Opacity" ); |
550 | opacity_mask_att = fz_xml_att(root, "OpacityMask" ); |
551 | |
552 | for (node = fz_xml_down(root); node; node = fz_xml_next(node)) |
553 | { |
554 | if (fz_xml_is_tag(node, "Glyphs.RenderTransform" )) |
555 | transform_tag = fz_xml_down(node); |
556 | if (fz_xml_is_tag(node, "Glyphs.OpacityMask" )) |
557 | opacity_mask_tag = fz_xml_down(node); |
558 | if (fz_xml_is_tag(node, "Glyphs.Clip" )) |
559 | clip_tag = fz_xml_down(node); |
560 | if (fz_xml_is_tag(node, "Glyphs.Fill" )) |
561 | fill_tag = fz_xml_down(node); |
562 | } |
563 | |
564 | fill_uri = base_uri; |
565 | opacity_mask_uri = base_uri; |
566 | |
567 | xps_resolve_resource_reference(ctx, doc, dict, &transform_att, &transform_tag, NULL); |
568 | xps_resolve_resource_reference(ctx, doc, dict, &clip_att, &clip_tag, NULL); |
569 | xps_resolve_resource_reference(ctx, doc, dict, &fill_att, &fill_tag, &fill_uri); |
570 | xps_resolve_resource_reference(ctx, doc, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri); |
571 | |
572 | /* |
573 | * Check that we have all the necessary information. |
574 | */ |
575 | |
576 | if (!font_size_att || !font_uri_att || !origin_x_att || !origin_y_att) { |
577 | fz_warn(ctx, "missing attributes in glyphs element" ); |
578 | return; |
579 | } |
580 | |
581 | if (!indices_att && !unicode_att) |
582 | return; /* nothing to draw */ |
583 | |
584 | if (is_sideways_att) |
585 | is_sideways = !strcmp(is_sideways_att, "true" ); |
586 | |
587 | if (bidi_level_att) |
588 | bidi_level = atoi(bidi_level_att); |
589 | |
590 | /* |
591 | * Find and load the font resource. |
592 | */ |
593 | |
594 | font = xps_lookup_font(ctx, doc, base_uri, font_uri_att, style_att); |
595 | if (!font) |
596 | font = fz_new_base14_font(ctx, "Times-Roman" ); |
597 | |
598 | fz_try(ctx) |
599 | { |
600 | /* |
601 | * Set up graphics state. |
602 | */ |
603 | |
604 | ctm = xps_parse_transform(ctx, doc, transform_att, transform_tag, ctm); |
605 | |
606 | if (clip_att || clip_tag) |
607 | xps_clip(ctx, doc, ctm, dict, clip_att, clip_tag); |
608 | |
609 | font_size = fz_atof(font_size_att); |
610 | |
611 | text = xps_parse_glyphs_imp(ctx, doc, ctm, font, font_size, |
612 | fz_atof(origin_x_att), fz_atof(origin_y_att), |
613 | is_sideways, bidi_level, indices_att, unicode_att); |
614 | |
615 | area = fz_bound_text(ctx, text, NULL, ctm); |
616 | |
617 | xps_begin_opacity(ctx, doc, ctm, area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); |
618 | |
619 | /* If it's a solid color brush fill/stroke do a simple fill */ |
620 | |
621 | if (fz_xml_is_tag(fill_tag, "SolidColorBrush" )) |
622 | { |
623 | fill_opacity_att = fz_xml_att(fill_tag, "Opacity" ); |
624 | fill_att = fz_xml_att(fill_tag, "Color" ); |
625 | fill_tag = NULL; |
626 | } |
627 | |
628 | if (fill_att) |
629 | { |
630 | float samples[FZ_MAX_COLORS]; |
631 | fz_colorspace *colorspace; |
632 | |
633 | xps_parse_color(ctx, doc, base_uri, fill_att, &colorspace, samples); |
634 | if (fill_opacity_att) |
635 | samples[0] *= fz_atof(fill_opacity_att); |
636 | xps_set_color(ctx, doc, colorspace, samples); |
637 | |
638 | fz_fill_text(ctx, dev, text, ctm, doc->colorspace, doc->color, doc->alpha, fz_default_color_params); |
639 | } |
640 | |
641 | /* If it's a complex brush, use the charpath as a clip mask */ |
642 | |
643 | if (fill_tag) |
644 | { |
645 | fz_clip_text(ctx, dev, text, ctm, area); |
646 | xps_parse_brush(ctx, doc, ctm, area, fill_uri, dict, fill_tag); |
647 | fz_pop_clip(ctx, dev); |
648 | } |
649 | |
650 | xps_end_opacity(ctx, doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); |
651 | |
652 | if (clip_att || clip_tag) |
653 | fz_pop_clip(ctx, dev); |
654 | } |
655 | fz_always(ctx) |
656 | { |
657 | fz_drop_text(ctx, text); |
658 | fz_drop_font(ctx, font); |
659 | } |
660 | fz_catch(ctx) |
661 | fz_rethrow(ctx); |
662 | } |
663 | |