summaryrefslogtreecommitdiff
path: root/source/xps/xps-glyphs.c
diff options
context:
space:
mode:
authorTor Andersson <tor.andersson@artifex.com>2013-06-19 15:29:44 +0200
committerTor Andersson <tor.andersson@artifex.com>2013-06-20 16:45:35 +0200
commit0a927854a10e1e6b9770a81e2e1d9f3093631757 (patch)
tree3d65d820d9fdba2d0d394d99c36290c851b78ca0 /source/xps/xps-glyphs.c
parent1ae8f19179c5f0f8c6352b3c7855465325d5449a (diff)
downloadmupdf-0a927854a10e1e6b9770a81e2e1d9f3093631757.tar.xz
Rearrange source files.
Diffstat (limited to 'source/xps/xps-glyphs.c')
-rw-r--r--source/xps/xps-glyphs.c623
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);
+}