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
9static inline int ishex(int a)
10{
11 return (a >= 'A' && a <= 'F') ||
12 (a >= 'a' && a <= 'f') ||
13 (a >= '0' && a <= '9');
14}
15
16static 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
24int
25xps_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
31void
32xps_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
39void
40xps_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
46int
47xps_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
56void
57xps_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
73static fz_font *
74xps_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
83static void
84xps_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 */
111static void
112xps_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
155static void
156xps_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
190fz_font *
191xps_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
300static char *
301xps_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
312static char *
313xps_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
324static char *
325xps_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
336static char *
337xps_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
344static char *
345xps_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 */
365fz_text *
366xps_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
491void
492xps_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