From 8ac2e88deff3d069c19fd59d0fbbfb88a762b113 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sun, 21 Jul 2013 16:06:49 -0400 Subject: [PATCH] [uniscribe] Use unique font name When installing per-process fonts using AddFontMemResourceEx(), if a font with the same family name is already installed, sometimes that one gets used. Which is problematic for us. As such, we now mangle the font to install a new 'name' table with a unique name, which we then use to choose the font. Patch from Jonathan Kew. --- configure.ac | 2 +- src/hb-uniscribe.cc | 136 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 116 insertions(+), 22 deletions(-) diff --git a/configure.ac b/configure.ac index 4ce1f5941..015ab9e4b 100644 --- a/configure.ac +++ b/configure.ac @@ -298,7 +298,7 @@ if test "x$with_uniscribe" = "xyes" -a "x$have_uniscribe" != "xtrue"; then fi if $have_uniscribe; then UNISCRIBE_CFLAGS= - UNISCRIBE_LIBS="-lusp10 -lgdi32" + UNISCRIBE_LIBS="-lusp10 -lgdi32 -lrpcrt4" AC_SUBST(UNISCRIBE_CFLAGS) AC_SUBST(UNISCRIBE_LIBS) AC_DEFINE(HAVE_UNISCRIBE, 1, [Have Uniscribe library]) diff --git a/src/hb-uniscribe.cc b/src/hb-uniscribe.cc index 4d27b9bae..73af43ddc 100644 --- a/src/hb-uniscribe.cc +++ b/src/hb-uniscribe.cc @@ -32,9 +32,11 @@ #include #include +#include #include "hb-uniscribe.h" +#include "hb-open-file-private.hh" #include "hb-ot-name-table.hh" #include "hb-ot-tag.h" @@ -267,8 +269,112 @@ HB_SHAPER_DATA_ENSURE_DECLARE(uniscribe, font) struct hb_uniscribe_shaper_face_data_t { HANDLE fh; hb_uniscribe_shaper_funcs_t *funcs; + wchar_t face_name[LF_FACESIZE]; }; +static char * +_hb_rename_font (const char *orig_sfnt_data, unsigned int length, + unsigned int *new_length, wchar_t *new_name) +{ + /* We'll create a private name for the font from a UUID using a simple, + * somewhat base64-like encoding scheme */ + const char *enc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-"; + UUID id; + UuidCreate ((UUID*) &id); + unsigned int name_str_len = 0; + new_name[name_str_len++] = 'F'; + new_name[name_str_len++] = '_'; + unsigned char *p = (unsigned char *) &id; + for (unsigned int i = 0; i < 16; i += 2) + { + /* Spread the 16 bits from two bytes of the UUID across three chars of face_name, + * using the bits in groups of 5,5,6 to select chars from enc. + * This will generate 24 characters; with the 'F_' prefix we already provided, + * the name will be 26 chars (plus the NUL terminator), so will always fit within + * face_name (LF_FACESIZE = 32). */ + new_name[name_str_len++] = enc[p[i] >> 3]; + new_name[name_str_len++] = enc[((p[i] << 2) | (p[i + 1] >> 6)) & 0x1f]; + new_name[name_str_len++] = enc[p[i + 1] & 0x3f]; + } + new_name[name_str_len] = 0; + + /* Create a copy of the font data, with the 'name' table replaced by a table + * that names the font with our private F_* name created above. + * For simplicity, we just append a new 'name' table and update the sfnt directory; + * the original table is left in place, but unused. */ + + /* The new table will contain just 5 name IDs: family, style, unique, full, PS. + * All of them point to the same name data with our unique F_* name. */ + + static const uint16_t name_IDs[] = { 1, 2, 3, 4, 6 }; + + const unsigned int name_header_size = 3 * 2; /* NameTableHeader: USHORT x 3 */ + const unsigned int name_record_size = 6 * 2; /* NameRecord: USHORT x 6 */ + const unsigned int name_count = sizeof (name_IDs) / sizeof (name_IDs[0]); + unsigned int name_table_length = name_header_size + + name_count * name_record_size + + name_str_len * 2; /* for name data in UTF16BE form */ + + unsigned int name_table_offset = (length + 3) & ~3; + + *new_length = name_table_offset + (name_table_length + 3) & ~3; + char *new_sfnt_data = (char *) calloc (1, *new_length); + if (!new_sfnt_data) + return NULL; + + memcpy(new_sfnt_data, orig_sfnt_data, length); + + unsigned char *name_table_data = (unsigned char *) (new_sfnt_data + name_table_offset); + + p = name_table_data; + *p++ = 0x00; *p++ = 0x00; /* format = 0 */ + *p++ = 0x00; *p++ = name_count; /* number of name records */ + *p++ = 0x00; *p++ = name_header_size + name_count * name_record_size; /* offset to string data */ + + for (unsigned int i = 0; i < name_count; i++) { + *p++ = 0x00; *p++ = 0x03; /* platformID */ + *p++ = 0x00; *p++ = 0x01; /* encodingID */ + *p++ = 0x04; *p++ = 0x09; /* languageID = 0x0409 */ + *p++ = 0x00; *p++ = name_IDs[i]; /* nameID */ + *p++ = 0x00; *p++ = name_str_len * 2; /* string length (bytes) */ + *p++ = 0x00; *p++ = 0x00; /* string offset */ + } + + for (unsigned int i = 0; i < name_str_len; i++) { + /* copy string data from face_name, converting wchar_t to UTF16BE */ + *p++ = new_name[i] >> 8; + *p++ = new_name[i] & 0xff; + } + + /* calculate new name table checksum */ + uint32_t checksum = 0; + while (name_table_data < p) { + checksum += (name_table_data[0] << 24) + + (name_table_data[1] << 16) + + (name_table_data[2] << 8) + + name_table_data[3]; + name_table_data += 4; + } + + /* adjust name table entry to point to new name table */ + OT::OpenTypeFontFace *face = (OT::OpenTypeFontFace *) (new_sfnt_data); + unsigned int index; + if (face->find_table_index (HB_OT_TAG_name, &index)) + { + const OT::TableRecord& record = face->get_table (index); + OT::TableRecord *rp = const_cast (&record); + rp->checkSum.set(checksum); + rp->offset.set(name_table_offset); + rp->length.set(name_table_length); + } + + /* The checkSumAdjustment field in the 'head' table is now wrong, + but AFAIK that doesn't actually cause any problems so I haven't + bothered to fix it. */ + + return new_sfnt_data; +} + hb_uniscribe_shaper_face_data_t * _hb_uniscribe_shaper_face_data_create (hb_face_t *face) { @@ -289,9 +395,13 @@ _hb_uniscribe_shaper_face_data_create (hb_face_t *face) if (unlikely (!blob_length)) DEBUG_MSG (UNISCRIBE, face, "Face has empty blob"); - DWORD num_fonts_installed; - data->fh = AddFontMemResourceEx ((void *) blob_data, blob_length, 0, &num_fonts_installed); + unsigned int new_length; + char *new_font_data = _hb_rename_font (blob_data, blob_length, &new_length, data->face_name); hb_blob_destroy (blob); + + DWORD num_fonts_installed; + data->fh = AddFontMemResourceEx (new_font_data, new_length, 0, &num_fonts_installed); + free (new_font_data); if (unlikely (!data->fh)) { DEBUG_MSG (UNISCRIBE, face, "Face AddFontMemResourceEx() failed"); free (data); @@ -328,26 +438,10 @@ populate_log_font (LOGFONTW *lf, lf->lfHeight = -font->y_scale; lf->lfCharSet = DEFAULT_CHARSET; - hb_blob_t *blob = OT::Sanitizer::sanitize (hb_face_reference_table (font->face, HB_TAG ('n','a','m','e'))); - const OT::name *name_table = OT::Sanitizer::lock_instance (blob); - unsigned int len = name_table->get_name (3, 1, 0x409, 4, - lf->lfFaceName, - sizeof (lf->lfFaceName[0]) * LF_FACESIZE) - / sizeof (lf->lfFaceName[0]); - hb_blob_destroy (blob); + hb_face_t *face = font->face; + hb_uniscribe_shaper_face_data_t *face_data = HB_SHAPER_DATA_GET (face); - if (unlikely (!len)) { - DEBUG_MSG (UNISCRIBE, NULL, "Didn't find English name table entry"); - return false; - } - if (unlikely (len >= LF_FACESIZE)) { - DEBUG_MSG (UNISCRIBE, NULL, "Font name too long"); - return false; - } - - for (unsigned int i = 0; i < len; i++) - lf->lfFaceName[i] = hb_be_uint16 (lf->lfFaceName[i]); - lf->lfFaceName[len] = 0; + memcpy (lf->lfFaceName, face_data->face_name, sizeof (lf->lfFaceName)); return true; }