/src/ghostpdl/xps/xpsglyphs.c
Line | Count | Source |
1 | | /* Copyright (C) 2001-2026 Artifex Software, Inc. |
2 | | All Rights Reserved. |
3 | | |
4 | | This software is provided AS-IS with no warranty, either express or |
5 | | implied. |
6 | | |
7 | | This software is distributed under license and may not be copied, |
8 | | modified or distributed except as expressly authorized under the terms |
9 | | of the license contained in the file LICENSE in this distribution. |
10 | | |
11 | | Refer to licensing information at http://www.artifex.com or contact |
12 | | Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
13 | | CA 94129, USA, for further information. |
14 | | */ |
15 | | |
16 | | |
17 | | /* XPS interpreter - text drawing support */ |
18 | | |
19 | | #include "ghostxps.h" |
20 | | #include <stdlib.h> |
21 | | |
22 | 66 | #define XPS_TEXT_BUFFER_SIZE 300 |
23 | | |
24 | | typedef struct xps_text_buffer_s xps_text_buffer_t; |
25 | | |
26 | | struct xps_text_buffer_s |
27 | | { |
28 | | int count; |
29 | | float x[XPS_TEXT_BUFFER_SIZE + 1]; |
30 | | float y[XPS_TEXT_BUFFER_SIZE + 1]; |
31 | | gs_glyph g[XPS_TEXT_BUFFER_SIZE]; |
32 | | }; |
33 | | |
34 | | static inline int unhex(int c) |
35 | 64 | { |
36 | 64 | if (c >= '0' && c <= '9') |
37 | 34 | return c - '0'; |
38 | 30 | if (c >= 'A' && c <= 'Z') |
39 | 30 | return c - 'A' + 10; |
40 | 0 | return c - 'a' + 10; |
41 | 30 | } |
42 | | |
43 | | void |
44 | | xps_debug_path(xps_context_t *ctx) |
45 | 0 | { |
46 | 0 | segment *seg; |
47 | 0 | curve_segment *cseg; |
48 | |
|
49 | 0 | seg = (segment*)ctx->pgs->path->first_subpath; |
50 | 0 | while (seg) |
51 | 0 | { |
52 | 0 | switch (seg->type) |
53 | 0 | { |
54 | 0 | case s_start: |
55 | 0 | dmprintf2(ctx->memory, "%g %g moveto\n", |
56 | 0 | fixed2float(seg->pt.x) * 0.001, |
57 | 0 | fixed2float(seg->pt.y) * 0.001); |
58 | 0 | break; |
59 | 0 | case s_line: |
60 | 0 | dmprintf2(ctx->memory, "%g %g lineto\n", |
61 | 0 | fixed2float(seg->pt.x) * 0.001, |
62 | 0 | fixed2float(seg->pt.y) * 0.001); |
63 | 0 | break; |
64 | 0 | case s_line_close: |
65 | 0 | dmputs(ctx->memory, "closepath\n"); |
66 | 0 | break; |
67 | 0 | case s_curve: |
68 | 0 | cseg = (curve_segment*)seg; |
69 | 0 | dmprintf6(ctx->memory, "%g %g %g %g %g %g curveto\n", |
70 | 0 | fixed2float(cseg->p1.x) * 0.001, |
71 | 0 | fixed2float(cseg->p1.y) * 0.001, |
72 | 0 | fixed2float(cseg->p2.x) * 0.001, |
73 | 0 | fixed2float(cseg->p2.y) * 0.001, |
74 | 0 | fixed2float(seg->pt.x) * 0.001, |
75 | 0 | fixed2float(seg->pt.y) * 0.001); |
76 | 0 | break; |
77 | 0 | } |
78 | 0 | seg = seg->next; |
79 | 0 | } |
80 | 0 | } |
81 | | |
82 | | /* |
83 | | * Some fonts in XPS are obfuscated by XOR:ing the first 32 bytes of the |
84 | | * data with the GUID in the fontname. |
85 | | */ |
86 | | static void |
87 | | xps_deobfuscate_font_resource(xps_context_t *ctx, xps_part_t *part) |
88 | 2 | { |
89 | 2 | byte buf[33]; |
90 | 2 | byte key[16]; |
91 | 2 | char *p; |
92 | 2 | int i; |
93 | | |
94 | | /* Ensure the part has at least 32 bytes we can write */ |
95 | 2 | if (part->size < 32) |
96 | 0 | { |
97 | 0 | gs_warn("obfuscated font part is too small"); |
98 | 0 | return; |
99 | 0 | } |
100 | | |
101 | 2 | p = strrchr(part->name, '/'); |
102 | 2 | if (!p) |
103 | 0 | p = part->name; |
104 | | |
105 | 76 | for (i = 0; i < 32 && *p; p++) |
106 | 74 | { |
107 | 74 | if (isxdigit(*p)) |
108 | 64 | buf[i++] = *p; |
109 | 74 | } |
110 | 2 | buf[i] = 0; |
111 | | |
112 | 2 | if (i != 32) |
113 | 0 | { |
114 | 0 | gs_warn("cannot extract GUID from obfuscated font part name"); |
115 | 0 | return; |
116 | 0 | } |
117 | | |
118 | 34 | for (i = 0; i < 16; i++) |
119 | 32 | key[i] = unhex(buf[i*2+0]) * 16 + unhex(buf[i*2+1]); |
120 | | |
121 | 34 | for (i = 0; i < 16; i++) |
122 | 32 | { |
123 | 32 | part->data[i] ^= key[15-i]; |
124 | 32 | part->data[i+16] ^= key[15-i]; |
125 | 32 | } |
126 | 2 | } |
127 | | |
128 | | static void |
129 | | xps_select_best_font_encoding(xps_font_t *font) |
130 | 2 | { |
131 | 2 | static struct { int pid, eid; } xps_cmap_list[] = |
132 | 2 | { |
133 | 2 | { 3, 10 }, /* Unicode with surrogates */ |
134 | 2 | { 3, 1 }, /* Unicode without surrogates */ |
135 | 2 | { 3, 5 }, /* Wansung */ |
136 | 2 | { 3, 4 }, /* Big5 */ |
137 | 2 | { 3, 3 }, /* Prc */ |
138 | 2 | { 3, 2 }, /* ShiftJis */ |
139 | 2 | { 3, 0 }, /* Symbol */ |
140 | 2 | { 1, 0 }, |
141 | 2 | { -1, -1 }, |
142 | 2 | }; |
143 | | |
144 | 2 | int i, k, n, pid, eid; |
145 | | |
146 | 2 | n = xps_count_font_encodings(font); |
147 | 14 | for (k = 0; xps_cmap_list[k].pid != -1; k++) |
148 | 14 | { |
149 | 40 | for (i = 0; i < n; i++) |
150 | 28 | { |
151 | 28 | xps_identify_font_encoding(font, i, &pid, &eid); |
152 | 28 | if (pid == xps_cmap_list[k].pid && eid == xps_cmap_list[k].eid) |
153 | 2 | { |
154 | 2 | if (xps_select_font_encoding(font, i)) |
155 | 2 | return; |
156 | 2 | } |
157 | 28 | } |
158 | 14 | } |
159 | | |
160 | 0 | gs_warn("could not find a suitable cmap"); |
161 | 0 | } |
162 | | |
163 | | /* |
164 | | * Call text drawing primitives. |
165 | | */ |
166 | | |
167 | | /* See if the device can handle Text Rendering Mode natively |
168 | | * returns < 0 for error, 0 for can't handle it, 1 for can handle it |
169 | | */ |
170 | | static int |
171 | | PreserveTrMode(xps_context_t *ctx) |
172 | 0 | { |
173 | |
|
174 | 0 | gs_c_param_list list; |
175 | 0 | dev_param_req_t request; |
176 | 0 | gs_param_name ParamName = "PreserveTrMode"; |
177 | 0 | gs_param_typed_value Param; |
178 | 0 | char *data; |
179 | 0 | gs_gstate *pgs = ctx->pgs; |
180 | 0 | int code = 0; |
181 | | |
182 | | /* Interrogate the device to see if it supports Text Rendering Mode */ |
183 | 0 | data = (char *)gs_alloc_bytes(ctx->memory, 15, "temporary special_op string"); |
184 | 0 | if (data == NULL) |
185 | 0 | return_error(gs_error_VMerror); |
186 | | |
187 | 0 | memset(data, 0x00, 15); |
188 | 0 | memcpy(data, "PreserveTrMode", 15); |
189 | 0 | gs_c_param_list_write(&list, ctx->memory); |
190 | | /* Make a null object so that the param list won't check for requests */ |
191 | 0 | Param.type = gs_param_type_null; |
192 | 0 | list.procs->xmit_typed((gs_param_list *)&list, ParamName, &Param); |
193 | | /* Stuff the data into a structure for passing to the spec_op */ |
194 | 0 | request.Param = data; |
195 | 0 | request.list = &list; |
196 | |
|
197 | 0 | code = dev_proc(gs_currentdevice(pgs), dev_spec_op)(gs_currentdevice(pgs), gxdso_get_dev_param, |
198 | 0 | &request, sizeof(dev_param_req_t)); |
199 | |
|
200 | 0 | if (code != gs_error_undefined) { |
201 | | /* The parameter is present in the device, now we need to see its value */ |
202 | 0 | gs_c_param_list_read(&list); |
203 | 0 | list.procs->xmit_typed((gs_param_list *)&list, ParamName, &Param); |
204 | |
|
205 | 0 | if (Param.type != gs_param_type_bool) { |
206 | | /* This really shoudn't happen, but its best to be sure */ |
207 | 0 | gs_free_object(ctx->memory, data,"temporary special_op string"); |
208 | 0 | gs_c_param_list_release(&list); |
209 | 0 | return gs_error_typecheck; |
210 | 0 | } |
211 | | |
212 | 0 | if (Param.value.b) { |
213 | 0 | code = 1; |
214 | 0 | } else { |
215 | 0 | code = 0; |
216 | 0 | } |
217 | 0 | } else { |
218 | 0 | code = 0; |
219 | 0 | } |
220 | 0 | gs_free_object(ctx->memory, data,"temporary special_op string"); |
221 | 0 | gs_c_param_list_release(&list); |
222 | 0 | return code; |
223 | 0 | } |
224 | | |
225 | | static int |
226 | | xps_flush_text_buffer(xps_context_t *ctx, xps_font_t *font, |
227 | | xps_text_buffer_t *buf, int is_charpath) |
228 | 14 | { |
229 | 14 | gs_text_params_t params; |
230 | 14 | gs_text_enum_t *textenum; |
231 | 14 | float initial_x, x = buf->x[0]; |
232 | 14 | float initial_y, y = buf->y[0]; |
233 | 14 | int code; |
234 | 14 | int i; |
235 | 14 | gs_gstate_color saved; |
236 | | |
237 | | /* dmprintf1(ctx->memory, "flushing text buffer (%d glyphs)\n", buf->count); */ |
238 | | |
239 | 14 | initial_x = x; |
240 | 14 | initial_y = y; |
241 | | |
242 | 14 | params.operation = TEXT_FROM_GLYPHS | TEXT_REPLACE_WIDTHS; |
243 | 14 | if (is_charpath) |
244 | 0 | params.operation |= TEXT_DO_FALSE_CHARPATH; |
245 | 14 | else |
246 | 14 | params.operation |= TEXT_DO_DRAW; |
247 | 14 | params.data.glyphs = buf->g; |
248 | 14 | params.size = buf->count; |
249 | 14 | params.x_widths = buf->x + 1; |
250 | 14 | params.y_widths = buf->y + 1; |
251 | 14 | params.widths_size = buf->count; |
252 | | |
253 | 80 | for (i = 0; i < buf->count; i++) |
254 | 66 | { |
255 | 66 | buf->x[i] = buf->x[i] - x; |
256 | 66 | buf->y[i] = buf->y[i] - y; |
257 | 66 | x += buf->x[i]; |
258 | 66 | y += buf->y[i]; |
259 | 66 | } |
260 | 14 | buf->x[buf->count] = 0; |
261 | 14 | buf->y[buf->count] = 0; |
262 | | |
263 | 14 | if (ctx->pgs->text_rendering_mode == 2 ) { |
264 | 0 | gs_text_enum_t *Tr_textenum; |
265 | 0 | gs_text_params_t Tr_params; |
266 | | |
267 | | /* Save the 'stroke' colour, which XPS doesn't normally use, or set. |
268 | | * This isn't used by rendering, but it is used by the pdfwrite |
269 | | * device family, and must be correct for stroking text rendering |
270 | | * modes. |
271 | | */ |
272 | 0 | saved = ctx->pgs->color[1]; |
273 | | /* And now make the stroke color the same as the fill color */ |
274 | 0 | ctx->pgs->color[1] = ctx->pgs->color[0]; |
275 | |
|
276 | 0 | if (PreserveTrMode(ctx) != 1) { |
277 | | /* The device doesn't want (or can't handle) Text Rendering Modes |
278 | | * So start by doing a 'charpath stroke' to embolden the text |
279 | | */ |
280 | 0 | gs_moveto(ctx->pgs, initial_x, initial_y); |
281 | 0 | Tr_params.operation = TEXT_FROM_GLYPHS | TEXT_REPLACE_WIDTHS | TEXT_DO_TRUE_CHARPATH; |
282 | 0 | Tr_params.data.glyphs = params.data.glyphs; |
283 | 0 | Tr_params.size = params.size; |
284 | 0 | Tr_params.x_widths = params.x_widths; |
285 | 0 | Tr_params.y_widths = params.y_widths; |
286 | 0 | Tr_params.widths_size = params.widths_size; |
287 | |
|
288 | 0 | code = gs_text_begin(ctx->pgs, &Tr_params, ctx->memory, &Tr_textenum); |
289 | 0 | if (code != 0) |
290 | 0 | return gs_throw1(-1, "cannot gs_text_begin() (%d)", code); |
291 | | |
292 | 0 | code = gs_text_process(Tr_textenum); |
293 | |
|
294 | 0 | if (code != 0) |
295 | 0 | return gs_throw1(-1, "cannot gs_text_process() (%d)", code); |
296 | | |
297 | 0 | gs_text_release(ctx->pgs, Tr_textenum, "gslt font render"); |
298 | |
|
299 | 0 | gs_stroke(ctx->pgs); |
300 | 0 | } |
301 | 0 | } |
302 | | |
303 | 14 | gs_moveto(ctx->pgs, initial_x, initial_y); |
304 | 14 | code = gs_text_begin(ctx->pgs, ¶ms, ctx->memory, &textenum); |
305 | 14 | if (code != 0) |
306 | 0 | return gs_throw1(-1, "cannot gs_text_begin() (%d)", code); |
307 | | |
308 | 14 | code = gs_text_process(textenum); |
309 | | |
310 | 14 | if (code != 0) |
311 | 0 | return gs_throw1(-1, "cannot gs_text_process() (%d)", code); |
312 | | |
313 | 14 | gs_text_release(ctx->pgs, textenum, "gslt font render"); |
314 | | |
315 | 14 | buf->count = 0; |
316 | | |
317 | 14 | if (ctx->pgs->text_rendering_mode == 2 ) { |
318 | | /* Restore the stroke colour which we overwrote above */ |
319 | 0 | ctx->pgs->color[1] = saved; |
320 | 0 | } |
321 | 14 | return 0; |
322 | 14 | } |
323 | | |
324 | | /* |
325 | | * Parse and draw an XPS <Glyphs> element. |
326 | | * |
327 | | * Indices syntax: |
328 | | |
329 | | GlyphIndices = GlyphMapping ( ";" GlyphMapping ) |
330 | | GlyphMapping = ( [ClusterMapping] GlyphIndex ) [GlyphMetrics] |
331 | | ClusterMapping = "(" ClusterCodeUnitCount [":" ClusterGlyphCount] ")" |
332 | | ClusterCodeUnitCount = * DIGIT |
333 | | ClusterGlyphCount = * DIGIT |
334 | | GlyphIndex = * DIGIT |
335 | | GlyphMetrics = "," AdvanceWidth ["," uOffset ["," vOffset]] |
336 | | AdvanceWidth = ["+"] RealNum |
337 | | uOffset = ["+" | "-"] RealNum |
338 | | vOffset = ["+" | "-"] RealNum |
339 | | RealNum = ((DIGIT ["." DIGIT]) | ("." DIGIT)) [Exponent] |
340 | | Exponent = ( ("E"|"e") ("+"|"-") DIGIT ) |
341 | | |
342 | | */ |
343 | | |
344 | | static char * |
345 | | xps_parse_digits(char *s, int *digit) |
346 | 66 | { |
347 | 66 | *digit = 0; |
348 | 198 | while (*s >= '0' && *s <= '9') |
349 | 132 | { |
350 | 132 | *digit = *digit * 10 + (*s - '0'); |
351 | 132 | s ++; |
352 | 132 | } |
353 | 66 | return s; |
354 | 66 | } |
355 | | |
356 | | static char * |
357 | | xps_parse_real_num(char *s, float *number, bool *number_parsed) |
358 | 18 | { |
359 | 18 | char *tail; |
360 | 18 | float v; |
361 | 18 | v = (float)strtod(s, &tail); |
362 | 18 | *number_parsed = tail != s; |
363 | 18 | if (*number_parsed) |
364 | 18 | *number = v; |
365 | 18 | return tail; |
366 | 18 | } |
367 | | |
368 | | static char * |
369 | | xps_parse_cluster_mapping(char *s, int *code_count, int *glyph_count) |
370 | 66 | { |
371 | 66 | if (*s == '(') |
372 | 0 | s = xps_parse_digits(s + 1, code_count); |
373 | 66 | if (*s == ':') |
374 | 0 | s = xps_parse_digits(s + 1, glyph_count); |
375 | 66 | if (*s == ')') |
376 | 0 | s ++; |
377 | 66 | return s; |
378 | 66 | } |
379 | | |
380 | | static char * |
381 | | xps_parse_glyph_index(char *s, int *glyph_index) |
382 | 66 | { |
383 | 66 | if (*s >= '0' && *s <= '9') |
384 | 66 | s = xps_parse_digits(s, glyph_index); |
385 | 66 | return s; |
386 | 66 | } |
387 | | |
388 | | static char * |
389 | | xps_parse_glyph_advance(char *s, float *advance, int bidi_level) |
390 | 52 | { |
391 | 52 | bool advance_overridden = false; |
392 | | |
393 | 52 | if (*s == ',') { |
394 | 18 | s = xps_parse_real_num(s + 1, advance, &advance_overridden); |
395 | | |
396 | | /* |
397 | | * If the advance has been derived from the font and not |
398 | | * overridden by the Indices Attribute the sign has already |
399 | | * been direction adjusted. |
400 | | */ |
401 | | |
402 | 18 | if (advance_overridden && (bidi_level & 1)) |
403 | 0 | *advance *= -1; |
404 | 18 | } |
405 | 52 | return s; |
406 | 52 | } |
407 | | |
408 | | static char * |
409 | | xps_parse_glyph_offsets(char *s, float *uofs, float *vofs) |
410 | 52 | { |
411 | 52 | bool offsets_overridden; /* not used */ |
412 | | |
413 | 52 | if (*s == ',') |
414 | 0 | s = xps_parse_real_num(s + 1, uofs, &offsets_overridden); |
415 | 52 | if (*s == ',') |
416 | 0 | s = xps_parse_real_num(s + 1, vofs, &offsets_overridden); |
417 | 52 | return s; |
418 | 52 | } |
419 | | |
420 | | static char * |
421 | | xps_parse_glyph_metrics(char *s, float *advance, float *uofs, float *vofs, int bidi_level) |
422 | 52 | { |
423 | 52 | s = xps_parse_glyph_advance(s, advance, bidi_level); |
424 | 52 | s = xps_parse_glyph_offsets(s, uofs, vofs); |
425 | 52 | return s; |
426 | 52 | } |
427 | | |
428 | | /* |
429 | | * Parse unicode and indices strings and encode glyphs. |
430 | | * Calculate metrics for positioning. |
431 | | */ |
432 | | static int |
433 | | xps_parse_glyphs_imp(xps_context_t *ctx, xps_font_t *font, float size, |
434 | | float originx, float originy, int is_sideways, int bidi_level, |
435 | | char *indices, char *unicode, int is_charpath, int sim_bold) |
436 | 14 | { |
437 | 14 | xps_text_buffer_t buf; |
438 | 14 | xps_glyph_metrics_t mtx; |
439 | 14 | float x = originx; |
440 | 14 | float y = originy; |
441 | 14 | char *us = unicode; |
442 | 14 | char *is = indices; |
443 | 14 | int un = 0; |
444 | 14 | int code; |
445 | | |
446 | 14 | buf.count = 0; |
447 | | |
448 | 14 | if (!unicode && !indices) |
449 | 0 | return gs_throw(-1, "no text in glyphs element"); |
450 | | |
451 | 14 | if (us) |
452 | 14 | { |
453 | 14 | if (us[0] == '{' && us[1] == '}') |
454 | 0 | us = us + 2; |
455 | 14 | un = strlen(us); |
456 | 14 | } |
457 | | |
458 | 80 | while ((us && un > 0) || (is && *is)) |
459 | 66 | { |
460 | 66 | int char_code = '?'; |
461 | 66 | int code_count = 1; |
462 | 66 | int glyph_count = 1; |
463 | | |
464 | 66 | if (is && *is) |
465 | 66 | { |
466 | 66 | is = xps_parse_cluster_mapping(is, &code_count, &glyph_count); |
467 | 66 | } |
468 | | |
469 | 66 | if (code_count < 1) |
470 | 0 | code_count = 1; |
471 | 66 | if (glyph_count < 1) |
472 | 0 | glyph_count = 1; |
473 | | /* HACK. Impose an upper limit on code_count and glyph_count as a |
474 | | * workaround for Bug 709192 Issue 558. An Indices value of |
475 | | * (1:50000000) will cause us to generate 50000000 chars to |
476 | | * render. */ |
477 | 66 | if (code_count > 9) |
478 | 0 | code_count = 9; |
479 | 66 | if (glyph_count > 9) |
480 | 0 | glyph_count = 9; |
481 | | |
482 | 132 | while (code_count--) |
483 | 66 | { |
484 | 66 | if (us && un > 0) |
485 | 66 | { |
486 | 66 | int t = xps_utf8_to_ucs(&char_code, us, un); |
487 | 66 | if (t < 0) |
488 | 0 | return gs_rethrow(-1, "error decoding UTF-8 string"); |
489 | 66 | us += t; un -= t; |
490 | 66 | } |
491 | 66 | } |
492 | | |
493 | 132 | while (glyph_count--) |
494 | 66 | { |
495 | 66 | int glyph_index = -1; |
496 | 66 | float u_offset = 0.0; |
497 | 66 | float v_offset = 0.0; |
498 | 66 | float advance; |
499 | | |
500 | 66 | if (is && *is) |
501 | 66 | is = xps_parse_glyph_index(is, &glyph_index); |
502 | | |
503 | 66 | if (glyph_index == -1) |
504 | 0 | glyph_index = xps_encode_font_char(font, char_code); |
505 | | |
506 | 66 | xps_measure_font_glyph(ctx, font, glyph_index, &mtx); |
507 | 66 | if (is_sideways) |
508 | 0 | advance = mtx.vadv * 100.0; |
509 | 66 | else if (bidi_level & 1) |
510 | 0 | advance = -mtx.hadv * 100.0; |
511 | 66 | else |
512 | 66 | advance = mtx.hadv * 100.0; |
513 | | |
514 | 66 | if (is && *is) |
515 | 52 | { |
516 | 52 | is = xps_parse_glyph_metrics(is, &advance, &u_offset, &v_offset, bidi_level); |
517 | 52 | if (*is == ';') |
518 | 52 | is ++; |
519 | 52 | } |
520 | | |
521 | 66 | if (bidi_level & 1) |
522 | 0 | u_offset = -mtx.hadv * 100 - u_offset; |
523 | | |
524 | 66 | u_offset = u_offset * 0.01 * size; |
525 | 66 | v_offset = v_offset * 0.01 * size; |
526 | | |
527 | | /* Adjust glyph offset and advance width for emboldening */ |
528 | 66 | if (sim_bold) |
529 | 0 | { |
530 | 0 | advance *= 1.02f; |
531 | 0 | u_offset += 0.01 * size; |
532 | 0 | v_offset += 0.01 * size; |
533 | 0 | } |
534 | | |
535 | 66 | if (buf.count == XPS_TEXT_BUFFER_SIZE) |
536 | 0 | { |
537 | 0 | code = xps_flush_text_buffer(ctx, font, &buf, is_charpath); |
538 | 0 | if (code) |
539 | 0 | return gs_rethrow(code, "cannot flush buffered text"); |
540 | 0 | } |
541 | | |
542 | 66 | if (is_sideways) |
543 | 0 | { |
544 | 0 | buf.x[buf.count] = x + u_offset + (mtx.vorg * size); |
545 | 0 | buf.y[buf.count] = y - v_offset + (mtx.hadv * 0.5 * size); |
546 | 0 | } |
547 | 66 | else |
548 | 66 | { |
549 | 66 | buf.x[buf.count] = x + u_offset; |
550 | 66 | buf.y[buf.count] = y - v_offset; |
551 | 66 | } |
552 | 66 | buf.g[buf.count] = glyph_index; |
553 | 66 | buf.count ++; |
554 | | |
555 | 66 | x += advance * 0.01 * size; |
556 | 66 | } |
557 | 66 | } |
558 | | |
559 | 14 | if (buf.count > 0) |
560 | 14 | { |
561 | 14 | code = xps_flush_text_buffer(ctx, font, &buf, is_charpath); |
562 | 14 | if (code) |
563 | 0 | return gs_rethrow(code, "cannot flush buffered text"); |
564 | 14 | } |
565 | | |
566 | 14 | return 0; |
567 | 14 | } |
568 | | |
569 | | int |
570 | | xps_parse_glyphs(xps_context_t *ctx, |
571 | | char *base_uri, xps_resource_t *dict, xps_item_t *root) |
572 | 14 | { |
573 | 14 | xps_item_t *node; |
574 | 14 | int code; |
575 | | |
576 | 14 | char *fill_uri; |
577 | 14 | char *opacity_mask_uri; |
578 | | |
579 | 14 | char *bidi_level_att; |
580 | | /*char *caret_stops_att;*/ |
581 | 14 | char *fill_att; |
582 | 14 | char *font_size_att; |
583 | 14 | char *font_uri_att; |
584 | 14 | char *origin_x_att; |
585 | 14 | char *origin_y_att; |
586 | 14 | char *is_sideways_att; |
587 | 14 | char *indices_att; |
588 | 14 | char *unicode_att; |
589 | 14 | char *style_att; |
590 | 14 | char *transform_att; |
591 | 14 | char *clip_att; |
592 | 14 | char *opacity_att; |
593 | 14 | char *opacity_mask_att; |
594 | | |
595 | 14 | xps_item_t *transform_tag = NULL; |
596 | 14 | xps_item_t *clip_tag = NULL; |
597 | 14 | xps_item_t *fill_tag = NULL; |
598 | 14 | xps_item_t *opacity_mask_tag = NULL; |
599 | | |
600 | 14 | char *fill_opacity_att = NULL; |
601 | | |
602 | 14 | xps_part_t *part; |
603 | 14 | xps_font_t *font; |
604 | | |
605 | 14 | char partname[1024]; |
606 | 14 | char *subfont; |
607 | | |
608 | 14 | gs_matrix matrix; |
609 | 14 | float font_size = 10.0; |
610 | 14 | int subfontid = 0; |
611 | 14 | int is_sideways = 0; |
612 | 14 | int bidi_level = 0; |
613 | | |
614 | 14 | int sim_bold = 0; |
615 | 14 | int sim_italic = 0; |
616 | | |
617 | 14 | gs_matrix shear = { 1, 0, 0.36397f, 1, 0, 0 }; /* shear by 20 degrees */ |
618 | | |
619 | | /* |
620 | | * Extract attributes and extended attributes. |
621 | | */ |
622 | | |
623 | 14 | bidi_level_att = xps_att(root, "BidiLevel"); |
624 | | /*caret_stops_att = xps_att(root, "CaretStops");*/ |
625 | 14 | fill_att = xps_att(root, "Fill"); |
626 | 14 | font_size_att = xps_att(root, "FontRenderingEmSize"); |
627 | 14 | font_uri_att = xps_att(root, "FontUri"); |
628 | 14 | origin_x_att = xps_att(root, "OriginX"); |
629 | 14 | origin_y_att = xps_att(root, "OriginY"); |
630 | 14 | is_sideways_att = xps_att(root, "IsSideways"); |
631 | 14 | indices_att = xps_att(root, "Indices"); |
632 | 14 | unicode_att = xps_att(root, "UnicodeString"); |
633 | 14 | style_att = xps_att(root, "StyleSimulations"); |
634 | 14 | transform_att = xps_att(root, "RenderTransform"); |
635 | 14 | clip_att = xps_att(root, "Clip"); |
636 | 14 | opacity_att = xps_att(root, "Opacity"); |
637 | 14 | opacity_mask_att = xps_att(root, "OpacityMask"); |
638 | | |
639 | 14 | for (node = xps_down(root); node; node = xps_next(node)) |
640 | 0 | { |
641 | 0 | if (!strcmp(xps_tag(node), "Glyphs.RenderTransform")) |
642 | 0 | transform_tag = xps_down(node); |
643 | |
|
644 | 0 | if (!strcmp(xps_tag(node), "Glyphs.OpacityMask")) |
645 | 0 | opacity_mask_tag = xps_down(node); |
646 | |
|
647 | 0 | if (!strcmp(xps_tag(node), "Glyphs.Clip")) |
648 | 0 | clip_tag = xps_down(node); |
649 | |
|
650 | 0 | if (!strcmp(xps_tag(node), "Glyphs.Fill")) |
651 | 0 | fill_tag = xps_down(node); |
652 | 0 | } |
653 | | |
654 | 14 | fill_uri = base_uri; |
655 | 14 | opacity_mask_uri = base_uri; |
656 | | |
657 | 14 | xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL); |
658 | 14 | xps_resolve_resource_reference(ctx, dict, &clip_att, &clip_tag, NULL); |
659 | 14 | xps_resolve_resource_reference(ctx, dict, &fill_att, &fill_tag, &fill_uri); |
660 | 14 | xps_resolve_resource_reference(ctx, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri); |
661 | | |
662 | | /* |
663 | | * Check that we have all the necessary information. |
664 | | */ |
665 | | |
666 | 14 | if (!font_size_att || !font_uri_att || !origin_x_att || !origin_y_att) |
667 | 0 | return gs_throw(-1, "missing attributes in glyphs element"); |
668 | | |
669 | 14 | if (!indices_att && !unicode_att) |
670 | 0 | return 0; /* nothing to draw */ |
671 | | |
672 | 14 | if (is_sideways_att) |
673 | 0 | is_sideways = !strcmp(is_sideways_att, "true"); |
674 | | |
675 | 14 | if (bidi_level_att) |
676 | 0 | bidi_level = atoi(bidi_level_att); |
677 | | |
678 | | /* |
679 | | * Find and load the font resource |
680 | | */ |
681 | | |
682 | 14 | xps_absolute_path(partname, base_uri, font_uri_att, sizeof partname); |
683 | 14 | subfont = strrchr(partname, '#'); |
684 | 14 | if (subfont) |
685 | 0 | { |
686 | 0 | subfontid = atoi(subfont + 1); |
687 | 0 | *subfont = 0; |
688 | 0 | } |
689 | | |
690 | 14 | font = xps_hash_lookup(ctx->font_table, partname); |
691 | 14 | if (!font) |
692 | 2 | { |
693 | 2 | part = xps_read_part(ctx, partname); |
694 | 2 | if (!part) |
695 | 0 | return gs_throw1(-1, "cannot find font resource part '%s'", partname); |
696 | | |
697 | | /* deobfuscate if necessary */ |
698 | 2 | if (strstr(part->name, ".odttf")) |
699 | 2 | xps_deobfuscate_font_resource(ctx, part); |
700 | 2 | if (strstr(part->name, ".ODTTF")) |
701 | 0 | xps_deobfuscate_font_resource(ctx, part); |
702 | | |
703 | 2 | font = xps_new_font(ctx, part->data, part->size, subfontid); |
704 | 2 | if (!font) |
705 | 0 | return gs_rethrow1(-1, "cannot load font resource '%s'", partname); |
706 | | |
707 | 2 | xps_select_best_font_encoding(font); |
708 | | |
709 | 2 | xps_hash_insert(ctx, ctx->font_table, part->name, font); |
710 | | |
711 | | /* NOTE: we kept part->name in the hashtable and part->data in the font */ |
712 | 2 | xps_free(ctx, part); |
713 | 2 | } |
714 | | |
715 | 14 | if (style_att) |
716 | 14 | { |
717 | 14 | if (!strcmp(style_att, "BoldSimulation")) |
718 | 0 | sim_bold = 1; |
719 | 14 | else if (!strcmp(style_att, "ItalicSimulation")) |
720 | 0 | sim_italic = 1; |
721 | 14 | else if (!strcmp(style_att, "BoldItalicSimulation")) |
722 | 0 | sim_bold = sim_italic = 1; |
723 | 14 | } |
724 | | |
725 | | /* |
726 | | * Set up graphics state. |
727 | | */ |
728 | | |
729 | 14 | gs_gsave(ctx->pgs); |
730 | | |
731 | 14 | if (transform_att || transform_tag) |
732 | 0 | { |
733 | 0 | gs_matrix transform; |
734 | |
|
735 | 0 | if (transform_att) |
736 | 0 | xps_parse_render_transform(ctx, transform_att, &transform); |
737 | 0 | if (transform_tag) |
738 | 0 | xps_parse_matrix_transform(ctx, transform_tag, &transform); |
739 | |
|
740 | 0 | gs_concat(ctx->pgs, &transform); |
741 | 0 | } |
742 | | |
743 | 14 | if (clip_att || clip_tag) |
744 | 0 | { |
745 | 0 | if (clip_att) |
746 | 0 | xps_parse_abbreviated_geometry(ctx, clip_att); |
747 | 0 | if (clip_tag) |
748 | 0 | xps_parse_path_geometry(ctx, dict, clip_tag, 0); |
749 | 0 | xps_clip(ctx); |
750 | 0 | } |
751 | | |
752 | 14 | font_size = atof(font_size_att); |
753 | | |
754 | 14 | gs_setfont(ctx->pgs, font->font); |
755 | 14 | gs_make_scaling(font_size, -font_size, &matrix); |
756 | 14 | if (is_sideways) |
757 | 0 | gs_matrix_rotate(&matrix, 90.0, &matrix); |
758 | | |
759 | 14 | if (sim_italic) |
760 | 0 | gs_matrix_multiply(&shear, &matrix, &matrix); |
761 | | |
762 | 14 | gs_setcharmatrix(ctx->pgs, &matrix); |
763 | | |
764 | 14 | gs_matrix_multiply(&matrix, &font->font->orig_FontMatrix, &font->font->FontMatrix); |
765 | | |
766 | 14 | code = xps_begin_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag, false, false); |
767 | 14 | if (code) |
768 | 0 | { |
769 | 0 | gs_grestore(ctx->pgs); |
770 | 0 | return gs_rethrow(code, "cannot create transparency group"); |
771 | 0 | } |
772 | | |
773 | | /* |
774 | | * If it's a solid color brush fill/stroke do a simple fill |
775 | | */ |
776 | | |
777 | 14 | if (fill_tag && !strcmp(xps_tag(fill_tag), "SolidColorBrush")) |
778 | 0 | { |
779 | 0 | fill_opacity_att = xps_att(fill_tag, "Opacity"); |
780 | 0 | fill_att = xps_att(fill_tag, "Color"); |
781 | 0 | fill_tag = NULL; |
782 | 0 | } |
783 | | |
784 | 14 | if (fill_att) |
785 | 14 | { |
786 | 14 | float samples[XPS_MAX_COLORS]; |
787 | 14 | gs_color_space *colorspace; |
788 | | |
789 | 14 | xps_parse_color(ctx, base_uri, fill_att, &colorspace, samples); |
790 | 14 | if (fill_opacity_att) |
791 | 0 | samples[0] *= atof(fill_opacity_att); |
792 | 14 | xps_set_color(ctx, colorspace, samples); |
793 | 14 | rc_decrement(colorspace, "xps_parse_glyphs"); |
794 | | |
795 | 14 | if (sim_bold) |
796 | 0 | { |
797 | 0 | if (!ctx->preserve_tr_mode) |
798 | | /* widening strokes by 1% of em size */ |
799 | 0 | gs_setlinewidth(ctx->pgs, font_size * 0.02); |
800 | 0 | else |
801 | | /* Undo CTM scaling */ |
802 | 0 | gs_setlinewidth(ctx->pgs, font_size * 0.02 * fabs(ctx->pgs->ctm.xx) / (ctx->pgs->device->HWResolution[0] / 72.0)); |
803 | 0 | gs_settextrenderingmode(ctx->pgs, 2); |
804 | 0 | } |
805 | | |
806 | 14 | code = xps_parse_glyphs_imp(ctx, font, font_size, |
807 | 14 | atof(origin_x_att), atof(origin_y_att), |
808 | 14 | is_sideways, bidi_level, |
809 | 14 | indices_att, unicode_att, sim_bold && !ctx->preserve_tr_mode, sim_bold); |
810 | 14 | if (code) |
811 | 0 | { |
812 | 0 | xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); |
813 | 0 | gs_grestore(ctx->pgs); |
814 | 0 | return gs_rethrow(code, "cannot parse glyphs data"); |
815 | 0 | } |
816 | | |
817 | 14 | if (sim_bold && !ctx->preserve_tr_mode) |
818 | 0 | { |
819 | 0 | gs_gsave(ctx->pgs); |
820 | 0 | gs_fill(ctx->pgs); |
821 | 0 | gs_grestore(ctx->pgs); |
822 | 0 | gs_stroke(ctx->pgs); |
823 | 0 | } |
824 | | |
825 | 14 | gs_settextrenderingmode(ctx->pgs, 0); |
826 | 14 | } |
827 | | |
828 | | /* |
829 | | * If it's a visual brush or image, use the charpath as a clip mask to paint brush |
830 | | */ |
831 | | |
832 | 14 | if (fill_tag) |
833 | 0 | { |
834 | 0 | if (ctx->in_high_level_pattern) |
835 | 0 | { |
836 | 0 | float value[2]; /* alpha and gray */ |
837 | |
|
838 | 0 | value[0] = gs_getfillconstantalpha(ctx->pgs); |
839 | 0 | value[1] = 0; |
840 | 0 | xps_set_color(ctx, ctx->gray, value); |
841 | 0 | } |
842 | |
|
843 | 0 | ctx->fill_rule = 1; /* always use non-zero winding rule for char paths */ |
844 | 0 | code = xps_parse_glyphs_imp(ctx, font, font_size, |
845 | 0 | atof(origin_x_att), atof(origin_y_att), |
846 | 0 | is_sideways, bidi_level, indices_att, unicode_att, 1, sim_bold); |
847 | 0 | if (code) |
848 | 0 | { |
849 | 0 | xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); |
850 | 0 | gs_grestore(ctx->pgs); |
851 | 0 | return gs_rethrow(code, "cannot parse glyphs data"); |
852 | 0 | } |
853 | | |
854 | 0 | code = xps_parse_brush(ctx, fill_uri, dict, fill_tag); |
855 | 0 | if (code) |
856 | 0 | { |
857 | 0 | xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); |
858 | 0 | gs_grestore(ctx->pgs); |
859 | 0 | return gs_rethrow(code, "cannot parse fill brush"); |
860 | 0 | } |
861 | 0 | } |
862 | | |
863 | 14 | xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); |
864 | | |
865 | 14 | gs_grestore(ctx->pgs); |
866 | | |
867 | 14 | return 0; |
868 | 14 | } |