/* * Methods for dealing with opentype font tables * * Copyright 2014 Aric Stewart for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #define COBJMACROS #define NONAMELESSUNION #include "config.h" #include "dwrite_private.h" #include "winternl.h" WINE_DEFAULT_DEBUG_CHANNEL(dwrite); #define MS_HEAD_TAG DWRITE_MAKE_OPENTYPE_TAG('h','e','a','d') #define MS_HHEA_TAG DWRITE_MAKE_OPENTYPE_TAG('h','h','e','a') #define MS_OTTO_TAG DWRITE_MAKE_OPENTYPE_TAG('O','T','T','O') #define MS_OS2_TAG DWRITE_MAKE_OPENTYPE_TAG('O','S','/','2') #define MS_POST_TAG DWRITE_MAKE_OPENTYPE_TAG('p','o','s','t') #define MS_TTCF_TAG DWRITE_MAKE_OPENTYPE_TAG('t','t','c','f') #define MS_GDEF_TAG DWRITE_MAKE_OPENTYPE_TAG('G','D','E','F') #define MS_NAME_TAG DWRITE_MAKE_OPENTYPE_TAG('n','a','m','e') #define MS_GLYF_TAG DWRITE_MAKE_OPENTYPE_TAG('g','l','y','f') #define MS_CFF__TAG DWRITE_MAKE_OPENTYPE_TAG('C','F','F',' ') #define MS_CFF2_TAG DWRITE_MAKE_OPENTYPE_TAG('C','F','F','2') #define MS_CPAL_TAG DWRITE_MAKE_OPENTYPE_TAG('C','P','A','L') #define MS_COLR_TAG DWRITE_MAKE_OPENTYPE_TAG('C','O','L','R') #define MS_SVG__TAG DWRITE_MAKE_OPENTYPE_TAG('S','V','G',' ') #define MS_SBIX_TAG DWRITE_MAKE_OPENTYPE_TAG('s','b','i','x') #define MS_MAXP_TAG DWRITE_MAKE_OPENTYPE_TAG('m','a','x','p') #define MS_CBLC_TAG DWRITE_MAKE_OPENTYPE_TAG('C','B','L','C') #define MS_CMAP_TAG DWRITE_MAKE_OPENTYPE_TAG('c','m','a','p') #define MS_META_TAG DWRITE_MAKE_OPENTYPE_TAG('m','e','t','a') /* 'sbix' formats */ #define MS_PNG__TAG DWRITE_MAKE_OPENTYPE_TAG('p','n','g',' ') #define MS_JPG__TAG DWRITE_MAKE_OPENTYPE_TAG('j','p','g',' ') #define MS_TIFF_TAG DWRITE_MAKE_OPENTYPE_TAG('t','i','f','f') #define MS_WOFF_TAG DWRITE_MAKE_OPENTYPE_TAG('w','O','F','F') #define MS_WOF2_TAG DWRITE_MAKE_OPENTYPE_TAG('w','O','F','2') /* 'meta' tags */ #define MS_DLNG_TAG DWRITE_MAKE_OPENTYPE_TAG('d','l','n','g') #define MS_SLNG_TAG DWRITE_MAKE_OPENTYPE_TAG('s','l','n','g') #ifdef WORDS_BIGENDIAN #define GET_BE_WORD(x) (x) #define GET_BE_DWORD(x) (x) #else #define GET_BE_WORD(x) RtlUshortByteSwap(x) #define GET_BE_DWORD(x) RtlUlongByteSwap(x) #endif typedef struct { CHAR TTCTag[4]; DWORD Version; DWORD numFonts; DWORD OffsetTable[1]; } TTC_Header_V1; typedef struct { DWORD version; WORD numTables; WORD searchRange; WORD entrySelector; WORD rangeShift; } TTC_SFNT_V1; typedef struct { DWORD tag; DWORD checkSum; DWORD offset; DWORD length; } TT_TableRecord; struct cmap_encoding_record { WORD platformID; WORD encodingID; DWORD offset; }; struct cmap_header { WORD version; WORD num_tables; struct cmap_encoding_record tables[1]; } CMAP_Header; typedef struct { DWORD startCharCode; DWORD endCharCode; DWORD startGlyphID; } CMAP_SegmentedCoverage_group; struct cmap_segmented_coverage { WORD format; WORD reserved; DWORD length; DWORD language; DWORD num_groups; CMAP_SegmentedCoverage_group groups[1]; }; struct cmap_segment_mapping { WORD format; WORD length; WORD language; WORD seg_count_x2; WORD search_range; WORD entry_selector; WORD range_shift; WORD end_code[1]; }; enum OPENTYPE_CMAP_TABLE_FORMAT { OPENTYPE_CMAP_TABLE_SEGMENT_MAPPING = 4, OPENTYPE_CMAP_TABLE_SEGMENTED_COVERAGE = 12 }; enum opentype_cmap_table_platform { OPENTYPE_CMAP_TABLE_PLATFORM_WIN = 3, }; enum opentype_cmap_table_encoding { OPENTYPE_CMAP_TABLE_ENCODING_SYMBOL = 0, OPENTYPE_CMAP_TABLE_ENCODING_UNICODE_BMP = 1, OPENTYPE_CMAP_TABLE_ENCODING_UNICODE_FULL = 10, }; /* PANOSE is 10 bytes in size, need to pack the structure properly */ #include "pshpack2.h" typedef struct { USHORT majorVersion; USHORT minorVersion; ULONG revision; ULONG checksumadj; ULONG magic; USHORT flags; USHORT unitsPerEm; ULONGLONG created; ULONGLONG modified; SHORT xMin; SHORT yMin; SHORT xMax; SHORT yMax; USHORT macStyle; USHORT lowestRecPPEM; SHORT direction_hint; SHORT index_format; SHORT glyphdata_format; } TT_HEAD; enum TT_HEAD_MACSTYLE { TT_HEAD_MACSTYLE_BOLD = 1 << 0, TT_HEAD_MACSTYLE_ITALIC = 1 << 1, TT_HEAD_MACSTYLE_UNDERLINE = 1 << 2, TT_HEAD_MACSTYLE_OUTLINE = 1 << 3, TT_HEAD_MACSTYLE_SHADOW = 1 << 4, TT_HEAD_MACSTYLE_CONDENSED = 1 << 5, TT_HEAD_MACSTYLE_EXTENDED = 1 << 6, }; typedef struct { ULONG Version; ULONG italicAngle; SHORT underlinePosition; SHORT underlineThickness; ULONG fixed_pitch; ULONG minmemType42; ULONG maxmemType42; ULONG minmemType1; ULONG maxmemType1; } TT_POST; typedef struct { USHORT version; SHORT xAvgCharWidth; USHORT usWeightClass; USHORT usWidthClass; SHORT fsType; SHORT ySubscriptXSize; SHORT ySubscriptYSize; SHORT ySubscriptXOffset; SHORT ySubscriptYOffset; SHORT ySuperscriptXSize; SHORT ySuperscriptYSize; SHORT ySuperscriptXOffset; SHORT ySuperscriptYOffset; SHORT yStrikeoutSize; SHORT yStrikeoutPosition; SHORT sFamilyClass; PANOSE panose; ULONG ulUnicodeRange1; ULONG ulUnicodeRange2; ULONG ulUnicodeRange3; ULONG ulUnicodeRange4; CHAR achVendID[4]; USHORT fsSelection; USHORT usFirstCharIndex; USHORT usLastCharIndex; /* According to the Apple spec, original version didn't have the below fields, * version numbers were taken from the OpenType spec. */ /* version 0 (TrueType 1.5) */ USHORT sTypoAscender; USHORT sTypoDescender; USHORT sTypoLineGap; USHORT usWinAscent; USHORT usWinDescent; /* version 1 (TrueType 1.66) */ ULONG ulCodePageRange1; ULONG ulCodePageRange2; /* version 2 (OpenType 1.2) */ SHORT sxHeight; SHORT sCapHeight; USHORT usDefaultChar; USHORT usBreakChar; USHORT usMaxContext; } TT_OS2_V2; typedef struct { USHORT majorVersion; USHORT minorVersion; SHORT ascender; SHORT descender; SHORT linegap; USHORT advanceWidthMax; SHORT minLeftSideBearing; SHORT minRightSideBearing; SHORT xMaxExtent; SHORT caretSlopeRise; SHORT caretSlopeRun; SHORT caretOffset; SHORT reserved[4]; SHORT metricDataFormat; USHORT numberOfHMetrics; } TT_HHEA; struct sbix_header { WORD version; WORD flags; DWORD num_strikes; DWORD strike_offset[1]; }; struct sbix_strike { WORD ppem; WORD ppi; DWORD glyphdata_offsets[1]; }; struct sbix_glyph_data { WORD originOffsetX; WORD originOffsetY; DWORD graphic_type; BYTE data[1]; }; struct maxp { DWORD version; WORD num_glyphs; }; struct cblc_header { WORD major_version; WORD minor_version; DWORD num_sizes; }; typedef struct { BYTE res[12]; } sbitLineMetrics; struct cblc_bitmapsize_table { DWORD indexSubTableArrayOffset; DWORD indexTablesSize; DWORD numberofIndexSubTables; DWORD colorRef; sbitLineMetrics hori; sbitLineMetrics vert; WORD startGlyphIndex; WORD endGlyphIndex; BYTE ppemX; BYTE ppemY; BYTE bit_depth; BYTE flags; }; struct gasp_range { WORD max_ppem; WORD flags; }; struct gasp_header { WORD version; WORD num_ranges; struct gasp_range ranges[1]; }; enum OS2_FSSELECTION { OS2_FSSELECTION_ITALIC = 1 << 0, OS2_FSSELECTION_UNDERSCORE = 1 << 1, OS2_FSSELECTION_NEGATIVE = 1 << 2, OS2_FSSELECTION_OUTLINED = 1 << 3, OS2_FSSELECTION_STRIKEOUT = 1 << 4, OS2_FSSELECTION_BOLD = 1 << 5, OS2_FSSELECTION_REGULAR = 1 << 6, OS2_FSSELECTION_USE_TYPO_METRICS = 1 << 7, OS2_FSSELECTION_WWS = 1 << 8, OS2_FSSELECTION_OBLIQUE = 1 << 9 }; typedef struct { WORD platformID; WORD encodingID; WORD languageID; WORD nameID; WORD length; WORD offset; } TT_NameRecord; typedef struct { WORD format; WORD count; WORD stringOffset; TT_NameRecord nameRecord[1]; } TT_NAME_V0; struct vdmx_header { WORD version; WORD num_recs; WORD num_ratios; }; struct vdmx_ratio { BYTE bCharSet; BYTE xRatio; BYTE yStartRatio; BYTE yEndRatio; }; struct vdmx_vtable { WORD yPelHeight; SHORT yMax; SHORT yMin; }; struct vdmx_group { WORD recs; BYTE startsz; BYTE endsz; struct vdmx_vtable entries[1]; }; struct ot_feature_record { DWORD tag; WORD offset; }; struct ot_feature_list { WORD feature_count; struct ot_feature_record features[1]; }; struct ot_langsys { WORD lookup_order; /* Reserved */ WORD required_feature_index; WORD feature_count; WORD feature_index[1]; }; struct ot_langsys_record { CHAR tag[4]; WORD langsys; }; struct ot_script { WORD default_langsys; WORD langsys_count; struct ot_langsys_record langsys[1]; } OT_Script; struct ot_script_record { CHAR tag[4]; WORD script; }; struct ot_script_list { WORD script_count; struct ot_script_record scripts[1]; }; enum ot_gdef_class { GDEF_CLASS_UNCLASSIFIED = 0, GDEF_CLASS_BASE = 1, GDEF_CLASS_LIGATURE = 2, GDEF_CLASS_MARK = 3, GDEF_CLASS_COMPONENT = 4, GDEF_CLASS_MAX = GDEF_CLASS_COMPONENT, }; struct gdef_header { DWORD version; WORD classdef; WORD attach_list; WORD ligcaret_list; WORD markattach_classdef; }; struct ot_gdef_classdef_format1 { WORD format; WORD start_glyph; WORD glyph_count; WORD classes[1]; }; struct ot_gdef_class_range { WORD start_glyph; WORD end_glyph; WORD glyph_class; }; struct ot_gdef_classdef_format2 { WORD format; WORD range_count; struct ot_gdef_class_range ranges[1]; }; struct gpos_gsub_header { DWORD version; WORD script_list; WORD feature_list; WORD lookup_list; }; enum gsub_gpos_lookup_flags { LOOKUP_FLAG_RTL = 0x1, LOOKUP_FLAG_IGNORE_BASE = 0x2, LOOKUP_FLAG_IGNORE_LIGATURES = 0x4, LOOKUP_FLAG_IGNORE_MARKS = 0x8, LOOKUP_FLAG_IGNORE_MASK = 0xe, }; enum gpos_lookup_type { GPOS_LOOKUP_SINGLE_ADJUSTMENT = 1, GPOS_LOOKUP_PAIR_ADJUSTMENT = 2, GPOS_LOOKUP_CURSIVE_ATTACHMENT = 3, GPOS_LOOKUP_MARK_TO_BASE_ATTACHMENT = 4, GPOS_LOOKUP_MARK_TO_LIGATURE_ATTACHMENT = 5, GPOS_LOOKUP_MARK_TO_MARK_ATTACHMENT = 6, GPOS_LOOKUP_CONTEXTUAL_POSITION = 7, GPOS_LOOKUP_CONTEXTUAL_CHAINING_POSITION = 8, GPOS_LOOKUP_EXTENSION_POSITION = 9, }; enum gsub_lookup_type { GSUB_LOOKUP_SINGLE_SUBST = 1, GSUB_LOOKUP_MULTIPLE_SUBST = 2, GSUB_LOOKUP_ALTERNATE_SUBST = 3, GSUB_LOOKUP_LIGATURE_SUBST = 4, GSUB_LOOKUP_CONTEXTUAL_SUBST = 5, GSUB_LOOKUP_CHAINING_CONTEXTUAL_SUBST = 6, GSUB_LOOKUP_EXTENSION_SUBST = 7, GSUB_LOOKUP_REVERSE_CHAINING_CONTEXTUAL_SUBST = 8, }; enum gpos_value_format { GPOS_VALUE_X_PLACEMENT = 0x1, GPOS_VALUE_Y_PLACEMENT = 0x2, GPOS_VALUE_X_ADVANCE = 0x4, GPOS_VALUE_Y_ADVANCE = 0x8, GPOS_VALUE_X_PLACEMENT_DEVICE = 0x10, GPOS_VALUE_Y_PLACEMENT_DEVICE = 0x20, GPOS_VALUE_X_ADVANCE_DEVICE = 0x40, GPOS_VALUE_Y_ADVANCE_DEVICE = 0x80, }; enum OPENTYPE_PLATFORM_ID { OPENTYPE_PLATFORM_UNICODE = 0, OPENTYPE_PLATFORM_MAC, OPENTYPE_PLATFORM_ISO, OPENTYPE_PLATFORM_WIN, OPENTYPE_PLATFORM_CUSTOM }; struct ot_gsubgpos_extensionpos_format1 { UINT16 format; UINT16 lookup_type; DWORD extension_offset; }; struct ot_gsub_singlesubst_format1 { UINT16 format; UINT16 coverage; short delta; }; struct ot_gsub_singlesubst_format2 { UINT16 format; UINT16 coverage; UINT16 count; UINT16 substitutes[1]; }; struct ot_gsub_chaincontext_subst_format1 { UINT16 format; UINT16 coverage; UINT16 ruleset_count; UINT16 rulesets[1]; }; struct ot_feature { WORD feature_params; WORD lookup_count; WORD lookuplist_index[1]; }; struct ot_lookup_list { WORD lookup_count; WORD lookup[1]; }; struct ot_lookup_table { WORD lookup_type; WORD flags; WORD subtable_count; WORD subtable[1]; }; #define GLYPH_NOT_COVERED (~0u) struct ot_coverage_format1 { WORD format; WORD glyph_count; WORD glyphs[1]; }; struct ot_coverage_range { WORD start_glyph; WORD end_glyph; WORD startcoverage_index; }; struct ot_coverage_format2 { WORD format; WORD range_count; struct ot_coverage_range ranges[1]; }; struct ot_gpos_device_table { WORD start_size; WORD end_size; WORD format; WORD values[1]; }; struct ot_gpos_singlepos_format1 { WORD format; WORD coverage; WORD value_format; WORD value[1]; }; struct ot_gpos_singlepos_format2 { WORD format; WORD coverage; WORD value_format; WORD value_count; WORD values[1]; }; struct ot_gpos_pairvalue { WORD second_glyph; BYTE data[1]; }; struct ot_gpos_pairset { WORD pairvalue_count; struct ot_gpos_pairvalue pairvalues[1]; }; struct ot_gpos_pairpos_format1 { WORD format; WORD coverage; WORD value_format1; WORD value_format2; WORD pairset_count; WORD pairsets[1]; }; struct ot_gpos_pairpos_format2 { WORD format; WORD coverage; WORD value_format1; WORD value_format2; WORD class_def1; WORD class_def2; WORD class1_count; WORD class2_count; WORD values[1]; }; struct ot_gpos_anchor_format1 { WORD format; short x_coord; short y_coord; }; struct ot_gpos_anchor_format2 { WORD format; short x_coord; short y_coord; WORD anchor_point; }; struct ot_gpos_anchor_format3 { WORD format; short x_coord; short y_coord; WORD x_dev_offset; WORD y_dev_offset; }; struct ot_gpos_cursive_format1 { WORD format; WORD coverage; WORD count; WORD anchors[1]; }; struct ot_gpos_mark_record { WORD mark_class; WORD mark_anchor; }; struct ot_gpos_mark_array { WORD count; struct ot_gpos_mark_record records[1]; }; struct ot_gpos_base_array { WORD count; WORD offsets[1]; }; struct ot_gpos_mark_to_base_format1 { WORD format; WORD mark_coverage; WORD base_coverage; WORD mark_class_count; WORD mark_array; WORD base_array; }; struct ot_gpos_mark_to_lig_format1 { WORD format; WORD mark_coverage; WORD lig_coverage; WORD mark_class_count; WORD mark_array; WORD lig_array; }; struct ot_gpos_mark_to_mark_format1 { WORD format; WORD mark1_coverage; WORD mark2_coverage; WORD mark_class_count; WORD mark1_array; WORD mark2_array; }; typedef struct { WORD SubstFormat; WORD Coverage; WORD DeltaGlyphID; } GSUB_SingleSubstFormat1; typedef struct { WORD SubstFormat; WORD Coverage; WORD GlyphCount; WORD Substitute[1]; } GSUB_SingleSubstFormat2; typedef struct { WORD SubstFormat; WORD ExtensionLookupType; DWORD ExtensionOffset; } GSUB_ExtensionPosFormat1; #include "poppack.h" enum TT_NAME_WINDOWS_ENCODING_ID { TT_NAME_WINDOWS_ENCODING_SYMBOL = 0, TT_NAME_WINDOWS_ENCODING_UCS2, TT_NAME_WINDOWS_ENCODING_SJIS, TT_NAME_WINDOWS_ENCODING_PRC, TT_NAME_WINDOWS_ENCODING_BIG5, TT_NAME_WINDOWS_ENCODING_WANSUNG, TT_NAME_WINDOWS_ENCODING_JOHAB, TT_NAME_WINDOWS_ENCODING_RESERVED1, TT_NAME_WINDOWS_ENCODING_RESERVED2, TT_NAME_WINDOWS_ENCODING_RESERVED3, TT_NAME_WINDOWS_ENCODING_UCS4 }; enum TT_NAME_MAC_ENCODING_ID { TT_NAME_MAC_ENCODING_ROMAN = 0, TT_NAME_MAC_ENCODING_JAPANESE, TT_NAME_MAC_ENCODING_TRAD_CHINESE, TT_NAME_MAC_ENCODING_KOREAN, TT_NAME_MAC_ENCODING_ARABIC, TT_NAME_MAC_ENCODING_HEBREW, TT_NAME_MAC_ENCODING_GREEK, TT_NAME_MAC_ENCODING_RUSSIAN, TT_NAME_MAC_ENCODING_RSYMBOL, TT_NAME_MAC_ENCODING_DEVANAGARI, TT_NAME_MAC_ENCODING_GURMUKHI, TT_NAME_MAC_ENCODING_GUJARATI, TT_NAME_MAC_ENCODING_ORIYA, TT_NAME_MAC_ENCODING_BENGALI, TT_NAME_MAC_ENCODING_TAMIL, TT_NAME_MAC_ENCODING_TELUGU, TT_NAME_MAC_ENCODING_KANNADA, TT_NAME_MAC_ENCODING_MALAYALAM, TT_NAME_MAC_ENCODING_SINHALESE, TT_NAME_MAC_ENCODING_BURMESE, TT_NAME_MAC_ENCODING_KHMER, TT_NAME_MAC_ENCODING_THAI, TT_NAME_MAC_ENCODING_LAOTIAN, TT_NAME_MAC_ENCODING_GEORGIAN, TT_NAME_MAC_ENCODING_ARMENIAN, TT_NAME_MAC_ENCODING_SIMPL_CHINESE, TT_NAME_MAC_ENCODING_TIBETAN, TT_NAME_MAC_ENCODING_MONGOLIAN, TT_NAME_MAC_ENCODING_GEEZ, TT_NAME_MAC_ENCODING_SLAVIC, TT_NAME_MAC_ENCODING_VIETNAMESE, TT_NAME_MAC_ENCODING_SINDHI, TT_NAME_MAC_ENCODING_UNINTERPRETED }; enum TT_NAME_MAC_LANGUAGE_ID { TT_NAME_MAC_LANGID_ENGLISH = 0, TT_NAME_MAC_LANGID_FRENCH, TT_NAME_MAC_LANGID_GERMAN, TT_NAME_MAC_LANGID_ITALIAN, TT_NAME_MAC_LANGID_DUTCH, TT_NAME_MAC_LANGID_SWEDISH, TT_NAME_MAC_LANGID_SPANISH, TT_NAME_MAC_LANGID_DANISH, TT_NAME_MAC_LANGID_PORTUGUESE, TT_NAME_MAC_LANGID_NORWEGIAN, TT_NAME_MAC_LANGID_HEBREW, TT_NAME_MAC_LANGID_JAPANESE, TT_NAME_MAC_LANGID_ARABIC, TT_NAME_MAC_LANGID_FINNISH, TT_NAME_MAC_LANGID_GREEK, TT_NAME_MAC_LANGID_ICELANDIC, TT_NAME_MAC_LANGID_MALTESE, TT_NAME_MAC_LANGID_TURKISH, TT_NAME_MAC_LANGID_CROATIAN, TT_NAME_MAC_LANGID_TRAD_CHINESE, TT_NAME_MAC_LANGID_URDU, TT_NAME_MAC_LANGID_HINDI, TT_NAME_MAC_LANGID_THAI, TT_NAME_MAC_LANGID_KOREAN, TT_NAME_MAC_LANGID_LITHUANIAN, TT_NAME_MAC_LANGID_POLISH, TT_NAME_MAC_LANGID_HUNGARIAN, TT_NAME_MAC_LANGID_ESTONIAN, TT_NAME_MAC_LANGID_LATVIAN, TT_NAME_MAC_LANGID_SAMI, TT_NAME_MAC_LANGID_FAROESE, TT_NAME_MAC_LANGID_FARSI, TT_NAME_MAC_LANGID_RUSSIAN, TT_NAME_MAC_LANGID_SIMPL_CHINESE, TT_NAME_MAC_LANGID_FLEMISH, TT_NAME_MAC_LANGID_GAELIC, TT_NAME_MAC_LANGID_ALBANIAN, TT_NAME_MAC_LANGID_ROMANIAN, TT_NAME_MAC_LANGID_CZECH, TT_NAME_MAC_LANGID_SLOVAK, TT_NAME_MAC_LANGID_SLOVENIAN, TT_NAME_MAC_LANGID_YIDDISH, TT_NAME_MAC_LANGID_SERBIAN, TT_NAME_MAC_LANGID_MACEDONIAN, TT_NAME_MAC_LANGID_BULGARIAN, TT_NAME_MAC_LANGID_UKRAINIAN, TT_NAME_MAC_LANGID_BYELORUSSIAN, TT_NAME_MAC_LANGID_UZBEK, TT_NAME_MAC_LANGID_KAZAKH, TT_NAME_MAC_LANGID_AZERB_CYR, TT_NAME_MAC_LANGID_AZERB_ARABIC, TT_NAME_MAC_LANGID_ARMENIAN, TT_NAME_MAC_LANGID_GEORGIAN, TT_NAME_MAC_LANGID_MOLDAVIAN, TT_NAME_MAC_LANGID_KIRGHIZ, TT_NAME_MAC_LANGID_TAJIKI, TT_NAME_MAC_LANGID_TURKMEN, TT_NAME_MAC_LANGID_MONGOLIAN, TT_NAME_MAC_LANGID_MONGOLIAN_CYR, TT_NAME_MAC_LANGID_PASHTO, TT_NAME_MAC_LANGID_KURDISH, TT_NAME_MAC_LANGID_KASHMIRI, TT_NAME_MAC_LANGID_SINDHI, TT_NAME_MAC_LANGID_TIBETAN, TT_NAME_MAC_LANGID_NEPALI, TT_NAME_MAC_LANGID_SANSKRIT, TT_NAME_MAC_LANGID_MARATHI, TT_NAME_MAC_LANGID_BENGALI, TT_NAME_MAC_LANGID_ASSAMESE, TT_NAME_MAC_LANGID_GUJARATI, TT_NAME_MAC_LANGID_PUNJABI, TT_NAME_MAC_LANGID_ORIYA, TT_NAME_MAC_LANGID_MALAYALAM, TT_NAME_MAC_LANGID_KANNADA, TT_NAME_MAC_LANGID_TAMIL, TT_NAME_MAC_LANGID_TELUGU, TT_NAME_MAC_LANGID_SINHALESE, TT_NAME_MAC_LANGID_BURMESE, TT_NAME_MAC_LANGID_KHMER, TT_NAME_MAC_LANGID_LAO, TT_NAME_MAC_LANGID_VIETNAMESE, TT_NAME_MAC_LANGID_INDONESIAN, TT_NAME_MAC_LANGID_TAGALOG, TT_NAME_MAC_LANGID_MALAY_ROMAN, TT_NAME_MAC_LANGID_MALAY_ARABIC, TT_NAME_MAC_LANGID_AMHARIC, TT_NAME_MAC_LANGID_TIGRINYA, TT_NAME_MAC_LANGID_GALLA, TT_NAME_MAC_LANGID_SOMALI, TT_NAME_MAC_LANGID_SWAHILI, TT_NAME_MAC_LANGID_KINYARWANDA, TT_NAME_MAC_LANGID_RUNDI, TT_NAME_MAC_LANGID_NYANJA, TT_NAME_MAC_LANGID_MALAGASY, TT_NAME_MAC_LANGID_ESPERANTO, TT_NAME_MAC_LANGID_WELSH = 128, TT_NAME_MAC_LANGID_BASQUE, TT_NAME_MAC_LANGID_CATALAN, TT_NAME_MAC_LANGID_LATIN, TT_NAME_MAC_LANGID_QUECHUA, TT_NAME_MAC_LANGID_GUARANI, TT_NAME_MAC_LANGID_AYMARA, TT_NAME_MAC_LANGID_TATAR, TT_NAME_MAC_LANGID_UIGHUR, TT_NAME_MAC_LANGID_DZONGKHA, TT_NAME_MAC_LANGID_JAVANESE, TT_NAME_MAC_LANGID_SUNDANESE, TT_NAME_MAC_LANGID_GALICIAN, TT_NAME_MAC_LANGID_AFRIKAANS, TT_NAME_MAC_LANGID_BRETON, TT_NAME_MAC_LANGID_INUKTITUT, TT_NAME_MAC_LANGID_SCOTTISH_GAELIC, TT_NAME_MAC_LANGID_MANX_GAELIC, TT_NAME_MAC_LANGID_IRISH_GAELIC, TT_NAME_MAC_LANGID_TONGAN, TT_NAME_MAC_LANGID_GREEK_POLYTONIC, TT_NAME_MAC_LANGID_GREENLANDIC, TT_NAME_MAC_LANGID_AZER_ROMAN }; /* Names are indexed with TT_NAME_MAC_LANGUAGE_ID values */ static const char name_mac_langid_to_locale[][10] = { "en-US", "fr-FR", "de-DE", "it-IT", "nl-NL", "sv-SE", "es-ES", "da-DA", "pt-PT", "no-NO", "he-IL", "ja-JP", "ar-AR", "fi-FI", "el-GR", "is-IS", "mt-MT", "tr-TR", "hr-HR", "zh-HK", "ur-PK", "hi-IN", "th-TH", "ko-KR", "lt-LT", "pl-PL", "hu-HU", "et-EE", "lv-LV", "se-NO", "fo-FO", "fa-IR", "ru-RU", "zh-CN", "nl-BE", "gd-GB", "sq-AL", "ro-RO", "cs-CZ", "sk-SK", "sl-SI", "", "sr-Latn", "mk-MK", "bg-BG", "uk-UA", "be-BY", "uz-Latn", "kk-KZ", "az-Cyrl-AZ", "az-AZ", "hy-AM", "ka-GE", "", "", "tg-TJ", "tk-TM", "mn-Mong", "mn-MN", "ps-AF", "ku-Arab", "", "sd-Arab", "bo-CN", "ne-NP", "sa-IN", "mr-IN", "bn-IN", "as-IN", "gu-IN", "pa-Arab", "or-IN", "ml-IN", "kn-IN", "ta-LK", "te-IN", "si-LK", "", "km-KH", "lo-LA", "vi-VN", "id-ID", "", "ms-MY", "ms-Arab", "am-ET", "ti-ET", "", "", "sw-KE", "rw-RW", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "cy-GB", "eu-ES", "ca-ES", "", "", "", "", "tt-RU", "ug-CN", "", "", "", "gl-ES", "af-ZA", "br-FR", "iu-Latn-CA", "gd-GB", "", "ga-IE", "", "", "kl-GL", "az-Latn" }; enum OPENTYPE_STRING_ID { OPENTYPE_STRING_COPYRIGHT_NOTICE = 0, OPENTYPE_STRING_FAMILY_NAME, OPENTYPE_STRING_SUBFAMILY_NAME, OPENTYPE_STRING_UNIQUE_IDENTIFIER, OPENTYPE_STRING_FULL_FONTNAME, OPENTYPE_STRING_VERSION_STRING, OPENTYPE_STRING_POSTSCRIPT_FONTNAME, OPENTYPE_STRING_TRADEMARK, OPENTYPE_STRING_MANUFACTURER, OPENTYPE_STRING_DESIGNER, OPENTYPE_STRING_DESCRIPTION, OPENTYPE_STRING_VENDOR_URL, OPENTYPE_STRING_DESIGNER_URL, OPENTYPE_STRING_LICENSE_DESCRIPTION, OPENTYPE_STRING_LICENSE_INFO_URL, OPENTYPE_STRING_RESERVED_ID15, OPENTYPE_STRING_TYPOGRAPHIC_FAMILY_NAME, OPENTYPE_STRING_TYPOGRAPHIC_SUBFAMILY_NAME, OPENTYPE_STRING_COMPATIBLE_FULLNAME, OPENTYPE_STRING_SAMPLE_TEXT, OPENTYPE_STRING_POSTSCRIPT_CID_NAME, OPENTYPE_STRING_WWS_FAMILY_NAME, OPENTYPE_STRING_WWS_SUBFAMILY_NAME }; static const UINT16 dwriteid_to_opentypeid[DWRITE_INFORMATIONAL_STRING_WEIGHT_STRETCH_STYLE_FAMILY_NAME + 1] = { (UINT16)-1, /* DWRITE_INFORMATIONAL_STRING_NONE is not used */ OPENTYPE_STRING_COPYRIGHT_NOTICE, OPENTYPE_STRING_VERSION_STRING, OPENTYPE_STRING_TRADEMARK, OPENTYPE_STRING_MANUFACTURER, OPENTYPE_STRING_DESIGNER, OPENTYPE_STRING_DESIGNER_URL, OPENTYPE_STRING_DESCRIPTION, OPENTYPE_STRING_VENDOR_URL, OPENTYPE_STRING_LICENSE_DESCRIPTION, OPENTYPE_STRING_LICENSE_INFO_URL, OPENTYPE_STRING_FAMILY_NAME, OPENTYPE_STRING_SUBFAMILY_NAME, OPENTYPE_STRING_TYPOGRAPHIC_FAMILY_NAME, OPENTYPE_STRING_TYPOGRAPHIC_SUBFAMILY_NAME, OPENTYPE_STRING_SAMPLE_TEXT, OPENTYPE_STRING_FULL_FONTNAME, OPENTYPE_STRING_POSTSCRIPT_FONTNAME, OPENTYPE_STRING_POSTSCRIPT_CID_NAME, OPENTYPE_STRING_WWS_FAMILY_NAME, }; /* CPAL table */ struct cpal_header_0 { USHORT version; USHORT num_palette_entries; USHORT num_palettes; USHORT num_color_records; ULONG offset_first_color_record; USHORT color_record_indices[1]; }; struct cpal_color_record { BYTE blue; BYTE green; BYTE red; BYTE alpha; }; /* COLR table */ struct colr_header { USHORT version; USHORT num_baseglyph_records; ULONG offset_baseglyph_records; ULONG offset_layer_records; USHORT num_layer_records; }; struct colr_baseglyph_record { USHORT glyph; USHORT first_layer_index; USHORT num_layers; }; struct colr_layer_record { USHORT glyph; USHORT palette_index; }; struct meta_data_map { DWORD tag; DWORD offset; DWORD length; }; struct meta_header { DWORD version; DWORD flags; DWORD reserved; DWORD data_maps_count; struct meta_data_map maps[1]; }; static const void *table_read_ensure(const struct dwrite_fonttable *table, unsigned int offset, unsigned int size) { if (size > table->size || offset > table->size - size) return NULL; return table->data + offset; } static WORD table_read_be_word(const struct dwrite_fonttable *table, unsigned int offset) { const WORD *ptr = table_read_ensure(table, offset, sizeof(*ptr)); return ptr ? GET_BE_WORD(*ptr) : 0; } static DWORD table_read_be_dword(const struct dwrite_fonttable *table, unsigned int offset) { const DWORD *ptr = table_read_ensure(table, offset, sizeof(*ptr)); return ptr ? GET_BE_DWORD(*ptr) : 0; } static DWORD table_read_dword(const struct dwrite_fonttable *table, unsigned int offset) { const DWORD *ptr = table_read_ensure(table, offset, sizeof(*ptr)); return ptr ? *ptr : 0; } BOOL is_face_type_supported(DWRITE_FONT_FACE_TYPE type) { return (type == DWRITE_FONT_FACE_TYPE_CFF) || (type == DWRITE_FONT_FACE_TYPE_TRUETYPE) || (type == DWRITE_FONT_FACE_TYPE_OPENTYPE_COLLECTION) || (type == DWRITE_FONT_FACE_TYPE_RAW_CFF); } typedef HRESULT (*dwrite_fontfile_analyzer)(IDWriteFontFileStream *stream, UINT32 *font_count, DWRITE_FONT_FILE_TYPE *file_type, DWRITE_FONT_FACE_TYPE *face_type); static HRESULT opentype_ttc_analyzer(IDWriteFontFileStream *stream, UINT32 *font_count, DWRITE_FONT_FILE_TYPE *file_type, DWRITE_FONT_FACE_TYPE *face_type) { static const DWORD ttctag = MS_TTCF_TAG; const TTC_Header_V1 *header; void *context; HRESULT hr; hr = IDWriteFontFileStream_ReadFileFragment(stream, (const void**)&header, 0, sizeof(header), &context); if (FAILED(hr)) return hr; if (!memcmp(header->TTCTag, &ttctag, sizeof(ttctag))) { *font_count = GET_BE_DWORD(header->numFonts); *file_type = DWRITE_FONT_FILE_TYPE_OPENTYPE_COLLECTION; *face_type = DWRITE_FONT_FACE_TYPE_OPENTYPE_COLLECTION; } IDWriteFontFileStream_ReleaseFileFragment(stream, context); return *file_type != DWRITE_FONT_FILE_TYPE_UNKNOWN ? S_OK : S_FALSE; } static HRESULT opentype_ttf_analyzer(IDWriteFontFileStream *stream, UINT32 *font_count, DWRITE_FONT_FILE_TYPE *file_type, DWRITE_FONT_FACE_TYPE *face_type) { const DWORD *header; void *context; HRESULT hr; hr = IDWriteFontFileStream_ReadFileFragment(stream, (const void**)&header, 0, sizeof(*header), &context); if (FAILED(hr)) return hr; if (GET_BE_DWORD(*header) == 0x10000) { *font_count = 1; *file_type = DWRITE_FONT_FILE_TYPE_TRUETYPE; *face_type = DWRITE_FONT_FACE_TYPE_TRUETYPE; } IDWriteFontFileStream_ReleaseFileFragment(stream, context); return *file_type != DWRITE_FONT_FILE_TYPE_UNKNOWN ? S_OK : S_FALSE; } static HRESULT opentype_otf_analyzer(IDWriteFontFileStream *stream, UINT32 *font_count, DWRITE_FONT_FILE_TYPE *file_type, DWRITE_FONT_FACE_TYPE *face_type) { const DWORD *header; void *context; HRESULT hr; hr = IDWriteFontFileStream_ReadFileFragment(stream, (const void**)&header, 0, sizeof(*header), &context); if (FAILED(hr)) return hr; if (GET_BE_DWORD(*header) == MS_OTTO_TAG) { *font_count = 1; *file_type = DWRITE_FONT_FILE_TYPE_CFF; *face_type = DWRITE_FONT_FACE_TYPE_CFF; } IDWriteFontFileStream_ReleaseFileFragment(stream, context); return *file_type != DWRITE_FONT_FILE_TYPE_UNKNOWN ? S_OK : S_FALSE; } static HRESULT opentype_type1_analyzer(IDWriteFontFileStream *stream, UINT32 *font_count, DWRITE_FONT_FILE_TYPE *file_type, DWRITE_FONT_FACE_TYPE *face_type) { #include "pshpack1.h" /* Specified in Adobe TechNote #5178 */ struct pfm_header { WORD dfVersion; DWORD dfSize; char data0[95]; DWORD dfDevice; char data1[12]; }; #include "poppack.h" struct type1_header { WORD tag; char data[14]; }; const struct type1_header *header; void *context; HRESULT hr; hr = IDWriteFontFileStream_ReadFileFragment(stream, (const void**)&header, 0, sizeof(*header), &context); if (FAILED(hr)) return hr; /* tag is followed by plain text section */ if (header->tag == 0x8001 && (!memcmp(header->data, "%!PS-AdobeFont", 14) || !memcmp(header->data, "%!FontType", 10))) { *font_count = 1; *file_type = DWRITE_FONT_FILE_TYPE_TYPE1_PFB; *face_type = DWRITE_FONT_FACE_TYPE_TYPE1; } IDWriteFontFileStream_ReleaseFileFragment(stream, context); /* let's see if it's a .pfm metrics file */ if (*file_type == DWRITE_FONT_FILE_TYPE_UNKNOWN) { const struct pfm_header *pfm_header; UINT64 filesize; DWORD offset; BOOL header_checked; hr = IDWriteFontFileStream_GetFileSize(stream, &filesize); if (FAILED(hr)) return hr; hr = IDWriteFontFileStream_ReadFileFragment(stream, (const void**)&pfm_header, 0, sizeof(*pfm_header), &context); if (FAILED(hr)) return hr; offset = pfm_header->dfDevice; header_checked = pfm_header->dfVersion == 0x100 && pfm_header->dfSize == filesize; IDWriteFontFileStream_ReleaseFileFragment(stream, context); /* as a last test check static string in PostScript information section */ if (header_checked) { static const char postscript[] = "PostScript"; char *devtype_name; hr = IDWriteFontFileStream_ReadFileFragment(stream, (const void**)&devtype_name, offset, sizeof(postscript), &context); if (FAILED(hr)) return hr; if (!memcmp(devtype_name, postscript, sizeof(postscript))) { *font_count = 1; *file_type = DWRITE_FONT_FILE_TYPE_TYPE1_PFM; *face_type = DWRITE_FONT_FACE_TYPE_TYPE1; } IDWriteFontFileStream_ReleaseFileFragment(stream, context); } } return *file_type != DWRITE_FONT_FILE_TYPE_UNKNOWN ? S_OK : S_FALSE; } HRESULT opentype_analyze_font(IDWriteFontFileStream *stream, BOOL *supported, DWRITE_FONT_FILE_TYPE *file_type, DWRITE_FONT_FACE_TYPE *face_type, UINT32 *face_count) { static dwrite_fontfile_analyzer fontfile_analyzers[] = { opentype_ttf_analyzer, opentype_otf_analyzer, opentype_ttc_analyzer, opentype_type1_analyzer, NULL }; dwrite_fontfile_analyzer *analyzer = fontfile_analyzers; DWRITE_FONT_FACE_TYPE face; HRESULT hr; if (!face_type) face_type = &face; *file_type = DWRITE_FONT_FILE_TYPE_UNKNOWN; *face_type = DWRITE_FONT_FACE_TYPE_UNKNOWN; *face_count = 0; while (*analyzer) { hr = (*analyzer)(stream, face_count, file_type, face_type); if (FAILED(hr)) return hr; if (hr == S_OK) break; analyzer++; } *supported = is_face_type_supported(*face_type); return S_OK; } HRESULT opentype_try_get_font_table(const struct file_stream_desc *stream_desc, UINT32 tag, const void **table_data, void **table_context, UINT32 *table_size, BOOL *found) { void *table_directory_context, *sfnt_context; TT_TableRecord *table_record = NULL; TTC_SFNT_V1 *font_header = NULL; UINT32 table_offset = 0; UINT16 table_count; HRESULT hr; if (found) *found = FALSE; if (table_size) *table_size = 0; *table_data = NULL; *table_context = NULL; if (stream_desc->face_type == DWRITE_FONT_FACE_TYPE_OPENTYPE_COLLECTION) { const TTC_Header_V1 *ttc_header; void * ttc_context; hr = IDWriteFontFileStream_ReadFileFragment(stream_desc->stream, (const void**)&ttc_header, 0, sizeof(*ttc_header), &ttc_context); if (SUCCEEDED(hr)) { if (stream_desc->face_index >= GET_BE_DWORD(ttc_header->numFonts)) hr = E_INVALIDARG; else { table_offset = GET_BE_DWORD(ttc_header->OffsetTable[stream_desc->face_index]); hr = IDWriteFontFileStream_ReadFileFragment(stream_desc->stream, (const void**)&font_header, table_offset, sizeof(*font_header), &sfnt_context); } IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, ttc_context); } } else hr = IDWriteFontFileStream_ReadFileFragment(stream_desc->stream, (const void**)&font_header, 0, sizeof(*font_header), &sfnt_context); if (FAILED(hr)) return hr; table_count = GET_BE_WORD(font_header->numTables); table_offset += sizeof(*font_header); IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, sfnt_context); hr = IDWriteFontFileStream_ReadFileFragment(stream_desc->stream, (const void **)&table_record, table_offset, table_count * sizeof(*table_record), &table_directory_context); if (hr == S_OK) { UINT16 i; for (i = 0; i < table_count; i++) { if (table_record->tag == tag) { UINT32 offset = GET_BE_DWORD(table_record->offset); UINT32 length = GET_BE_DWORD(table_record->length); if (found) *found = TRUE; if (table_size) *table_size = length; hr = IDWriteFontFileStream_ReadFileFragment(stream_desc->stream, table_data, offset, length, table_context); break; } table_record++; } IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, table_directory_context); } return hr; } static HRESULT opentype_get_font_table(const struct file_stream_desc *stream_desc, UINT32 tag, struct dwrite_fonttable *table) { return opentype_try_get_font_table(stream_desc, tag, (const void **)&table->data, &table->context, &table->size, &table->exists); } /********** * CMAP **********/ static unsigned int opentype_cmap_get_unicode_ranges_count(const struct dwrite_fonttable *cmap) { unsigned int i, num_tables, count = 0; const struct cmap_header *header; num_tables = table_read_be_word(cmap, FIELD_OFFSET(struct cmap_header, num_tables)); header = table_read_ensure(cmap, 0, FIELD_OFFSET(struct cmap_header, tables[num_tables])); if (!header) return 0; for (i = 0; i < num_tables; ++i) { unsigned int format, offset; if (GET_BE_WORD(header->tables[i].platformID) != 3) continue; offset = GET_BE_DWORD(header->tables[i].offset); format = table_read_be_word(cmap, offset); switch (format) { case OPENTYPE_CMAP_TABLE_SEGMENT_MAPPING: { count += table_read_be_word(cmap, offset + FIELD_OFFSET(struct cmap_segment_mapping, seg_count_x2)) / 2; break; } case OPENTYPE_CMAP_TABLE_SEGMENTED_COVERAGE: { count += table_read_be_dword(cmap, offset + FIELD_OFFSET(struct cmap_segmented_coverage, num_groups)); break; } default: FIXME("table format %u is not supported.\n", format); } } return count; } HRESULT opentype_cmap_get_unicode_ranges(const struct file_stream_desc *stream_desc, unsigned int max_count, DWRITE_UNICODE_RANGE *ranges, unsigned int *count) { unsigned int i, num_tables, k = 0; const struct cmap_header *header; struct dwrite_fonttable cmap; opentype_get_font_table(stream_desc, MS_CMAP_TAG, &cmap); if (!cmap.exists) return E_FAIL; *count = opentype_cmap_get_unicode_ranges_count(&cmap); num_tables = table_read_be_word(&cmap, FIELD_OFFSET(struct cmap_header, num_tables)); header = table_read_ensure(&cmap, 0, FIELD_OFFSET(struct cmap_header, tables[num_tables])); if (!header) { IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, cmap.context); return S_OK; } for (i = 0; i < num_tables && k < max_count; ++i) { unsigned int j, offset, format; if (GET_BE_WORD(header->tables[i].platformID) != 3) continue; offset = GET_BE_DWORD(header->tables[i].offset); format = table_read_be_word(&cmap, offset); switch (format) { case OPENTYPE_CMAP_TABLE_SEGMENT_MAPPING: { unsigned int segment_count = table_read_be_word(&cmap, offset + FIELD_OFFSET(struct cmap_segment_mapping, seg_count_x2)) / 2; const UINT16 *start_code = table_read_ensure(&cmap, offset, FIELD_OFFSET(struct cmap_segment_mapping, end_code[segment_count]) + 2 /* reservedPad */ + 2 * segment_count /* start code array */); const UINT16 *end_code = table_read_ensure(&cmap, offset, FIELD_OFFSET(struct cmap_segment_mapping, end_code[segment_count])); if (!start_code || !end_code) continue; for (j = 0; j < segment_count && GET_BE_WORD(end_code[j]) != 0xffff && k < max_count; ++j, ++k) { ranges[k].first = GET_BE_WORD(start_code[j]); ranges[k].last = GET_BE_WORD(end_code[j]); } break; } case OPENTYPE_CMAP_TABLE_SEGMENTED_COVERAGE: { unsigned int num_groups = table_read_be_dword(&cmap, offset + FIELD_OFFSET(struct cmap_segmented_coverage, num_groups)); const struct cmap_segmented_coverage *coverage; coverage = table_read_ensure(&cmap, offset, FIELD_OFFSET(struct cmap_segmented_coverage, groups[num_groups])); for (j = 0; j < num_groups && k < max_count; j++, k++) { ranges[k].first = GET_BE_DWORD(coverage->groups[j].startCharCode); ranges[k].last = GET_BE_DWORD(coverage->groups[j].endCharCode); } break; } default: FIXME("table format %u unhandled.\n", format); } } IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, cmap.context); return *count > max_count ? E_NOT_SUFFICIENT_BUFFER : S_OK; } void opentype_get_font_typo_metrics(struct file_stream_desc *stream_desc, unsigned int *ascent, unsigned int *descent) { struct dwrite_fonttable os2; const TT_OS2_V2 *data; opentype_get_font_table(stream_desc, MS_OS2_TAG, &os2); data = (const TT_OS2_V2 *)os2.data; *ascent = *descent = 0; if (os2.size >= FIELD_OFFSET(TT_OS2_V2, sTypoLineGap)) { SHORT value = GET_BE_WORD(data->sTypoDescender); *ascent = GET_BE_WORD(data->sTypoAscender); *descent = value < 0 ? -value : 0; } if (data) IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, os2.context); } void opentype_get_font_metrics(struct file_stream_desc *stream_desc, DWRITE_FONT_METRICS1 *metrics, DWRITE_CARET_METRICS *caret) { struct dwrite_fonttable os2, head, post, hhea; const TT_OS2_V2 *tt_os2; const TT_HEAD *tt_head; const TT_POST *tt_post; const TT_HHEA *tt_hhea; memset(metrics, 0, sizeof(*metrics)); opentype_get_font_table(stream_desc, MS_OS2_TAG, &os2); opentype_get_font_table(stream_desc, MS_HEAD_TAG, &head); opentype_get_font_table(stream_desc, MS_POST_TAG, &post); opentype_get_font_table(stream_desc, MS_HHEA_TAG, &hhea); tt_head = (const TT_HEAD *)head.data; tt_os2 = (const TT_OS2_V2 *)os2.data; tt_post = (const TT_POST *)post.data; tt_hhea = (const TT_HHEA *)hhea.data; if (tt_head) { metrics->designUnitsPerEm = GET_BE_WORD(tt_head->unitsPerEm); metrics->glyphBoxLeft = GET_BE_WORD(tt_head->xMin); metrics->glyphBoxTop = GET_BE_WORD(tt_head->yMax); metrics->glyphBoxRight = GET_BE_WORD(tt_head->xMax); metrics->glyphBoxBottom = GET_BE_WORD(tt_head->yMin); } if (caret) { if (tt_hhea) { caret->slopeRise = GET_BE_WORD(tt_hhea->caretSlopeRise); caret->slopeRun = GET_BE_WORD(tt_hhea->caretSlopeRun); caret->offset = GET_BE_WORD(tt_hhea->caretOffset); } else { caret->slopeRise = 0; caret->slopeRun = 0; caret->offset = 0; } } if (tt_os2) { USHORT version = GET_BE_WORD(tt_os2->version); metrics->ascent = GET_BE_WORD(tt_os2->usWinAscent); /* Some fonts have usWinDescent value stored as signed short, which could be wrongly interpreted as large unsigned value. */ metrics->descent = abs((SHORT)GET_BE_WORD(tt_os2->usWinDescent)); /* line gap is estimated using two sets of ascender/descender values and 'hhea' line gap */ if (tt_hhea) { SHORT descender = (SHORT)GET_BE_WORD(tt_hhea->descender); INT32 linegap; linegap = GET_BE_WORD(tt_hhea->ascender) + abs(descender) + GET_BE_WORD(tt_hhea->linegap) - metrics->ascent - metrics->descent; metrics->lineGap = linegap > 0 ? linegap : 0; } metrics->strikethroughPosition = GET_BE_WORD(tt_os2->yStrikeoutPosition); metrics->strikethroughThickness = GET_BE_WORD(tt_os2->yStrikeoutSize); metrics->subscriptPositionX = GET_BE_WORD(tt_os2->ySubscriptXOffset); /* Y offset is stored as positive offset below baseline */ metrics->subscriptPositionY = -GET_BE_WORD(tt_os2->ySubscriptYOffset); metrics->subscriptSizeX = GET_BE_WORD(tt_os2->ySubscriptXSize); metrics->subscriptSizeY = GET_BE_WORD(tt_os2->ySubscriptYSize); metrics->superscriptPositionX = GET_BE_WORD(tt_os2->ySuperscriptXOffset); metrics->superscriptPositionY = GET_BE_WORD(tt_os2->ySuperscriptYOffset); metrics->superscriptSizeX = GET_BE_WORD(tt_os2->ySuperscriptXSize); metrics->superscriptSizeY = GET_BE_WORD(tt_os2->ySuperscriptYSize); /* version 2 fields */ if (version >= 2) { metrics->capHeight = GET_BE_WORD(tt_os2->sCapHeight); metrics->xHeight = GET_BE_WORD(tt_os2->sxHeight); } if (GET_BE_WORD(tt_os2->fsSelection) & OS2_FSSELECTION_USE_TYPO_METRICS) { SHORT descent = GET_BE_WORD(tt_os2->sTypoDescender); metrics->ascent = GET_BE_WORD(tt_os2->sTypoAscender); metrics->descent = descent < 0 ? -descent : 0; metrics->lineGap = GET_BE_WORD(tt_os2->sTypoLineGap); metrics->hasTypographicMetrics = TRUE; } } else { metrics->strikethroughPosition = metrics->designUnitsPerEm / 3; if (tt_hhea) { metrics->ascent = GET_BE_WORD(tt_hhea->ascender); metrics->descent = abs((SHORT)GET_BE_WORD(tt_hhea->descender)); } } if (tt_post) { metrics->underlinePosition = GET_BE_WORD(tt_post->underlinePosition); metrics->underlineThickness = GET_BE_WORD(tt_post->underlineThickness); } if (metrics->underlineThickness == 0) metrics->underlineThickness = metrics->designUnitsPerEm / 14; if (metrics->strikethroughThickness == 0) metrics->strikethroughThickness = metrics->underlineThickness; /* estimate missing metrics */ if (metrics->xHeight == 0) metrics->xHeight = metrics->designUnitsPerEm / 2; if (metrics->capHeight == 0) metrics->capHeight = metrics->designUnitsPerEm * 7 / 10; if (tt_os2) IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, os2.context); if (tt_head) IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, head.context); if (tt_post) IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, post.context); if (tt_hhea) IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, hhea.context); } void opentype_get_font_properties(struct file_stream_desc *stream_desc, struct dwrite_font_props *props) { struct dwrite_fonttable os2, head, colr, cpal; BOOL is_symbol, is_monospaced; const TT_OS2_V2 *tt_os2; const TT_HEAD *tt_head; opentype_get_font_table(stream_desc, MS_OS2_TAG, &os2); opentype_get_font_table(stream_desc, MS_HEAD_TAG, &head); tt_os2 = (const TT_OS2_V2 *)os2.data; tt_head = (const TT_HEAD *)head.data; /* default stretch, weight and style to normal */ props->stretch = DWRITE_FONT_STRETCH_NORMAL; props->weight = DWRITE_FONT_WEIGHT_NORMAL; props->style = DWRITE_FONT_STYLE_NORMAL; memset(&props->panose, 0, sizeof(props->panose)); memset(&props->fontsig, 0, sizeof(props->fontsig)); memset(&props->lf, 0, sizeof(props->lf)); props->flags = 0; /* DWRITE_FONT_STRETCH enumeration values directly match font data values */ if (tt_os2) { USHORT version = GET_BE_WORD(tt_os2->version); USHORT fsSelection = GET_BE_WORD(tt_os2->fsSelection); USHORT usWeightClass = GET_BE_WORD(tt_os2->usWeightClass); USHORT usWidthClass = GET_BE_WORD(tt_os2->usWidthClass); if (usWidthClass > DWRITE_FONT_STRETCH_UNDEFINED && usWidthClass <= DWRITE_FONT_STRETCH_ULTRA_EXPANDED) props->stretch = usWidthClass; if (usWeightClass >= 1 && usWeightClass <= 9) usWeightClass *= 100; if (usWeightClass > DWRITE_FONT_WEIGHT_ULTRA_BLACK) props->weight = DWRITE_FONT_WEIGHT_ULTRA_BLACK; else if (usWeightClass > 0) props->weight = usWeightClass; if (version >= 4 && (fsSelection & OS2_FSSELECTION_OBLIQUE)) props->style = DWRITE_FONT_STYLE_OBLIQUE; else if (fsSelection & OS2_FSSELECTION_ITALIC) props->style = DWRITE_FONT_STYLE_ITALIC; props->lf.lfItalic = !!(fsSelection & OS2_FSSELECTION_ITALIC); memcpy(&props->panose, &tt_os2->panose, sizeof(props->panose)); /* FONTSIGNATURE */ props->fontsig.fsUsb[0] = GET_BE_DWORD(tt_os2->ulUnicodeRange1); props->fontsig.fsUsb[1] = GET_BE_DWORD(tt_os2->ulUnicodeRange2); props->fontsig.fsUsb[2] = GET_BE_DWORD(tt_os2->ulUnicodeRange3); props->fontsig.fsUsb[3] = GET_BE_DWORD(tt_os2->ulUnicodeRange4); if (version) { props->fontsig.fsCsb[0] = GET_BE_DWORD(tt_os2->ulCodePageRange1); props->fontsig.fsCsb[1] = GET_BE_DWORD(tt_os2->ulCodePageRange2); } } else if (tt_head) { USHORT macStyle = GET_BE_WORD(tt_head->macStyle); if (macStyle & TT_HEAD_MACSTYLE_CONDENSED) props->stretch = DWRITE_FONT_STRETCH_CONDENSED; else if (macStyle & TT_HEAD_MACSTYLE_EXTENDED) props->stretch = DWRITE_FONT_STRETCH_EXPANDED; if (macStyle & TT_HEAD_MACSTYLE_BOLD) props->weight = DWRITE_FONT_WEIGHT_BOLD; if (macStyle & TT_HEAD_MACSTYLE_ITALIC) { props->style = DWRITE_FONT_STYLE_ITALIC; props->lf.lfItalic = 1; } } props->lf.lfWeight = props->weight; /* FONT_IS_SYMBOL */ if (!(is_symbol = props->panose.familyKind == DWRITE_PANOSE_FAMILY_SYMBOL)) { struct dwrite_fonttable cmap; int i, offset, num_tables; opentype_get_font_table(stream_desc, MS_CMAP_TAG, &cmap); if (cmap.data) { num_tables = table_read_be_word(&cmap, FIELD_OFFSET(struct cmap_header, num_tables)); offset = FIELD_OFFSET(struct cmap_header, tables); for (i = 0; !is_symbol && i < num_tables; ++i) { WORD platform, encoding; platform = table_read_be_word(&cmap, offset + i * sizeof(struct cmap_encoding_record) + FIELD_OFFSET(struct cmap_encoding_record, platformID)); encoding = table_read_be_word(&cmap, offset + i * sizeof(struct cmap_encoding_record) + FIELD_OFFSET(struct cmap_encoding_record, encodingID)); is_symbol = platform == OPENTYPE_CMAP_TABLE_PLATFORM_WIN && encoding == OPENTYPE_CMAP_TABLE_ENCODING_SYMBOL; } IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, cmap.context); } } if (is_symbol) props->flags |= FONT_IS_SYMBOL; /* FONT_IS_MONOSPACED */ if (!(is_monospaced = props->panose.text.proportion == DWRITE_PANOSE_PROPORTION_MONOSPACED)) { struct dwrite_fonttable post; opentype_get_font_table(stream_desc, MS_POST_TAG, &post); if (post.data) { is_monospaced = !!table_read_dword(&post, FIELD_OFFSET(TT_POST, fixed_pitch)); IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, post.context); } } if (is_monospaced) props->flags |= FONT_IS_MONOSPACED; /* FONT_IS_COLORED */ opentype_get_font_table(stream_desc, MS_COLR_TAG, &colr); if (colr.data) { opentype_get_font_table(stream_desc, MS_CPAL_TAG, &cpal); if (cpal.data) { props->flags |= FONT_IS_COLORED; IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, cpal.context); } IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, colr.context); } TRACE("stretch=%d, weight=%d, style %d\n", props->stretch, props->weight, props->style); if (os2.data) IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, os2.context); if (head.data) IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, head.context); } static UINT get_name_record_codepage(enum OPENTYPE_PLATFORM_ID platform, USHORT encoding) { UINT codepage = 0; switch (platform) { case OPENTYPE_PLATFORM_UNICODE: break; case OPENTYPE_PLATFORM_MAC: switch (encoding) { case TT_NAME_MAC_ENCODING_ROMAN: codepage = 10000; break; case TT_NAME_MAC_ENCODING_JAPANESE: codepage = 10001; break; case TT_NAME_MAC_ENCODING_TRAD_CHINESE: codepage = 10002; break; case TT_NAME_MAC_ENCODING_KOREAN: codepage = 10003; break; case TT_NAME_MAC_ENCODING_ARABIC: codepage = 10004; break; case TT_NAME_MAC_ENCODING_HEBREW: codepage = 10005; break; case TT_NAME_MAC_ENCODING_GREEK: codepage = 10006; break; case TT_NAME_MAC_ENCODING_RUSSIAN: codepage = 10007; break; case TT_NAME_MAC_ENCODING_SIMPL_CHINESE: codepage = 10008; break; case TT_NAME_MAC_ENCODING_THAI: codepage = 10021; break; default: FIXME("encoding %u not handled, platform %d.\n", encoding, platform); break; } break; case OPENTYPE_PLATFORM_WIN: switch (encoding) { case TT_NAME_WINDOWS_ENCODING_SYMBOL: case TT_NAME_WINDOWS_ENCODING_UCS2: break; case TT_NAME_WINDOWS_ENCODING_SJIS: codepage = 932; break; case TT_NAME_WINDOWS_ENCODING_PRC: codepage = 936; break; case TT_NAME_WINDOWS_ENCODING_BIG5: codepage = 950; break; case TT_NAME_WINDOWS_ENCODING_WANSUNG: codepage = 20949; break; case TT_NAME_WINDOWS_ENCODING_JOHAB: codepage = 1361; break; default: FIXME("encoding %u not handled, platform %d.\n", encoding, platform); break; } break; default: FIXME("unknown platform %d\n", platform); } return codepage; } static void get_name_record_locale(enum OPENTYPE_PLATFORM_ID platform, USHORT lang_id, WCHAR *locale, USHORT locale_len) { static const WCHAR enusW[] = {'e','n','-','U','S',0}; switch (platform) { case OPENTYPE_PLATFORM_MAC: { const char *locale_name = NULL; if (lang_id > TT_NAME_MAC_LANGID_AZER_ROMAN) WARN("invalid mac lang id %d\n", lang_id); else if (!name_mac_langid_to_locale[lang_id][0]) FIXME("failed to map mac lang id %d to locale name\n", lang_id); else locale_name = name_mac_langid_to_locale[lang_id]; if (locale_name) MultiByteToWideChar(CP_ACP, 0, name_mac_langid_to_locale[lang_id], -1, locale, locale_len); else strcpyW(locale, enusW); break; } case OPENTYPE_PLATFORM_WIN: if (!LCIDToLocaleName(MAKELCID(lang_id, SORT_DEFAULT), locale, locale_len, 0)) { FIXME("failed to get locale name for lcid=0x%08x\n", MAKELCID(lang_id, SORT_DEFAULT)); strcpyW(locale, enusW); } break; case OPENTYPE_PLATFORM_UNICODE: strcpyW(locale, enusW); break; default: FIXME("unknown platform %d\n", platform); } } static BOOL opentype_decode_namerecord(const TT_NAME_V0 *header, BYTE *storage_area, USHORT recid, IDWriteLocalizedStrings *strings) { const TT_NameRecord *record = &header->nameRecord[recid]; USHORT lang_id, length, offset, encoding, platform; BOOL ret = FALSE; platform = GET_BE_WORD(record->platformID); lang_id = GET_BE_WORD(record->languageID); length = GET_BE_WORD(record->length); offset = GET_BE_WORD(record->offset); encoding = GET_BE_WORD(record->encodingID); if (lang_id < 0x8000) { WCHAR locale[LOCALE_NAME_MAX_LENGTH]; WCHAR *name_string; UINT codepage; codepage = get_name_record_codepage(platform, encoding); get_name_record_locale(platform, lang_id, locale, ARRAY_SIZE(locale)); if (codepage) { DWORD len = MultiByteToWideChar(codepage, 0, (LPSTR)(storage_area + offset), length, NULL, 0); name_string = heap_alloc(sizeof(WCHAR) * (len+1)); MultiByteToWideChar(codepage, 0, (LPSTR)(storage_area + offset), length, name_string, len); name_string[len] = 0; } else { int i; length /= sizeof(WCHAR); name_string = heap_strdupnW((LPWSTR)(storage_area + offset), length); for (i = 0; i < length; i++) name_string[i] = GET_BE_WORD(name_string[i]); } TRACE("string %s for locale %s found\n", debugstr_w(name_string), debugstr_w(locale)); add_localizedstring(strings, locale, name_string); heap_free(name_string); ret = TRUE; } else FIXME("handle NAME format 1\n"); return ret; } static HRESULT opentype_get_font_strings_from_id(const void *table_data, enum OPENTYPE_STRING_ID id, IDWriteLocalizedStrings **strings) { int i, count, candidate_mac, candidate_unicode; const TT_NAME_V0 *header; BYTE *storage_area = 0; WORD format; BOOL exists; HRESULT hr; if (!table_data) return E_FAIL; hr = create_localizedstrings(strings); if (FAILED(hr)) return hr; header = table_data; format = GET_BE_WORD(header->format); switch (format) { case 0: case 1: break; default: FIXME("unsupported NAME format %d\n", format); } storage_area = (LPBYTE)table_data + GET_BE_WORD(header->stringOffset); count = GET_BE_WORD(header->count); exists = FALSE; candidate_unicode = candidate_mac = -1; for (i = 0; i < count; i++) { const TT_NameRecord *record = &header->nameRecord[i]; USHORT platform; if (GET_BE_WORD(record->nameID) != id) continue; platform = GET_BE_WORD(record->platformID); switch (platform) { /* Skip Unicode or Mac entries for now, fonts tend to duplicate those strings as WIN platform entries. If font does not have WIN entry for this id, we will use Mac or Unicode platform entry while assuming en-US locale. */ case OPENTYPE_PLATFORM_UNICODE: if (candidate_unicode == -1) candidate_unicode = i; break; case OPENTYPE_PLATFORM_MAC: if (candidate_mac == -1) candidate_mac = i; break; case OPENTYPE_PLATFORM_WIN: if (opentype_decode_namerecord(header, storage_area, i, *strings)) exists = TRUE; break; default: FIXME("platform %i not supported\n", platform); break; } } if (!exists) { if (candidate_mac != -1) exists = opentype_decode_namerecord(header, storage_area, candidate_mac, *strings); if (!exists && candidate_unicode != -1) exists = opentype_decode_namerecord(header, storage_area, candidate_unicode, *strings); if (!exists) { IDWriteLocalizedStrings_Release(*strings); *strings = NULL; } } if (*strings) sort_localizedstrings(*strings); return exists ? S_OK : E_FAIL; } static WCHAR *meta_get_lng_name(WCHAR *str, WCHAR **ctx) { static const WCHAR delimW[] = {',',' ',0}; WCHAR *ret; if (!str) str = *ctx; while (*str && strchrW(delimW, *str)) str++; if (!*str) return NULL; ret = str++; while (*str && !strchrW(delimW, *str)) str++; if (*str) *str++ = 0; *ctx = str; return ret; } static HRESULT opentype_get_font_strings_from_meta(const struct file_stream_desc *stream_desc, DWRITE_INFORMATIONAL_STRING_ID id, IDWriteLocalizedStrings **ret) { static const WCHAR emptyW[] = { 0 }; const struct meta_data_map *maps; IDWriteLocalizedStrings *strings; struct dwrite_fonttable meta; DWORD version, i, count, tag; HRESULT hr; *ret = NULL; switch (id) { case DWRITE_INFORMATIONAL_STRING_DESIGN_SCRIPT_LANGUAGE_TAG: tag = MS_DLNG_TAG; break; case DWRITE_INFORMATIONAL_STRING_SUPPORTED_SCRIPT_LANGUAGE_TAG: tag = MS_SLNG_TAG; break; default: WARN("Unexpected id %d.\n", id); return S_OK; } if (FAILED(hr = create_localizedstrings(&strings))) return hr; opentype_get_font_table(stream_desc, MS_META_TAG, &meta); if (meta.data) { version = table_read_be_dword(&meta, 0); if (version != 1) { WARN("Unexpected meta table version %d.\n", version); goto end; } count = table_read_be_dword(&meta, FIELD_OFFSET(struct meta_header, data_maps_count)); if (!(maps = table_read_ensure(&meta, FIELD_OFFSET(struct meta_header, maps), count * sizeof(struct meta_data_map)))) goto end; for (i = 0; i < count; ++i) { const char *data; if (maps[i].tag == tag && maps[i].length) { DWORD length = GET_BE_DWORD(maps[i].length), j; if ((data = table_read_ensure(&meta, GET_BE_DWORD(maps[i].offset), length))) { WCHAR *ptrW = heap_alloc((length + 1) * sizeof(WCHAR)), *ctx, *token; if (!ptrW) { hr = E_OUTOFMEMORY; goto end; } /* Data is stored in comma separated list, ASCII range only. */ for (j = 0; j < length; ++j) ptrW[j] = data[j]; ptrW[length] = 0; token = meta_get_lng_name(ptrW, &ctx); while (token) { add_localizedstring(strings, emptyW, token); token = meta_get_lng_name(NULL, &ctx); } heap_free(ptrW); } } } end: IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, meta.context); } if (IDWriteLocalizedStrings_GetCount(strings)) *ret = strings; else IDWriteLocalizedStrings_Release(strings); return hr; } HRESULT opentype_get_font_info_strings(const struct file_stream_desc *stream_desc, DWRITE_INFORMATIONAL_STRING_ID id, IDWriteLocalizedStrings **strings) { struct dwrite_fonttable name; switch (id) { case DWRITE_INFORMATIONAL_STRING_DESIGN_SCRIPT_LANGUAGE_TAG: case DWRITE_INFORMATIONAL_STRING_SUPPORTED_SCRIPT_LANGUAGE_TAG: opentype_get_font_strings_from_meta(stream_desc, id, strings); break; default: opentype_get_font_table(stream_desc, MS_NAME_TAG, &name); opentype_get_font_strings_from_id(name.data, dwriteid_to_opentypeid[id], strings); if (name.context) IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, name.context); } return S_OK; } /* FamilyName locating order is WWS Family Name -> Preferred Family Name -> Family Name. If font claims to have 'Preferred Family Name' in WWS format, then WWS name is not used. */ HRESULT opentype_get_font_familyname(struct file_stream_desc *stream_desc, IDWriteLocalizedStrings **names) { struct dwrite_fonttable os2, name; const TT_OS2_V2 *tt_os2; const void *name_table; HRESULT hr; opentype_get_font_table(stream_desc, MS_OS2_TAG, &os2); opentype_get_font_table(stream_desc, MS_NAME_TAG, &name); tt_os2 = (const TT_OS2_V2 *)os2.data; name_table = (const void *)name.data; *names = NULL; /* if Preferred Family doesn't conform to WWS model try WWS name */ if (tt_os2 && !(GET_BE_WORD(tt_os2->fsSelection) & OS2_FSSELECTION_WWS)) hr = opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_WWS_FAMILY_NAME, names); else hr = E_FAIL; if (FAILED(hr)) hr = opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_TYPOGRAPHIC_FAMILY_NAME, names); if (FAILED(hr)) hr = opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_FAMILY_NAME, names); if (os2.context) IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, os2.context); if (name.context) IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, name.context); return hr; } /* FaceName locating order is WWS Face Name -> Preferred Face Name -> Face Name. If font claims to have 'Preferred Face Name' in WWS format, then WWS name is not used. */ HRESULT opentype_get_font_facename(struct file_stream_desc *stream_desc, WCHAR *lfname, IDWriteLocalizedStrings **names) { struct dwrite_fonttable os2, name; IDWriteLocalizedStrings *lfnames; const TT_OS2_V2 *tt_os2; const void *name_table; HRESULT hr; opentype_get_font_table(stream_desc, MS_OS2_TAG, &os2); opentype_get_font_table(stream_desc, MS_NAME_TAG, &name); tt_os2 = (const TT_OS2_V2 *)os2.data; name_table = name.data; *names = NULL; /* if Preferred Family doesn't conform to WWS model try WWS name */ if (tt_os2 && !(GET_BE_WORD(tt_os2->fsSelection) & OS2_FSSELECTION_WWS)) hr = opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_WWS_SUBFAMILY_NAME, names); else hr = E_FAIL; if (FAILED(hr)) hr = opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_TYPOGRAPHIC_SUBFAMILY_NAME, names); if (FAILED(hr)) hr = opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_SUBFAMILY_NAME, names); /* User locale is preferred, with fallback to en-us. */ *lfname = 0; if (SUCCEEDED(opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_FAMILY_NAME, &lfnames))) { static const WCHAR enusW[] = {'e','n','-','u','s',0}; WCHAR localeW[LOCALE_NAME_MAX_LENGTH]; UINT32 index; BOOL exists; exists = FALSE; if (GetSystemDefaultLocaleName(localeW, ARRAY_SIZE(localeW))) IDWriteLocalizedStrings_FindLocaleName(lfnames, localeW, &index, &exists); if (!exists) IDWriteLocalizedStrings_FindLocaleName(lfnames, enusW, &index, &exists); if (exists) { UINT32 length = 0; WCHAR *nameW; IDWriteLocalizedStrings_GetStringLength(lfnames, index, &length); nameW = heap_alloc((length + 1) * sizeof(WCHAR)); if (nameW) { *nameW = 0; IDWriteLocalizedStrings_GetString(lfnames, index, nameW, length + 1); lstrcpynW(lfname, nameW, LF_FACESIZE); heap_free(nameW); } } IDWriteLocalizedStrings_Release(lfnames); } if (os2.context) IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, os2.context); if (name.context) IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, name.context); return hr; } static inline const struct ot_script *opentype_get_script(const struct ot_script_list *scriptlist, UINT32 scripttag) { UINT16 j; for (j = 0; j < GET_BE_WORD(scriptlist->script_count); j++) { const char *tag = scriptlist->scripts[j].tag; if (scripttag == DWRITE_MAKE_OPENTYPE_TAG(tag[0], tag[1], tag[2], tag[3])) return (struct ot_script*)((BYTE*)scriptlist + GET_BE_WORD(scriptlist->scripts[j].script)); } return NULL; } static inline const struct ot_langsys *opentype_get_langsys(const struct ot_script *script, UINT32 languagetag) { UINT16 j; for (j = 0; j < GET_BE_WORD(script->langsys_count); j++) { const char *tag = script->langsys[j].tag; if (languagetag == DWRITE_MAKE_OPENTYPE_TAG(tag[0], tag[1], tag[2], tag[3])) return (struct ot_langsys *)((BYTE*)script + GET_BE_WORD(script->langsys[j].langsys)); } return NULL; } static void opentype_add_font_features(const struct gpos_gsub_header *header, const struct ot_langsys *langsys, UINT32 max_tagcount, UINT32 *count, DWRITE_FONT_FEATURE_TAG *tags) { const struct ot_feature_list *features = (const struct ot_feature_list *)((const BYTE*)header + GET_BE_WORD(header->feature_list)); UINT16 j; for (j = 0; j < GET_BE_WORD(langsys->feature_count); j++) { const struct ot_feature_record *feature = &features->features[langsys->feature_index[j]]; if (*count < max_tagcount) tags[*count] = GET_BE_DWORD(feature->tag); (*count)++; } } HRESULT opentype_get_typographic_features(IDWriteFontFace *fontface, UINT32 scripttag, UINT32 languagetag, UINT32 max_tagcount, UINT32 *count, DWRITE_FONT_FEATURE_TAG *tags) { UINT32 tables[2] = { MS_GSUB_TAG, MS_GPOS_TAG }; HRESULT hr; UINT8 i; *count = 0; for (i = 0; i < ARRAY_SIZE(tables); i++) { const struct ot_script_list *scriptlist; const struct gpos_gsub_header *header; const struct ot_script *script; const void *ptr; void *context; UINT32 size; BOOL exists; exists = FALSE; hr = IDWriteFontFace_TryGetFontTable(fontface, tables[i], &ptr, &size, &context, &exists); if (FAILED(hr)) return hr; if (!exists) continue; header = (const struct gpos_gsub_header *)ptr; scriptlist = (const struct ot_script_list *)((const BYTE*)header + GET_BE_WORD(header->script_list)); script = opentype_get_script(scriptlist, scripttag); if (script) { const struct ot_langsys *langsys = opentype_get_langsys(script, languagetag); if (langsys) opentype_add_font_features(header, langsys, max_tagcount, count, tags); } IDWriteFontFace_ReleaseFontTable(fontface, context); } return *count > max_tagcount ? E_NOT_SUFFICIENT_BUFFER : S_OK; } static unsigned int find_vdmx_group(const struct vdmx_header *hdr) { WORD num_ratios, i; const struct vdmx_ratio *ratios = (struct vdmx_ratio *)(hdr + 1); BYTE dev_x_ratio = 1, dev_y_ratio = 1; unsigned int group_offset = 0; num_ratios = GET_BE_WORD(hdr->num_ratios); for (i = 0; i < num_ratios; i++) { if (!ratios[i].bCharSet) continue; if ((ratios[i].xRatio == 0 && ratios[i].yStartRatio == 0 && ratios[i].yEndRatio == 0) || (ratios[i].xRatio == dev_x_ratio && ratios[i].yStartRatio <= dev_y_ratio && ratios[i].yEndRatio >= dev_y_ratio)) { group_offset = GET_BE_WORD(*((WORD *)(ratios + num_ratios) + i)); break; } } return group_offset; } BOOL opentype_get_vdmx_size(const struct dwrite_fonttable *vdmx, INT emsize, UINT16 *ascent, UINT16 *descent) { unsigned int num_ratios, num_recs, group_offset, i; const struct vdmx_header *header; const struct vdmx_group *group; if (!vdmx->exists) return FALSE; num_ratios = table_read_be_word(vdmx, FIELD_OFFSET(struct vdmx_header, num_ratios)); num_recs = table_read_be_word(vdmx, FIELD_OFFSET(struct vdmx_header, num_recs)); header = table_read_ensure(vdmx, 0, sizeof(*header) + num_ratios * sizeof(struct vdmx_ratio) + num_recs * sizeof(*group)); if (!header) return FALSE; group_offset = find_vdmx_group(header); if (!group_offset) return FALSE; num_recs = table_read_be_word(vdmx, group_offset); group = table_read_ensure(vdmx, group_offset, FIELD_OFFSET(struct vdmx_group, entries[num_recs])); if (!group) return FALSE; if (emsize < group->startsz || emsize >= group->endsz) return FALSE; for (i = 0; i < num_recs; ++i) { WORD ppem = GET_BE_WORD(group->entries[i].yPelHeight); if (ppem > emsize) { FIXME("interpolate %d\n", emsize); return FALSE; } if (ppem == emsize) { *ascent = (SHORT)GET_BE_WORD(group->entries[i].yMax); *descent = -(SHORT)GET_BE_WORD(group->entries[i].yMin); return TRUE; } } return FALSE; } unsigned int opentype_get_gasp_flags(const struct dwrite_fonttable *gasp, float emsize) { unsigned int version, num_ranges, i; const struct gasp_header *table; WORD flags = 0; if (!gasp->exists) return 0; num_ranges = table_read_be_word(gasp, FIELD_OFFSET(struct gasp_header, num_ranges)); table = table_read_ensure(gasp, 0, FIELD_OFFSET(struct gasp_header, ranges[num_ranges])); if (!table) return 0; version = GET_BE_WORD(table->version); if (version > 1) { ERR("Unsupported gasp table format version %u.\n", version); goto done; } for (i = 0; i < num_ranges; ++i) { flags = GET_BE_WORD(table->ranges[i].flags); if (emsize <= GET_BE_WORD(table->ranges[i].max_ppem)) break; } done: return flags; } unsigned int opentype_get_cpal_palettecount(const struct dwrite_fonttable *cpal) { return table_read_be_word(cpal, FIELD_OFFSET(struct cpal_header_0, num_palettes)); } unsigned int opentype_get_cpal_paletteentrycount(const struct dwrite_fonttable *cpal) { return table_read_be_word(cpal, FIELD_OFFSET(struct cpal_header_0, num_palette_entries)); } HRESULT opentype_get_cpal_entries(const struct dwrite_fonttable *cpal, unsigned int palette, unsigned int first_entry_index, unsigned int entry_count, DWRITE_COLOR_F *entries) { unsigned int num_palettes, num_palette_entries, i; const struct cpal_color_record *records; const struct cpal_header_0 *header; header = table_read_ensure(cpal, 0, sizeof(*header)); if (!cpal->exists || !header) return DWRITE_E_NOCOLOR; num_palettes = GET_BE_WORD(header->num_palettes); if (palette >= num_palettes) return DWRITE_E_NOCOLOR; header = table_read_ensure(cpal, 0, FIELD_OFFSET(struct cpal_header_0, color_record_indices[palette])); if (!header) return DWRITE_E_NOCOLOR; num_palette_entries = GET_BE_WORD(header->num_palette_entries); if (first_entry_index + entry_count > num_palette_entries) return E_INVALIDARG; records = table_read_ensure(cpal, GET_BE_DWORD(header->offset_first_color_record), sizeof(*records) * GET_BE_WORD(header->num_color_records)); if (!records) return DWRITE_E_NOCOLOR; first_entry_index += GET_BE_WORD(header->color_record_indices[palette]); for (i = 0; i < entry_count; i++) { entries[i].u1.r = records[first_entry_index + i].red / 255.0f; entries[i].u2.g = records[first_entry_index + i].green / 255.0f; entries[i].u3.b = records[first_entry_index + i].blue / 255.0f; entries[i].u4.a = records[first_entry_index + i].alpha / 255.0f; } return S_OK; } static int colr_compare_gid(const void *g, const void *r) { const struct colr_baseglyph_record *record = r; UINT16 glyph = *(UINT16*)g, GID = GET_BE_WORD(record->glyph); int ret = 0; if (glyph > GID) ret = 1; else if (glyph < GID) ret = -1; return ret; } HRESULT opentype_get_colr_glyph(const struct dwrite_fonttable *colr, UINT16 glyph, struct dwrite_colorglyph *ret) { unsigned int num_baseglyph_records, offset_baseglyph_records; const struct colr_baseglyph_record *record; const struct colr_layer_record *layer; const struct colr_header *header; memset(ret, 0, sizeof(*ret)); ret->glyph = glyph; ret->palette_index = 0xffff; header = table_read_ensure(colr, 0, sizeof(*header)); if (!header) return S_FALSE; num_baseglyph_records = GET_BE_WORD(header->num_baseglyph_records); offset_baseglyph_records = GET_BE_DWORD(header->offset_baseglyph_records); if (!table_read_ensure(colr, offset_baseglyph_records, num_baseglyph_records * sizeof(*record))) { return S_FALSE; } record = bsearch(&glyph, colr->data + offset_baseglyph_records, num_baseglyph_records, sizeof(*record), colr_compare_gid); if (!record) return S_FALSE; ret->first_layer = GET_BE_WORD(record->first_layer_index); ret->num_layers = GET_BE_WORD(record->num_layers); if ((layer = table_read_ensure(colr, GET_BE_DWORD(header->offset_layer_records), (ret->first_layer + ret->layer) * sizeof(*layer)))) { layer += ret->first_layer + ret->layer; ret->glyph = GET_BE_WORD(layer->glyph); ret->palette_index = GET_BE_WORD(layer->palette_index); } return S_OK; } void opentype_colr_next_glyph(const struct dwrite_fonttable *colr, struct dwrite_colorglyph *glyph) { const struct colr_layer_record *layer; const struct colr_header *header; /* iterated all the way through */ if (glyph->layer == glyph->num_layers) return; if (!(header = table_read_ensure(colr, 0, sizeof(*header)))) return; glyph->layer++; if ((layer = table_read_ensure(colr, GET_BE_DWORD(header->offset_layer_records), (glyph->first_layer + glyph->layer) * sizeof(*layer)))) { layer += glyph->first_layer + glyph->layer; glyph->glyph = GET_BE_WORD(layer->glyph); glyph->palette_index = GET_BE_WORD(layer->palette_index); } } BOOL opentype_has_vertical_variants(IDWriteFontFace5 *fontface) { const struct gpos_gsub_header *header; const struct ot_feature_list *featurelist; const struct ot_lookup_list *lookup_list; BOOL exists = FALSE, ret = FALSE; unsigned int i, j; const void *data; void *context; UINT32 size; HRESULT hr; hr = IDWriteFontFace5_TryGetFontTable(fontface, MS_GSUB_TAG, &data, &size, &context, &exists); if (FAILED(hr) || !exists) return FALSE; header = data; featurelist = (struct ot_feature_list *)((BYTE*)header + GET_BE_WORD(header->feature_list)); lookup_list = (const struct ot_lookup_list *)((BYTE*)header + GET_BE_WORD(header->lookup_list)); for (i = 0; i < GET_BE_WORD(featurelist->feature_count); i++) { if (featurelist->features[i].tag == DWRITE_FONT_FEATURE_TAG_VERTICAL_WRITING) { const struct ot_feature *feature = (const struct ot_feature*)((BYTE*)featurelist + GET_BE_WORD(featurelist->features[i].offset)); UINT16 lookup_count = GET_BE_WORD(feature->lookup_count), index, count, type; const GSUB_SingleSubstFormat2 *subst2; const struct ot_lookup_table *lookup_table; UINT32 offset; if (lookup_count == 0) continue; for (j = 0; j < lookup_count; ++j) { /* check if lookup is empty */ index = GET_BE_WORD(feature->lookuplist_index[j]); lookup_table = (const struct ot_lookup_table *)((BYTE*)lookup_list + GET_BE_WORD(lookup_list->lookup[index])); type = GET_BE_WORD(lookup_table->lookup_type); if (type != GSUB_LOOKUP_SINGLE_SUBST && type != GSUB_LOOKUP_EXTENSION_SUBST) continue; count = GET_BE_WORD(lookup_table->subtable_count); if (count == 0) continue; offset = GET_BE_WORD(lookup_table->subtable[0]); if (type == GSUB_LOOKUP_EXTENSION_SUBST) { const GSUB_ExtensionPosFormat1 *ext = (const GSUB_ExtensionPosFormat1 *)((const BYTE *)lookup_table + offset); if (GET_BE_WORD(ext->SubstFormat) == 1) offset += GET_BE_DWORD(ext->ExtensionOffset); else FIXME("Unhandled Extension Substitution Format %u\n", GET_BE_WORD(ext->SubstFormat)); } subst2 = (const GSUB_SingleSubstFormat2*)((BYTE*)lookup_table + offset); index = GET_BE_WORD(subst2->SubstFormat); if (index == 1) FIXME("Validate Single Substitution Format 1\n"); else if (index == 2) { /* SimSun-ExtB has 0 glyph count for this substitution */ if (GET_BE_WORD(subst2->GlyphCount) > 0) { ret = TRUE; break; } } else WARN("Unknown Single Substitution Format, %u\n", index); } } } IDWriteFontFace5_ReleaseFontTable(fontface, context); return ret; } static BOOL opentype_has_font_table(IDWriteFontFace5 *fontface, UINT32 tag) { BOOL exists = FALSE; const void *data; void *context; UINT32 size; HRESULT hr; hr = IDWriteFontFace5_TryGetFontTable(fontface, tag, &data, &size, &context, &exists); if (FAILED(hr)) return FALSE; if (exists) IDWriteFontFace5_ReleaseFontTable(fontface, context); return exists; } static unsigned int opentype_get_sbix_formats(IDWriteFontFace5 *fontface) { unsigned int num_strikes, num_glyphs, i, j, ret = 0; const struct sbix_header *sbix_header; struct dwrite_fonttable table; memset(&table, 0, sizeof(table)); table.exists = TRUE; if (!get_fontface_table(fontface, MS_MAXP_TAG, &table)) return 0; num_glyphs = table_read_be_word(&table, FIELD_OFFSET(struct maxp, num_glyphs)); IDWriteFontFace5_ReleaseFontTable(fontface, table.context); memset(&table, 0, sizeof(table)); table.exists = TRUE; if (!get_fontface_table(fontface, MS_SBIX_TAG, &table)) return 0; num_strikes = table_read_be_dword(&table, FIELD_OFFSET(struct sbix_header, num_strikes)); sbix_header = table_read_ensure(&table, 0, FIELD_OFFSET(struct sbix_header, strike_offset[num_strikes])); if (sbix_header) { for (i = 0; i < num_strikes; ++i) { unsigned int strike_offset = GET_BE_DWORD(sbix_header->strike_offset[i]); const struct sbix_strike *strike = table_read_ensure(&table, strike_offset, FIELD_OFFSET(struct sbix_strike, glyphdata_offsets[num_glyphs + 1])); if (!strike) continue; for (j = 0; j < num_glyphs; j++) { unsigned int offset = GET_BE_DWORD(strike->glyphdata_offsets[j]); unsigned int next_offset = GET_BE_DWORD(strike->glyphdata_offsets[j + 1]); const struct sbix_glyph_data *glyph_data; if (offset == next_offset) continue; glyph_data = table_read_ensure(&table, strike_offset + offset, sizeof(*glyph_data)); if (!glyph_data) continue; switch (glyph_data->graphic_type) { case MS_PNG__TAG: ret |= DWRITE_GLYPH_IMAGE_FORMATS_PNG; break; case MS_JPG__TAG: ret |= DWRITE_GLYPH_IMAGE_FORMATS_JPEG; break; case MS_TIFF_TAG: ret |= DWRITE_GLYPH_IMAGE_FORMATS_TIFF; break; default: FIXME("unexpected bitmap format %s\n", debugstr_tag(GET_BE_DWORD(glyph_data->graphic_type))); } } } } IDWriteFontFace5_ReleaseFontTable(fontface, table.context); return ret; } static unsigned int opentype_get_cblc_formats(IDWriteFontFace5 *fontface) { const unsigned int format_mask = DWRITE_GLYPH_IMAGE_FORMATS_PNG | DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8; const struct cblc_bitmapsize_table *sizes; struct dwrite_fonttable cblc = { 0 }; unsigned int num_sizes, i, ret = 0; const struct cblc_header *header; cblc.exists = TRUE; if (!get_fontface_table(fontface, MS_CBLC_TAG, &cblc)) return 0; num_sizes = table_read_be_dword(&cblc, FIELD_OFFSET(struct cblc_header, num_sizes)); sizes = table_read_ensure(&cblc, sizeof(*header), num_sizes * sizeof(*sizes)); if (sizes) { for (i = 0; i < num_sizes; ++i) { BYTE bpp = sizes[i].bit_depth; if ((ret & format_mask) == format_mask) break; if (bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8) ret |= DWRITE_GLYPH_IMAGE_FORMATS_PNG; else if (bpp == 32) ret |= DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8; } } IDWriteFontFace5_ReleaseFontTable(fontface, cblc.context); return ret; } UINT32 opentype_get_glyph_image_formats(IDWriteFontFace5 *fontface) { UINT32 ret = DWRITE_GLYPH_IMAGE_FORMATS_NONE; if (opentype_has_font_table(fontface, MS_GLYF_TAG)) ret |= DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE; if (opentype_has_font_table(fontface, MS_CFF__TAG) || opentype_has_font_table(fontface, MS_CFF2_TAG)) ret |= DWRITE_GLYPH_IMAGE_FORMATS_CFF; if (opentype_has_font_table(fontface, MS_COLR_TAG)) ret |= DWRITE_GLYPH_IMAGE_FORMATS_COLR; if (opentype_has_font_table(fontface, MS_SVG__TAG)) ret |= DWRITE_GLYPH_IMAGE_FORMATS_SVG; if (opentype_has_font_table(fontface, MS_SBIX_TAG)) ret |= opentype_get_sbix_formats(fontface); if (opentype_has_font_table(fontface, MS_CBLC_TAG)) ret |= opentype_get_cblc_formats(fontface); return ret; } DWRITE_CONTAINER_TYPE opentype_analyze_container_type(void const *data, UINT32 data_size) { DWORD signature; if (data_size < sizeof(DWORD)) return DWRITE_CONTAINER_TYPE_UNKNOWN; /* Both WOFF and WOFF2 start with 4 bytes signature. */ signature = *(DWORD *)data; switch (signature) { case MS_WOFF_TAG: return DWRITE_CONTAINER_TYPE_WOFF; case MS_WOF2_TAG: return DWRITE_CONTAINER_TYPE_WOFF2; default: return DWRITE_CONTAINER_TYPE_UNKNOWN; } } void opentype_layout_scriptshaping_cache_init(struct scriptshaping_cache *cache) { cache->font->grab_font_table(cache->context, MS_GSUB_TAG, &cache->gsub.table.data, &cache->gsub.table.size, &cache->gsub.table.context); if (cache->gsub.table.data) { cache->gsub.script_list = table_read_be_word(&cache->gsub.table, FIELD_OFFSET(struct gpos_gsub_header, script_list)); cache->gsub.feature_list = table_read_be_word(&cache->gsub.table, FIELD_OFFSET(struct gpos_gsub_header, feature_list)); cache->gsub.lookup_list = table_read_be_word(&cache->gsub.table, FIELD_OFFSET(struct gpos_gsub_header, lookup_list)); } cache->font->grab_font_table(cache->context, MS_GPOS_TAG, &cache->gpos.table.data, &cache->gpos.table.size, &cache->gpos.table.context); if (cache->gpos.table.data) { cache->gpos.script_list = table_read_be_word(&cache->gpos.table, FIELD_OFFSET(struct gpos_gsub_header, script_list)); cache->gpos.feature_list = table_read_be_word(&cache->gpos.table, FIELD_OFFSET(struct gpos_gsub_header, feature_list)); cache->gpos.lookup_list = table_read_be_word(&cache->gpos.table, FIELD_OFFSET(struct gpos_gsub_header, lookup_list)); } cache->font->grab_font_table(cache->context, MS_GDEF_TAG, &cache->gdef.table.data, &cache->gdef.table.size, &cache->gdef.table.context); if (cache->gdef.table.data) cache->gdef.classdef = table_read_be_word(&cache->gdef.table, FIELD_OFFSET(struct gdef_header, classdef)); } unsigned int opentype_layout_find_script(const struct scriptshaping_cache *cache, unsigned int kind, DWORD script, unsigned int *script_index) { const struct ot_gsubgpos_table *table = kind == MS_GSUB_TAG ? &cache->gsub : &cache->gpos; UINT16 script_count; unsigned int i; *script_index = ~0u; script_count = table_read_be_word(&table->table, table->script_list); if (!script_count) return 0; for (i = 0; i < script_count; i++) { unsigned int tag = table_read_dword(&table->table, table->script_list + FIELD_OFFSET(struct ot_script_list, scripts) + i * sizeof(struct ot_script_record)); if (!tag) continue; if (tag == script) { *script_index = i; return script; } } return 0; } unsigned int opentype_layout_find_language(const struct scriptshaping_cache *cache, unsigned int kind, DWORD language, unsigned int script_index, unsigned int *language_index) { const struct ot_gsubgpos_table *table = kind == MS_GSUB_TAG ? &cache->gsub : &cache->gpos; UINT16 table_offset, lang_count; unsigned int i; *language_index = ~0u; table_offset = table_read_be_word(&table->table, table->script_list + FIELD_OFFSET(struct ot_script_list, scripts) + script_index * sizeof(struct ot_script_record) + FIELD_OFFSET(struct ot_script_record, script)); if (!table_offset) return 0; lang_count = table_read_be_word(&table->table, table->script_list + table_offset + FIELD_OFFSET(struct ot_script, langsys_count)); for (i = 0; i < lang_count; i++) { unsigned int tag = table_read_dword(&table->table, table->script_list + table_offset + FIELD_OFFSET(struct ot_script, langsys) + i * sizeof(struct ot_langsys_record)); if (tag == language) { *language_index = i; return language; } } /* Try 'defaultLangSys' if it's set. */ if (table_read_be_word(&table->table, table->script_list + table_offset)) return ~0u; return 0; } static int gdef_class_compare_format2(const void *g, const void *r) { const struct ot_gdef_class_range *range = r; UINT16 glyph = *(UINT16 *)g; if (glyph < GET_BE_WORD(range->start_glyph)) return -1; else if (glyph > GET_BE_WORD(range->end_glyph)) return 1; else return 0; } static unsigned int opentype_layout_get_glyph_class(const struct dwrite_fonttable *table, unsigned int offset, UINT16 glyph) { WORD format = table_read_be_word(table, offset), count; unsigned int glyph_class = GDEF_CLASS_UNCLASSIFIED; if (format == 1) { const struct ot_gdef_classdef_format1 *format1; count = table_read_be_word(table, offset + FIELD_OFFSET(struct ot_gdef_classdef_format1, glyph_count)); format1 = table_read_ensure(table, offset, FIELD_OFFSET(struct ot_gdef_classdef_format1, classes[count])); if (format1) { WORD start_glyph = GET_BE_WORD(format1->start_glyph); if (glyph >= start_glyph && (glyph - start_glyph) < count) { glyph_class = GET_BE_WORD(format1->classes[glyph - start_glyph]); if (glyph_class > GDEF_CLASS_MAX) glyph_class = GDEF_CLASS_UNCLASSIFIED; } } } else if (format == 2) { const struct ot_gdef_classdef_format2 *format2; count = table_read_be_word(table, offset + FIELD_OFFSET(struct ot_gdef_classdef_format2, range_count)); format2 = table_read_ensure(table, offset, FIELD_OFFSET(struct ot_gdef_classdef_format2, ranges[count])); if (format2) { const struct ot_gdef_class_range *range = bsearch(&glyph, format2->ranges, count, sizeof(struct ot_gdef_class_range), gdef_class_compare_format2); glyph_class = range && glyph <= GET_BE_WORD(range->end_glyph) ? GET_BE_WORD(range->glyph_class) : GDEF_CLASS_UNCLASSIFIED; if (glyph_class > GDEF_CLASS_MAX) glyph_class = GDEF_CLASS_UNCLASSIFIED; } } else WARN("Unknown GDEF format %u.\n", format); return glyph_class; } struct coverage_compare_format1_context { UINT16 glyph; const UINT16 *table_base; unsigned int *coverage_index; }; static int coverage_compare_format1(const void *left, const void *right) { const struct coverage_compare_format1_context *context = left; UINT16 glyph = GET_BE_WORD(*(UINT16 *)right); int ret; ret = context->glyph - glyph; if (!ret) *context->coverage_index = (UINT16 *)right - context->table_base; return ret; } static int coverage_compare_format2(const void *g, const void *r) { const struct ot_coverage_range *range = r; UINT16 glyph = *(UINT16 *)g; if (glyph < GET_BE_WORD(range->start_glyph)) return -1; else if (glyph > GET_BE_WORD(range->end_glyph)) return 1; else return 0; } static unsigned int opentype_layout_is_glyph_covered(const struct dwrite_fonttable *table, DWORD coverage, UINT16 glyph) { WORD format = table_read_be_word(table, coverage), count; count = table_read_be_word(table, coverage + 2); if (format == 1) { const struct ot_coverage_format1 *format1 = table_read_ensure(table, coverage, FIELD_OFFSET(struct ot_coverage_format1, glyphs[count])); struct coverage_compare_format1_context context; unsigned int coverage_index = GLYPH_NOT_COVERED; if (format1) { context.glyph = glyph; context.table_base = format1->glyphs; context.coverage_index = &coverage_index; bsearch(&context, format1->glyphs, count, sizeof(glyph), coverage_compare_format1); } return coverage_index; } else if (format == 2) { const struct ot_coverage_format2 *format2 = table_read_ensure(table, coverage, FIELD_OFFSET(struct ot_coverage_format2, ranges[count])); if (format2) { const struct ot_coverage_range *range = bsearch(&glyph, format2->ranges, count, sizeof(struct ot_coverage_range), coverage_compare_format2); return range && glyph <= GET_BE_WORD(range->end_glyph) ? GET_BE_WORD(range->startcoverage_index) + glyph - GET_BE_WORD(range->start_glyph) : GLYPH_NOT_COVERED; } } else WARN("Unknown coverage format %u.\n", format); return -1; } static inline unsigned int dwrite_popcount(unsigned int x) { #ifdef HAVE___BUILTIN_POPCOUNT return __builtin_popcount(x); #else x -= x >> 1 & 0x55555555; x = (x & 0x33333333) + (x >> 2 & 0x33333333); return ((x + (x >> 4)) & 0x0f0f0f0f) * 0x01010101 >> 24; #endif } static float opentype_scale_gpos_be_value(WORD value, float emsize, UINT16 upem) { return (short)GET_BE_WORD(value) * emsize / upem; } static int opentype_layout_gpos_get_dev_value(const struct scriptshaping_context *context, unsigned int offset) { const struct scriptshaping_cache *cache = context->cache; unsigned int start_size, end_size, format, value_word; unsigned int index, ppem, mask; int value; if (!offset) return 0; start_size = table_read_be_word(&cache->gpos.table, offset); end_size = table_read_be_word(&cache->gpos.table, offset + FIELD_OFFSET(struct ot_gpos_device_table, end_size)); ppem = context->emsize; if (ppem < start_size || ppem > end_size) return 0; format = table_read_be_word(&cache->gpos.table, offset + FIELD_OFFSET(struct ot_gpos_device_table, format)); if (format < 1 || format > 3) return 0; index = ppem - start_size; value_word = table_read_be_word(&cache->gpos.table, offset + FIELD_OFFSET(struct ot_gpos_device_table, values[index >> (4 - format)])); mask = 0xffff >> (16 - (1 << format)); value = (value_word >> ((index % (4 - format)) * (1 << format))) & mask; if ((unsigned int)value >= ((mask + 1) >> 1)) value -= mask + 1; return value; } static void opentype_layout_apply_gpos_value(struct scriptshaping_context *context, unsigned int table_offset, WORD value_format, const WORD *values, unsigned int glyph) { const struct scriptshaping_cache *cache = context->cache; DWRITE_GLYPH_OFFSET *offset = &context->offsets[glyph]; float *advance = &context->advances[glyph]; if (!value_format) return; if (value_format & GPOS_VALUE_X_PLACEMENT) { offset->advanceOffset += opentype_scale_gpos_be_value(*values, context->emsize, cache->upem); values++; } if (value_format & GPOS_VALUE_Y_PLACEMENT) { offset->ascenderOffset += opentype_scale_gpos_be_value(*values, context->emsize, cache->upem); values++; } if (value_format & GPOS_VALUE_X_ADVANCE) { *advance += opentype_scale_gpos_be_value(*values, context->emsize, cache->upem); values++; } if (value_format & GPOS_VALUE_Y_ADVANCE) { values++; } if (value_format & GPOS_VALUE_X_PLACEMENT_DEVICE) { offset->advanceOffset += opentype_layout_gpos_get_dev_value(context, table_offset + GET_BE_WORD(*values)); values++; } if (value_format & GPOS_VALUE_Y_PLACEMENT_DEVICE) { offset->ascenderOffset += opentype_layout_gpos_get_dev_value(context, table_offset + GET_BE_WORD(*values)); values++; } if (value_format & GPOS_VALUE_X_ADVANCE_DEVICE) { *advance += opentype_layout_gpos_get_dev_value(context, table_offset + GET_BE_WORD(*values)); values++; } if (value_format & GPOS_VALUE_Y_ADVANCE_DEVICE) { values++; } } static unsigned int opentype_layout_get_gpos_subtable(const struct scriptshaping_cache *cache, unsigned int lookup_offset, unsigned int subtable) { WORD lookup_type = table_read_be_word(&cache->gpos.table, lookup_offset); unsigned int subtable_offset = table_read_be_word(&cache->gpos.table, lookup_offset + FIELD_OFFSET(struct ot_lookup_table, subtable[subtable])); if (lookup_type == GPOS_LOOKUP_EXTENSION_POSITION) { const struct ot_gsubgpos_extensionpos_format1 *format1 = table_read_ensure(&cache->gpos.table, lookup_offset + subtable_offset, sizeof(*format1)); subtable_offset += GET_BE_DWORD(format1->extension_offset); } return lookup_offset + subtable_offset; } static unsigned int opentype_layout_get_gsub_subtable(const struct scriptshaping_cache *cache, unsigned int lookup_offset, unsigned int subtable) { UINT16 lookup_type = table_read_be_word(&cache->gsub.table, lookup_offset); unsigned int subtable_offset = table_read_be_word(&cache->gsub.table, lookup_offset + FIELD_OFFSET(struct ot_lookup_table, subtable[subtable])); if (lookup_type == GSUB_LOOKUP_EXTENSION_SUBST) { const struct ot_gsubgpos_extensionpos_format1 *format1 = table_read_ensure(&cache->gsub.table, lookup_offset + subtable_offset, sizeof(*format1)); subtable_offset += GET_BE_DWORD(format1->extension_offset); } return lookup_offset + subtable_offset; } struct lookup { unsigned int offset; unsigned int subtable_count; unsigned int flags; }; struct glyph_iterator { struct scriptshaping_context *context; unsigned int flags; unsigned int pos; unsigned int len; }; static void glyph_iterator_init(struct scriptshaping_context *context, unsigned int flags, unsigned int pos, unsigned int len, struct glyph_iterator *iter) { iter->context = context; iter->flags = flags; iter->pos = pos; iter->len = len; } static BOOL glyph_iterator_match(const struct glyph_iterator *iter) { struct scriptshaping_cache *cache = iter->context->cache; if (cache->gdef.classdef) { unsigned int glyph_class = opentype_layout_get_glyph_class(&cache->gdef.table, cache->gdef.classdef, iter->context->u.pos.glyphs[iter->pos]); if ((1 << glyph_class) & iter->flags & LOOKUP_FLAG_IGNORE_MASK) return FALSE; } return TRUE; } static BOOL glyph_iterator_next(struct glyph_iterator *iter) { while (iter->pos + iter->len < iter->context->glyph_count) { ++iter->pos; if (glyph_iterator_match(iter)) { --iter->len; return TRUE; } } return FALSE; } static BOOL glyph_iterator_prev(struct glyph_iterator *iter) { if (!iter->pos) return FALSE; while (iter->pos > iter->len - 1) { --iter->pos; if (glyph_iterator_match(iter)) { --iter->len; return TRUE; } } return FALSE; } static BOOL opentype_layout_apply_gpos_single_adjustment(struct scriptshaping_context *context, struct glyph_iterator *iter, const struct lookup *lookup) { struct scriptshaping_cache *cache = context->cache; WORD format, value_format, value_len, coverage; unsigned int i; for (i = 0; i < lookup->subtable_count; ++i) { unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i); unsigned int coverage_index; format = table_read_be_word(&cache->gpos.table, subtable_offset); coverage = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_singlepos_format1, coverage)); value_format = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_singlepos_format1, value_format)); value_len = dwrite_popcount(value_format); if (format == 1) { const struct ot_gpos_singlepos_format1 *format1 = table_read_ensure(&cache->gpos.table, subtable_offset, FIELD_OFFSET(struct ot_gpos_singlepos_format1, value[value_len])); coverage_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + coverage, context->u.pos.glyphs[iter->pos]); if (coverage_index == GLYPH_NOT_COVERED) continue; opentype_layout_apply_gpos_value(context, subtable_offset, value_format, format1->value, iter->pos); break; } else if (format == 2) { WORD value_count = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_singlepos_format2, value_count)); const struct ot_gpos_singlepos_format2 *format2 = table_read_ensure(&cache->gpos.table, subtable_offset, FIELD_OFFSET(struct ot_gpos_singlepos_format2, values) + value_count * value_len * sizeof(WORD)); coverage_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + coverage, context->u.pos.glyphs[iter->pos]); if (coverage_index == GLYPH_NOT_COVERED || coverage_index >= value_count) continue; opentype_layout_apply_gpos_value(context, subtable_offset, value_format, &format2->values[coverage_index * value_len], iter->pos); break; } else WARN("Unknown single adjustment format %u.\n", format); } return FALSE; } static int gpos_pair_adjustment_compare_format1(const void *g, const void *r) { const struct ot_gpos_pairvalue *pairvalue = r; UINT16 second_glyph = GET_BE_WORD(pairvalue->second_glyph); return *(UINT16 *)g - second_glyph; } static BOOL opentype_layout_apply_gpos_pair_adjustment(struct scriptshaping_context *context, struct glyph_iterator *iter, const struct lookup *lookup) { struct scriptshaping_cache *cache = context->cache; unsigned int i, first_glyph, second_glyph; struct glyph_iterator iter_pair; WORD format, coverage; glyph_iterator_init(context, iter->flags, iter->pos, 1, &iter_pair); if (!glyph_iterator_next(&iter_pair)) return FALSE; if (context->is_rtl) { first_glyph = iter_pair.pos; second_glyph = iter->pos; } else { first_glyph = iter->pos; second_glyph = iter_pair.pos; } for (i = 0; i < lookup->subtable_count; ++i) { unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i); WORD value_format1, value_format2, value_len1, value_len2; unsigned int coverage_index; format = table_read_be_word(&cache->gpos.table, subtable_offset); coverage = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_pairpos_format1, coverage)); if (!coverage) continue; coverage_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + coverage, context->u.pos.glyphs[first_glyph]); if (coverage_index == GLYPH_NOT_COVERED) continue; if (format == 1) { const struct ot_gpos_pairpos_format1 *format1; WORD pairset_count = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_pairpos_format1, pairset_count)); unsigned int pairvalue_len, pairset_offset; const struct ot_gpos_pairset *pairset; const WORD *pairvalue; WORD pairvalue_count; if (!pairset_count || coverage_index >= pairset_count) continue; format1 = table_read_ensure(&cache->gpos.table, subtable_offset, FIELD_OFFSET(struct ot_gpos_pairpos_format1, pairsets[pairset_count])); if (!format1) continue; /* Ordered paired values. */ pairvalue_count = table_read_be_word(&cache->gpos.table, subtable_offset + GET_BE_WORD(format1->pairsets[coverage_index])); if (!pairvalue_count) continue; /* Structure length is variable, but does not change across the subtable. */ value_format1 = GET_BE_WORD(format1->value_format1) & 0xff; value_format2 = GET_BE_WORD(format1->value_format2) & 0xff; value_len1 = dwrite_popcount(value_format1); value_len2 = dwrite_popcount(value_format2); pairvalue_len = FIELD_OFFSET(struct ot_gpos_pairvalue, data) + value_len1 * sizeof(WORD) + value_len2 * sizeof(WORD); pairset_offset = subtable_offset + GET_BE_WORD(format1->pairsets[coverage_index]); pairset = table_read_ensure(&cache->gpos.table, subtable_offset + pairset_offset, pairvalue_len * pairvalue_count); if (!pairset) continue; pairvalue = bsearch(&context->u.pos.glyphs[second_glyph], pairset->pairvalues, pairvalue_count, pairvalue_len, gpos_pair_adjustment_compare_format1); if (!pairvalue) continue; pairvalue += 1; /* Skip SecondGlyph. */ opentype_layout_apply_gpos_value(context, pairset_offset, value_format1, pairvalue, first_glyph); opentype_layout_apply_gpos_value(context, pairset_offset, value_format2, pairvalue + value_len1, second_glyph); iter->pos = iter_pair.pos; if (value_len2) iter->pos++; return TRUE; } else if (format == 2) { const struct ot_gpos_pairpos_format2 *format2; WORD class1_count, class2_count; unsigned int class1, class2; value_format1 = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_pairpos_format2, value_format1)) & 0xff; value_format2 = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_pairpos_format2, value_format2)) & 0xff; class1_count = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_pairpos_format2, class1_count)); class2_count = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_pairpos_format2, class2_count)); value_len1 = dwrite_popcount(value_format1); value_len2 = dwrite_popcount(value_format2); format2 = table_read_ensure(&cache->gpos.table, subtable_offset, FIELD_OFFSET(struct ot_gpos_pairpos_format2, values[class1_count * class2_count * (value_len1 + value_len2)])); if (!format2) continue; class1 = opentype_layout_get_glyph_class(&cache->gpos.table, subtable_offset + GET_BE_WORD(format2->class_def1), context->u.pos.glyphs[first_glyph]); class2 = opentype_layout_get_glyph_class(&cache->gpos.table, subtable_offset + GET_BE_WORD(format2->class_def2), context->u.pos.glyphs[second_glyph]); if (class1 < class1_count && class2 < class2_count) { const WCHAR *values = &format2->values[(class1 * class2_count + class2) * (value_len1 + value_len2)]; opentype_layout_apply_gpos_value(context, subtable_offset, value_format1, values, first_glyph); opentype_layout_apply_gpos_value(context, subtable_offset, value_format2, values + value_len1, second_glyph); iter->pos = iter_pair.pos; if (value_len2) iter->pos++; return TRUE; } } else { WARN("Unknown pair adjustment format %u.\n", format); continue; } } return FALSE; } static void opentype_layout_gpos_get_anchor(const struct scriptshaping_context *context, unsigned int anchor_offset, unsigned int glyph_index, float *x, float *y) { const struct scriptshaping_cache *cache = context->cache; WORD format = table_read_be_word(&cache->gpos.table, anchor_offset); *x = *y = 0.0f; if (format == 1) { const struct ot_gpos_anchor_format1 *format1 = table_read_ensure(&cache->gpos.table, anchor_offset, sizeof(*format1)); if (format1) { *x = opentype_scale_gpos_be_value(format1->x_coord, context->emsize, cache->upem); *y = opentype_scale_gpos_be_value(format1->y_coord, context->emsize, cache->upem); } } else if (format == 2) { const struct ot_gpos_anchor_format2 *format2 = table_read_ensure(&cache->gpos.table, anchor_offset, sizeof(*format2)); if (format2) { if (context->measuring_mode != DWRITE_MEASURING_MODE_NATURAL) FIXME("Use outline anchor point for glyph %u.\n", context->u.pos.glyphs[glyph_index]); *x = opentype_scale_gpos_be_value(format2->x_coord, context->emsize, cache->upem); *y = opentype_scale_gpos_be_value(format2->y_coord, context->emsize, cache->upem); } } else if (format == 3) { const struct ot_gpos_anchor_format3 *format3 = table_read_ensure(&cache->gpos.table, anchor_offset, sizeof(*format3)); if (format3) { *x = opentype_scale_gpos_be_value(format3->x_coord, context->emsize, cache->upem); *y = opentype_scale_gpos_be_value(format3->y_coord, context->emsize, cache->upem); if (context->measuring_mode != DWRITE_MEASURING_MODE_NATURAL) { if (format3->x_dev_offset) *x += opentype_layout_gpos_get_dev_value(context, anchor_offset + GET_BE_WORD(format3->x_dev_offset)); if (format3->y_dev_offset) *y += opentype_layout_gpos_get_dev_value(context, anchor_offset + GET_BE_WORD(format3->y_dev_offset)); } } } else WARN("Unknown anchor format %u.\n", format); } static BOOL opentype_layout_apply_gpos_cursive_attachment(struct scriptshaping_context *context, struct glyph_iterator *iter, const struct lookup *lookup) { struct scriptshaping_cache *cache = context->cache; unsigned int i; for (i = 0; i < lookup->subtable_count; ++i) { unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i); WORD format; format = table_read_be_word(&cache->gpos.table, subtable_offset); if (format == 1) { WORD coverage_offset = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_cursive_format1, coverage)); unsigned int glyph_index, entry_count, entry_anchor, exit_anchor; float entry_x, entry_y, exit_x, exit_y, delta; struct glyph_iterator prev_iter; if (!coverage_offset) continue; entry_count = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_cursive_format1, count)); glyph_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + coverage_offset, context->u.pos.glyphs[iter->pos]); if (glyph_index == GLYPH_NOT_COVERED || glyph_index >= entry_count) continue; entry_anchor = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_cursive_format1, anchors[glyph_index * 2])); if (!entry_anchor) continue; glyph_iterator_init(context, iter->flags, iter->pos, 1, &prev_iter); if (!glyph_iterator_prev(&prev_iter)) continue; glyph_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + coverage_offset, context->u.pos.glyphs[prev_iter.pos]); if (glyph_index == GLYPH_NOT_COVERED || glyph_index >= entry_count) continue; exit_anchor = table_read_be_word(&cache->gpos.table, subtable_offset + FIELD_OFFSET(struct ot_gpos_cursive_format1, anchors[glyph_index * 2 + 1])); if (!exit_anchor) continue; opentype_layout_gpos_get_anchor(context, subtable_offset + exit_anchor, prev_iter.pos, &exit_x, &exit_y); opentype_layout_gpos_get_anchor(context, subtable_offset + entry_anchor, iter->pos, &entry_x, &entry_y); if (context->is_rtl) { delta = exit_x + context->offsets[prev_iter.pos].advanceOffset; context->advances[prev_iter.pos] -= delta; context->advances[iter->pos] = entry_x + context->offsets[iter->pos].advanceOffset; context->offsets[prev_iter.pos].advanceOffset -= delta; } else { delta = entry_x + context->offsets[iter->pos].advanceOffset; context->advances[prev_iter.pos] = exit_x + context->offsets[prev_iter.pos].advanceOffset; context->advances[iter->pos] -= delta; context->offsets[iter->pos].advanceOffset -= delta; } if (lookup->flags & LOOKUP_FLAG_RTL) context->offsets[prev_iter.pos].ascenderOffset = entry_y - exit_y; else context->offsets[iter->pos].ascenderOffset = exit_y - entry_y; break; } else WARN("Unknown cursive attachment format %u.\n", format); } return FALSE; } static BOOL opentype_layout_apply_gpos_mark_to_base_attachment(struct scriptshaping_context *context, struct glyph_iterator *iter, const struct lookup *lookup) { struct scriptshaping_cache *cache = context->cache; unsigned int i; WORD format; for (i = 0; i < lookup->subtable_count; ++i) { unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i); format = table_read_be_word(&cache->gpos.table, subtable_offset); if (format == 1) { const struct ot_gpos_mark_to_base_format1 *format1 = table_read_ensure(&cache->gpos.table, subtable_offset, sizeof(*format1)); unsigned int mark_class_count, count, mark_array_offset, base_array_offset; const struct ot_gpos_mark_array *mark_array; const struct ot_gpos_base_array *base_array; float mark_x, mark_y, base_x, base_y; unsigned int base_index, mark_index; struct glyph_iterator base_iter; unsigned int base_anchor; if (!format1) continue; mark_array_offset = subtable_offset + GET_BE_WORD(format1->mark_array); if (!(count = table_read_be_word(&cache->gpos.table, mark_array_offset))) continue; mark_array = table_read_ensure(&cache->gpos.table, mark_array_offset, FIELD_OFFSET(struct ot_gpos_mark_array, records[count])); if (!mark_array) continue; base_array_offset = subtable_offset + GET_BE_WORD(format1->base_array); if (!(count = table_read_be_word(&cache->gpos.table, base_array_offset))) continue; base_array = table_read_ensure(&cache->gpos.table, base_array_offset, FIELD_OFFSET(struct ot_gpos_base_array, offsets[count * GET_BE_WORD(format1->mark_class_count)])); if (!base_array) continue; mark_class_count = GET_BE_WORD(format1->mark_class_count); mark_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + GET_BE_WORD(format1->mark_coverage), context->u.pos.glyphs[iter->pos]); if (mark_index == GLYPH_NOT_COVERED || mark_index >= GET_BE_WORD(mark_array->count)) continue; /* Look back for first base glyph. */ glyph_iterator_init(context, LOOKUP_FLAG_IGNORE_MARKS, iter->pos, 1, &base_iter); if (!glyph_iterator_prev(&base_iter)) continue; base_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + GET_BE_WORD(format1->base_coverage), context->u.pos.glyphs[base_iter.pos]); if (base_index == GLYPH_NOT_COVERED || base_index >= GET_BE_WORD(base_array->count)) continue; base_anchor = GET_BE_WORD(base_array->offsets[base_index * mark_class_count + GET_BE_WORD(mark_array->records[mark_index].mark_class)]); opentype_layout_gpos_get_anchor(context, mark_array_offset + GET_BE_WORD(mark_array->records[mark_index].mark_anchor), iter->pos, &mark_x, &mark_y); opentype_layout_gpos_get_anchor(context, base_array_offset + base_anchor, base_iter.pos, &base_x, &base_y); context->offsets[iter->pos].advanceOffset = (context->is_rtl ? -1.0f : 1.0f) * (base_x - mark_x); context->offsets[iter->pos].ascenderOffset = base_y - mark_y; break; } else WARN("Unknown mark-to-base format %u.\n", format); } return FALSE; } static BOOL opentype_layout_apply_gpos_mark_to_lig_attachment(struct scriptshaping_context *context, struct glyph_iterator *iter, const struct lookup *lookup) { struct scriptshaping_cache *cache = context->cache; unsigned int i; WORD format; for (i = 0; i < lookup->subtable_count; ++i) { unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i); format = table_read_be_word(&cache->gpos.table, subtable_offset); if (format == 1) { const struct ot_gpos_mark_to_lig_format1 *format1 = table_read_ensure(&cache->gpos.table, subtable_offset, sizeof(*format1)); unsigned int mark_index, lig_index; struct glyph_iterator lig_iter; if (!format1) continue; mark_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + GET_BE_WORD(format1->mark_coverage), context->u.pos.glyphs[iter->pos]); if (mark_index == GLYPH_NOT_COVERED) continue; glyph_iterator_init(context, LOOKUP_FLAG_IGNORE_MARKS, iter->pos, 1, &lig_iter); if (!glyph_iterator_prev(&lig_iter)) continue; lig_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + GET_BE_WORD(format1->lig_coverage), context->u.pos.glyphs[lig_iter.pos]); if (lig_index == GLYPH_NOT_COVERED) continue; FIXME("Unimplemented.\n"); } else WARN("Unknown mark-to-ligature format %u.\n", format); } return FALSE; } static BOOL opentype_layout_apply_gpos_mark_to_mark_attachment(struct scriptshaping_context *context, struct glyph_iterator *iter, const struct lookup *lookup) { struct scriptshaping_cache *cache = context->cache; unsigned int i; WORD format; for (i = 0; i < lookup->subtable_count; ++i) { unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i); format = table_read_be_word(&cache->gpos.table, subtable_offset); if (format == 1) { const struct ot_gpos_mark_to_mark_format1 *format1 = table_read_ensure(&cache->gpos.table, subtable_offset, sizeof(*format1)); unsigned int count, mark1_array_offset, mark2_array_offset, mark_class_count; unsigned int mark1_index, mark2_index, mark2_anchor; const struct ot_gpos_mark_array *mark1_array; const struct ot_gpos_base_array *mark2_array; float mark1_x, mark1_y, mark2_x, mark2_y; struct glyph_iterator mark_iter; if (!format1) continue; mark1_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + GET_BE_WORD(format1->mark1_coverage), context->u.pos.glyphs[iter->pos]); mark1_array_offset = subtable_offset + GET_BE_WORD(format1->mark1_array); if (!(count = table_read_be_word(&cache->gpos.table, mark1_array_offset))) continue; mark1_array = table_read_ensure(&cache->gpos.table, mark1_array_offset, FIELD_OFFSET(struct ot_gpos_mark_array, records[count])); if (!mark1_array) continue; if (mark1_index == GLYPH_NOT_COVERED || mark1_index >= count) continue; glyph_iterator_init(context, lookup->flags & ~LOOKUP_FLAG_IGNORE_MASK, iter->pos, 1, &mark_iter); if (!glyph_iterator_prev(&mark_iter)) continue; if (!context->u.pos.glyph_props[mark_iter.pos].isDiacritic) continue; mark2_array_offset = subtable_offset + GET_BE_WORD(format1->mark2_array); if (!(count = table_read_be_word(&cache->gpos.table, mark2_array_offset))) continue; mark_class_count = GET_BE_WORD(format1->mark_class_count); mark2_array = table_read_ensure(&cache->gpos.table, mark2_array_offset, FIELD_OFFSET(struct ot_gpos_base_array, offsets[count * mark_class_count])); if (!mark2_array) continue; mark2_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + GET_BE_WORD(format1->mark2_coverage), context->u.pos.glyphs[mark_iter.pos]); if (mark2_index == GLYPH_NOT_COVERED || mark2_index >= count) continue; mark2_anchor = GET_BE_WORD(mark2_array->offsets[mark2_index * mark_class_count + GET_BE_WORD(mark1_array->records[mark1_index].mark_class)]); opentype_layout_gpos_get_anchor(context, mark1_array_offset + GET_BE_WORD(mark1_array->records[mark1_index].mark_anchor), iter->pos, &mark1_x, &mark1_y); opentype_layout_gpos_get_anchor(context, mark2_array_offset + mark2_anchor, mark_iter.pos, &mark2_x, &mark2_y); context->offsets[iter->pos].advanceOffset = mark2_x - mark1_x; context->offsets[iter->pos].ascenderOffset = mark2_y - mark1_y; break; } else WARN("Unknown mark-to-mark format %u.\n", format); } return FALSE; } static BOOL opentype_layout_apply_gpos_contextual_positioning(const struct scriptshaping_context *context, struct glyph_iterator *iter, const struct lookup *lookup) { return FALSE; } static BOOL opentype_layout_apply_gpos_chaining_contextual_positioning(const struct scriptshaping_context *context, struct glyph_iterator *iter, const struct lookup *lookup) { return FALSE; } static void opentype_layout_apply_gpos_lookup(struct scriptshaping_context *context, int lookup_index) { struct scriptshaping_cache *cache = context->cache; const struct ot_lookup_table *lookup_table; struct glyph_iterator iter; struct lookup lookup; WORD lookup_type; lookup.offset = table_read_be_word(&cache->gpos.table, cache->gpos.lookup_list + FIELD_OFFSET(struct ot_lookup_list, lookup[lookup_index])); if (!lookup.offset) return; lookup.offset += cache->gpos.lookup_list; if (!(lookup_table = table_read_ensure(&cache->gpos.table, lookup.offset, sizeof(*lookup_table)))) return; lookup.subtable_count = GET_BE_WORD(lookup_table->subtable_count); if (!lookup.subtable_count) return; lookup_type = GET_BE_WORD(lookup_table->lookup_type); if (lookup_type == GPOS_LOOKUP_EXTENSION_POSITION) { const struct ot_gsubgpos_extensionpos_format1 *extension = table_read_ensure(&cache->gpos.table, lookup.offset + GET_BE_WORD(lookup_table->subtable[0]), sizeof(*extension)); WORD format; if (!extension) return; format = GET_BE_WORD(extension->format); if (format != 1) { WARN("Unexpected extension table format %u.\n", format); return; } lookup_type = GET_BE_WORD(extension->lookup_type); } lookup.flags = GET_BE_WORD(lookup_table->flags); glyph_iterator_init(context, lookup.flags, 0, context->glyph_count, &iter); while (iter.pos < context->glyph_count) { BOOL ret; if (!glyph_iterator_match(&iter)) { ++iter.pos; continue; } switch (lookup_type) { case GPOS_LOOKUP_SINGLE_ADJUSTMENT: ret = opentype_layout_apply_gpos_single_adjustment(context, &iter, &lookup); break; case GPOS_LOOKUP_PAIR_ADJUSTMENT: ret = opentype_layout_apply_gpos_pair_adjustment(context, &iter, &lookup); break; case GPOS_LOOKUP_CURSIVE_ATTACHMENT: ret = opentype_layout_apply_gpos_cursive_attachment(context, &iter, &lookup); break; case GPOS_LOOKUP_MARK_TO_BASE_ATTACHMENT: ret = opentype_layout_apply_gpos_mark_to_base_attachment(context, &iter, &lookup); break; case GPOS_LOOKUP_MARK_TO_LIGATURE_ATTACHMENT: ret = opentype_layout_apply_gpos_mark_to_lig_attachment(context, &iter, &lookup); break; case GPOS_LOOKUP_MARK_TO_MARK_ATTACHMENT: ret = opentype_layout_apply_gpos_mark_to_mark_attachment(context, &iter, &lookup); break; case GPOS_LOOKUP_CONTEXTUAL_POSITION: ret = opentype_layout_apply_gpos_contextual_positioning(context, &iter, &lookup); break; case GPOS_LOOKUP_CONTEXTUAL_CHAINING_POSITION: ret = opentype_layout_apply_gpos_chaining_contextual_positioning(context, &iter, &lookup); break; case GPOS_LOOKUP_EXTENSION_POSITION: WARN("Recursive extension lookup.\n"); ret = FALSE; break; default: WARN("Unknown lookup type %u.\n", lookup_type); return; } /* Some lookups update position after making changes. */ if (!ret) ++iter.pos; } } struct lookups { int *indexes; size_t capacity; size_t count; }; static int lookups_sorting_compare(const void *left, const void *right) { return *(int *)left - *(int *)right; }; static void opentype_layout_collect_lookups(struct scriptshaping_context *context, unsigned int script_index, unsigned int language_index, const struct shaping_features *features, struct ot_gsubgpos_table *table, struct lookups *lookups) { UINT16 table_offset, langsys_offset, script_feature_count, total_feature_count, total_lookup_count; const struct ot_feature_list *feature_list; unsigned int i, j, l; /* ScriptTable offset. */ table_offset = table_read_be_word(&table->table, table->script_list + FIELD_OFFSET(struct ot_script_list, scripts) + script_index * sizeof(struct ot_script_record) + FIELD_OFFSET(struct ot_script_record, script)); if (!table_offset) return; if (language_index == ~0u) langsys_offset = table_read_be_word(&table->table, table->script_list + table_offset); else langsys_offset = table_read_be_word(&table->table, table->script_list + table_offset + FIELD_OFFSET(struct ot_script, langsys) + language_index * sizeof(struct ot_langsys_record) + FIELD_OFFSET(struct ot_langsys_record, langsys)); script_feature_count = table_read_be_word(&table->table, table->script_list + table_offset + langsys_offset + FIELD_OFFSET(struct ot_langsys, feature_count)); if (!script_feature_count) return; total_feature_count = table_read_be_word(&table->table, table->feature_list); if (!total_feature_count) return; total_lookup_count = table_read_be_word(&table->table, table->lookup_list); if (!total_lookup_count) return; feature_list = table_read_ensure(&table->table, table->feature_list, FIELD_OFFSET(struct ot_feature_list, features[total_feature_count])); if (!feature_list) return; /* Collect lookups for all given features. */ for (i = 0; i < features->count; ++i) { for (j = 0; j < script_feature_count; ++j) { UINT16 feature_index = table_read_be_word(&table->table, table->script_list + table_offset + langsys_offset + FIELD_OFFSET(struct ot_langsys, feature_index[j])); if (feature_index >= total_feature_count) continue; if (feature_list->features[feature_index].tag == features->features[i].tag) { WORD feature_offset = GET_BE_WORD(feature_list->features[feature_index].offset); WORD lookup_count; lookup_count = table_read_be_word(&table->table, table->feature_list + feature_offset + FIELD_OFFSET(struct ot_feature, lookup_count)); if (!lookup_count) continue; if (!dwrite_array_reserve((void **)&lookups->indexes, &lookups->capacity, lookups->count + lookup_count, sizeof(*lookups->indexes))) { return; } for (l = 0; l < lookup_count; ++l) { UINT16 lookup_index = table_read_be_word(&table->table, table->feature_list + feature_offset + FIELD_OFFSET(struct ot_feature, lookuplist_index[l])); if (lookup_index >= total_lookup_count) continue; lookups->indexes[lookups->count++] = lookup_index; } } } } /* Sort lookups. */ qsort(lookups->indexes, lookups->count, sizeof(*lookups->indexes), lookups_sorting_compare); } void opentype_layout_apply_gpos_features(struct scriptshaping_context *context, unsigned int script_index, unsigned int language_index, const struct shaping_features *features) { struct lookups lookups = { 0 }; unsigned int i; opentype_layout_collect_lookups(context, script_index, language_index, features, &context->cache->gpos, &lookups); for (i = 0; i < lookups.count; ++i) { /* Skip duplicates. */ if (i && lookups.indexes[i] == lookups.indexes[i - 1]) continue; opentype_layout_apply_gpos_lookup(context, lookups.indexes[i]); } heap_free(lookups.indexes); } static BOOL opentype_layout_apply_gsub_single_substitution(struct glyph_iterator *iter, const struct lookup *lookup) { struct scriptshaping_cache *cache = iter->context->cache; UINT16 format, coverage; unsigned int i; for (i = 0; i < lookup->subtable_count; ++i) { unsigned int subtable_offset = opentype_layout_get_gsub_subtable(cache, lookup->offset, i); UINT16 glyph = iter->context->u.subst.glyphs[iter->pos]; unsigned int coverage_index; format = table_read_be_word(&cache->gsub.table, subtable_offset); coverage = table_read_be_word(&cache->gsub.table, subtable_offset + FIELD_OFFSET(struct ot_gsub_singlesubst_format1, coverage)); if (format == 1) { const struct ot_gsub_singlesubst_format1 *format1 = table_read_ensure(&cache->gsub.table, subtable_offset, sizeof(*format1)); coverage_index = opentype_layout_is_glyph_covered(&cache->gsub.table, subtable_offset + coverage, glyph); if (coverage_index == GLYPH_NOT_COVERED) continue; iter->context->u.subst.glyphs[iter->pos] = glyph + GET_BE_WORD(format1->delta); break; } else if (format == 2) { UINT16 count = table_read_be_word(&cache->gsub.table, subtable_offset + FIELD_OFFSET(struct ot_gsub_singlesubst_format2, count)); const struct ot_gsub_singlesubst_format2 *format2 = table_read_ensure(&cache->gsub.table, subtable_offset, FIELD_OFFSET(struct ot_gsub_singlesubst_format2, count) + count * sizeof(UINT16)); coverage_index = opentype_layout_is_glyph_covered(&cache->gsub.table, subtable_offset + coverage, glyph); if (coverage_index == GLYPH_NOT_COVERED || coverage_index >= count) continue; iter->context->u.subst.glyphs[iter->pos] = GET_BE_WORD(format2->substitutes[coverage_index]); break; } else WARN("Unknown single substitution format %u.\n", format); } return FALSE; } static BOOL opentype_layout_context_match_input(struct glyph_iterator *iter, unsigned int subtable_offset, unsigned int count, const UINT16 *input) { struct scriptshaping_cache *cache = iter->context->cache; unsigned int i, pos; if (iter->pos + count > iter->len) return FALSE; pos = iter->pos; for (i = 0; i < count; ++i, ++pos) { UINT16 glyph = iter->context->u.subst.glyphs[pos]; if (opentype_layout_is_glyph_covered(&cache->gsub.table, subtable_offset + GET_BE_WORD(input[i]), glyph) == GLYPH_NOT_COVERED) { return FALSE; } } return TRUE; } static BOOL opentype_layout_context_match_backtrack(struct glyph_iterator *iter, unsigned int subtable_offset, unsigned int count, const UINT16 *backtrack) { struct scriptshaping_cache *cache = iter->context->cache; unsigned int i; if (iter->pos < count) return FALSE; for (i = 0; i < count; ++i) { UINT16 glyph = iter->context->u.subst.glyphs[iter->pos - i - 1]; if (opentype_layout_is_glyph_covered(&cache->gsub.table, subtable_offset + GET_BE_WORD(backtrack[i]), glyph) == GLYPH_NOT_COVERED) { return FALSE; } } return TRUE; } static BOOL opentype_layout_context_match_lookahead(struct glyph_iterator *iter, unsigned int subtable_offset, unsigned int count, const UINT16 *lookahead, unsigned int offset) { struct scriptshaping_cache *cache = iter->context->cache; unsigned int i, pos; if (iter->pos + offset + count > iter->len) return FALSE; pos = iter->pos + offset; for (i = 0; i < count; ++i, ++pos) { UINT16 glyph = iter->context->u.subst.glyphs[pos]; if (opentype_layout_is_glyph_covered(&cache->gsub.table, subtable_offset + GET_BE_WORD(lookahead[i]), glyph) == GLYPH_NOT_COVERED) { return FALSE; } } return TRUE; } static void opentype_layout_apply_gsub_lookup(struct scriptshaping_context *context, unsigned int first_glyph, unsigned int glyph_count, int lookup_index); static BOOL opentype_layout_context_gsub_apply_lookup(struct glyph_iterator *iter, unsigned int count, unsigned int lookup_count, const UINT16 *lookup_records) { if (lookup_count > 1) FIXME("Only first lookup used.\n"); opentype_layout_apply_gsub_lookup(iter->context, iter->pos + GET_BE_WORD(lookup_records[0]), count, GET_BE_WORD(lookup_records[1])); return TRUE; } static BOOL opentype_layout_apply_gsub_chain_context_lookup(struct glyph_iterator *iter, unsigned int subtable_offset, unsigned int backtrack_count, const UINT16 *backtrack, unsigned int input_count, const UINT16 *input, unsigned int lookahead_count, const UINT16 *lookahead, unsigned int lookup_count, const UINT16 *lookup_records) { return opentype_layout_context_match_input(iter, subtable_offset, input_count, input) && opentype_layout_context_match_backtrack(iter, subtable_offset, backtrack_count, backtrack) && opentype_layout_context_match_lookahead(iter, subtable_offset, lookahead_count, lookahead, input_count) && opentype_layout_context_gsub_apply_lookup(iter, input_count, lookup_count, lookup_records); } static BOOL opentype_layout_apply_gsub_chain_context_substitution(struct glyph_iterator *iter, const struct lookup *lookup) { struct scriptshaping_cache *cache = iter->context->cache; UINT16 format, coverage; unsigned int i; for (i = 0; i < lookup->subtable_count; ++i) { unsigned int subtable_offset = opentype_layout_get_gsub_subtable(cache, lookup->offset, i); UINT16 glyph = iter->context->u.subst.glyphs[iter->pos]; unsigned int coverage_index = GLYPH_NOT_COVERED; format = table_read_be_word(&cache->gsub.table, subtable_offset); if (format == 1) { coverage = table_read_be_word(&cache->gsub.table, subtable_offset + FIELD_OFFSET(struct ot_gsub_chaincontext_subst_format1, coverage)); coverage_index = opentype_layout_is_glyph_covered(&cache->gsub.table, subtable_offset + coverage, glyph); if (coverage_index == GLYPH_NOT_COVERED) continue; WARN("Chaining contextual substitution (1) is not supported.\n"); break; } else if (format == 2) { coverage = table_read_be_word(&cache->gsub.table, subtable_offset + FIELD_OFFSET(struct ot_gsub_chaincontext_subst_format1, coverage)); coverage_index = opentype_layout_is_glyph_covered(&cache->gsub.table, subtable_offset + coverage, glyph); if (coverage_index == GLYPH_NOT_COVERED) continue; WARN("Chaining contextual substitution (2) is not supported.\n"); break; } else if (format == 3) { unsigned int backtrack_count, input_count, lookahead_count, lookup_count; const UINT16 *backtrack, *lookahead, *input, *lookup_records; unsigned int offset = subtable_offset + 2 /* format */; backtrack_count = table_read_be_word(&cache->gsub.table, offset); offset += 2; backtrack = table_read_ensure(&cache->gsub.table, offset, backtrack_count * sizeof(*backtrack)); offset += backtrack_count * sizeof(*backtrack); input_count = table_read_be_word(&cache->gsub.table, offset); offset += 2; input = table_read_ensure(&cache->gsub.table, offset, input_count * sizeof(*input)); offset += input_count * sizeof(*input); lookahead_count = table_read_be_word(&cache->gsub.table, offset); offset += 2; lookahead = table_read_ensure(&cache->gsub.table, offset, lookahead_count * sizeof(*lookahead)); offset += lookahead_count * sizeof(*lookahead); lookup_count = table_read_be_word(&cache->gsub.table, offset); offset += 2; lookup_records = table_read_ensure(&cache->gsub.table, offset, lookup_count * 2 * sizeof(*lookup_records)); if (input) { coverage_index = opentype_layout_is_glyph_covered(&cache->gsub.table, subtable_offset + GET_BE_WORD(input[0]), glyph); } if (coverage_index == GLYPH_NOT_COVERED) continue; if (opentype_layout_apply_gsub_chain_context_lookup(iter, subtable_offset, backtrack_count, backtrack, input_count, input, lookahead_count, lookahead, lookup_count, lookup_records)) { break; } } else WARN("Unknown chaining contextual substitution format %u.\n", format); } return FALSE; } static void opentype_layout_apply_gsub_lookup(struct scriptshaping_context *context, unsigned int first_glyph, unsigned int glyph_count, int lookup_index) { struct ot_gsubgpos_table *table = &context->cache->gsub; const struct ot_lookup_table *lookup_table; struct glyph_iterator iter; struct lookup lookup; WORD lookup_type; lookup.offset = table_read_be_word(&table->table, table->lookup_list + FIELD_OFFSET(struct ot_lookup_list, lookup[lookup_index])); if (!lookup.offset) return; lookup.offset += table->lookup_list; if (!(lookup_table = table_read_ensure(&table->table, lookup.offset, sizeof(*lookup_table)))) return; lookup.subtable_count = GET_BE_WORD(lookup_table->subtable_count); if (!lookup.subtable_count) return; lookup_type = GET_BE_WORD(lookup_table->lookup_type); lookup.flags = GET_BE_WORD(lookup_table->flags); glyph_iterator_init(context, lookup.flags, first_glyph, glyph_count, &iter); while (iter.pos < first_glyph + iter.len) { BOOL ret; if (!glyph_iterator_match(&iter)) { ++iter.pos; continue; } switch (lookup_type) { case GSUB_LOOKUP_SINGLE_SUBST: ret = opentype_layout_apply_gsub_single_substitution(&iter, &lookup); break; case GSUB_LOOKUP_CHAINING_CONTEXTUAL_SUBST: ret = opentype_layout_apply_gsub_chain_context_substitution(&iter, &lookup); break; case GSUB_LOOKUP_MULTIPLE_SUBST: case GSUB_LOOKUP_ALTERNATE_SUBST: case GSUB_LOOKUP_LIGATURE_SUBST: case GSUB_LOOKUP_CONTEXTUAL_SUBST: case GSUB_LOOKUP_REVERSE_CHAINING_CONTEXTUAL_SUBST: ret = FALSE; WARN("Unimplemented lookup %d.\n", lookup_type); break; default: WARN("Unknown lookup type %u.\n", lookup_type); return; } /* Some lookups update position after making changes. */ if (!ret) ++iter.pos; } } HRESULT opentype_layout_apply_gsub_features(struct scriptshaping_context *context, unsigned int script_index, unsigned int language_index, const struct shaping_features *features) { struct lookups lookups = { 0 }; unsigned int i; opentype_layout_collect_lookups(context, script_index, language_index, features, &context->cache->gsub, &lookups); for (i = 0; i < lookups.count; ++i) { /* Skip duplicates. */ if (i && lookups.indexes[i] == lookups.indexes[i - 1]) continue; opentype_layout_apply_gsub_lookup(context, 0, context->glyph_count, lookups.indexes[i]); } heap_free(lookups.indexes); return S_OK; }