5236 lines
163 KiB
C
5236 lines
163 KiB
C
/*
|
|
* 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 glyph_prop_flags
|
|
{
|
|
GLYPH_PROP_BASE = LOOKUP_FLAG_IGNORE_BASE,
|
|
GLYPH_PROP_LIGATURE = LOOKUP_FLAG_IGNORE_LIGATURES,
|
|
GLYPH_PROP_MARK = LOOKUP_FLAG_IGNORE_MARKS,
|
|
};
|
|
|
|
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_extension_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_multsubst_format1
|
|
{
|
|
UINT16 format;
|
|
UINT16 coverage;
|
|
UINT16 seq_count;
|
|
UINT16 seq[1];
|
|
};
|
|
|
|
struct ot_gsub_altsubst_format1
|
|
{
|
|
UINT16 format;
|
|
UINT16 coverage;
|
|
UINT16 count;
|
|
UINT16 sets[1];
|
|
};
|
|
|
|
struct ot_gsub_ligsubst_format1
|
|
{
|
|
UINT16 format;
|
|
UINT16 coverage;
|
|
UINT16 lig_set_count;
|
|
UINT16 lig_sets[1];
|
|
};
|
|
|
|
struct ot_gsub_ligset
|
|
{
|
|
UINT16 count;
|
|
UINT16 offsets[1];
|
|
};
|
|
|
|
struct ot_gsub_lig
|
|
{
|
|
UINT16 lig_glyph;
|
|
UINT16 comp_count;
|
|
UINT16 components[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_UNICODE_BMP,
|
|
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_UNICODE_FULL
|
|
};
|
|
|
|
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_UNICODE_BMP:
|
|
case TT_NAME_WINDOWS_ENCODING_UNICODE_FULL:
|
|
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;
|
|
}
|
|
|
|
static unsigned int opentype_set_glyph_props(struct scriptshaping_context *context, unsigned int g, UINT16 glyph)
|
|
{
|
|
struct scriptshaping_cache *cache = context->cache;
|
|
unsigned int glyph_class = 0, props;
|
|
|
|
if (cache->gdef.classdef)
|
|
{
|
|
glyph_class = opentype_layout_get_glyph_class(&cache->gdef.table, cache->gdef.classdef, glyph);
|
|
}
|
|
|
|
switch (glyph_class)
|
|
{
|
|
case GDEF_CLASS_BASE:
|
|
props = GLYPH_PROP_BASE;
|
|
break;
|
|
case GDEF_CLASS_LIGATURE:
|
|
props = GLYPH_PROP_LIGATURE;
|
|
break;
|
|
case GDEF_CLASS_MARK:
|
|
props = GLYPH_PROP_MARK;
|
|
break;
|
|
default:
|
|
props = 0;
|
|
}
|
|
|
|
context->glyph_infos[g].props = props;
|
|
|
|
return props;
|
|
}
|
|
|
|
static void opentype_set_subst_glyph_props(struct scriptshaping_context *context, unsigned int g, UINT16 glyph)
|
|
{
|
|
unsigned int glyph_props = opentype_set_glyph_props(context, g, glyph);
|
|
context->u.subst.glyph_props[g].isDiacritic = !!(glyph_props == GLYPH_PROP_MARK);
|
|
context->u.subst.glyph_props[g].isZeroWidthSpace = !!(glyph_props == GLYPH_PROP_MARK);
|
|
}
|
|
|
|
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 scriptshaping_context *context, unsigned int coverage,
|
|
UINT16 glyph)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
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 inline unsigned int dwrite_ctz(unsigned int x)
|
|
{
|
|
#if defined(__GNUC__) && ((__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)))
|
|
return __builtin_ctz(x);
|
|
#else
|
|
unsigned int c = 32;
|
|
x &= - (int) x;
|
|
if (x) c--;
|
|
if (x & 0x0000ffff) c -= 16;
|
|
if (x & 0x00ff00ff) c -= 8;
|
|
if (x & 0x0f0f0f0f) c -= 4;
|
|
if (x & 0x33333333) c -= 2;
|
|
if (x & 0x55555555) c -= 1;
|
|
return c;
|
|
#endif
|
|
}
|
|
|
|
static inline unsigned int dwrite_log2i(unsigned int x)
|
|
{
|
|
#if defined(__GNUC__) && ((__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)))
|
|
return __builtin_clz(x) ^ 0x1f;
|
|
#else
|
|
static const unsigned int l[] =
|
|
{
|
|
~0u, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
|
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
|
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
|
};
|
|
unsigned int i;
|
|
|
|
return (i = x >> 16) ? (x = i >> 8) ? l[x] + 24 : l[i] + 16 : (i = x >> 8) ? l[i] + 8 : l[x];
|
|
#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_gsubgpos_subtable(const struct scriptshaping_context *context,
|
|
unsigned int lookup_offset, unsigned int subtable)
|
|
{
|
|
unsigned int subtable_offset = table_read_be_word(&context->table->table, lookup_offset +
|
|
FIELD_OFFSET(struct ot_lookup_table, subtable[subtable]));
|
|
|
|
return lookup_offset + subtable_offset;
|
|
}
|
|
|
|
struct ot_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 lookup_is_glyph_match(unsigned int glyph_props, unsigned int lookup_flags)
|
|
{
|
|
if (glyph_props & lookup_flags & LOOKUP_FLAG_IGNORE_MASK)
|
|
return FALSE;
|
|
|
|
/* FIXME: match mark properties */
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL glyph_iterator_match(const struct glyph_iterator *iter)
|
|
{
|
|
return lookup_is_glyph_match(iter->context->glyph_infos[iter->pos].props, iter->flags);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
struct lookup
|
|
{
|
|
unsigned short index;
|
|
unsigned short type;
|
|
unsigned short flags;
|
|
unsigned short subtable_count;
|
|
|
|
unsigned int mask;
|
|
unsigned int offset;
|
|
};
|
|
|
|
static BOOL opentype_layout_apply_gpos_single_adjustment(struct scriptshaping_context *context,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
struct scriptshaping_cache *cache = context->cache;
|
|
UINT16 format, value_format, value_len, coverage, glyph;
|
|
|
|
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);
|
|
|
|
glyph = context->u.pos.glyphs[context->cur];
|
|
|
|
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(context, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
opentype_layout_apply_gpos_value(context, subtable_offset, value_format, format1->value, context->cur);
|
|
}
|
|
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(context, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED || coverage_index >= value_count)
|
|
return FALSE;
|
|
|
|
opentype_layout_apply_gpos_value(context, subtable_offset, value_format, &format2->values[coverage_index * value_len],
|
|
context->cur);
|
|
}
|
|
else
|
|
{
|
|
WARN("Unknown single adjustment format %u.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
context->cur++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
struct scriptshaping_cache *cache = context->cache;
|
|
unsigned int first_glyph, second_glyph;
|
|
struct glyph_iterator iter_pair;
|
|
WORD format, coverage;
|
|
|
|
WORD value_format1, value_format2, value_len1, value_len2;
|
|
unsigned int coverage_index;
|
|
|
|
glyph_iterator_init(context, lookup->flags, context->cur, 1, &iter_pair);
|
|
if (!glyph_iterator_next(&iter_pair))
|
|
return FALSE;
|
|
|
|
if (context->is_rtl)
|
|
{
|
|
first_glyph = iter_pair.pos;
|
|
second_glyph = context->cur;
|
|
}
|
|
else
|
|
{
|
|
first_glyph = context->cur;
|
|
second_glyph = iter_pair.pos;
|
|
}
|
|
|
|
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)
|
|
return FALSE;
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(context, subtable_offset + coverage, context->u.pos.glyphs[first_glyph]);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
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)
|
|
return FALSE;
|
|
|
|
format1 = table_read_ensure(&cache->gpos.table, subtable_offset,
|
|
FIELD_OFFSET(struct ot_gpos_pairpos_format1, pairsets[pairset_count]));
|
|
if (!format1)
|
|
return FALSE;
|
|
|
|
/* Ordered paired values. */
|
|
pairvalue_count = table_read_be_word(&cache->gpos.table, subtable_offset +
|
|
GET_BE_WORD(format1->pairsets[coverage_index]));
|
|
if (!pairvalue_count)
|
|
return FALSE;
|
|
|
|
/* 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)
|
|
return FALSE;
|
|
|
|
pairvalue = bsearch(&context->u.pos.glyphs[second_glyph], pairset->pairvalues, pairvalue_count,
|
|
pairvalue_len, gpos_pair_adjustment_compare_format1);
|
|
if (!pairvalue)
|
|
return FALSE;
|
|
|
|
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);
|
|
|
|
context->cur = iter_pair.pos;
|
|
if (value_len2)
|
|
context->cur++;
|
|
}
|
|
else if (format == 2)
|
|
{
|
|
const struct ot_gpos_pairpos_format2 *format2;
|
|
WORD class1_count, class2_count;
|
|
unsigned int class1, class2;
|
|
const WCHAR *values;
|
|
|
|
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)
|
|
return FALSE;
|
|
|
|
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))
|
|
return FALSE;
|
|
|
|
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);
|
|
|
|
context->cur = iter_pair.pos;
|
|
if (value_len2)
|
|
context->cur++;
|
|
}
|
|
else
|
|
{
|
|
WARN("Unknown pair adjustment format %u.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
struct scriptshaping_cache *cache = context->cache;
|
|
WORD format = table_read_be_word(&cache->gpos.table, subtable_offset);
|
|
UINT16 glyph = context->u.pos.glyphs[context->cur];
|
|
|
|
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)
|
|
return FALSE;
|
|
|
|
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(context, subtable_offset + coverage_offset, glyph);
|
|
if (glyph_index == GLYPH_NOT_COVERED || glyph_index >= entry_count)
|
|
return FALSE;
|
|
|
|
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)
|
|
return FALSE;
|
|
|
|
glyph_iterator_init(context, lookup->flags, context->cur, 1, &prev_iter);
|
|
if (!glyph_iterator_prev(&prev_iter))
|
|
return FALSE;
|
|
|
|
glyph_index = opentype_layout_is_glyph_covered(context, subtable_offset + coverage_offset,
|
|
context->u.pos.glyphs[prev_iter.pos]);
|
|
if (glyph_index == GLYPH_NOT_COVERED || glyph_index >= entry_count)
|
|
return FALSE;
|
|
|
|
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)
|
|
return FALSE;
|
|
|
|
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, context->cur, &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[context->cur] = entry_x + context->offsets[context->cur].advanceOffset;
|
|
context->offsets[prev_iter.pos].advanceOffset -= delta;
|
|
}
|
|
else
|
|
{
|
|
delta = entry_x + context->offsets[context->cur].advanceOffset;
|
|
context->advances[prev_iter.pos] = exit_x + context->offsets[prev_iter.pos].advanceOffset;
|
|
context->advances[context->cur] -= delta;
|
|
context->offsets[context->cur].advanceOffset -= delta;
|
|
}
|
|
|
|
if (lookup->flags & LOOKUP_FLAG_RTL)
|
|
context->offsets[prev_iter.pos].ascenderOffset = entry_y - exit_y;
|
|
else
|
|
context->offsets[context->cur].ascenderOffset = exit_y - entry_y;
|
|
|
|
context->cur++;
|
|
}
|
|
else
|
|
{
|
|
WARN("Unknown cursive attachment format %u.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gpos_mark_to_base_attachment(struct scriptshaping_context *context,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
struct scriptshaping_cache *cache = context->cache;
|
|
WORD format;
|
|
|
|
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)
|
|
return FALSE;
|
|
|
|
mark_array_offset = subtable_offset + GET_BE_WORD(format1->mark_array);
|
|
if (!(count = table_read_be_word(&cache->gpos.table, mark_array_offset)))
|
|
return FALSE;
|
|
|
|
mark_array = table_read_ensure(&cache->gpos.table, mark_array_offset,
|
|
FIELD_OFFSET(struct ot_gpos_mark_array, records[count]));
|
|
if (!mark_array)
|
|
return FALSE;
|
|
|
|
base_array_offset = subtable_offset + GET_BE_WORD(format1->base_array);
|
|
if (!(count = table_read_be_word(&cache->gpos.table, base_array_offset)))
|
|
return FALSE;
|
|
|
|
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)
|
|
return FALSE;
|
|
|
|
mark_class_count = GET_BE_WORD(format1->mark_class_count);
|
|
|
|
mark_index = opentype_layout_is_glyph_covered(context, subtable_offset + GET_BE_WORD(format1->mark_coverage),
|
|
context->u.pos.glyphs[context->cur]);
|
|
|
|
if (mark_index == GLYPH_NOT_COVERED || mark_index >= GET_BE_WORD(mark_array->count))
|
|
return FALSE;
|
|
|
|
/* Look back for first base glyph. */
|
|
glyph_iterator_init(context, LOOKUP_FLAG_IGNORE_MARKS, context->cur, 1, &base_iter);
|
|
if (!glyph_iterator_prev(&base_iter))
|
|
return FALSE;
|
|
|
|
base_index = opentype_layout_is_glyph_covered(context, 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))
|
|
return FALSE;
|
|
|
|
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), context->cur, &mark_x, &mark_y);
|
|
opentype_layout_gpos_get_anchor(context, base_array_offset + base_anchor, base_iter.pos, &base_x, &base_y);
|
|
|
|
context->offsets[context->cur].advanceOffset = (context->is_rtl ? -1.0f : 1.0f) * (base_x - mark_x);
|
|
context->offsets[context->cur].ascenderOffset = base_y - mark_y;
|
|
|
|
context->cur++;
|
|
}
|
|
else
|
|
{
|
|
WARN("Unknown mark-to-base format %u.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gpos_mark_to_lig_attachment(struct scriptshaping_context *context,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
struct scriptshaping_cache *cache = context->cache;
|
|
WORD format;
|
|
|
|
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)
|
|
return FALSE;
|
|
|
|
mark_index = opentype_layout_is_glyph_covered(context, subtable_offset + GET_BE_WORD(format1->mark_coverage),
|
|
context->u.pos.glyphs[context->cur]);
|
|
if (mark_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
glyph_iterator_init(context, LOOKUP_FLAG_IGNORE_MARKS, context->cur, 1, &lig_iter);
|
|
if (!glyph_iterator_prev(&lig_iter))
|
|
return FALSE;
|
|
|
|
lig_index = opentype_layout_is_glyph_covered(context, subtable_offset + GET_BE_WORD(format1->lig_coverage),
|
|
context->u.pos.glyphs[lig_iter.pos]);
|
|
if (lig_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
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,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
struct scriptshaping_cache *cache = context->cache;
|
|
WORD format;
|
|
|
|
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)
|
|
return FALSE;
|
|
|
|
mark1_index = opentype_layout_is_glyph_covered(context, subtable_offset + GET_BE_WORD(format1->mark1_coverage),
|
|
context->u.pos.glyphs[context->cur]);
|
|
|
|
mark1_array_offset = subtable_offset + GET_BE_WORD(format1->mark1_array);
|
|
if (!(count = table_read_be_word(&cache->gpos.table, mark1_array_offset)))
|
|
return FALSE;
|
|
|
|
mark1_array = table_read_ensure(&cache->gpos.table, mark1_array_offset,
|
|
FIELD_OFFSET(struct ot_gpos_mark_array, records[count]));
|
|
if (!mark1_array)
|
|
return FALSE;
|
|
|
|
if (mark1_index == GLYPH_NOT_COVERED || mark1_index >= count)
|
|
return FALSE;
|
|
|
|
glyph_iterator_init(context, lookup->flags & ~LOOKUP_FLAG_IGNORE_MASK, context->cur, 1, &mark_iter);
|
|
if (!glyph_iterator_prev(&mark_iter))
|
|
return FALSE;
|
|
|
|
if (!context->u.pos.glyph_props[mark_iter.pos].isDiacritic)
|
|
return FALSE;
|
|
|
|
mark2_array_offset = subtable_offset + GET_BE_WORD(format1->mark2_array);
|
|
if (!(count = table_read_be_word(&cache->gpos.table, mark2_array_offset)))
|
|
return FALSE;
|
|
|
|
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)
|
|
return FALSE;
|
|
|
|
mark2_index = opentype_layout_is_glyph_covered(context, subtable_offset + GET_BE_WORD(format1->mark2_coverage),
|
|
context->u.pos.glyphs[mark_iter.pos]);
|
|
|
|
if (mark2_index == GLYPH_NOT_COVERED || mark2_index >= count)
|
|
return FALSE;
|
|
|
|
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), context->cur, &mark1_x, &mark1_y);
|
|
opentype_layout_gpos_get_anchor(context, mark2_array_offset + mark2_anchor, mark_iter.pos,
|
|
&mark2_x, &mark2_y);
|
|
|
|
context->offsets[context->cur].advanceOffset = mark2_x - mark1_x;
|
|
context->offsets[context->cur].ascenderOffset = mark2_y - mark1_y;
|
|
|
|
context->cur++;
|
|
}
|
|
else
|
|
{
|
|
WARN("Unknown mark-to-mark format %u.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gpos_contextual_positioning(const struct scriptshaping_context *context,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gpos_chaining_contextual_positioning(const struct scriptshaping_context *context,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static unsigned int opentype_layout_adjust_extension_subtable(struct scriptshaping_context *context,
|
|
unsigned int *subtable_offset)
|
|
{
|
|
const struct ot_gsubgpos_extension_format1 *format1;
|
|
|
|
if (!(format1 = table_read_ensure(&context->table->table, *subtable_offset, sizeof(*format1))))
|
|
return 0;
|
|
|
|
if (GET_BE_WORD(format1->format) != 1)
|
|
{
|
|
WARN("Unexpected extension table format %#x.\n", format1->format);
|
|
return 0;
|
|
}
|
|
|
|
*subtable_offset = *subtable_offset + GET_BE_DWORD(format1->extension_offset);
|
|
|
|
return GET_BE_WORD(format1->lookup_type);
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gpos_lookup(struct scriptshaping_context *context, const struct lookup *lookup)
|
|
{
|
|
unsigned int i, lookup_type;
|
|
BOOL ret = FALSE;
|
|
|
|
for (i = 0; i < lookup->subtable_count; ++i)
|
|
{
|
|
unsigned int subtable_offset = opentype_layout_get_gsubgpos_subtable(context, lookup->offset, i);
|
|
|
|
if (lookup->type == GPOS_LOOKUP_EXTENSION_POSITION)
|
|
{
|
|
lookup_type = opentype_layout_adjust_extension_subtable(context, &subtable_offset);
|
|
if (!lookup_type)
|
|
continue;
|
|
}
|
|
else
|
|
lookup_type = lookup->type;
|
|
|
|
switch (lookup_type)
|
|
{
|
|
case GPOS_LOOKUP_SINGLE_ADJUSTMENT:
|
|
ret = opentype_layout_apply_gpos_single_adjustment(context, lookup, subtable_offset);
|
|
break;
|
|
case GPOS_LOOKUP_PAIR_ADJUSTMENT:
|
|
ret = opentype_layout_apply_gpos_pair_adjustment(context, lookup, subtable_offset);
|
|
break;
|
|
case GPOS_LOOKUP_CURSIVE_ATTACHMENT:
|
|
ret = opentype_layout_apply_gpos_cursive_attachment(context, lookup, subtable_offset);
|
|
break;
|
|
case GPOS_LOOKUP_MARK_TO_BASE_ATTACHMENT:
|
|
ret = opentype_layout_apply_gpos_mark_to_base_attachment(context, lookup, subtable_offset);
|
|
break;
|
|
case GPOS_LOOKUP_MARK_TO_LIGATURE_ATTACHMENT:
|
|
ret = opentype_layout_apply_gpos_mark_to_lig_attachment(context, lookup, subtable_offset);
|
|
break;
|
|
case GPOS_LOOKUP_MARK_TO_MARK_ATTACHMENT:
|
|
ret = opentype_layout_apply_gpos_mark_to_mark_attachment(context, lookup, subtable_offset);
|
|
break;
|
|
case GPOS_LOOKUP_CONTEXTUAL_POSITION:
|
|
ret = opentype_layout_apply_gpos_contextual_positioning(context, lookup, subtable_offset);
|
|
break;
|
|
case GPOS_LOOKUP_CONTEXTUAL_CHAINING_POSITION:
|
|
ret = opentype_layout_apply_gpos_chaining_contextual_positioning(context, lookup, subtable_offset);
|
|
break;
|
|
case GPOS_LOOKUP_EXTENSION_POSITION:
|
|
WARN("Recursive extension lookup.\n");
|
|
break;
|
|
default:
|
|
WARN("Unknown lookup type %u.\n", lookup_type);
|
|
}
|
|
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct lookups
|
|
{
|
|
struct lookup *lookups;
|
|
size_t capacity;
|
|
size_t count;
|
|
};
|
|
|
|
static int lookups_sorting_compare(const void *a, const void *b)
|
|
{
|
|
const struct lookup *left = (const struct lookup *)a;
|
|
const struct lookup *right = (const struct lookup *)b;
|
|
return left->index < right->index ? -1 : left->index > right->index ? 1 : 0;
|
|
};
|
|
|
|
static BOOL opentype_layout_init_lookup(const struct ot_gsubgpos_table *table, unsigned short lookup_index, unsigned int mask,
|
|
struct lookup *lookup)
|
|
{
|
|
unsigned short subtable_count, lookup_type, flags;
|
|
const struct ot_lookup_table *lookup_table;
|
|
unsigned int offset;
|
|
|
|
if (!(offset = table_read_be_word(&table->table, table->lookup_list +
|
|
FIELD_OFFSET(struct ot_lookup_list, lookup[lookup_index]))))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
offset += table->lookup_list;
|
|
|
|
if (!(lookup_table = table_read_ensure(&table->table, offset, sizeof(*lookup_table))))
|
|
return FALSE;
|
|
|
|
if (!(subtable_count = GET_BE_WORD(lookup_table->subtable_count)))
|
|
return FALSE;
|
|
|
|
lookup_type = GET_BE_WORD(lookup_table->lookup_type);
|
|
flags = GET_BE_WORD(lookup_table->flags);
|
|
|
|
lookup->index = lookup_index;
|
|
lookup->type = lookup_type;
|
|
lookup->flags = flags;
|
|
lookup->subtable_count = subtable_count;
|
|
lookup->mask = mask;
|
|
lookup->offset = offset;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void opentype_layout_add_lookups(const struct ot_feature_list *feature_list, UINT16 total_lookup_count,
|
|
const struct ot_gsubgpos_table *table, struct shaping_feature *feature, struct lookups *lookups)
|
|
{
|
|
UINT16 feature_offset, lookup_count;
|
|
unsigned int i;
|
|
|
|
/* Feature wasn't found */
|
|
if (feature->index == 0xffff)
|
|
return;
|
|
|
|
/* FIXME: skip non-global ones for now. */
|
|
if (!(feature->flags & FEATURE_GLOBAL))
|
|
return;
|
|
|
|
feature_offset = GET_BE_WORD(feature_list->features[feature->index].offset);
|
|
|
|
lookup_count = table_read_be_word(&table->table, table->feature_list + feature_offset +
|
|
FIELD_OFFSET(struct ot_feature, lookup_count));
|
|
if (!lookup_count)
|
|
return;
|
|
|
|
if (!dwrite_array_reserve((void **)&lookups->lookups, &lookups->capacity, lookups->count + lookup_count,
|
|
sizeof(*lookups->lookups)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < lookup_count; ++i)
|
|
{
|
|
UINT16 lookup_index = table_read_be_word(&table->table, table->feature_list + feature_offset +
|
|
FIELD_OFFSET(struct ot_feature, lookuplist_index[i]));
|
|
|
|
if (lookup_index >= total_lookup_count)
|
|
continue;
|
|
|
|
if (opentype_layout_init_lookup(table, lookup_index, feature->mask, &lookups->lookups[lookups->count]))
|
|
lookups->count++;
|
|
}
|
|
}
|
|
|
|
static void opentype_layout_collect_lookups(struct scriptshaping_context *context, unsigned int script_index,
|
|
unsigned int language_index, const struct shaping_features *features, const 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 last_num_lookups = 0, stage;
|
|
struct shaping_feature *feature;
|
|
unsigned int i, j, next_bit;
|
|
unsigned int global_bit_shift = 1;
|
|
unsigned int global_bit_mask = 1;
|
|
UINT16 feature_index;
|
|
|
|
if (!table->table.data)
|
|
return;
|
|
|
|
/* 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;
|
|
|
|
context->global_mask = global_bit_mask;
|
|
next_bit = global_bit_shift + 1;
|
|
for (i = 0; i < features->count; ++i)
|
|
{
|
|
unsigned int bits_needed;
|
|
BOOL found = FALSE;
|
|
|
|
feature = &features->features[i];
|
|
|
|
feature->index = 0xffff;
|
|
|
|
if ((feature->flags & FEATURE_GLOBAL) && feature->max_value == 1)
|
|
bits_needed = 0;
|
|
else
|
|
bits_needed = min(8, dwrite_log2i(feature->max_value));
|
|
|
|
if (!feature->max_value || next_bit + bits_needed > 8 * sizeof (feature->mask))
|
|
continue;
|
|
|
|
for (j = 0; j < script_feature_count; ++j)
|
|
{
|
|
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 ((found = feature_list->features[feature_index].tag == feature->tag))
|
|
{
|
|
feature->index = feature_index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found && (features->features[i].flags & FEATURE_GLOBAL_SEARCH))
|
|
{
|
|
for (j = 0; j < total_feature_count; ++j)
|
|
{
|
|
if ((found = (feature_list->features[j].tag == feature->tag)))
|
|
{
|
|
feature->index = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
continue;
|
|
|
|
if (feature->flags & FEATURE_GLOBAL && feature->max_value == 1)
|
|
{
|
|
feature->shift = global_bit_shift;
|
|
feature->mask = global_bit_mask;
|
|
}
|
|
else
|
|
{
|
|
feature->shift = next_bit;
|
|
feature->mask = (1 << (next_bit + bits_needed)) - (1 << next_bit);
|
|
next_bit += bits_needed;
|
|
context->global_mask |= (feature->default_value << feature->shift) & feature->mask;
|
|
}
|
|
}
|
|
|
|
for (stage = 0; stage <= features->stage; ++stage)
|
|
{
|
|
for (i = 0; i < features->count; ++i)
|
|
{
|
|
if (features->features[i].stage == stage)
|
|
opentype_layout_add_lookups(feature_list, total_lookup_count, table, &features->features[i], lookups);
|
|
}
|
|
|
|
/* Sort and merge lookups for current stage. */
|
|
if (last_num_lookups < lookups->count)
|
|
{
|
|
qsort(lookups->lookups + last_num_lookups, lookups->count - last_num_lookups, sizeof(*lookups->lookups),
|
|
lookups_sorting_compare);
|
|
|
|
j = last_num_lookups;
|
|
for (i = j + 1; i < lookups->count; ++i)
|
|
{
|
|
if (lookups->lookups[i].index != lookups->lookups[j].index)
|
|
{
|
|
lookups->lookups[++j] = lookups->lookups[i];
|
|
}
|
|
else
|
|
{
|
|
lookups->lookups[j].mask |= lookups->lookups[i].mask;
|
|
}
|
|
}
|
|
lookups->count = j + 1;
|
|
}
|
|
|
|
last_num_lookups = lookups->count;
|
|
}
|
|
}
|
|
|
|
static int feature_search_compare(const void *a, const void* b)
|
|
{
|
|
unsigned int tag = *(unsigned int *)a;
|
|
const struct shaping_feature *feature = b;
|
|
|
|
return tag < feature->tag ? -1 : tag > feature->tag ? 1 : 0;
|
|
}
|
|
|
|
static unsigned int shaping_features_get_mask(const struct shaping_features *features, unsigned int tag, unsigned int *shift)
|
|
{
|
|
struct shaping_feature *feature;
|
|
|
|
feature = bsearch(&tag, features->features, features->count, sizeof(*features->features), feature_search_compare);
|
|
|
|
if (!feature || feature->index == 0xffff)
|
|
return 0;
|
|
|
|
if (shift) *shift = feature->shift;
|
|
return feature->mask;
|
|
}
|
|
|
|
static void opentype_layout_get_glyph_range_for_text(struct scriptshaping_context *context, unsigned int start_char,
|
|
unsigned int end_char, unsigned int *start_glyph, unsigned int *end_glyph)
|
|
{
|
|
*start_glyph = context->u.buffer.clustermap[start_char];
|
|
if (end_char >= context->length - 1)
|
|
*end_glyph = context->glyph_count - 1;
|
|
else
|
|
*end_glyph = context->u.buffer.clustermap[end_char + 1] - 1;
|
|
}
|
|
|
|
static void opentype_layout_set_glyph_masks(struct scriptshaping_context *context, const struct shaping_features *features)
|
|
{
|
|
const DWRITE_TYPOGRAPHIC_FEATURES **user_features = context->user_features.features;
|
|
unsigned int f, r, g, start_char, mask, shift, value;
|
|
|
|
for (g = 0; g < context->glyph_count; ++g)
|
|
context->glyph_infos[g].mask = context->global_mask;
|
|
|
|
/* FIXME: set shaper masks */
|
|
|
|
for (r = 0, start_char = 0; r < context->user_features.range_count; ++r)
|
|
{
|
|
unsigned int start_glyph, end_glyph;
|
|
|
|
if (start_char >= context->length)
|
|
break;
|
|
|
|
opentype_layout_get_glyph_range_for_text(context, start_char, start_char + context->user_features.range_lengths[r],
|
|
&start_glyph, &end_glyph);
|
|
start_char += context->user_features.range_lengths[r];
|
|
|
|
if (start_glyph > end_glyph || end_glyph >= context->glyph_count)
|
|
continue;
|
|
|
|
for (f = 0; f < user_features[r]->featureCount; ++f)
|
|
{
|
|
mask = shaping_features_get_mask(features, user_features[r]->features[f].nameTag, &shift);
|
|
if (!mask)
|
|
continue;
|
|
|
|
value = (user_features[r]->features[f].parameter << shift) & mask;
|
|
|
|
for (g = start_glyph; g <= end_glyph; ++g)
|
|
context->glyph_infos[g].mask = (context->glyph_infos[g].mask & ~mask) | value;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
BOOL ret;
|
|
|
|
opentype_layout_collect_lookups(context, script_index, language_index, features, &context->cache->gpos, &lookups);
|
|
|
|
for (i = 0; i < context->glyph_count; ++i)
|
|
opentype_set_glyph_props(context, i, context->u.pos.glyphs[i]);
|
|
opentype_layout_set_glyph_masks(context, features);
|
|
|
|
for (i = 0; i < lookups.count; ++i)
|
|
{
|
|
const struct lookup *lookup = &lookups.lookups[i];
|
|
|
|
context->cur = 0;
|
|
while (context->cur < context->glyph_count)
|
|
{
|
|
ret = FALSE;
|
|
|
|
if ((context->glyph_infos[context->cur].mask & lookup->mask) &&
|
|
lookup_is_glyph_match(context->glyph_infos[context->cur].props, lookup->flags))
|
|
{
|
|
ret = opentype_layout_apply_gpos_lookup(context, lookup);
|
|
}
|
|
|
|
if (!ret)
|
|
context->cur++;
|
|
}
|
|
}
|
|
|
|
heap_free(lookups.lookups);
|
|
}
|
|
|
|
static void opentype_layout_replace_glyph(struct scriptshaping_context *context, UINT16 glyph)
|
|
{
|
|
UINT16 orig_glyph = context->u.subst.glyphs[context->cur];
|
|
if (glyph != orig_glyph)
|
|
{
|
|
context->u.subst.glyphs[context->cur] = glyph;
|
|
opentype_set_subst_glyph_props(context, context->cur, glyph);
|
|
}
|
|
context->cur++;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gsub_single_substitution(struct scriptshaping_context *context, const struct lookup *lookup,
|
|
unsigned int subtable_offset)
|
|
{
|
|
const struct dwrite_fonttable *gsub = &context->table->table;
|
|
UINT16 format, coverage, orig_glyph, glyph;
|
|
unsigned int idx, coverage_index;
|
|
|
|
idx = context->cur;
|
|
orig_glyph = glyph = context->u.subst.glyphs[idx];
|
|
|
|
format = table_read_be_word(gsub, subtable_offset);
|
|
|
|
coverage = table_read_be_word(gsub, subtable_offset + FIELD_OFFSET(struct ot_gsub_singlesubst_format1, coverage));
|
|
|
|
if (format == 1)
|
|
{
|
|
const struct ot_gsub_singlesubst_format1 *format1 = table_read_ensure(gsub, subtable_offset, sizeof(*format1));
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(context, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
glyph = orig_glyph + GET_BE_WORD(format1->delta);
|
|
}
|
|
else if (format == 2)
|
|
{
|
|
UINT16 count = table_read_be_word(gsub, subtable_offset + FIELD_OFFSET(struct ot_gsub_singlesubst_format2, count));
|
|
const struct ot_gsub_singlesubst_format2 *format2 = table_read_ensure(gsub, subtable_offset,
|
|
FIELD_OFFSET(struct ot_gsub_singlesubst_format2, count) + count * sizeof(UINT16));
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(context, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED || coverage_index >= count)
|
|
return FALSE;
|
|
|
|
glyph = GET_BE_WORD(format2->substitutes[coverage_index]);
|
|
}
|
|
else
|
|
{
|
|
WARN("Unknown single substitution format %u.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
opentype_layout_replace_glyph(context, glyph);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_gsub_ensure_buffer(struct scriptshaping_context *context, unsigned int count)
|
|
{
|
|
DWRITE_SHAPING_GLYPH_PROPERTIES *glyph_props;
|
|
struct shaping_glyph_info *glyph_infos;
|
|
unsigned int new_capacity;
|
|
UINT16 *glyphs;
|
|
BOOL ret;
|
|
|
|
if (context->u.subst.capacity >= count)
|
|
return TRUE;
|
|
|
|
new_capacity = context->u.subst.capacity * 2;
|
|
|
|
if ((glyphs = heap_realloc(context->u.subst.glyphs, new_capacity * sizeof(*glyphs))))
|
|
context->u.subst.glyphs = glyphs;
|
|
if ((glyph_props = heap_realloc(context->u.subst.glyph_props, new_capacity * sizeof(*glyph_props))))
|
|
context->u.subst.glyph_props = glyph_props;
|
|
if ((glyph_infos = heap_realloc(context->glyph_infos, new_capacity * sizeof(*glyph_infos))))
|
|
context->glyph_infos = glyph_infos;
|
|
|
|
if ((ret = (glyphs && glyph_props && glyph_infos)))
|
|
context->u.subst.capacity = new_capacity;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int opentype_layout_get_next_char_index(const struct scriptshaping_context *context, unsigned int idx)
|
|
{
|
|
unsigned int start = 0, end = context->length - 1, mid, ret = ~0u;
|
|
|
|
for (;;)
|
|
{
|
|
mid = (start + end) / 2;
|
|
|
|
if (context->u.buffer.clustermap[mid] <= idx)
|
|
{
|
|
if (mid == end) break;
|
|
start = mid + 1;
|
|
}
|
|
else
|
|
{
|
|
ret = mid;
|
|
if (mid == start) break;
|
|
end = mid - 1;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gsub_mult_substitution(struct scriptshaping_context *context, const struct lookup *lookup,
|
|
unsigned int subtable_offset)
|
|
{
|
|
const struct dwrite_fonttable *gsub = &context->table->table;
|
|
UINT16 format, coverage, glyph, seq_count, glyph_count;
|
|
const struct ot_gsub_multsubst_format1 *format1;
|
|
unsigned int i, idx, coverage_index;
|
|
const UINT16 *glyphs;
|
|
|
|
idx = context->cur;
|
|
glyph = context->u.subst.glyphs[idx];
|
|
|
|
format = table_read_be_word(gsub, subtable_offset);
|
|
|
|
coverage = table_read_be_word(gsub, subtable_offset + FIELD_OFFSET(struct ot_gsub_multsubst_format1, coverage));
|
|
|
|
if (format == 1)
|
|
{
|
|
coverage_index = opentype_layout_is_glyph_covered(context, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
seq_count = table_read_be_word(gsub, subtable_offset + FIELD_OFFSET(struct ot_gsub_multsubst_format1, seq_count));
|
|
|
|
format1 = table_read_ensure(gsub, subtable_offset, FIELD_OFFSET(struct ot_gsub_multsubst_format1, seq[seq_count]));
|
|
|
|
if (!seq_count || seq_count <= coverage_index || !format1)
|
|
return FALSE;
|
|
|
|
glyph_count = table_read_be_word(gsub, subtable_offset + GET_BE_WORD(format1->seq[coverage_index]));
|
|
|
|
glyphs = table_read_ensure(gsub, subtable_offset + GET_BE_WORD(format1->seq[coverage_index]) + 2,
|
|
glyph_count * sizeof(*glyphs));
|
|
if (!glyphs)
|
|
return FALSE;
|
|
|
|
if (glyph_count == 1)
|
|
{
|
|
/* Equivalent of single substitution. */
|
|
opentype_layout_replace_glyph(context, GET_BE_WORD(glyphs[0]));
|
|
}
|
|
else if (glyph_count == 0)
|
|
{
|
|
context->cur++;
|
|
}
|
|
else
|
|
{
|
|
unsigned int shift_len, src_idx, dest_idx, mask;
|
|
|
|
/* Current glyph is also replaced. */
|
|
glyph_count--;
|
|
|
|
if (!(opentype_layout_gsub_ensure_buffer(context, context->glyph_count + glyph_count)))
|
|
return FALSE;
|
|
|
|
shift_len = context->cur + 1 < context->glyph_count ? context->glyph_count - context->cur - 1 : 0;
|
|
|
|
if (shift_len)
|
|
{
|
|
src_idx = context->cur + 1;
|
|
dest_idx = src_idx + glyph_count;
|
|
|
|
memmove(&context->u.subst.glyphs[dest_idx], &context->u.subst.glyphs[src_idx],
|
|
shift_len * sizeof(*context->u.subst.glyphs));
|
|
memmove(&context->u.subst.glyph_props[dest_idx], &context->u.subst.glyph_props[src_idx],
|
|
shift_len * sizeof(*context->u.subst.glyph_props));
|
|
memmove(&context->glyph_infos[dest_idx], &context->glyph_infos[src_idx],
|
|
shift_len * sizeof(*context->glyph_infos));
|
|
}
|
|
|
|
mask = context->glyph_infos[context->cur].mask;
|
|
for (i = 0, idx = context->cur; i <= glyph_count; ++i)
|
|
{
|
|
glyph = GET_BE_WORD(glyphs[i]);
|
|
context->u.subst.glyphs[idx + i] = glyph;
|
|
if (i)
|
|
{
|
|
context->u.subst.glyph_props[idx + i].isClusterStart = 0;
|
|
context->u.buffer.glyph_props[idx + i].components = 0;
|
|
}
|
|
opentype_set_subst_glyph_props(context, idx + i, glyph);
|
|
/* Inherit feature mask from original matched glyph. */
|
|
context->glyph_infos[idx + i].mask = mask;
|
|
}
|
|
|
|
/* Update following clusters. */
|
|
idx = opentype_layout_get_next_char_index(context, context->cur);
|
|
if (idx != ~0u)
|
|
{
|
|
for (i = idx; i < context->length; ++i)
|
|
context->u.subst.clustermap[i] += glyph_count;
|
|
}
|
|
|
|
context->cur += glyph_count + 1;
|
|
context->glyph_count += glyph_count;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARN("Unknown multiple substitution format %u.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gsub_alt_substitution(struct scriptshaping_context *context, const struct lookup *lookup,
|
|
unsigned int subtable_offset)
|
|
{
|
|
const struct dwrite_fonttable *gsub = &context->table->table;
|
|
unsigned int idx, coverage_index, offset;
|
|
UINT16 format, coverage, glyph;
|
|
|
|
idx = context->cur;
|
|
glyph = context->u.subst.glyphs[idx];
|
|
|
|
format = table_read_be_word(gsub, subtable_offset);
|
|
|
|
if (format == 1)
|
|
{
|
|
const struct ot_gsub_altsubst_format1 *format1 = table_read_ensure(gsub, subtable_offset, sizeof(*format1));
|
|
unsigned int count, shift, alt_index;
|
|
|
|
coverage = table_read_be_word(gsub, subtable_offset + FIELD_OFFSET(struct ot_gsub_altsubst_format1, coverage));
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(context, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
if (coverage_index >= GET_BE_WORD(format1->count))
|
|
return FALSE;
|
|
|
|
offset = table_read_be_word(gsub, subtable_offset +
|
|
FIELD_OFFSET(struct ot_gsub_altsubst_format1, sets[coverage_index]));
|
|
|
|
count = table_read_be_word(gsub, subtable_offset + offset);
|
|
if (!count)
|
|
return FALSE;
|
|
|
|
shift = dwrite_ctz(lookup->mask);
|
|
alt_index = (lookup->mask & context->glyph_infos[idx].mask) >> shift;
|
|
|
|
if (alt_index > count || !alt_index)
|
|
return FALSE;
|
|
|
|
glyph = table_read_be_word(gsub, subtable_offset + offset + sizeof(count) + (alt_index - 1) * sizeof(glyph));
|
|
}
|
|
else
|
|
{
|
|
WARN("Unexpected alternate substitution format %d.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
opentype_layout_replace_glyph(context, glyph);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_ligature(struct scriptshaping_context *context, unsigned int offset)
|
|
{
|
|
const struct dwrite_fonttable *gsub = &context->table->table;
|
|
const struct ot_gsub_lig *lig;
|
|
unsigned int comp_count;
|
|
|
|
comp_count = table_read_be_word(gsub, offset + FIELD_OFFSET(struct ot_gsub_lig, comp_count));
|
|
|
|
if (!comp_count)
|
|
return FALSE;
|
|
|
|
lig = table_read_ensure(gsub, offset, FIELD_OFFSET(struct ot_gsub_lig, components[comp_count-1]));
|
|
if (!lig)
|
|
return FALSE;
|
|
|
|
if (comp_count == 1)
|
|
{
|
|
opentype_layout_replace_glyph(context, GET_BE_WORD(lig->lig_glyph));
|
|
return TRUE;
|
|
}
|
|
|
|
/* TODO: actually apply ligature */
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gsub_lig_substitution(struct scriptshaping_context *context, const struct lookup *lookup,
|
|
unsigned int subtable_offset)
|
|
{
|
|
const struct dwrite_fonttable *gsub = &context->table->table;
|
|
unsigned int coverage_index, offset, lig_set_offset, lig_set_count;
|
|
UINT16 format, coverage, glyph;
|
|
|
|
glyph = context->u.subst.glyphs[context->cur];
|
|
|
|
format = table_read_be_word(gsub, subtable_offset);
|
|
|
|
if (format == 1)
|
|
{
|
|
const struct ot_gsub_ligsubst_format1 *format1 = table_read_ensure(gsub, subtable_offset, sizeof(*format1));
|
|
const struct ot_gsub_ligset *lig_set;
|
|
unsigned int i, lig_count;
|
|
|
|
coverage = table_read_be_word(gsub, subtable_offset + FIELD_OFFSET(struct ot_gsub_ligsubst_format1, coverage));
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(context, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
lig_set_count = table_read_be_word(gsub, subtable_offset + FIELD_OFFSET(struct ot_gsub_ligsubst_format1, lig_set_count));
|
|
if (coverage_index >= lig_set_count || !table_read_ensure(gsub, subtable_offset,
|
|
FIELD_OFFSET(struct ot_gsub_ligsubst_format1, lig_sets[lig_set_count])))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
lig_set_offset = table_read_be_word(gsub, subtable_offset +
|
|
FIELD_OFFSET(struct ot_gsub_ligsubst_format1, lig_sets[coverage_index]));
|
|
offset = subtable_offset + lig_set_offset;
|
|
|
|
lig_count = table_read_be_word(gsub, offset);
|
|
lig_set = table_read_ensure(gsub, offset, FIELD_OFFSET(struct ot_gsub_ligset, offsets[lig_count]));
|
|
if (!lig_count || !lig_set)
|
|
return FALSE;
|
|
|
|
/* First applicable ligature is used. */
|
|
for (i = 0; i < lig_count; ++i)
|
|
{
|
|
if (opentype_layout_apply_ligature(context, offset + GET_BE_WORD(lig_set->offsets[i])))
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
WARN("Unexpected ligature substitution format %d.\n", format);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
#define CHAIN_CONTEXT_MAX_LENGTH 64
|
|
|
|
static BOOL opentype_layout_context_match_input(struct scriptshaping_context *context, unsigned int subtable_offset,
|
|
unsigned int count, const UINT16 *input, unsigned int *end_offset, unsigned int *match_positions)
|
|
{
|
|
struct glyph_iterator iter;
|
|
unsigned int i;
|
|
UINT16 glyph;
|
|
|
|
if (count > CHAIN_CONTEXT_MAX_LENGTH)
|
|
return FALSE;
|
|
|
|
match_positions[0] = context->cur;
|
|
|
|
glyph_iterator_init(context, 0, context->cur, count - 1, &iter);
|
|
|
|
for (i = 1; i < count; ++i)
|
|
{
|
|
if (!glyph_iterator_next(&iter))
|
|
return FALSE;
|
|
|
|
/* TODO: this only covers Format3 substitution */
|
|
glyph = context->u.subst.glyphs[iter.pos];
|
|
if (opentype_layout_is_glyph_covered(context, subtable_offset + GET_BE_WORD(input[i]), glyph) == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
match_positions[i] = iter.pos;
|
|
}
|
|
|
|
*end_offset = iter.pos - context->cur + 1;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_context_match_backtrack(struct scriptshaping_context *context, unsigned int subtable_offset,
|
|
unsigned int count, const UINT16 *backtrack, unsigned int *match_start)
|
|
{
|
|
struct glyph_iterator iter;
|
|
unsigned int i;
|
|
UINT16 glyph;
|
|
|
|
glyph_iterator_init(context, 0, context->cur, count, &iter);
|
|
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
if (!glyph_iterator_prev(&iter))
|
|
return FALSE;
|
|
|
|
glyph = context->u.subst.glyphs[iter.pos];
|
|
if (opentype_layout_is_glyph_covered(context, subtable_offset + GET_BE_WORD(backtrack[i]), glyph) == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
}
|
|
|
|
*match_start = iter.pos;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_context_match_lookahead(struct scriptshaping_context *context, unsigned int subtable_offset,
|
|
unsigned int count, const UINT16 *lookahead, unsigned int offset, unsigned int *end_index)
|
|
{
|
|
struct glyph_iterator iter;
|
|
unsigned int i;
|
|
UINT16 glyph;
|
|
|
|
glyph_iterator_init(context, 0, context->cur + offset - 1, count, &iter);
|
|
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
if (!glyph_iterator_next(&iter))
|
|
return FALSE;
|
|
|
|
glyph = context->u.subst.glyphs[iter.pos];
|
|
if (opentype_layout_is_glyph_covered(context, subtable_offset + GET_BE_WORD(lookahead[i]), glyph) == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
}
|
|
|
|
*end_index = iter.pos;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gsub_lookup(struct scriptshaping_context *context, const struct lookup *lookup);
|
|
|
|
static BOOL opentype_layout_context_gsub_apply_lookup(struct scriptshaping_context *context, unsigned int count,
|
|
unsigned int *match_positions, unsigned int lookup_count, const UINT16 *lookup_records, unsigned int match_length)
|
|
{
|
|
struct lookup lookup = { 0 };
|
|
unsigned int i, j;
|
|
int end, delta;
|
|
|
|
end = context->cur + match_length;
|
|
|
|
for (i = 0; i < lookup_count; ++i)
|
|
{
|
|
unsigned int idx = GET_BE_WORD(lookup_records[i]);
|
|
unsigned int orig_len, lookup_index, next;
|
|
|
|
if (idx >= count)
|
|
continue;
|
|
|
|
context->cur = match_positions[idx];
|
|
|
|
orig_len = context->glyph_count;
|
|
|
|
lookup_index = GET_BE_WORD(lookup_records[i+1]);
|
|
if (opentype_layout_init_lookup(context->table, lookup_index, 0, &lookup))
|
|
opentype_layout_apply_gsub_lookup(context, &lookup);
|
|
|
|
delta = context->glyph_count - orig_len;
|
|
if (!delta)
|
|
continue;
|
|
|
|
end += delta;
|
|
if (end <= (int)match_positions[idx])
|
|
{
|
|
end = match_positions[idx];
|
|
break;
|
|
}
|
|
|
|
next = idx + 1;
|
|
|
|
if (delta > 0)
|
|
{
|
|
if (delta + count > CHAIN_CONTEXT_MAX_LENGTH)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
delta = max(delta, (int)next - (int)count);
|
|
next -= delta;
|
|
}
|
|
|
|
memmove(match_positions + next + delta, match_positions + next,
|
|
(count - next) * sizeof (*match_positions));
|
|
next += delta;
|
|
count += delta;
|
|
|
|
for (j = idx + 1; j < next; j++)
|
|
match_positions[j] = match_positions[j - 1] + 1;
|
|
|
|
for (; next < count; next++)
|
|
match_positions[next] += delta;
|
|
}
|
|
|
|
context->cur = end;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gsub_chain_context_lookup(struct scriptshaping_context *context, 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)
|
|
{
|
|
unsigned int start_index = 0, match_length = 0, end_index = 0;
|
|
unsigned int match_positions[CHAIN_CONTEXT_MAX_LENGTH];
|
|
|
|
return opentype_layout_context_match_input(context, subtable_offset, input_count, input, &match_length, match_positions) &&
|
|
opentype_layout_context_match_backtrack(context, subtable_offset, backtrack_count, backtrack, &start_index) &&
|
|
opentype_layout_context_match_lookahead(context, subtable_offset, lookahead_count, lookahead, input_count, &end_index) &&
|
|
opentype_layout_context_gsub_apply_lookup(context, input_count, match_positions, lookup_count, lookup_records, match_length);
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gsub_chain_context_substitution(struct scriptshaping_context *context,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
unsigned int coverage_index = GLYPH_NOT_COVERED;
|
|
UINT16 glyph, format, coverage;
|
|
BOOL ret = FALSE;
|
|
|
|
glyph = context->u.subst.glyphs[context->cur];
|
|
|
|
format = table_read_be_word(table, subtable_offset);
|
|
|
|
if (format == 1)
|
|
{
|
|
coverage = table_read_be_word(table, subtable_offset +
|
|
FIELD_OFFSET(struct ot_gsub_chaincontext_subst_format1, coverage));
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(context, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
WARN("Chaining contextual substitution (1) is not supported.\n");
|
|
}
|
|
else if (format == 2)
|
|
{
|
|
coverage = table_read_be_word(table, subtable_offset +
|
|
FIELD_OFFSET(struct ot_gsub_chaincontext_subst_format1, coverage));
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(context, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
WARN("Chaining contextual substitution (2) is not supported.\n");
|
|
}
|
|
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(table, offset);
|
|
offset += 2;
|
|
backtrack = table_read_ensure(table, offset, backtrack_count * sizeof(*backtrack));
|
|
offset += backtrack_count * sizeof(*backtrack);
|
|
|
|
input_count = table_read_be_word(table, offset);
|
|
offset += 2;
|
|
input = table_read_ensure(table, offset, input_count * sizeof(*input));
|
|
offset += input_count * sizeof(*input);
|
|
|
|
lookahead_count = table_read_be_word(table, offset);
|
|
offset += 2;
|
|
lookahead = table_read_ensure(table, offset, lookahead_count * sizeof(*lookahead));
|
|
offset += lookahead_count * sizeof(*lookahead);
|
|
|
|
lookup_count = table_read_be_word(table, offset);
|
|
offset += 2;
|
|
lookup_records = table_read_ensure(table, offset, lookup_count * 2 * sizeof(*lookup_records));
|
|
|
|
if (input)
|
|
coverage_index = opentype_layout_is_glyph_covered(context, subtable_offset + GET_BE_WORD(input[0]), glyph);
|
|
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
ret = opentype_layout_apply_gsub_chain_context_lookup(context, subtable_offset, backtrack_count, backtrack,
|
|
input_count, input + 1, lookahead_count, lookahead, lookup_count, lookup_records);
|
|
}
|
|
else
|
|
WARN("Unknown chaining contextual substitution format %u.\n", format);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gsub_lookup(struct scriptshaping_context *context, const struct lookup *lookup)
|
|
{
|
|
unsigned int i, lookup_type;
|
|
BOOL ret = FALSE;
|
|
|
|
for (i = 0; i < lookup->subtable_count; ++i)
|
|
{
|
|
unsigned int subtable_offset = opentype_layout_get_gsubgpos_subtable(context, lookup->offset, i);
|
|
|
|
if (lookup->type == GSUB_LOOKUP_EXTENSION_SUBST)
|
|
{
|
|
lookup_type = opentype_layout_adjust_extension_subtable(context, &subtable_offset);
|
|
if (!lookup_type)
|
|
continue;
|
|
}
|
|
else
|
|
lookup_type = lookup->type;
|
|
|
|
switch (lookup_type)
|
|
{
|
|
case GSUB_LOOKUP_SINGLE_SUBST:
|
|
ret = opentype_layout_apply_gsub_single_substitution(context, lookup, subtable_offset);
|
|
break;
|
|
case GSUB_LOOKUP_MULTIPLE_SUBST:
|
|
ret = opentype_layout_apply_gsub_mult_substitution(context, lookup, subtable_offset);
|
|
break;
|
|
case GSUB_LOOKUP_ALTERNATE_SUBST:
|
|
ret = opentype_layout_apply_gsub_alt_substitution(context, lookup, subtable_offset);
|
|
break;
|
|
case GSUB_LOOKUP_LIGATURE_SUBST:
|
|
ret = opentype_layout_apply_gsub_lig_substitution(context, lookup, subtable_offset);
|
|
break;
|
|
case GSUB_LOOKUP_CHAINING_CONTEXTUAL_SUBST:
|
|
ret = opentype_layout_apply_gsub_chain_context_substitution(context, lookup, subtable_offset);
|
|
break;
|
|
case GSUB_LOOKUP_CONTEXTUAL_SUBST:
|
|
case GSUB_LOOKUP_REVERSE_CHAINING_CONTEXTUAL_SUBST:
|
|
WARN("Unimplemented lookup %d.\n", lookup->type);
|
|
break;
|
|
case GSUB_LOOKUP_EXTENSION_SUBST:
|
|
WARN("Invalid lookup type for extension substitution %#x.\n", lookup_type);
|
|
break;
|
|
default:
|
|
WARN("Unknown lookup type %u.\n", lookup_type);
|
|
}
|
|
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int unicode_get_mirrored_char(unsigned int codepoint)
|
|
{
|
|
extern const WCHAR wine_mirror_map[] DECLSPEC_HIDDEN;
|
|
WCHAR mirror;
|
|
/* TODO: check if mirroring for higher planes makes sense at all */
|
|
if (codepoint > 0xffff) return codepoint;
|
|
mirror = get_table_entry(wine_mirror_map, codepoint);
|
|
return mirror ? mirror : codepoint;
|
|
}
|
|
|
|
/*
|
|
* 034F # Mn COMBINING GRAPHEME JOINER
|
|
* 061C # Cf ARABIC LETTER MARK
|
|
* 180B..180D # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE
|
|
* 180E # Cf MONGOLIAN VOWEL SEPARATOR
|
|
* 200B..200F # Cf [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK
|
|
* FEFF # Cf ZERO WIDTH NO-BREAK SPACE
|
|
*/
|
|
static unsigned int opentype_is_default_ignorable(unsigned int codepoint)
|
|
{
|
|
return codepoint == 0x34f || codepoint == 0x61c || codepoint == 0xfeff ||
|
|
(codepoint >= 0x180b && codepoint <= 0x180e) || (codepoint >= 0x200b && codepoint <= 0x200f);
|
|
}
|
|
|
|
static void opentype_get_nominal_glyphs(struct scriptshaping_context *context, const struct shaping_features *features)
|
|
{
|
|
unsigned int rtlm_mask = shaping_features_get_mask(features, DWRITE_MAKE_OPENTYPE_TAG('r','t','l','m'), NULL);
|
|
const struct shaping_font_ops *font = context->cache->font;
|
|
UINT16 *clustermap = context->u.subst.clustermap;
|
|
const WCHAR *text = context->text;
|
|
unsigned int i, g, c, codepoint;
|
|
BOOL bmp;
|
|
|
|
memset(context->u.subst.glyph_props, 0, context->u.subst.max_glyph_count * sizeof(*context->u.subst.glyph_props));
|
|
|
|
for (i = 0; i < context->length; ++i)
|
|
{
|
|
g = context->glyph_count;
|
|
|
|
if ((bmp = !(IS_HIGH_SURROGATE(text[i]) && (i < context->length - 1) && IS_LOW_SURROGATE(text[i + 1]))))
|
|
{
|
|
codepoint = text[i];
|
|
}
|
|
else
|
|
{
|
|
codepoint = 0x10000 + ((text[i] - 0xd800) << 10) + (text[i + 1] - 0xdc00);
|
|
}
|
|
|
|
if (context->is_rtl)
|
|
{
|
|
c = unicode_get_mirrored_char(codepoint);
|
|
if (c != codepoint && font->has_glyph(context->cache->context, c))
|
|
codepoint = c;
|
|
else
|
|
context->glyph_infos[i].mask |= rtlm_mask;
|
|
}
|
|
|
|
/* TODO: should this check for glyph availability? */
|
|
if (*context->u.subst.digits && codepoint >= '0' && codepoint <= '9')
|
|
codepoint = context->u.subst.digits[codepoint - '0'];
|
|
|
|
context->u.subst.glyphs[g] = font->get_glyph(context->cache->context, codepoint);
|
|
context->u.buffer.glyph_props[g].justification = SCRIPT_JUSTIFY_CHARACTER;
|
|
context->u.buffer.glyph_props[g].isClusterStart = 1;
|
|
opentype_set_subst_glyph_props(context, g, context->u.subst.glyphs[g]);
|
|
if (opentype_is_default_ignorable(codepoint))
|
|
context->u.buffer.glyph_props[g].isZeroWidthSpace = 1;
|
|
context->u.buffer.glyph_props[g].components = 1;
|
|
context->glyph_count++;
|
|
|
|
clustermap[i] = i;
|
|
if (!bmp)
|
|
{
|
|
clustermap[i + 1] = i;
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
void 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;
|
|
BOOL ret;
|
|
|
|
opentype_layout_collect_lookups(context, script_index, language_index, features, context->table, &lookups);
|
|
|
|
opentype_get_nominal_glyphs(context, features);
|
|
opentype_layout_set_glyph_masks(context, features);
|
|
|
|
for (i = 0; i < lookups.count; ++i)
|
|
{
|
|
const struct lookup *lookup = &lookups.lookups[i];
|
|
|
|
context->cur = 0;
|
|
while (context->cur < context->glyph_count)
|
|
{
|
|
ret = FALSE;
|
|
|
|
if ((context->glyph_infos[context->cur].mask & lookup->mask) &&
|
|
lookup_is_glyph_match(context->glyph_infos[context->cur].props, lookup->flags))
|
|
{
|
|
ret = opentype_layout_apply_gsub_lookup(context, lookup);
|
|
}
|
|
|
|
if (!ret)
|
|
context->cur++;
|
|
}
|
|
}
|
|
|
|
heap_free(lookups.lookups);
|
|
}
|