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