diff options
Diffstat (limited to 'xps/xpsglyphs.c')
-rw-r--r-- | xps/xpsglyphs.c | 683 |
1 files changed, 683 insertions, 0 deletions
diff --git a/xps/xpsglyphs.c b/xps/xpsglyphs.c new file mode 100644 index 000000000..f8c6cc8ae --- /dev/null +++ b/xps/xpsglyphs.c @@ -0,0 +1,683 @@ +#include "ghostxps.h" + +#include <ctype.h> + +#define XPS_TEXT_BUFFER_SIZE 300 + +typedef struct xps_text_buffer_s xps_text_buffer_t; + +struct xps_text_buffer_s +{ + int count; + float x[XPS_TEXT_BUFFER_SIZE + 1]; + float y[XPS_TEXT_BUFFER_SIZE + 1]; + gs_glyph g[XPS_TEXT_BUFFER_SIZE]; +}; + +static inline int unhex(int i) +{ + if (isdigit(i)) + return i - '0'; + return tolower(i) - 'a' + 10; +} + +void xps_debug_path(xps_context_t *ctx) +{ + segment *seg; + curve_segment *cseg; + + seg = (segment*)ctx->pgs->path->first_subpath; + while (seg) + { + switch (seg->type) + { + case s_start: + dprintf2("%g %g moveto\n", + fixed2float(seg->pt.x) * 0.001, + fixed2float(seg->pt.y) * 0.001); + break; + case s_line: + dprintf2("%g %g lineto\n", + fixed2float(seg->pt.x) * 0.001, + fixed2float(seg->pt.y) * 0.001); + break; + case s_line_close: + dputs("closepath\n"); + break; + case s_curve: + cseg = (curve_segment*)seg; + dprintf6("%g %g %g %g %g %g curveto\n", + fixed2float(cseg->p1.x) * 0.001, + fixed2float(cseg->p1.y) * 0.001, + fixed2float(cseg->p2.x) * 0.001, + fixed2float(cseg->p2.y) * 0.001, + fixed2float(seg->pt.x) * 0.001, + fixed2float(seg->pt.y) * 0.001); + break; + } + seg = seg->next; + } +} + +/* + * Some fonts in XPS are obfuscated by XOR:ing the first 32 bytes of the + * data with the GUID in the fontname. + */ +int +xps_deobfuscate_font_resource(xps_context_t *ctx, xps_part_t *part) +{ + byte buf[33]; + byte key[16]; + char *p; + int i; + + dprintf1("deobfuscating font '%s'\n", part->name); + + p = strrchr(part->name, '/'); + if (!p) + p = part->name; + + for (i = 0; i < 32 && *p; p++) + { + if (isxdigit(*p)) + buf[i++] = *p; + } + buf[i] = 0; + + if (i != 32) + return gs_throw(-1, "cannot extract GUID from part name"); + + for (i = 0; i < 16; i++) + key[i] = unhex(buf[i*2+0]) * 16 + unhex(buf[i*2+1]); + + for (i = 0; i < 16; i++) + { + part->data[i] ^= key[15-i]; + part->data[i+16] ^= key[15-i]; + } + + if (getenv("XPS_SAVE_FONTS")) + { + static int id = 0; + char buf[25]; + FILE *fp; + sprintf(buf, "font%d.otf", id++); + dprintf1("saving font data to %s\n", buf); + fp = fopen(buf, "wb"); + fwrite(part->data, part->size, 1, fp); + fclose(fp); + } + + return 0; +} + +int +xps_select_best_font_encoding(xps_font_t *font) +{ + static struct { int pid, eid; } xps_cmap_list[] = + { + { 3, 10 }, /* Unicode with surrogates */ + { 3, 1 }, /* Unicode without surrogates */ + { 3, 5 }, /* Wansung */ + { 3, 4 }, /* Big5 */ + { 3, 3 }, /* Prc */ + { 3, 2 }, /* ShiftJis */ + { 3, 0 }, /* Symbol */ + // { 0, * }, -- Unicode (deprecated) + { 1, 0 }, + { -1, -1 }, + }; + + int i, k, n, pid, eid; + + n = xps_count_font_encodings(font); + for (k = 0; xps_cmap_list[k].pid != -1; k++) + { + for (i = 0; i < n; i++) + { + xps_identify_font_encoding(font, i, &pid, &eid); + if (pid == xps_cmap_list[k].pid && eid == xps_cmap_list[k].eid) + { + xps_select_font_encoding(font, i); + return 0; + } + } + } + + return gs_throw(-1, "could not find a suitable cmap"); +} + +/* + * Call text drawing primitives. + */ + +int xps_flush_text_buffer(xps_context_t *ctx, xps_font_t *font, + xps_text_buffer_t *buf, int is_charpath) +{ + gs_text_params_t params; + gs_text_enum_t *textenum; + float x = buf->x[0]; + float y = buf->y[0]; + int code; + int i; + + // dprintf1("flushing text buffer (%d glyphs)\n", buf->count); + +#if 0 /* one glyph at a time */ + + for (i = 0; i < buf->count; i++) + { + gs_moveto(ctx->pgs, buf->x[i], buf->y[i]); + + params.operation = TEXT_FROM_SINGLE_GLYPH; + if (is_charpath) + params.operation |= TEXT_DO_FALSE_CHARPATH; + else + params.operation |= TEXT_DO_DRAW; + params.data.d_glyph = buf->g[i]; + params.size = 1; + + code = gs_text_begin(ctx->pgs, ¶ms, ctx->memory, &textenum); + if (code != 0) + return gs_throw1(-1, "cannot gs_text_begin() (%d)", code); + + code = gs_text_process(textenum); + if (code != 0) + return gs_throw1(-1, "cannot gs_text_process() (%d)", code); + + gs_text_release(textenum, "gslt font render"); + } + +#else + + gs_moveto(ctx->pgs, x, y); + + params.operation = TEXT_FROM_GLYPHS | TEXT_REPLACE_WIDTHS; + if (is_charpath) + params.operation |= TEXT_DO_FALSE_CHARPATH; + else + params.operation |= TEXT_DO_DRAW; + params.data.glyphs = buf->g; + params.size = buf->count; + params.x_widths = buf->x + 1; + params.y_widths = buf->y + 1; + params.widths_size = buf->count; + + for (i = 0; i < buf->count; i++) + { + buf->x[i] = buf->x[i] - x; + buf->y[i] = buf->y[i] - y; + x += buf->x[i]; + y += buf->y[i]; + } + buf->x[buf->count] = 0; + buf->y[buf->count] = 0; + + code = gs_text_begin(ctx->pgs, ¶ms, ctx->memory, &textenum); + if (code != 0) + return gs_throw1(-1, "cannot gs_text_begin() (%d)", code); + + code = gs_text_process(textenum); + if (code != 0) + return gs_throw1(-1, "cannot gs_text_process() (%d)", code); + + gs_text_release(textenum, "gslt font render"); +#endif + + buf->count = 0; + + return 0; +} + +/* + * Parse and draw an XPS <Glyphs> element. + * + * Indices syntax: + + GlyphIndices = GlyphMapping ( ";" GlyphMapping ) + GlyphMapping = ( [ClusterMapping] GlyphIndex ) [GlyphMetrics] + ClusterMapping = "(" ClusterCodeUnitCount [":" ClusterGlyphCount] ")" + ClusterCodeUnitCount = * DIGIT + ClusterGlyphCount = * DIGIT + GlyphIndex = * DIGIT + GlyphMetrics = "," AdvanceWidth ["," uOffset ["," vOffset]] + AdvanceWidth = ["+"] RealNum + uOffset = ["+" | "-"] RealNum + vOffset = ["+" | "-"] RealNum + RealNum = ((DIGIT ["." DIGIT]) | ("." DIGIT)) [Exponent] + Exponent = ( ("E"|"e") ("+"|"-") DIGIT ) + + */ + +static char * +xps_parse_digits(char *s, int *digit) +{ + *digit = 0; + while (*s >= '0' && *s <= '9') + { + *digit = *digit * 10 + (*s - '0'); + s ++; + } + return s; +} + +static int is_real_num_char(int c) +{ + return (c >= '0' && c <= '9') || c == 'e' || c == 'E' || c == '+' || c == '-' || c == '.'; +} + +static char * +xps_parse_real_num(char *s, float *number) +{ + char buf[64]; + char *p = buf; + while (is_real_num_char(*s)) + *p++ = *s++; + *p = 0; + if (buf[0]) + *number = atof(buf); + return s; +} + +static char * +xps_parse_cluster_mapping(char *s, int *code_count, int *glyph_count) +{ + if (*s == '(') + s = xps_parse_digits(s + 1, code_count); + if (*s == ':') + s = xps_parse_digits(s + 1, glyph_count); + if (*s == ')') + s ++; + return s; +} + +static char * +xps_parse_glyph_index(char *s, int *glyph_index) +{ + if (*s >= '0' && *s <= '9') + s = xps_parse_digits(s, glyph_index); + return s; +} + +static char * +xps_parse_glyph_metrics(char *s, float *advance, float *uofs, float *vofs) +{ + if (*s == ',') + s = xps_parse_real_num(s + 1, advance); + if (*s == ',') + s = xps_parse_real_num(s + 1, uofs); + if (*s == ',') + s = xps_parse_real_num(s + 1, vofs); + return s; +} + +int +xps_parse_glyphs_imp(xps_context_t *ctx, xps_font_t *font, float size, + float originx, float originy, int is_sideways, int bidi_level, + char *indices, char *unicode, int is_charpath) +{ + // parse unicode and indices strings and encode glyphs + // and calculate metrics for positioning + + xps_text_buffer_t buf; + xps_glyph_metrics_t mtx; + float x = originx; + float y = originy; + char *us = unicode; + char *is = indices; + int un; + + // dprintf1("string (%s)\n", us); + // dprintf1("indices %.50s\n", is); + + buf.count = 0; + + if (!unicode && !indices) + return gs_throw(-1, "no text in glyphs element"); + + if (us) + { + if (us[0] == '{' && us[1] == '}') + us = us + 2; + un = strlen(us); + } + + while ((us && un > 0) || (is && *is)) + { + int code_count = 1; + int glyph_count = 1; + + if (is && *is) + { + is = xps_parse_cluster_mapping(is, &code_count, &glyph_count); + } + + if (code_count < 1) + code_count = 1; + if (glyph_count < 1) + glyph_count = 1; + + while (code_count > 0 || glyph_count > 0) + { + int char_code = '?'; + int glyph_index = -1; + float u_offset = 0.0; + float v_offset = 0.0; + float advance; + + if (glyph_count) + { + if (is && *is) + is = xps_parse_glyph_index(is, &glyph_index); + glyph_count --; + } + + if (code_count) + { + if (us && un > 0) + { + int t = xps_utf8_to_ucs(&char_code, us, un); + if (t < 0) + return gs_rethrow(-1, "error decoding UTF-8 string"); + us += t; un -= t; + } + code_count --; + } + + if (glyph_index == -1) + glyph_index = xps_encode_font_char(font, char_code); + + xps_measure_font_glyph(ctx, font, glyph_index, &mtx); + if (is_sideways) + advance = mtx.vadv * 100.0; + else if (bidi_level & 1) + advance = -mtx.hadv * 100.0; + else + advance = mtx.hadv * 100.0; + + if (is && *is) + { + is = xps_parse_glyph_metrics(is, &advance, &u_offset, &v_offset); + if (*is == ';') + is ++; + } + +#if 0 + dprintf6("glyph mapping (%d:%d)%d,%g,%g,%g\n", + code_count, glyph_count, glyph_index, + advance, u_offset, v_offset); +#endif + + if (bidi_level & 1) + u_offset = -mtx.hadv * 100 - u_offset; + + u_offset = u_offset * 0.01 * size; + v_offset = v_offset * 0.01 * size; + + if (buf.count < XPS_TEXT_BUFFER_SIZE) + { + if (is_sideways) + { + buf.x[buf.count] = x + u_offset + (mtx.vorg * size); + buf.y[buf.count] = y - v_offset + (mtx.hadv * 0.5 * size); + } + else + { + buf.x[buf.count] = x + u_offset; + buf.y[buf.count] = y - v_offset; + } + buf.g[buf.count] = glyph_index; + buf.count ++; + } + else + { + xps_flush_text_buffer(ctx, font, &buf, is_charpath); + } + + x += advance * 0.01 * size; + } + } + + if (buf.count > 0) + { + xps_flush_text_buffer(ctx, font, &buf, is_charpath); + } + + return 0; +} + +int +xps_parse_glyphs(xps_context_t *ctx, xps_resource_t *dict, xps_item_t *root) +{ + xps_item_t *node; + + char *bidi_level_att; + char *caret_stops_att; + char *fill_att; + char *font_size_att; + char *font_uri_att; + char *origin_x_att; + char *origin_y_att; + char *is_sideways_att; + char *indices_att; + char *unicode_att; + char *style_att; + char *transform_att; + char *clip_att; + char *opacity_att; + char *opacity_mask_att; + + xps_item_t *transform_tag = NULL; + xps_item_t *clip_tag = NULL; + xps_item_t *fill_tag = NULL; + xps_item_t *opacity_mask_tag = NULL; + + char *fill_opacity_att = NULL; + + xps_part_t *part; + xps_font_t *font; + + char partname[1024]; + char *parttype; + char *subfont; + + gs_matrix matrix; + float font_size = 10.0; + int subfontid = 0; + int is_sideways = 0; + int bidi_level = 0; + + gs_rect saved_bounds; + + /* + * Extract attributes and extended attributes. + */ + + bidi_level_att = xps_att(root, "BidiLevel"); + caret_stops_att = xps_att(root, "CaretStops"); + fill_att = xps_att(root, "Fill"); + font_size_att = xps_att(root, "FontRenderingEmSize"); + font_uri_att = xps_att(root, "FontUri"); + origin_x_att = xps_att(root, "OriginX"); + origin_y_att = xps_att(root, "OriginY"); + is_sideways_att = xps_att(root, "IsSideways"); + indices_att = xps_att(root, "Indices"); + unicode_att = xps_att(root, "UnicodeString"); + style_att = xps_att(root, "StyleSimulations"); + transform_att = xps_att(root, "RenderTransform"); + clip_att = xps_att(root, "Clip"); + opacity_att = xps_att(root, "Opacity"); + opacity_mask_att = xps_att(root, "OpacityMask"); + + for (node = xps_down(root); node; node = xps_next(node)) + { + if (!strcmp(xps_tag(node), "Glyphs.RenderTransform")) + transform_tag = xps_down(node); + + if (!strcmp(xps_tag(node), "Glyphs.OpacityMask")) + opacity_mask_tag = xps_down(node); + + if (!strcmp(xps_tag(node), "Glyphs.Clip")) + clip_tag = xps_down(node); + + if (!strcmp(xps_tag(node), "Glyphs.Fill")) + fill_tag = xps_down(node); + } + + xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag); + xps_resolve_resource_reference(ctx, dict, &clip_att, &clip_tag); + xps_resolve_resource_reference(ctx, dict, &fill_att, &fill_tag); + xps_resolve_resource_reference(ctx, dict, &opacity_mask_att, &opacity_mask_tag); + + /* + * Check that we have all the necessary information. + */ + + if (!font_size_att || !font_uri_att || !origin_x_att || !origin_y_att) + return gs_throw(-1, "missing attributes in glyphs element"); + + if (!indices_att && !unicode_att) + return 0; /* nothing to draw */ + + if (is_sideways_att) + is_sideways = !strcmp(is_sideways_att, "true"); + + if (bidi_level_att) + bidi_level = atoi(bidi_level_att); + + /* + * Find and load the font resource + */ + + // TODO: get subfont index from # part of uri + + xps_absolute_path(partname, ctx->pwd, font_uri_att); + subfont = strrchr(partname, '#'); + if (subfont) + { + subfontid = atoi(subfont + 1); + *subfont = 0; + } + part = xps_find_part(ctx, partname); + if (!part) + return gs_throw1(-1, "cannot find font resource part '%s'", partname); + + if (!part->deobfuscated) + { + /* deobfuscate if necessary */ + parttype = xps_get_content_type(ctx, part->name); + if (parttype && !strcmp(parttype, "application/vnd.ms-package.obfuscated-opentype")) + xps_deobfuscate_font_resource(ctx, part); + + /* stupid XPS files with content-types after the parts */ + if (!parttype && strstr(part->name, ".odttf")) + xps_deobfuscate_font_resource(ctx, part); + if (!parttype && strstr(part->name, ".ODTTF")) + xps_deobfuscate_font_resource(ctx, part); + + part->deobfuscated = 1; + } + + if (!part->font) + { + part->font = xps_new_font(ctx, part->data, part->size, subfontid); + if (!part->font) + return gs_rethrow1(-1, "cannot load font resource '%s'", partname); + + xps_select_best_font_encoding(part->font); + } + + font = part->font; + + /* + * Set up graphics state. + */ + + gs_gsave(ctx->pgs); + + if (transform_att || transform_tag) + { + gs_matrix transform; + + if (transform_att) + xps_parse_render_transform(ctx, transform_att, &transform); + if (transform_tag) + xps_parse_matrix_transform(ctx, transform_tag, &transform); + + gs_concat(ctx->pgs, &transform); + } + + if (clip_att || clip_tag) + { + if (clip_att) + xps_parse_abbreviated_geometry(ctx, clip_att); + if (clip_tag) + xps_parse_path_geometry(ctx, dict, clip_tag, 0); + + xps_clip(ctx, &saved_bounds); + } + + font_size = atof(font_size_att); + + gs_setfont(ctx->pgs, font->font); + gs_make_scaling(font_size, -font_size, &matrix); + if (is_sideways) + gs_matrix_rotate(&matrix, 90.0, &matrix); + + gs_setcharmatrix(ctx->pgs, &matrix); + + gs_matrix_multiply(&matrix, &font->font->orig_FontMatrix, + &font->font->FontMatrix); + + xps_begin_opacity(ctx, dict, opacity_att, opacity_mask_tag); + + /* + * If it's a solid color brush fill/stroke do a simple fill + */ + + if (fill_tag && !strcmp(xps_tag(fill_tag), "SolidColorBrush")) + { + fill_opacity_att = xps_att(fill_tag, "Opacity"); + fill_att = xps_att(fill_tag, "Color"); + fill_tag = NULL; + } + + if (fill_att) + { + float samples[32]; + gs_color_space *colorspace; + xps_parse_color(ctx, fill_att, &colorspace, samples); + if (fill_opacity_att) + samples[0] = atof(fill_opacity_att); + xps_set_color(ctx, colorspace, samples); + xps_parse_glyphs_imp(ctx, font, font_size, + atof(origin_x_att), atof(origin_y_att), + is_sideways, bidi_level, + indices_att, unicode_att, 0); + } + + /* + * If it's a visual brush or image, use the charpath as a clip mask to paint brush + */ + + if (fill_tag) + { + ctx->fill_rule = 1; /* always use non-zero winding rule for char paths */ + xps_parse_glyphs_imp(ctx, font, font_size, + atof(origin_x_att), atof(origin_y_att), + is_sideways, bidi_level, indices_att, unicode_att, 1); + xps_parse_brush(ctx, dict, fill_tag); + } + + xps_end_opacity(ctx, dict, opacity_att, opacity_mask_tag); + + gs_grestore(ctx->pgs); + + if (clip_att || clip_tag) + { + xps_unclip(ctx, &saved_bounds); + } + + return 0; +} + |