diff options
author | Tor Andersson <tor.andersson@artifex.com> | 2013-06-19 15:29:44 +0200 |
---|---|---|
committer | Tor Andersson <tor.andersson@artifex.com> | 2013-06-20 16:45:35 +0200 |
commit | 0a927854a10e1e6b9770a81e2e1d9f3093631757 (patch) | |
tree | 3d65d820d9fdba2d0d394d99c36290c851b78ca0 /source/xps/xps-glyphs.c | |
parent | 1ae8f19179c5f0f8c6352b3c7855465325d5449a (diff) | |
download | mupdf-0a927854a10e1e6b9770a81e2e1d9f3093631757.tar.xz |
Rearrange source files.
Diffstat (limited to 'source/xps/xps-glyphs.c')
-rw-r--r-- | source/xps/xps-glyphs.c | 623 |
1 files changed, 623 insertions, 0 deletions
diff --git a/source/xps/xps-glyphs.c b/source/xps/xps-glyphs.c new file mode 100644 index 00000000..72f37ca1 --- /dev/null +++ b/source/xps/xps-glyphs.c @@ -0,0 +1,623 @@ +#include "mupdf/xps.h" + +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_ADVANCES_H + +static inline int ishex(int a) +{ + return (a >= 'A' && a <= 'F') || + (a >= 'a' && a <= 'f') || + (a >= '0' && a <= '9'); +} + +static inline int unhex(int a) +{ + if (a >= 'A' && a <= 'F') return a - 'A' + 0xA; + if (a >= 'a' && a <= 'f') return a - 'a' + 0xA; + if (a >= '0' && a <= '9') return a - '0'; + return 0; +} + +int +xps_count_font_encodings(fz_font *font) +{ + FT_Face face = font->ft_face; + return face->num_charmaps; +} + +void +xps_identify_font_encoding(fz_font *font, int idx, int *pid, int *eid) +{ + FT_Face face = font->ft_face; + *pid = face->charmaps[idx]->platform_id; + *eid = face->charmaps[idx]->encoding_id; +} + +void +xps_select_font_encoding(fz_font *font, int idx) +{ + FT_Face face = font->ft_face; + FT_Set_Charmap(face, face->charmaps[idx]); +} + +int +xps_encode_font_char(fz_font *font, int code) +{ + FT_Face face = font->ft_face; + int gid = FT_Get_Char_Index(face, code); + if (gid == 0 && face->charmap && face->charmap->platform_id == 3 && face->charmap->encoding_id == 0) + gid = FT_Get_Char_Index(face, 0xF000 | code); + return gid; +} + +void +xps_measure_font_glyph(xps_document *doc, fz_font *font, int gid, xps_glyph_metrics *mtx) +{ + int mask = FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM; + FT_Face face = font->ft_face; + FT_Fixed hadv, vadv; + fz_context *ctx = doc->ctx; + + fz_lock(ctx, FZ_LOCK_FREETYPE); + FT_Set_Char_Size(face, 64, 64, 72, 72); + FT_Get_Advance(face, gid, mask, &hadv); + FT_Get_Advance(face, gid, mask | FT_LOAD_VERTICAL_LAYOUT, &vadv); + fz_unlock(ctx, FZ_LOCK_FREETYPE); + + mtx->hadv = hadv / 65536.0f; + mtx->vadv = vadv / 65536.0f; + mtx->vorg = face->ascender / (float) face->units_per_EM; +} + +static fz_font * +xps_lookup_font(xps_document *doc, char *name) +{ + xps_font_cache *cache; + for (cache = doc->font_table; cache; cache = cache->next) + if (!xps_strcasecmp(cache->name, name)) + return fz_keep_font(doc->ctx, cache->font); + return NULL; +} + +static void +xps_insert_font(xps_document *doc, char *name, fz_font *font) +{ + xps_font_cache *cache = fz_malloc_struct(doc->ctx, xps_font_cache); + cache->name = fz_strdup(doc->ctx, name); + cache->font = fz_keep_font(doc->ctx, font); + cache->next = doc->font_table; + doc->font_table = cache; +} + +/* + * Some fonts in XPS are obfuscated by XOR:ing the first 32 bytes of the + * data with the GUID in the fontname. + */ +static void +xps_deobfuscate_font_resource(xps_document *doc, xps_part *part) +{ + unsigned char buf[33]; + unsigned char key[16]; + char *p; + int i; + + p = strrchr(part->name, '/'); + if (!p) + p = part->name; + + for (i = 0; i < 32 && *p; p++) + { + if (ishex(*p)) + buf[i++] = *p; + } + buf[i] = 0; + + if (i != 32) + { + fz_warn(doc->ctx, "cannot extract GUID from obfuscated font part name"); + return; + } + + 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]; + } +} + +static void +xps_select_best_font_encoding(xps_document *doc, fz_font *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 */ + { 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; + } + } + } + + fz_warn(doc->ctx, "cannot find a suitable cmap"); +} + +/* + * 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 inline 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 = fz_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; +} + +/* + * Parse unicode and indices strings and encode glyphs. + * Calculate metrics for positioning. + */ +static fz_text * +xps_parse_glyphs_imp(xps_document *doc, const fz_matrix *ctm, + fz_font *font, float size, float originx, float originy, + int is_sideways, int bidi_level, + char *indices, char *unicode) +{ + xps_glyph_metrics mtx; + fz_text *text; + fz_matrix tm; + float e, f; + float x = originx; + float y = originy; + char *us = unicode; + char *is = indices; + int un = 0; + + if (!unicode && !indices) + fz_warn(doc->ctx, "glyphs element with neither characters nor indices"); + + if (us) + { + if (us[0] == '{' && us[1] == '}') + us = us + 2; + un = strlen(us); + } + + if (is_sideways) + { + fz_pre_scale(fz_rotate(&tm, 90), -size, size); + } + else + fz_scale(&tm, size, -size); + + text = fz_new_text(doc->ctx, font, &tm, is_sideways); + + while ((us && un > 0) || (is && *is)) + { + int char_code = '?'; + 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; + + /* TODO: add code chars with cluster mappings for text extraction */ + + while (code_count--) + { + if (us && un > 0) + { + int t = fz_chartorune(&char_code, us); + us += t; un -= t; + } + } + + while (glyph_count--) + { + int glyph_index = -1; + float u_offset = 0; + float v_offset = 0; + float advance; + + if (is && *is) + is = xps_parse_glyph_index(is, &glyph_index); + + if (glyph_index == -1) + glyph_index = xps_encode_font_char(font, char_code); + + xps_measure_font_glyph(doc, font, glyph_index, &mtx); + if (is_sideways) + advance = mtx.vadv * 100; + else if (bidi_level & 1) + advance = -mtx.hadv * 100; + else + advance = mtx.hadv * 100; + + if (font->ft_bold) + advance *= 1.02f; + + if (is && *is) + { + is = xps_parse_glyph_metrics(is, &advance, &u_offset, &v_offset); + if (*is == ';') + is ++; + } + + if (bidi_level & 1) + u_offset = -mtx.hadv * 100 - u_offset; + + u_offset = u_offset * 0.01f * size; + v_offset = v_offset * 0.01f * size; + + if (is_sideways) + { + e = x + u_offset + (mtx.vorg * size); + f = y - v_offset + (mtx.hadv * 0.5f * size); + } + else + { + e = x + u_offset; + f = y - v_offset; + } + + fz_add_text(doc->ctx, text, glyph_index, char_code, e, f); + + x += advance * 0.01f * size; + } + } + + return text; +} + +void +xps_parse_glyphs(xps_document *doc, const fz_matrix *ctm, + char *base_uri, xps_resource *dict, fz_xml *root) +{ + fz_xml *node; + + char *fill_uri; + char *opacity_mask_uri; + + char *bidi_level_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; + char *navigate_uri_att; + + fz_xml *transform_tag = NULL; + fz_xml *clip_tag = NULL; + fz_xml *fill_tag = NULL; + fz_xml *opacity_mask_tag = NULL; + + char *fill_opacity_att = NULL; + + xps_part *part; + fz_font *font; + + char partname[1024]; + char fakename[1024]; + char *subfont; + + float font_size = 10; + int subfontid = 0; + int is_sideways = 0; + int bidi_level = 0; + + fz_text *text; + fz_rect area; + + fz_matrix local_ctm = *ctm; + + /* + * Extract attributes and extended attributes. + */ + + bidi_level_att = fz_xml_att(root, "BidiLevel"); + fill_att = fz_xml_att(root, "Fill"); + font_size_att = fz_xml_att(root, "FontRenderingEmSize"); + font_uri_att = fz_xml_att(root, "FontUri"); + origin_x_att = fz_xml_att(root, "OriginX"); + origin_y_att = fz_xml_att(root, "OriginY"); + is_sideways_att = fz_xml_att(root, "IsSideways"); + indices_att = fz_xml_att(root, "Indices"); + unicode_att = fz_xml_att(root, "UnicodeString"); + style_att = fz_xml_att(root, "StyleSimulations"); + transform_att = fz_xml_att(root, "RenderTransform"); + clip_att = fz_xml_att(root, "Clip"); + opacity_att = fz_xml_att(root, "Opacity"); + opacity_mask_att = fz_xml_att(root, "OpacityMask"); + navigate_uri_att = fz_xml_att(root, "FixedPage.NavigateUri"); + + for (node = fz_xml_down(root); node; node = fz_xml_next(node)) + { + if (!strcmp(fz_xml_tag(node), "Glyphs.RenderTransform")) + transform_tag = fz_xml_down(node); + if (!strcmp(fz_xml_tag(node), "Glyphs.OpacityMask")) + opacity_mask_tag = fz_xml_down(node); + if (!strcmp(fz_xml_tag(node), "Glyphs.Clip")) + clip_tag = fz_xml_down(node); + if (!strcmp(fz_xml_tag(node), "Glyphs.Fill")) + fill_tag = fz_xml_down(node); + } + + fill_uri = base_uri; + opacity_mask_uri = base_uri; + + xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL); + xps_resolve_resource_reference(doc, dict, &clip_att, &clip_tag, NULL); + xps_resolve_resource_reference(doc, dict, &fill_att, &fill_tag, &fill_uri); + xps_resolve_resource_reference(doc, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri); + + /* + * Check that we have all the necessary information. + */ + + if (!font_size_att || !font_uri_att || !origin_x_att || !origin_y_att) { + fz_warn(doc->ctx, "missing attributes in glyphs element"); + return; + } + + if (!indices_att && !unicode_att) + return; /* 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 + */ + + xps_resolve_url(partname, base_uri, font_uri_att, sizeof partname); + subfont = strrchr(partname, '#'); + if (subfont) + { + subfontid = atoi(subfont + 1); + *subfont = 0; + } + + /* Make a new part name for font with style simulation applied */ + fz_strlcpy(fakename, partname, sizeof fakename); + if (style_att) + { + if (!strcmp(style_att, "BoldSimulation")) + fz_strlcat(fakename, "#Bold", sizeof fakename); + else if (!strcmp(style_att, "ItalicSimulation")) + fz_strlcat(fakename, "#Italic", sizeof fakename); + else if (!strcmp(style_att, "BoldItalicSimulation")) + fz_strlcat(fakename, "#BoldItalic", sizeof fakename); + } + + font = xps_lookup_font(doc, fakename); + if (!font) + { + fz_try(doc->ctx) + { + part = xps_read_part(doc, partname); + } + fz_catch(doc->ctx) + { + /* FIXME: TryLater ? */ + fz_warn(doc->ctx, "cannot find font resource part '%s'", partname); + return; + } + + /* deobfuscate if necessary */ + if (strstr(part->name, ".odttf")) + xps_deobfuscate_font_resource(doc, part); + if (strstr(part->name, ".ODTTF")) + xps_deobfuscate_font_resource(doc, part); + + fz_try(doc->ctx) + { + font = fz_new_font_from_memory(doc->ctx, NULL, part->data, part->size, subfontid, 1); + } + fz_catch(doc->ctx) + { + /* FIXME: TryLater ? */ + fz_warn(doc->ctx, "cannot load font resource '%s'", partname); + xps_free_part(doc, part); + return; + } + + if (style_att) + { + font->ft_bold = !!strstr(style_att, "Bold"); + font->ft_italic = !!strstr(style_att, "Italic"); + } + + xps_select_best_font_encoding(doc, font); + + xps_insert_font(doc, fakename, font); + + /* NOTE: we keep part->data in the font */ + font->ft_data = part->data; + font->ft_size = part->size; + fz_free(doc->ctx, part->name); + fz_free(doc->ctx, part); + } + + /* + * Set up graphics state. + */ + + if (transform_att || transform_tag) + { + fz_matrix transform; + if (transform_att) + xps_parse_render_transform(doc, transform_att, &transform); + if (transform_tag) + xps_parse_matrix_transform(doc, transform_tag, &transform); + fz_concat(&local_ctm, &transform, &local_ctm); + } + + if (clip_att || clip_tag) + xps_clip(doc, &local_ctm, dict, clip_att, clip_tag); + + font_size = fz_atof(font_size_att); + + text = xps_parse_glyphs_imp(doc, &local_ctm, font, font_size, + fz_atof(origin_x_att), fz_atof(origin_y_att), + is_sideways, bidi_level, indices_att, unicode_att); + + fz_bound_text(doc->ctx, text, NULL, &local_ctm, &area); + + if (navigate_uri_att) + xps_add_link(doc, &area, base_uri, navigate_uri_att); + + xps_begin_opacity(doc, &local_ctm, &area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); + + /* If it's a solid color brush fill/stroke do a simple fill */ + + if (fill_tag && !strcmp(fz_xml_tag(fill_tag), "SolidColorBrush")) + { + fill_opacity_att = fz_xml_att(fill_tag, "Opacity"); + fill_att = fz_xml_att(fill_tag, "Color"); + fill_tag = NULL; + } + + if (fill_att) + { + float samples[32]; + fz_colorspace *colorspace; + + xps_parse_color(doc, base_uri, fill_att, &colorspace, samples); + if (fill_opacity_att) + samples[0] *= fz_atof(fill_opacity_att); + xps_set_color(doc, colorspace, samples); + + fz_fill_text(doc->dev, text, &local_ctm, + doc->colorspace, doc->color, doc->alpha); + } + + /* If it's a complex brush, use the charpath as a clip mask */ + + if (fill_tag) + { + fz_clip_text(doc->dev, text, &local_ctm, 0); + xps_parse_brush(doc, &local_ctm, &area, fill_uri, dict, fill_tag); + fz_pop_clip(doc->dev); + } + + xps_end_opacity(doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); + + fz_free_text(doc->ctx, text); + + if (clip_att || clip_tag) + fz_pop_clip(doc->dev); + + fz_drop_font(doc->ctx, font); +} |