6594 lines
211 KiB
C
6594 lines
211 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 "dwrite_private.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')
|
|
#define MS_KERN_TAG DWRITE_MAKE_OPENTYPE_TAG('k','e','r','n')
|
|
|
|
/* '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
|
|
|
|
#define GLYPH_CONTEXT_MAX_LENGTH 64
|
|
#define SHAPE_MAX_NESTING_LEVEL 6
|
|
|
|
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];
|
|
};
|
|
|
|
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"
|
|
struct tt_head
|
|
{
|
|
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;
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
struct tt_post
|
|
{
|
|
ULONG Version;
|
|
ULONG italicAngle;
|
|
SHORT underlinePosition;
|
|
SHORT underlineThickness;
|
|
ULONG fixed_pitch;
|
|
ULONG minmemType42;
|
|
ULONG maxmemType42;
|
|
ULONG minmemType1;
|
|
ULONG maxmemType1;
|
|
};
|
|
|
|
struct tt_os2
|
|
{
|
|
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;
|
|
};
|
|
|
|
struct tt_hhea
|
|
{
|
|
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;
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
struct name_record
|
|
{
|
|
WORD platformID;
|
|
WORD encodingID;
|
|
WORD languageID;
|
|
WORD nameID;
|
|
WORD length;
|
|
WORD offset;
|
|
};
|
|
|
|
struct name_header
|
|
{
|
|
WORD format;
|
|
WORD count;
|
|
WORD stringOffset;
|
|
struct name_record records[1];
|
|
};
|
|
|
|
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];
|
|
};
|
|
|
|
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;
|
|
UINT16 classdef;
|
|
UINT16 attach_list;
|
|
UINT16 ligcaret_list;
|
|
UINT16 markattach_classdef;
|
|
UINT16 markglyphsetdef;
|
|
};
|
|
|
|
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, /* Only used for GPOS cursive attachments. */
|
|
|
|
LOOKUP_FLAG_IGNORE_BASE = 0x2,
|
|
LOOKUP_FLAG_IGNORE_LIGATURES = 0x4,
|
|
LOOKUP_FLAG_IGNORE_MARKS = 0x8,
|
|
LOOKUP_FLAG_IGNORE_MASK = 0xe, /* Combined LOOKUP_FLAG_IGNORE_* flags. */
|
|
|
|
LOOKUP_FLAG_USE_MARK_FILTERING_SET = 0x10,
|
|
LOOKUP_FLAG_MARK_ATTACHMENT_TYPE = 0xff00,
|
|
};
|
|
|
|
enum attach_type
|
|
{
|
|
GLYPH_ATTACH_NONE = 0,
|
|
GLYPH_ATTACH_MARK,
|
|
GLYPH_ATTACH_CURSIVE,
|
|
};
|
|
|
|
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,
|
|
|
|
GLYPH_PROP_ZWNJ = 0x10,
|
|
GLYPH_PROP_ZWJ = 0x20,
|
|
GLYPH_PROP_IGNORABLE = 0x40,
|
|
GLYPH_PROP_HIDDEN = 0x80,
|
|
|
|
GLYPH_PROP_MARK_ATTACH_CLASS_MASK = 0xff00, /* Used with LOOKUP_FLAG_MARK_ATTACHMENT_TYPE. */
|
|
GLYPH_PROP_ATTACH_TYPE_MASK = 0xff0000,
|
|
};
|
|
|
|
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_gsubgpos_context_format1
|
|
{
|
|
UINT16 format;
|
|
UINT16 coverage;
|
|
UINT16 ruleset_count;
|
|
UINT16 rulesets[1];
|
|
};
|
|
|
|
struct ot_gsubgpos_ruleset
|
|
{
|
|
UINT16 count;
|
|
UINT16 offsets[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;
|
|
};
|
|
|
|
struct kern_header
|
|
{
|
|
WORD version;
|
|
WORD table_count;
|
|
};
|
|
|
|
struct kern_subtable_header
|
|
{
|
|
WORD version;
|
|
WORD length;
|
|
WORD coverage;
|
|
};
|
|
|
|
#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 UINT16 opentype_cmap_format0_get_glyph(const struct dwrite_cmap *cmap, unsigned int ch)
|
|
{
|
|
const UINT8 *glyphs = cmap->data;
|
|
return (ch < 0xff) ? glyphs[ch] : 0;
|
|
}
|
|
|
|
static unsigned int opentype_cmap_format0_get_ranges(const struct dwrite_cmap *cmap, unsigned int count,
|
|
DWRITE_UNICODE_RANGE *ranges)
|
|
{
|
|
if (count > 0)
|
|
{
|
|
ranges->first = 0;
|
|
ranges->last = 255;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
struct cmap_format4_compare_context
|
|
{
|
|
const struct dwrite_cmap *cmap;
|
|
unsigned int ch;
|
|
};
|
|
|
|
static int __cdecl cmap_format4_compare_range(const void *a, const void *b)
|
|
{
|
|
const struct cmap_format4_compare_context *key = a;
|
|
const UINT16 *end = b;
|
|
unsigned int idx;
|
|
|
|
if (key->ch > GET_BE_WORD(*end))
|
|
return 1;
|
|
|
|
idx = end - key->cmap->u.format4.ends;
|
|
if (key->ch < GET_BE_WORD(key->cmap->u.format4.starts[idx]))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static UINT16 opentype_cmap_format4_get_glyph(const struct dwrite_cmap *cmap, unsigned int ch)
|
|
{
|
|
struct cmap_format4_compare_context key = { .cmap = cmap, .ch = ch };
|
|
unsigned int glyph, idx, range_offset;
|
|
const UINT16 *end_found;
|
|
|
|
/* Look up range. */
|
|
end_found = bsearch(&key, cmap->u.format4.ends, cmap->u.format4.seg_count, sizeof(*cmap->u.format4.ends),
|
|
cmap_format4_compare_range);
|
|
if (!end_found)
|
|
return 0;
|
|
|
|
idx = end_found - cmap->u.format4.ends;
|
|
|
|
range_offset = GET_BE_WORD(cmap->u.format4.id_range_offset[idx]);
|
|
|
|
if (!range_offset)
|
|
{
|
|
glyph = ch + GET_BE_WORD(cmap->u.format4.id_delta[idx]);
|
|
}
|
|
else
|
|
{
|
|
unsigned int index = range_offset / 2 + (ch - GET_BE_WORD(cmap->u.format4.starts[idx])) + idx - cmap->u.format4.seg_count;
|
|
if (index >= cmap->u.format4.glyph_id_array_len)
|
|
return 0;
|
|
glyph = GET_BE_WORD(cmap->u.format4.glyph_id_array[index]);
|
|
if (!glyph)
|
|
return 0;
|
|
glyph += GET_BE_WORD(cmap->u.format4.id_delta[idx]);
|
|
}
|
|
|
|
return glyph & 0xffff;
|
|
}
|
|
|
|
static unsigned int opentype_cmap_format4_get_ranges(const struct dwrite_cmap *cmap, unsigned int count,
|
|
DWRITE_UNICODE_RANGE *ranges)
|
|
{
|
|
unsigned int i;
|
|
|
|
count = min(count, cmap->u.format4.seg_count);
|
|
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
ranges[i].first = GET_BE_WORD(cmap->u.format4.starts[i]);
|
|
ranges[i].last = GET_BE_WORD(cmap->u.format4.ends[i]);
|
|
}
|
|
|
|
return cmap->u.format4.seg_count;
|
|
}
|
|
|
|
static UINT16 opentype_cmap_format6_10_get_glyph(const struct dwrite_cmap *cmap, unsigned int ch)
|
|
{
|
|
const UINT16 *glyphs = cmap->data;
|
|
if (ch < cmap->u.format6_10.first || ch > cmap->u.format6_10.last) return 0;
|
|
return glyphs[ch - cmap->u.format6_10.first];
|
|
}
|
|
|
|
static unsigned int opentype_cmap_format6_10_get_ranges(const struct dwrite_cmap *cmap, unsigned int count,
|
|
DWRITE_UNICODE_RANGE *ranges)
|
|
{
|
|
if (count > 0)
|
|
{
|
|
ranges->first = cmap->u.format6_10.first;
|
|
ranges->last = cmap->u.format6_10.last;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int __cdecl cmap_format12_13_compare_group(const void *a, const void *b)
|
|
{
|
|
const unsigned int *ch = a;
|
|
const UINT32 *group = b;
|
|
|
|
if (*ch > GET_BE_DWORD(group[1]))
|
|
return 1;
|
|
|
|
if (*ch < GET_BE_DWORD(group[0]))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static UINT16 opentype_cmap_format12_get_glyph(const struct dwrite_cmap *cmap, unsigned int ch)
|
|
{
|
|
const UINT32 *groups = cmap->data;
|
|
const UINT32 *group_found;
|
|
|
|
if (!(group_found = bsearch(&ch, groups, cmap->u.format12_13.group_count, 3 * sizeof(*groups),
|
|
cmap_format12_13_compare_group)))
|
|
return 0;
|
|
|
|
return GET_BE_DWORD(group_found[0]) <= GET_BE_DWORD(group_found[1]) ?
|
|
GET_BE_DWORD(group_found[2]) + (ch - GET_BE_DWORD(group_found[0])) : 0;
|
|
}
|
|
|
|
static unsigned int opentype_cmap_format12_13_get_ranges(const struct dwrite_cmap *cmap, unsigned int count,
|
|
DWRITE_UNICODE_RANGE *ranges)
|
|
{
|
|
unsigned int i, group_count = cmap->u.format12_13.group_count;
|
|
const UINT32 *groups = cmap->data;
|
|
|
|
count = min(count, group_count);
|
|
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
ranges[i].first = GET_BE_DWORD(groups[3 * i]);
|
|
ranges[i].last = GET_BE_DWORD(groups[3 * i + 1]);
|
|
}
|
|
|
|
return group_count;
|
|
}
|
|
|
|
static UINT16 opentype_cmap_format13_get_glyph(const struct dwrite_cmap *cmap, unsigned int ch)
|
|
{
|
|
const UINT32 *groups = cmap->data;
|
|
const UINT32 *group_found;
|
|
|
|
if (!(group_found = bsearch(&ch, groups, cmap->u.format12_13.group_count, 3 * sizeof(*groups),
|
|
cmap_format12_13_compare_group)))
|
|
return 0;
|
|
|
|
return GET_BE_DWORD(group_found[2]);
|
|
}
|
|
|
|
static UINT16 opentype_cmap_dummy_get_glyph(const struct dwrite_cmap *cmap, unsigned int ch)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int opentype_cmap_dummy_get_ranges(const struct dwrite_cmap *cmap, unsigned int count,
|
|
DWRITE_UNICODE_RANGE *ranges)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
UINT16 opentype_cmap_get_glyph(const struct dwrite_cmap *cmap, unsigned int ch)
|
|
{
|
|
UINT16 glyph;
|
|
|
|
if (!cmap->get_glyph) return 0;
|
|
glyph = cmap->get_glyph(cmap, ch);
|
|
if (!glyph && cmap->symbol && ch <= 0xff)
|
|
glyph = cmap->get_glyph(cmap, ch + 0xf000);
|
|
return glyph;
|
|
}
|
|
|
|
static int __cdecl cmap_header_compare(const void *a, const void *b)
|
|
{
|
|
const UINT16 *key = a;
|
|
const UINT16 *record = b;
|
|
|
|
/* Platform. */
|
|
if (key[0] < GET_BE_WORD(record[0])) return -1;
|
|
if (key[0] > GET_BE_WORD(record[0])) return 1;
|
|
/* Encoding. */
|
|
if (key[1] < GET_BE_WORD(record[1])) return -1;
|
|
if (key[1] > GET_BE_WORD(record[1])) return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void dwrite_cmap_init(struct dwrite_cmap *cmap, IDWriteFontFile *file, unsigned int face_index,
|
|
DWRITE_FONT_FACE_TYPE face_type)
|
|
{
|
|
static const UINT16 encodings[][2] =
|
|
{
|
|
{ 3, 0 }, /* MS Symbol encoding is preferred. */
|
|
{ 3, 10 },
|
|
{ 0, 6 },
|
|
{ 0, 4 },
|
|
{ 3, 1 },
|
|
{ 0, 3 },
|
|
{ 0, 2 },
|
|
{ 0, 1 },
|
|
{ 0, 0 },
|
|
};
|
|
const struct cmap_encoding_record *records, *found_record = NULL;
|
|
unsigned int length, offset, format, count, f, i, num_records;
|
|
struct file_stream_desc stream_desc;
|
|
struct dwrite_fonttable table;
|
|
const UINT16 *pair = NULL;
|
|
HRESULT hr;
|
|
|
|
if (cmap->data) return;
|
|
|
|
/* For fontface stream is already available and preset. */
|
|
if (!cmap->stream && FAILED(hr = get_filestream_from_file(file, &cmap->stream)))
|
|
{
|
|
WARN("Failed to get file stream, hr %#x.\n", hr);
|
|
goto failed;
|
|
}
|
|
|
|
stream_desc.stream = cmap->stream;
|
|
stream_desc.face_type = face_type;
|
|
stream_desc.face_index = face_index;
|
|
|
|
opentype_get_font_table(&stream_desc, MS_CMAP_TAG, &table);
|
|
if (!table.exists)
|
|
goto failed;
|
|
cmap->table_context = table.context;
|
|
|
|
num_records = table_read_be_word(&table, 2);
|
|
records = table_read_ensure(&table, 4, sizeof(*records) * num_records);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(encodings); ++i)
|
|
{
|
|
pair = encodings[i];
|
|
if ((found_record = bsearch(pair, records, num_records, sizeof(*records), cmap_header_compare)))
|
|
break;
|
|
}
|
|
|
|
if (!found_record)
|
|
{
|
|
WARN("No suitable cmap table were found.\n");
|
|
goto failed;
|
|
}
|
|
|
|
/* Symbol encoding. */
|
|
cmap->symbol = pair[0] == 3 && pair[1] == 0;
|
|
offset = GET_BE_DWORD(found_record->offset);
|
|
|
|
format = table_read_be_word(&table, offset);
|
|
|
|
switch (format)
|
|
{
|
|
case 0:
|
|
cmap->data = table_read_ensure(&table, offset + 6, 256);
|
|
cmap->get_glyph = opentype_cmap_format0_get_glyph;
|
|
cmap->get_ranges = opentype_cmap_format0_get_ranges;
|
|
break;
|
|
case 4:
|
|
length = table_read_be_word(&table, offset + 2);
|
|
cmap->u.format4.seg_count = count = table_read_be_word(&table, offset + 6) / 2;
|
|
cmap->u.format4.ends = table_read_ensure(&table, offset + 14, count * 2);
|
|
cmap->u.format4.starts = cmap->u.format4.ends + count + 1;
|
|
cmap->u.format4.id_delta = cmap->u.format4.starts + count;
|
|
cmap->u.format4.id_range_offset = cmap->u.format4.id_delta + count;
|
|
cmap->u.format4.glyph_id_array = cmap->data = cmap->u.format4.id_range_offset + count;
|
|
cmap->u.format4.glyph_id_array_len = (length - 16 - 8 * count) / 2;
|
|
cmap->get_glyph = opentype_cmap_format4_get_glyph;
|
|
cmap->get_ranges = opentype_cmap_format4_get_ranges;
|
|
break;
|
|
case 6:
|
|
case 10:
|
|
/* Format 10 uses 4 byte fields. */
|
|
f = format == 6 ? 1 : 2;
|
|
cmap->u.format6_10.first = table_read_be_word(&table, offset + f * 6);
|
|
count = table_read_be_word(&table, offset + f * 8);
|
|
cmap->u.format6_10.last = cmap->u.format6_10.first + count;
|
|
cmap->data = table_read_ensure(&table, offset + f * 10, count * 2);
|
|
cmap->get_glyph = opentype_cmap_format6_10_get_glyph;
|
|
cmap->get_ranges = opentype_cmap_format6_10_get_ranges;
|
|
break;
|
|
case 12:
|
|
case 13:
|
|
cmap->u.format12_13.group_count = count = table_read_be_dword(&table, offset + 12);
|
|
cmap->data = table_read_ensure(&table, offset + 16, count * 3 * 4);
|
|
cmap->get_glyph = format == 12 ? opentype_cmap_format12_get_glyph : opentype_cmap_format13_get_glyph;
|
|
cmap->get_ranges = opentype_cmap_format12_13_get_ranges;
|
|
break;
|
|
default:
|
|
WARN("Unhandled subtable format %u.\n", format);
|
|
}
|
|
|
|
failed:
|
|
|
|
if (!cmap->data)
|
|
{
|
|
/* Dummy implementation, returns 0 unconditionally. */
|
|
cmap->data = cmap;
|
|
cmap->get_glyph = opentype_cmap_dummy_get_glyph;
|
|
cmap->get_ranges = opentype_cmap_dummy_get_ranges;
|
|
}
|
|
}
|
|
|
|
void dwrite_cmap_release(struct dwrite_cmap *cmap)
|
|
{
|
|
if (cmap->stream)
|
|
{
|
|
IDWriteFontFileStream_ReleaseFileFragment(cmap->stream, cmap->table_context);
|
|
IDWriteFontFileStream_Release(cmap->stream);
|
|
}
|
|
cmap->data = NULL;
|
|
cmap->stream = NULL;
|
|
}
|
|
|
|
HRESULT opentype_cmap_get_unicode_ranges(const struct dwrite_cmap *cmap, unsigned int max_count, DWRITE_UNICODE_RANGE *ranges,
|
|
unsigned int *count)
|
|
{
|
|
if (!cmap->data)
|
|
return E_FAIL;
|
|
|
|
*count = cmap->get_ranges(cmap, max_count, ranges);
|
|
|
|
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;
|
|
|
|
opentype_get_font_table(stream_desc, MS_OS2_TAG, &os2);
|
|
|
|
*ascent = *descent = 0;
|
|
|
|
if (os2.size >= FIELD_OFFSET(struct tt_os2, sTypoLineGap))
|
|
{
|
|
SHORT value = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, sTypoDescender));
|
|
*ascent = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, sTypoAscender));
|
|
*descent = value < 0 ? -value : 0;
|
|
}
|
|
|
|
if (os2.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;
|
|
|
|
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);
|
|
|
|
if (head.data)
|
|
{
|
|
metrics->designUnitsPerEm = table_read_be_word(&head, FIELD_OFFSET(struct tt_head, unitsPerEm));
|
|
metrics->glyphBoxLeft = table_read_be_word(&head, FIELD_OFFSET(struct tt_head, xMin));
|
|
metrics->glyphBoxTop = table_read_be_word(&head, FIELD_OFFSET(struct tt_head, yMax));
|
|
metrics->glyphBoxRight = table_read_be_word(&head, FIELD_OFFSET(struct tt_head, xMax));
|
|
metrics->glyphBoxBottom = table_read_be_word(&head, FIELD_OFFSET(struct tt_head, yMin));
|
|
}
|
|
|
|
if (caret)
|
|
{
|
|
if (hhea.data)
|
|
{
|
|
caret->slopeRise = table_read_be_word(&hhea, FIELD_OFFSET(struct tt_hhea, caretSlopeRise));
|
|
caret->slopeRun = table_read_be_word(&hhea, FIELD_OFFSET(struct tt_hhea, caretSlopeRun));
|
|
caret->offset = table_read_be_word(&hhea, FIELD_OFFSET(struct tt_hhea, caretOffset));
|
|
}
|
|
else
|
|
memset(caret, 0, sizeof(*caret));
|
|
}
|
|
|
|
if (os2.data)
|
|
{
|
|
USHORT version = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, version));
|
|
|
|
metrics->ascent = table_read_be_word(&os2, FIELD_OFFSET(struct 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)table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, usWinDescent)));
|
|
|
|
/* Line gap is estimated using two sets of ascender/descender values and 'hhea' line gap. */
|
|
if (hhea.data)
|
|
{
|
|
SHORT descender = (SHORT)table_read_be_word(&hhea, FIELD_OFFSET(struct tt_hhea, descender));
|
|
INT32 linegap;
|
|
|
|
linegap = table_read_be_word(&hhea, FIELD_OFFSET(struct tt_hhea, ascender)) + abs(descender) +
|
|
table_read_be_word(&hhea, FIELD_OFFSET(struct tt_hhea, linegap)) - metrics->ascent - metrics->descent;
|
|
metrics->lineGap = linegap > 0 ? linegap : 0;
|
|
}
|
|
|
|
metrics->strikethroughPosition = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, yStrikeoutPosition));
|
|
metrics->strikethroughThickness = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, yStrikeoutSize));
|
|
metrics->subscriptPositionX = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, ySubscriptXOffset));
|
|
/* Y offset is stored as positive offset below baseline */
|
|
metrics->subscriptPositionY = -table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, ySubscriptYOffset));
|
|
metrics->subscriptSizeX = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, ySubscriptXSize));
|
|
metrics->subscriptSizeY = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, ySubscriptYSize));
|
|
metrics->superscriptPositionX = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, ySuperscriptXOffset));
|
|
metrics->superscriptPositionY = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, ySuperscriptYOffset));
|
|
metrics->superscriptSizeX = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, ySuperscriptXSize));
|
|
metrics->superscriptSizeY = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, ySuperscriptYSize));
|
|
|
|
/* version 2 fields */
|
|
if (version >= 2)
|
|
{
|
|
metrics->capHeight = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, sCapHeight));
|
|
metrics->xHeight = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, sxHeight));
|
|
}
|
|
|
|
if (table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, fsSelection)) & OS2_FSSELECTION_USE_TYPO_METRICS)
|
|
{
|
|
SHORT descent = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, sTypoDescender));
|
|
metrics->ascent = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, sTypoAscender));
|
|
metrics->descent = descent < 0 ? -descent : 0;
|
|
metrics->lineGap = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, sTypoLineGap));
|
|
metrics->hasTypographicMetrics = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
metrics->strikethroughPosition = metrics->designUnitsPerEm / 3;
|
|
if (hhea.data)
|
|
{
|
|
metrics->ascent = table_read_be_word(&hhea, FIELD_OFFSET(struct tt_hhea, ascender));
|
|
metrics->descent = abs((SHORT)table_read_be_word(&hhea, FIELD_OFFSET(struct tt_hhea, descender)));
|
|
}
|
|
}
|
|
|
|
if (post.data)
|
|
{
|
|
metrics->underlinePosition = table_read_be_word(&post, FIELD_OFFSET(struct tt_post, underlinePosition));
|
|
metrics->underlineThickness = table_read_be_word(&post, FIELD_OFFSET(struct 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 (os2.data)
|
|
IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, os2.context);
|
|
if (head.data)
|
|
IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, head.context);
|
|
if (post.data)
|
|
IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, post.context);
|
|
if (hhea.data)
|
|
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;
|
|
|
|
opentype_get_font_table(stream_desc, MS_OS2_TAG, &os2);
|
|
opentype_get_font_table(stream_desc, MS_HEAD_TAG, &head);
|
|
|
|
/* 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 (os2.data)
|
|
{
|
|
USHORT version = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, version));
|
|
USHORT fsSelection = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, fsSelection));
|
|
USHORT usWeightClass = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, usWeightClass));
|
|
USHORT usWidthClass = table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, usWidthClass));
|
|
const void *panose;
|
|
|
|
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);
|
|
|
|
if ((panose = table_read_ensure(&os2, FIELD_OFFSET(struct tt_os2, panose), sizeof(props->panose))))
|
|
memcpy(&props->panose, panose, sizeof(props->panose));
|
|
|
|
/* FONTSIGNATURE */
|
|
props->fontsig.fsUsb[0] = table_read_be_dword(&os2, FIELD_OFFSET(struct tt_os2, ulUnicodeRange1));
|
|
props->fontsig.fsUsb[1] = table_read_be_dword(&os2, FIELD_OFFSET(struct tt_os2, ulUnicodeRange2));
|
|
props->fontsig.fsUsb[2] = table_read_be_dword(&os2, FIELD_OFFSET(struct tt_os2, ulUnicodeRange3));
|
|
props->fontsig.fsUsb[3] = table_read_be_dword(&os2, FIELD_OFFSET(struct tt_os2, ulUnicodeRange4));
|
|
|
|
if (version)
|
|
{
|
|
props->fontsig.fsCsb[0] = table_read_be_dword(&os2, FIELD_OFFSET(struct tt_os2, ulCodePageRange1));
|
|
props->fontsig.fsCsb[1] = table_read_be_dword(&os2, FIELD_OFFSET(struct tt_os2, ulCodePageRange2));
|
|
}
|
|
}
|
|
else if (head.data)
|
|
{
|
|
USHORT macStyle = table_read_be_word(&head, FIELD_OFFSET(struct 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(struct 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)
|
|
{
|
|
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
|
|
wcscpy(locale, L"en-US");
|
|
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));
|
|
wcscpy(locale, L"en-US");
|
|
}
|
|
break;
|
|
case OPENTYPE_PLATFORM_UNICODE:
|
|
wcscpy(locale, L"en-US");
|
|
break;
|
|
default:
|
|
FIXME("unknown platform %d\n", platform);
|
|
}
|
|
}
|
|
|
|
static BOOL opentype_is_english_namerecord(const struct dwrite_fonttable *table, unsigned int idx)
|
|
{
|
|
const struct name_header *header = (const struct name_header *)table->data;
|
|
const struct name_record *record;
|
|
|
|
record = &header->records[idx];
|
|
|
|
return GET_BE_WORD(record->platformID) == OPENTYPE_PLATFORM_MAC &&
|
|
GET_BE_WORD(record->languageID) == TT_NAME_MAC_LANGID_ENGLISH;
|
|
}
|
|
|
|
static BOOL opentype_decode_namerecord(const struct dwrite_fonttable *table, unsigned int idx,
|
|
IDWriteLocalizedStrings *strings)
|
|
{
|
|
USHORT lang_id, length, offset, encoding, platform;
|
|
const struct name_header *header = (const struct name_header *)table->data;
|
|
const struct name_record *record;
|
|
unsigned int i, string_offset;
|
|
BOOL ret = FALSE;
|
|
const void *name;
|
|
|
|
string_offset = table_read_be_word(table, FIELD_OFFSET(struct name_header, stringOffset));
|
|
|
|
record = &header->records[idx];
|
|
|
|
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 (!(name = table_read_ensure(table, string_offset + offset, length)))
|
|
return FALSE;
|
|
|
|
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, name, length, NULL, 0);
|
|
name_string = malloc(sizeof(WCHAR) * (len+1));
|
|
MultiByteToWideChar(codepage, 0, name, length, name_string, len);
|
|
name_string[len] = 0;
|
|
}
|
|
else
|
|
{
|
|
length /= sizeof(WCHAR);
|
|
name_string = heap_strdupnW(name, 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);
|
|
free(name_string);
|
|
|
|
ret = !wcscmp(locale, L"en-US");
|
|
}
|
|
else
|
|
FIXME("handle NAME format 1\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static HRESULT opentype_get_font_strings_from_id(const struct dwrite_fonttable *table, enum OPENTYPE_STRING_ID id,
|
|
IDWriteLocalizedStrings **strings)
|
|
{
|
|
int i, count, candidate_mac, candidate_mac_en, candidate_unicode;
|
|
const struct name_record *records;
|
|
BOOL has_english;
|
|
WORD format;
|
|
HRESULT hr;
|
|
|
|
if (!table->data)
|
|
return E_FAIL;
|
|
|
|
if (FAILED(hr = create_localizedstrings(strings)))
|
|
return hr;
|
|
|
|
format = table_read_be_word(table, FIELD_OFFSET(struct name_header, format));
|
|
|
|
if (format != 0 && format != 1)
|
|
FIXME("unsupported NAME format %d\n", format);
|
|
|
|
count = table_read_be_word(table, FIELD_OFFSET(struct name_header, count));
|
|
|
|
if (!(records = table_read_ensure(table, FIELD_OFFSET(struct name_header, records),
|
|
count * sizeof(struct name_record))))
|
|
{
|
|
count = 0;
|
|
}
|
|
|
|
has_english = FALSE;
|
|
candidate_unicode = candidate_mac = candidate_mac_en = -1;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
unsigned short platform;
|
|
|
|
if (GET_BE_WORD(records[i].nameID) != id)
|
|
continue;
|
|
|
|
platform = GET_BE_WORD(records[i].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;
|
|
if (candidate_mac_en == -1 && opentype_is_english_namerecord(table, i))
|
|
candidate_mac_en = i;
|
|
break;
|
|
case OPENTYPE_PLATFORM_WIN:
|
|
has_english |= opentype_decode_namerecord(table, i, *strings);
|
|
break;
|
|
default:
|
|
FIXME("platform %i not supported\n", platform);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!get_localizedstrings_count(*strings) && candidate_mac != -1)
|
|
has_english |= opentype_decode_namerecord(table, candidate_mac, *strings);
|
|
if (!get_localizedstrings_count(*strings) && candidate_unicode != -1)
|
|
has_english |= opentype_decode_namerecord(table, candidate_unicode, *strings);
|
|
if (!has_english && candidate_mac_en != -1)
|
|
opentype_decode_namerecord(table, candidate_mac_en, *strings);
|
|
|
|
if (!get_localizedstrings_count(*strings))
|
|
{
|
|
IDWriteLocalizedStrings_Release(*strings);
|
|
*strings = NULL;
|
|
}
|
|
|
|
if (*strings)
|
|
sort_localizedstrings(*strings);
|
|
|
|
return *strings ? S_OK : E_FAIL;
|
|
}
|
|
|
|
static WCHAR *meta_get_lng_name(WCHAR *str, WCHAR **ctx)
|
|
{
|
|
WCHAR *ret;
|
|
|
|
if (!str) str = *ctx;
|
|
while (*str && wcschr(L", ", *str)) str++;
|
|
if (!*str) return NULL;
|
|
ret = str++;
|
|
while (*str && !wcschr(L", ", *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)
|
|
{
|
|
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, *ctx, *token;
|
|
|
|
if (!(ptrW = malloc((length + 1) * sizeof(WCHAR))))
|
|
{
|
|
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, L"", token);
|
|
token = meta_get_lng_name(NULL, &ctx);
|
|
}
|
|
|
|
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, 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;
|
|
UINT16 fsselection;
|
|
HRESULT hr;
|
|
|
|
opentype_get_font_table(stream_desc, MS_OS2_TAG, &os2);
|
|
opentype_get_font_table(stream_desc, MS_NAME_TAG, &name);
|
|
|
|
*names = NULL;
|
|
|
|
/* If Preferred Family doesn't conform to WWS model try WWS name. */
|
|
fsselection = os2.data ? table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, fsSelection)) : 0;
|
|
if (os2.data && !(fsselection & OS2_FSSELECTION_WWS))
|
|
hr = opentype_get_font_strings_from_id(&name, OPENTYPE_STRING_WWS_FAMILY_NAME, names);
|
|
else
|
|
hr = E_FAIL;
|
|
|
|
if (FAILED(hr))
|
|
hr = opentype_get_font_strings_from_id(&name, OPENTYPE_STRING_TYPOGRAPHIC_FAMILY_NAME, names);
|
|
if (FAILED(hr))
|
|
hr = opentype_get_font_strings_from_id(&name, 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;
|
|
UINT16 fsselection;
|
|
HRESULT hr;
|
|
|
|
opentype_get_font_table(stream_desc, MS_OS2_TAG, &os2);
|
|
opentype_get_font_table(stream_desc, MS_NAME_TAG, &name);
|
|
|
|
*names = NULL;
|
|
|
|
/* if Preferred Family doesn't conform to WWS model try WWS name */
|
|
fsselection = os2.data ? table_read_be_word(&os2, FIELD_OFFSET(struct tt_os2, fsSelection)) : 0;
|
|
if (os2.data && !(fsselection & OS2_FSSELECTION_WWS))
|
|
hr = opentype_get_font_strings_from_id(&name, OPENTYPE_STRING_WWS_SUBFAMILY_NAME, names);
|
|
else
|
|
hr = E_FAIL;
|
|
|
|
if (FAILED(hr))
|
|
hr = opentype_get_font_strings_from_id(&name, OPENTYPE_STRING_TYPOGRAPHIC_SUBFAMILY_NAME, names);
|
|
if (FAILED(hr))
|
|
hr = opentype_get_font_strings_from_id(&name, 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, OPENTYPE_STRING_FAMILY_NAME, &lfnames)))
|
|
{
|
|
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, L"en-us", &index, &exists);
|
|
|
|
if (exists) {
|
|
UINT32 length = 0;
|
|
WCHAR *nameW;
|
|
|
|
IDWriteLocalizedStrings_GetStringLength(lfnames, index, &length);
|
|
nameW = malloc((length + 1) * sizeof(WCHAR));
|
|
if (nameW)
|
|
{
|
|
*nameW = 0;
|
|
IDWriteLocalizedStrings_GetString(lfnames, index, nameW, length + 1);
|
|
lstrcpynW(lfname, nameW, LF_FACESIZE);
|
|
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 const struct ot_langsys *opentype_get_langsys(const struct ot_gsubgpos_table *table, unsigned int script_index,
|
|
unsigned int language_index, unsigned int *feature_count)
|
|
{
|
|
unsigned int table_offset, langsys_offset;
|
|
const struct ot_langsys *langsys = NULL;
|
|
|
|
*feature_count = 0;
|
|
|
|
if (!table->table.data || script_index == ~0u)
|
|
return NULL;
|
|
|
|
/* 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 NULL;
|
|
|
|
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));
|
|
langsys_offset += table->script_list + table_offset;
|
|
|
|
*feature_count = table_read_be_word(&table->table, langsys_offset + FIELD_OFFSET(struct ot_langsys, feature_count));
|
|
if (*feature_count)
|
|
langsys = table_read_ensure(&table->table, langsys_offset, FIELD_OFFSET(struct ot_langsys, feature_index[*feature_count]));
|
|
if (!langsys)
|
|
*feature_count = 0;
|
|
|
|
return langsys;
|
|
}
|
|
|
|
void opentype_get_typographic_features(struct ot_gsubgpos_table *table, unsigned int script_index,
|
|
unsigned int language_index, struct tag_array *t)
|
|
{
|
|
unsigned int i, total_feature_count, script_feature_count;
|
|
const struct ot_feature_list *feature_list;
|
|
const struct ot_langsys *langsys = NULL;
|
|
|
|
langsys = opentype_get_langsys(table, script_index, language_index, &script_feature_count);
|
|
|
|
total_feature_count = table_read_be_word(&table->table, table->feature_list);
|
|
if (!total_feature_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;
|
|
|
|
for (i = 0; i < script_feature_count; ++i)
|
|
{
|
|
unsigned int feature_index = GET_BE_WORD(langsys->feature_index[i]);
|
|
if (feature_index >= total_feature_count)
|
|
continue;
|
|
|
|
if (!dwrite_array_reserve((void **)&t->tags, &t->capacity, t->count + 1, sizeof(*t->tags)))
|
|
return;
|
|
|
|
t->tags[t->count++] = feature_list->features[feature_index].tag;
|
|
}
|
|
}
|
|
|
|
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;
|
|
struct d3d_color
|
|
{
|
|
float r;
|
|
float g;
|
|
float b;
|
|
float a;
|
|
} *colors = (void *)entries;
|
|
|
|
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)
|
|
{
|
|
colors[i].r = records[first_entry_index + i].red / 255.0f;
|
|
colors[i].g = records[first_entry_index + i].green / 255.0f;
|
|
colors[i].b = records[first_entry_index + i].blue / 255.0f;
|
|
colors[i].a = records[first_entry_index + i].alpha / 255.0f;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static int __cdecl 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);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
unsigned int version = table_read_be_dword(&cache->gdef.table, 0);
|
|
|
|
cache->gdef.classdef = table_read_be_word(&cache->gdef.table, FIELD_OFFSET(struct gdef_header, classdef));
|
|
cache->gdef.markattachclassdef = table_read_be_word(&cache->gdef.table,
|
|
FIELD_OFFSET(struct gdef_header, markattach_classdef));
|
|
if (version >= 0x00010002)
|
|
cache->gdef.markglyphsetdef = table_read_be_word(&cache->gdef.table,
|
|
FIELD_OFFSET(struct gdef_header, markglyphsetdef));
|
|
}
|
|
}
|
|
|
|
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 __cdecl 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 idx)
|
|
{
|
|
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,
|
|
context->u.buffer.glyphs[idx]);
|
|
}
|
|
|
|
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;
|
|
if (cache->gdef.markattachclassdef)
|
|
{
|
|
glyph_class = opentype_layout_get_glyph_class(&cache->gdef.table, cache->gdef.markattachclassdef,
|
|
context->u.buffer.glyphs[idx]);
|
|
props |= glyph_class << 8;
|
|
}
|
|
break;
|
|
default:
|
|
props = 0;
|
|
}
|
|
|
|
context->glyph_infos[idx].props = props;
|
|
|
|
return props;
|
|
}
|
|
|
|
static void opentype_set_subst_glyph_props(struct scriptshaping_context *context, unsigned int idx)
|
|
{
|
|
unsigned int glyph_props = opentype_set_glyph_props(context, idx) & LOOKUP_FLAG_IGNORE_MASK;
|
|
context->u.subst.glyph_props[idx].isDiacritic = !!(glyph_props == GLYPH_PROP_MARK);
|
|
context->u.subst.glyph_props[idx].isZeroWidthSpace = !!(glyph_props == GLYPH_PROP_MARK);
|
|
}
|
|
|
|
struct coverage_compare_format1_context
|
|
{
|
|
UINT16 glyph;
|
|
const UINT16 *table_base;
|
|
unsigned int *coverage_index;
|
|
};
|
|
|
|
static int __cdecl 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 __cdecl coverage_compare_format2(const void *g, const void *r)
|
|
{
|
|
const struct ot_coverage_range *range = r;
|
|
UINT16 glyph = *(UINT16 *)g;
|
|
|
|
if (glyph < GET_BE_WORD(range->start_glyph))
|
|
return -1;
|
|
else if (glyph > GET_BE_WORD(range->end_glyph))
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int opentype_layout_is_glyph_covered(const struct dwrite_fonttable *table, unsigned int coverage,
|
|
UINT16 glyph)
|
|
{
|
|
WORD format = table_read_be_word(table, coverage), count;
|
|
|
|
count = table_read_be_word(table, coverage + 2);
|
|
|
|
if (format == 1)
|
|
{
|
|
const struct ot_coverage_format1 *format1 = table_read_ensure(table, coverage,
|
|
FIELD_OFFSET(struct ot_coverage_format1, glyphs[count]));
|
|
struct coverage_compare_format1_context context;
|
|
unsigned int coverage_index = GLYPH_NOT_COVERED;
|
|
|
|
if (format1)
|
|
{
|
|
context.glyph = glyph;
|
|
context.table_base = format1->glyphs;
|
|
context.coverage_index = &coverage_index;
|
|
|
|
bsearch(&context, format1->glyphs, count, sizeof(glyph), coverage_compare_format1);
|
|
}
|
|
|
|
return coverage_index;
|
|
}
|
|
else if (format == 2)
|
|
{
|
|
const struct ot_coverage_format2 *format2 = table_read_ensure(table, coverage,
|
|
FIELD_OFFSET(struct ot_coverage_format2, ranges[count]));
|
|
if (format2)
|
|
{
|
|
const struct ot_coverage_range *range = bsearch(&glyph, format2->ranges, count,
|
|
sizeof(struct ot_coverage_range), coverage_compare_format2);
|
|
return range && glyph <= GET_BE_WORD(range->end_glyph) ?
|
|
GET_BE_WORD(range->startcoverage_index) + glyph - GET_BE_WORD(range->start_glyph) :
|
|
GLYPH_NOT_COVERED;
|
|
}
|
|
}
|
|
else
|
|
WARN("Unknown coverage format %u.\n", format);
|
|
|
|
return -1;
|
|
}
|
|
|
|
static inline unsigned int dwrite_popcount(unsigned int x)
|
|
{
|
|
#if defined(__GNUC__) && (__GNUC__ >= 4)
|
|
return __builtin_popcount(x);
|
|
#else
|
|
x -= x >> 1 & 0x55555555;
|
|
x = (x & 0x33333333) + (x >> 2 & 0x33333333);
|
|
return ((x + (x >> 4)) & 0x0f0f0f0f) * 0x01010101 >> 24;
|
|
#endif
|
|
}
|
|
|
|
static float opentype_scale_gpos_be_value(WORD value, float emsize, UINT16 upem)
|
|
{
|
|
return (short)GET_BE_WORD(value) * emsize / upem;
|
|
}
|
|
|
|
static int opentype_layout_gpos_get_dev_value(const struct scriptshaping_context *context, unsigned int offset)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
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(table, offset);
|
|
end_size = table_read_be_word(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(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(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++;
|
|
}
|
|
}
|
|
|
|
struct lookup
|
|
{
|
|
unsigned short index;
|
|
unsigned short type;
|
|
unsigned short flags;
|
|
unsigned short subtable_count;
|
|
|
|
unsigned int mask;
|
|
unsigned int offset;
|
|
unsigned int auto_zwnj : 1;
|
|
unsigned int auto_zwj : 1;
|
|
};
|
|
|
|
static unsigned int opentype_layout_is_subst_context(const struct scriptshaping_context *context)
|
|
{
|
|
return context->table == &context->cache->gsub;
|
|
}
|
|
|
|
static unsigned int opentype_layout_is_pos_context(const struct scriptshaping_context *context)
|
|
{
|
|
return context->table == &context->cache->gpos;
|
|
}
|
|
|
|
static unsigned int opentype_layout_get_gsubgpos_subtable(const struct scriptshaping_context *context,
|
|
const struct lookup *lookup, unsigned int subtable, unsigned int *lookup_type)
|
|
{
|
|
unsigned int subtable_offset = table_read_be_word(&context->table->table, lookup->offset +
|
|
FIELD_OFFSET(struct ot_lookup_table, subtable[subtable]));
|
|
const struct ot_gsubgpos_extension_format1 *format1;
|
|
|
|
subtable_offset += lookup->offset;
|
|
|
|
if ((opentype_layout_is_subst_context(context) && lookup->type != GSUB_LOOKUP_EXTENSION_SUBST) ||
|
|
(opentype_layout_is_pos_context(context) && lookup->type != GPOS_LOOKUP_EXTENSION_POSITION))
|
|
{
|
|
*lookup_type = lookup->type;
|
|
return subtable_offset;
|
|
}
|
|
|
|
*lookup_type = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
*lookup_type = GET_BE_WORD(format1->lookup_type);
|
|
return subtable_offset + GET_BE_DWORD(format1->extension_offset);
|
|
}
|
|
|
|
struct ot_lookup
|
|
{
|
|
unsigned int offset;
|
|
unsigned int subtable_count;
|
|
unsigned int flags;
|
|
};
|
|
|
|
enum iterator_match
|
|
{
|
|
/* First two to fit matching callback result. */
|
|
ITER_NO = 0,
|
|
ITER_YES = 1,
|
|
ITER_MAYBE,
|
|
};
|
|
|
|
struct match_context;
|
|
struct match_data
|
|
{
|
|
const struct match_context *mc;
|
|
unsigned int subtable_offset;
|
|
};
|
|
|
|
typedef BOOL (*p_match_func)(UINT16 glyph, UINT16 glyph_data, const struct match_data *match_data);
|
|
|
|
struct match_context
|
|
{
|
|
struct scriptshaping_context *context;
|
|
unsigned int backtrack_offset;
|
|
unsigned int input_offset;
|
|
unsigned int lookahead_offset;
|
|
p_match_func match_func;
|
|
const struct lookup *lookup;
|
|
};
|
|
|
|
struct glyph_iterator
|
|
{
|
|
struct scriptshaping_context *context;
|
|
unsigned int flags;
|
|
unsigned int pos;
|
|
unsigned int len;
|
|
unsigned int mask;
|
|
p_match_func match_func;
|
|
const UINT16 *glyph_data;
|
|
const struct match_data *match_data;
|
|
unsigned int ignore_zwnj;
|
|
unsigned int ignore_zwj;
|
|
};
|
|
|
|
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;
|
|
iter->mask = ~0u;
|
|
iter->match_func = NULL;
|
|
iter->match_data = NULL;
|
|
iter->glyph_data = NULL;
|
|
/* Context matching iterators will get these fixed up. */
|
|
iter->ignore_zwnj = !!opentype_layout_is_pos_context(context);
|
|
iter->ignore_zwj = context->auto_zwj;
|
|
}
|
|
|
|
struct ot_gdef_mark_glyph_sets
|
|
{
|
|
UINT16 format;
|
|
UINT16 count;
|
|
DWORD offsets[1];
|
|
};
|
|
|
|
static BOOL opentype_match_glyph_func(UINT16 glyph, UINT16 glyph_data, const struct match_data *data)
|
|
{
|
|
return glyph == glyph_data;
|
|
}
|
|
|
|
static BOOL opentype_match_class_func(UINT16 glyph, UINT16 glyph_data, const struct match_data *data)
|
|
{
|
|
const struct match_context *mc = data->mc;
|
|
UINT16 glyph_class = opentype_layout_get_glyph_class(&mc->context->table->table, data->subtable_offset, glyph);
|
|
return glyph_class == glyph_data;
|
|
}
|
|
|
|
static BOOL opentype_match_coverage_func(UINT16 glyph, UINT16 glyph_data, const struct match_data *data)
|
|
{
|
|
const struct match_context *mc = data->mc;
|
|
return opentype_layout_is_glyph_covered(&mc->context->table->table, data->subtable_offset + glyph_data, glyph)
|
|
!= GLYPH_NOT_COVERED;
|
|
}
|
|
|
|
static BOOL opentype_layout_mark_set_covers(const struct scriptshaping_cache *cache, unsigned int set_index,
|
|
UINT16 glyph)
|
|
{
|
|
unsigned int format, offset = cache->gdef.markglyphsetdef, coverage_offset, set_count;
|
|
|
|
if (!offset)
|
|
return FALSE;
|
|
|
|
format = table_read_be_word(&cache->gdef.table, offset);
|
|
|
|
if (format == 1)
|
|
{
|
|
set_count = table_read_be_word(&cache->gdef.table, offset + 2);
|
|
if (!set_count || set_index >= set_count)
|
|
return FALSE;
|
|
|
|
coverage_offset = table_read_be_dword(&cache->gdef.table, offset + 2 + set_index * sizeof(coverage_offset));
|
|
return opentype_layout_is_glyph_covered(&cache->gdef.table, offset + coverage_offset, glyph) != GLYPH_NOT_COVERED;
|
|
}
|
|
else
|
|
WARN("Unexpected MarkGlyphSets format %#x.\n", format);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL lookup_is_glyph_match(const struct scriptshaping_context *context, unsigned int idx, unsigned int match_props)
|
|
{
|
|
unsigned int glyph_props = context->glyph_infos[idx].props;
|
|
UINT16 glyph = context->u.buffer.glyphs[idx];
|
|
|
|
if (glyph_props & match_props & LOOKUP_FLAG_IGNORE_MASK)
|
|
return FALSE;
|
|
|
|
if (!(glyph_props & GLYPH_PROP_MARK))
|
|
return TRUE;
|
|
|
|
if (match_props & LOOKUP_FLAG_USE_MARK_FILTERING_SET)
|
|
return opentype_layout_mark_set_covers(context->cache, match_props >> 16, glyph);
|
|
|
|
if (match_props & LOOKUP_FLAG_MARK_ATTACHMENT_TYPE)
|
|
return (match_props & LOOKUP_FLAG_MARK_ATTACHMENT_TYPE) == (glyph_props & LOOKUP_FLAG_MARK_ATTACHMENT_TYPE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static enum iterator_match glyph_iterator_may_skip(const struct glyph_iterator *iter)
|
|
{
|
|
unsigned int glyph_props = iter->context->glyph_infos[iter->pos].props & (GLYPH_PROP_IGNORABLE | GLYPH_PROP_HIDDEN);
|
|
|
|
if (!lookup_is_glyph_match(iter->context, iter->pos, iter->flags))
|
|
return ITER_YES;
|
|
|
|
if (glyph_props == GLYPH_PROP_IGNORABLE && !iter->context->u.buffer.glyph_props[iter->pos].components &&
|
|
(iter->ignore_zwnj || !(iter->context->glyph_infos[iter->pos].props & GLYPH_PROP_ZWNJ)) &&
|
|
(iter->ignore_zwj || !(iter->context->glyph_infos[iter->pos].props & GLYPH_PROP_ZWJ)))
|
|
{
|
|
return ITER_MAYBE;
|
|
}
|
|
|
|
return ITER_NO;
|
|
}
|
|
|
|
static enum iterator_match glyph_iterator_may_match(const struct glyph_iterator *iter)
|
|
{
|
|
if (!(iter->mask & iter->context->glyph_infos[iter->pos].mask))
|
|
return ITER_NO;
|
|
|
|
/* Glyph data is used for input, backtrack, and lookahead arrays, swap it here instead of doing that
|
|
in all matching functions. */
|
|
if (iter->match_func)
|
|
return !!iter->match_func(iter->context->u.buffer.glyphs[iter->pos], GET_BE_WORD(*iter->glyph_data), iter->match_data);
|
|
|
|
return ITER_MAYBE;
|
|
}
|
|
|
|
static BOOL glyph_iterator_next(struct glyph_iterator *iter)
|
|
{
|
|
enum iterator_match skip, match;
|
|
|
|
while (iter->pos + iter->len < iter->context->glyph_count)
|
|
{
|
|
++iter->pos;
|
|
|
|
skip = glyph_iterator_may_skip(iter);
|
|
if (skip == ITER_YES)
|
|
continue;
|
|
|
|
match = glyph_iterator_may_match(iter);
|
|
if (match == ITER_YES || (match == ITER_MAYBE && skip == ITER_NO))
|
|
{
|
|
--iter->len;
|
|
if (iter->glyph_data)
|
|
++iter->glyph_data;
|
|
return TRUE;
|
|
}
|
|
|
|
if (skip == ITER_NO)
|
|
return FALSE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL glyph_iterator_prev(struct glyph_iterator *iter)
|
|
{
|
|
enum iterator_match skip, match;
|
|
|
|
while (iter->pos > iter->len - 1)
|
|
{
|
|
--iter->pos;
|
|
|
|
skip = glyph_iterator_may_skip(iter);
|
|
if (skip == ITER_YES)
|
|
continue;
|
|
|
|
match = glyph_iterator_may_match(iter);
|
|
if (match == ITER_YES || (match == ITER_MAYBE && skip == ITER_NO))
|
|
{
|
|
--iter->len;
|
|
if (iter->glyph_data)
|
|
++iter->glyph_data;
|
|
return TRUE;
|
|
}
|
|
|
|
if (skip == ITER_NO)
|
|
return FALSE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gpos_single_adjustment(struct scriptshaping_context *context,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
UINT16 format, value_format, value_len, coverage, glyph;
|
|
|
|
unsigned int coverage_index;
|
|
|
|
format = table_read_be_word(table, subtable_offset);
|
|
|
|
coverage = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gpos_singlepos_format1, coverage));
|
|
value_format = table_read_be_word(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(table, subtable_offset,
|
|
FIELD_OFFSET(struct ot_gpos_singlepos_format1, value[value_len]));
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(table, 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(table, subtable_offset +
|
|
FIELD_OFFSET(struct ot_gpos_singlepos_format2, value_count));
|
|
const struct ot_gpos_singlepos_format2 *format2 = table_read_ensure(table, subtable_offset,
|
|
FIELD_OFFSET(struct ot_gpos_singlepos_format2, values) + value_count * value_len * sizeof(WORD));
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(table, 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 __cdecl 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)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
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(table, subtable_offset);
|
|
|
|
coverage = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gpos_pairpos_format1, coverage));
|
|
if (!coverage)
|
|
return FALSE;
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(table, 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(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(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(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(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(table, subtable_offset +
|
|
FIELD_OFFSET(struct ot_gpos_pairpos_format2, value_format1)) & 0xff;
|
|
value_format2 = table_read_be_word(table, subtable_offset +
|
|
FIELD_OFFSET(struct ot_gpos_pairpos_format2, value_format2)) & 0xff;
|
|
|
|
class1_count = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gpos_pairpos_format2, class1_count));
|
|
class2_count = table_read_be_word(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(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(table, subtable_offset + GET_BE_WORD(format2->class_def1),
|
|
context->u.pos.glyphs[first_glyph]);
|
|
class2 = opentype_layout_get_glyph_class(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;
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
|
|
WORD format = table_read_be_word(table, anchor_offset);
|
|
|
|
*x = *y = 0.0f;
|
|
|
|
if (format == 1)
|
|
{
|
|
const struct ot_gpos_anchor_format1 *format1 = table_read_ensure(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(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(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 void opentype_set_glyph_attach_type(struct scriptshaping_context *context, unsigned int idx,
|
|
enum attach_type attach_type)
|
|
{
|
|
context->glyph_infos[idx].props &= ~GLYPH_PROP_ATTACH_TYPE_MASK;
|
|
context->glyph_infos[idx].props |= attach_type << 16;
|
|
}
|
|
|
|
static enum attach_type opentype_get_glyph_attach_type(const struct scriptshaping_context *context, unsigned int idx)
|
|
{
|
|
return (context->glyph_infos[idx].props >> 16) & 0xff;
|
|
}
|
|
|
|
static void opentype_reverse_cursive_offset(struct scriptshaping_context *context, unsigned int i,
|
|
unsigned int new_parent)
|
|
{
|
|
enum attach_type type = opentype_get_glyph_attach_type(context, i);
|
|
int chain = context->glyph_infos[i].attach_chain;
|
|
unsigned int j;
|
|
|
|
if (!chain || type != GLYPH_ATTACH_CURSIVE)
|
|
return;
|
|
|
|
context->glyph_infos[i].attach_chain = 0;
|
|
|
|
j = (int)i + chain;
|
|
if (j == new_parent)
|
|
return;
|
|
|
|
opentype_reverse_cursive_offset(context, j, new_parent);
|
|
|
|
/* FIXME: handle vertical flow direction */
|
|
context->offsets[j].ascenderOffset = -context->offsets[i].ascenderOffset;
|
|
|
|
context->glyph_infos[j].attach_chain = -chain;
|
|
opentype_set_glyph_attach_type(context, j, type);
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gpos_cursive_attachment(struct scriptshaping_context *context,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
UINT16 format, glyph;
|
|
|
|
format = table_read_be_word(table, subtable_offset);
|
|
glyph = context->u.pos.glyphs[context->cur];
|
|
|
|
if (format == 1)
|
|
{
|
|
WORD coverage_offset = table_read_be_word(table, subtable_offset +
|
|
FIELD_OFFSET(struct ot_gpos_cursive_format1, coverage));
|
|
unsigned int glyph_index, entry_count, entry_anchor, exit_anchor, child, parent;
|
|
float entry_x, entry_y, exit_x, exit_y, delta;
|
|
struct glyph_iterator prev_iter;
|
|
float y_offset;
|
|
|
|
if (!coverage_offset)
|
|
return FALSE;
|
|
|
|
entry_count = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gpos_cursive_format1, count));
|
|
|
|
glyph_index = opentype_layout_is_glyph_covered(table, subtable_offset + coverage_offset, glyph);
|
|
if (glyph_index == GLYPH_NOT_COVERED || glyph_index >= entry_count)
|
|
return FALSE;
|
|
|
|
entry_anchor = table_read_be_word(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(table, 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(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)
|
|
{
|
|
y_offset = entry_y - exit_y;
|
|
child = prev_iter.pos;
|
|
parent = context->cur;
|
|
}
|
|
else
|
|
{
|
|
y_offset = exit_y - entry_y;
|
|
child = context->cur;
|
|
parent = prev_iter.pos;
|
|
}
|
|
|
|
opentype_reverse_cursive_offset(context, child, parent);
|
|
|
|
context->offsets[child].ascenderOffset = y_offset;
|
|
|
|
opentype_set_glyph_attach_type(context, child, GLYPH_ATTACH_CURSIVE);
|
|
context->glyph_infos[child].attach_chain = (int)parent - (int)child;
|
|
context->has_gpos_attachment = 1;
|
|
|
|
if (context->glyph_infos[parent].attach_chain == -context->glyph_infos[child].attach_chain)
|
|
context->glyph_infos[parent].attach_chain = 0;
|
|
|
|
context->cur++;
|
|
}
|
|
else
|
|
{
|
|
WARN("Unknown cursive attachment format %u.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_mark_array(struct scriptshaping_context *context, unsigned int subtable_offset,
|
|
unsigned int mark_array, unsigned int mark_index, unsigned int glyph_index, unsigned int anchors_matrix,
|
|
unsigned int class_count, unsigned int glyph_pos)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
unsigned int mark_class, mark_count, glyph_count;
|
|
const struct ot_gpos_mark_record *record;
|
|
float mark_x, mark_y, base_x, base_y;
|
|
const UINT16 *anchors;
|
|
|
|
mark_count = table_read_be_word(table, subtable_offset + mark_array);
|
|
if (mark_index >= mark_count) return FALSE;
|
|
|
|
if (!(record = table_read_ensure(table, subtable_offset + mark_array +
|
|
FIELD_OFFSET(struct ot_gpos_mark_array, records[mark_index]), sizeof(*record))))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
mark_class = GET_BE_WORD(record->mark_class);
|
|
if (mark_class >= class_count) return FALSE;
|
|
|
|
glyph_count = table_read_be_word(table, subtable_offset + anchors_matrix);
|
|
if (glyph_index >= glyph_count) return FALSE;
|
|
|
|
/* Anchors data is stored as two dimensional array [glyph_count][class_count], starting with row count field. */
|
|
anchors = table_read_ensure(table, subtable_offset + anchors_matrix + 2, glyph_count * class_count * sizeof(*anchors));
|
|
if (!anchors) return FALSE;
|
|
|
|
opentype_layout_gpos_get_anchor(context, subtable_offset + mark_array + GET_BE_WORD(record->mark_anchor),
|
|
context->cur, &mark_x, &mark_y);
|
|
opentype_layout_gpos_get_anchor(context, subtable_offset + anchors_matrix +
|
|
GET_BE_WORD(anchors[glyph_index * class_count + mark_class]), glyph_pos, &base_x, &base_y);
|
|
|
|
if (context->is_rtl)
|
|
context->offsets[context->cur].advanceOffset = mark_x - base_x;
|
|
else
|
|
context->offsets[context->cur].advanceOffset = -context->advances[glyph_pos] + base_x - mark_x;
|
|
context->offsets[context->cur].ascenderOffset = base_y - mark_y;
|
|
opentype_set_glyph_attach_type(context, context->cur, GLYPH_ATTACH_MARK);
|
|
context->glyph_infos[context->cur].attach_chain = (int)glyph_pos - (int)context->cur;
|
|
context->has_gpos_attachment = 1;
|
|
|
|
context->cur++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gpos_mark_to_base_attachment(struct scriptshaping_context *context,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
WORD format;
|
|
|
|
format = table_read_be_word(table, subtable_offset);
|
|
|
|
if (format == 1)
|
|
{
|
|
const struct ot_gpos_mark_to_base_format1 *format1;
|
|
unsigned int base_index, mark_index;
|
|
struct glyph_iterator base_iter;
|
|
|
|
if (!(format1 = table_read_ensure(table, subtable_offset, sizeof(*format1)))) return FALSE;
|
|
|
|
mark_index = opentype_layout_is_glyph_covered(table, subtable_offset + GET_BE_WORD(format1->mark_coverage),
|
|
context->u.pos.glyphs[context->cur]);
|
|
if (mark_index == GLYPH_NOT_COVERED) 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(table, subtable_offset + GET_BE_WORD(format1->base_coverage),
|
|
context->u.pos.glyphs[base_iter.pos]);
|
|
if (base_index == GLYPH_NOT_COVERED) return FALSE;
|
|
|
|
return opentype_layout_apply_mark_array(context, subtable_offset, GET_BE_WORD(format1->mark_array), mark_index,
|
|
base_index, GET_BE_WORD(format1->base_array), GET_BE_WORD(format1->mark_class_count), base_iter.pos);
|
|
}
|
|
else
|
|
{
|
|
WARN("Unknown mark-to-base format %u.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static const UINT16 * table_read_array_be_word(const struct dwrite_fonttable *table, unsigned int offset,
|
|
unsigned int index, UINT16 *data)
|
|
{
|
|
unsigned int count = table_read_be_word(table, offset);
|
|
const UINT16 *array;
|
|
|
|
if (index != ~0u && index >= count) return NULL;
|
|
if (!(array = table_read_ensure(table, offset + 2, count * sizeof(*array)))) return FALSE;
|
|
*data = index == ~0u ? count : GET_BE_WORD(array[index]);
|
|
return array;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gpos_mark_to_lig_attachment(struct scriptshaping_context *context,
|
|
const struct lookup *lookup, unsigned int subtable_offset)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
WORD format;
|
|
|
|
format = table_read_be_word(table, subtable_offset);
|
|
|
|
if (format == 1)
|
|
{
|
|
unsigned int mark_index, lig_index, comp_index, class_count, comp_count;
|
|
const struct ot_gpos_mark_to_lig_format1 *format1;
|
|
struct glyph_iterator lig_iter;
|
|
unsigned int lig_array;
|
|
UINT16 lig_attach;
|
|
|
|
if (!(format1 = table_read_ensure(table, subtable_offset, sizeof(*format1)))) return FALSE;
|
|
|
|
mark_index = opentype_layout_is_glyph_covered(table, 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(table, subtable_offset + GET_BE_WORD(format1->lig_coverage),
|
|
context->u.pos.glyphs[lig_iter.pos]);
|
|
if (lig_index == GLYPH_NOT_COVERED) return FALSE;
|
|
|
|
class_count = GET_BE_WORD(format1->mark_class_count);
|
|
|
|
lig_array = GET_BE_WORD(format1->lig_array);
|
|
|
|
if (!table_read_array_be_word(table, subtable_offset + lig_array, lig_index, &lig_attach)) return FALSE;
|
|
|
|
comp_count = table_read_be_word(table, subtable_offset + lig_array + lig_attach);
|
|
if (!comp_count) return FALSE;
|
|
|
|
comp_index = context->u.buffer.glyph_props[lig_iter.pos].components -
|
|
context->u.buffer.glyph_props[context->cur].lig_component - 1;
|
|
if (comp_index >= comp_count) return FALSE;
|
|
|
|
return opentype_layout_apply_mark_array(context, subtable_offset, GET_BE_WORD(format1->mark_array), mark_index,
|
|
comp_index, lig_array + lig_attach, class_count, lig_iter.pos);
|
|
}
|
|
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)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
WORD format;
|
|
|
|
format = table_read_be_word(table, subtable_offset);
|
|
|
|
if (format == 1)
|
|
{
|
|
const struct ot_gpos_mark_to_mark_format1 *format1;
|
|
unsigned int mark1_index, mark2_index;
|
|
struct glyph_iterator mark_iter;
|
|
|
|
if (!(format1 = table_read_ensure(table, subtable_offset, sizeof(*format1)))) return FALSE;
|
|
|
|
mark1_index = opentype_layout_is_glyph_covered(table, subtable_offset + GET_BE_WORD(format1->mark1_coverage),
|
|
context->u.pos.glyphs[context->cur]);
|
|
if (mark1_index == GLYPH_NOT_COVERED) 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_index = opentype_layout_is_glyph_covered(table, subtable_offset + GET_BE_WORD(format1->mark2_coverage),
|
|
context->u.pos.glyphs[mark_iter.pos]);
|
|
if (mark2_index == GLYPH_NOT_COVERED) return FALSE;
|
|
|
|
return opentype_layout_apply_mark_array(context, subtable_offset, GET_BE_WORD(format1->mark1_array), mark1_index,
|
|
mark2_index, GET_BE_WORD(format1->mark2_array), GET_BE_WORD(format1->mark_class_count), mark_iter.pos);
|
|
}
|
|
else
|
|
{
|
|
WARN("Unknown mark-to-mark format %u.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_context(struct scriptshaping_context *context, const struct lookup *lookup,
|
|
unsigned int subtable_offset);
|
|
static BOOL opentype_layout_apply_chain_context(struct scriptshaping_context *context, const struct lookup *lookup,
|
|
unsigned int subtable_offset);
|
|
|
|
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, i, &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_context(context, lookup, subtable_offset);
|
|
break;
|
|
case GPOS_LOOKUP_CONTEXTUAL_CHAINING_POSITION:
|
|
ret = opentype_layout_apply_chain_context(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 __cdecl 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,
|
|
const struct shaping_feature *feature, struct lookup *lookup)
|
|
{
|
|
unsigned short subtable_count, lookup_type, flags, mark_filtering_set;
|
|
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);
|
|
|
|
if (flags & LOOKUP_FLAG_USE_MARK_FILTERING_SET)
|
|
{
|
|
mark_filtering_set = table_read_be_word(&table->table, offset +
|
|
FIELD_OFFSET(struct ot_lookup_table, subtable[subtable_count]));
|
|
flags |= mark_filtering_set << 16;
|
|
}
|
|
|
|
lookup->index = lookup_index;
|
|
lookup->type = lookup_type;
|
|
lookup->flags = flags;
|
|
lookup->subtable_count = subtable_count;
|
|
lookup->offset = offset;
|
|
if (feature)
|
|
{
|
|
lookup->mask = feature->mask;
|
|
lookup->auto_zwnj = !(feature->flags & FEATURE_MANUAL_ZWNJ);
|
|
lookup->auto_zwj = !(feature->flags & FEATURE_MANUAL_ZWJ);
|
|
}
|
|
|
|
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;
|
|
|
|
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, &lookups->lookups[lookups->count]))
|
|
lookups->count++;
|
|
}
|
|
}
|
|
|
|
static void opentype_layout_collect_lookups(struct scriptshaping_context *context, unsigned int script_index,
|
|
unsigned int language_index, struct shaping_features *features, const struct ot_gsubgpos_table *table,
|
|
struct lookups *lookups)
|
|
{
|
|
unsigned int last_num_lookups = 0, stage, script_feature_count = 0;
|
|
UINT16 total_feature_count, total_lookup_count;
|
|
struct shaping_feature required_feature = { 0 };
|
|
const struct ot_feature_list *feature_list;
|
|
const struct ot_langsys *langsys = NULL;
|
|
struct shaping_feature *feature;
|
|
unsigned int i, j, next_bit;
|
|
unsigned int global_bit_shift = 1;
|
|
unsigned int global_bit_mask = 2;
|
|
UINT16 feature_index;
|
|
|
|
if (!table->table.data)
|
|
return;
|
|
|
|
if (script_index != ~0u)
|
|
{
|
|
unsigned int table_offset, langsys_offset;
|
|
|
|
/* 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));
|
|
langsys_offset += table->script_list + table_offset;
|
|
|
|
script_feature_count = table_read_be_word(&table->table, langsys_offset + FIELD_OFFSET(struct ot_langsys, feature_count));
|
|
if (script_feature_count)
|
|
langsys = table_read_ensure(&table->table, langsys_offset,
|
|
FIELD_OFFSET(struct ot_langsys, feature_index[script_feature_count]));
|
|
if (!langsys)
|
|
script_feature_count = 0;
|
|
}
|
|
|
|
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;
|
|
|
|
/* Required feature. */
|
|
required_feature.index = langsys ? GET_BE_WORD(langsys->required_feature_index) : 0xffff;
|
|
if (required_feature.index < total_feature_count)
|
|
required_feature.tag = feature_list->features[required_feature.index].tag;
|
|
required_feature.mask = global_bit_mask;
|
|
|
|
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
|
|
{
|
|
BitScanReverse(&bits_needed, min(feature->max_value, 256));
|
|
bits_needed++;
|
|
}
|
|
|
|
if (!feature->max_value || next_bit + bits_needed > 8 * sizeof (feature->mask))
|
|
continue;
|
|
|
|
if (required_feature.tag == feature->tag)
|
|
required_feature.stage = feature->stage;
|
|
|
|
for (j = 0; j < script_feature_count; ++j)
|
|
{
|
|
feature_index = GET_BE_WORD(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 && !(features->features[i].flags & FEATURE_HAS_FALLBACK))
|
|
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;
|
|
}
|
|
if (!found)
|
|
feature->flags |= FEATURE_NEEDS_FALLBACK;
|
|
}
|
|
|
|
for (stage = 0; stage <= features->stage; ++stage)
|
|
{
|
|
if (required_feature.index != 0xffff && required_feature.stage == stage)
|
|
opentype_layout_add_lookups(feature_list, total_lookup_count, table, &required_feature, lookups);
|
|
|
|
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->lookups[j].auto_zwnj &= lookups->lookups[i].auto_zwnj;
|
|
lookups->lookups[j].auto_zwj &= lookups->lookups[i].auto_zwj;
|
|
}
|
|
}
|
|
lookups->count = j + 1;
|
|
}
|
|
|
|
last_num_lookups = lookups->count;
|
|
features->stages[stage].last_lookup = last_num_lookups;
|
|
}
|
|
}
|
|
|
|
static int __cdecl 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;
|
|
}
|
|
|
|
unsigned int shape_get_feature_1_mask(const struct shaping_features *features, unsigned int tag)
|
|
{
|
|
unsigned int shift, mask = shaping_features_get_mask(features, tag, &shift);
|
|
return (1 << shift) & 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;
|
|
}
|
|
|
|
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;
|
|
|
|
if (opentype_layout_is_subst_context(context) && context->shaper->setup_masks)
|
|
context->shaper->setup_masks(context, features);
|
|
|
|
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;
|
|
|
|
if (!context->user_features.range_lengths[r])
|
|
continue;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void opentype_layout_apply_gpos_context_lookup(struct scriptshaping_context *context, unsigned int lookup_index)
|
|
{
|
|
struct lookup lookup = { 0 };
|
|
if (opentype_layout_init_lookup(context->table, lookup_index, NULL, &lookup))
|
|
opentype_layout_apply_gpos_lookup(context, &lookup);
|
|
}
|
|
|
|
static void opentype_propagate_attachment_offsets(struct scriptshaping_context *context, unsigned int i)
|
|
{
|
|
enum attach_type type = opentype_get_glyph_attach_type(context, i);
|
|
int chain = context->glyph_infos[i].attach_chain;
|
|
unsigned int j, k;
|
|
|
|
if (!chain)
|
|
return;
|
|
|
|
context->glyph_infos[i].attach_chain = 0;
|
|
|
|
j = (int)i + chain;
|
|
if (j >= context->glyph_count)
|
|
return;
|
|
|
|
opentype_propagate_attachment_offsets(context, j);
|
|
|
|
if (type == GLYPH_ATTACH_CURSIVE)
|
|
{
|
|
/* FIXME: handle vertical direction. */
|
|
context->offsets[i].ascenderOffset += context->offsets[j].ascenderOffset;
|
|
}
|
|
else if (type == GLYPH_ATTACH_MARK)
|
|
{
|
|
context->offsets[i].advanceOffset += context->offsets[j].advanceOffset;
|
|
context->offsets[i].ascenderOffset += context->offsets[j].ascenderOffset;
|
|
|
|
/* FIXME: handle vertical adjustment. */
|
|
if (context->is_rtl)
|
|
{
|
|
for (k = j + 1; k < i + 1; ++k)
|
|
{
|
|
context->offsets[i].advanceOffset += context->advances[k];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (k = j; k < i; k++)
|
|
{
|
|
context->offsets[i].advanceOffset -= context->advances[k];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void opentype_layout_apply_gpos_features(struct scriptshaping_context *context, unsigned int script_index,
|
|
unsigned int language_index, struct shaping_features *features)
|
|
{
|
|
struct lookups lookups = { 0 };
|
|
unsigned int i;
|
|
BOOL ret;
|
|
|
|
context->nesting_level_left = SHAPE_MAX_NESTING_LEVEL;
|
|
context->u.buffer.apply_context_lookup = opentype_layout_apply_gpos_context_lookup;
|
|
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);
|
|
opentype_layout_set_glyph_masks(context, features);
|
|
|
|
for (i = 0; i < lookups.count; ++i)
|
|
{
|
|
const struct lookup *lookup = &lookups.lookups[i];
|
|
|
|
context->cur = 0;
|
|
context->lookup_mask = lookup->mask;
|
|
context->auto_zwnj = lookup->auto_zwnj;
|
|
context->auto_zwj = lookup->auto_zwj;
|
|
|
|
while (context->cur < context->glyph_count)
|
|
{
|
|
ret = FALSE;
|
|
|
|
if ((context->glyph_infos[context->cur].mask & lookup->mask) &&
|
|
lookup_is_glyph_match(context, context->cur, lookup->flags))
|
|
{
|
|
ret = opentype_layout_apply_gpos_lookup(context, lookup);
|
|
}
|
|
|
|
if (!ret)
|
|
context->cur++;
|
|
}
|
|
}
|
|
|
|
free(lookups.lookups);
|
|
|
|
if (context->has_gpos_attachment)
|
|
{
|
|
for (i = 0; i < context->glyph_count; ++i)
|
|
opentype_propagate_attachment_offsets(context, i);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gsub_single_substitution(struct scriptshaping_context *context, const struct lookup *lookup,
|
|
unsigned int subtable_offset)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
UINT16 format, coverage, orig_glyph, glyph;
|
|
unsigned int coverage_index;
|
|
|
|
orig_glyph = glyph = context->u.subst.glyphs[context->cur];
|
|
|
|
format = table_read_be_word(table, subtable_offset);
|
|
|
|
coverage = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsub_singlesubst_format1, coverage));
|
|
|
|
if (format == 1)
|
|
{
|
|
coverage_index = opentype_layout_is_glyph_covered(table, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED) return FALSE;
|
|
|
|
glyph = orig_glyph + table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsub_singlesubst_format1, delta));
|
|
}
|
|
else if (format == 2)
|
|
{
|
|
coverage_index = opentype_layout_is_glyph_covered(table, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED) return FALSE;
|
|
|
|
if (!table_read_array_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsub_singlesubst_format2, count),
|
|
coverage_index, &glyph))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARN("Unknown single substitution format %u.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
opentype_layout_replace_glyph(context, glyph);
|
|
context->cur++;
|
|
|
|
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 = realloc(context->u.subst.glyphs, new_capacity * sizeof(*glyphs))))
|
|
context->u.subst.glyphs = glyphs;
|
|
if ((glyph_props = realloc(context->u.subst.glyph_props, new_capacity * sizeof(*glyph_props))))
|
|
context->u.subst.glyph_props = glyph_props;
|
|
if ((glyph_infos = 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 BOOL opentype_layout_apply_gsub_mult_substitution(struct scriptshaping_context *context, const struct lookup *lookup,
|
|
unsigned int subtable_offset)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
UINT16 format, coverage, glyph, glyph_count;
|
|
unsigned int i, idx, coverage_index;
|
|
const UINT16 *glyphs;
|
|
|
|
idx = context->cur;
|
|
glyph = context->u.subst.glyphs[idx];
|
|
|
|
format = table_read_be_word(table, subtable_offset);
|
|
|
|
coverage = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsub_multsubst_format1, coverage));
|
|
|
|
if (format == 1)
|
|
{
|
|
UINT16 seq_offset;
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(table, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED) return FALSE;
|
|
|
|
if (!table_read_array_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsub_multsubst_format1, seq_count),
|
|
coverage_index, &seq_offset))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!(glyphs = table_read_array_be_word(table, subtable_offset + seq_offset, ~0u, &glyph_count))) return FALSE;
|
|
|
|
if (glyph_count == 1)
|
|
{
|
|
/* Equivalent of single substitution. */
|
|
opentype_layout_replace_glyph(context, GET_BE_WORD(glyphs[0]));
|
|
context->cur++;
|
|
}
|
|
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;
|
|
context->glyph_infos[idx + i].start_text_idx = 0;
|
|
}
|
|
opentype_set_subst_glyph_props(context, idx + i);
|
|
/* Inherit feature mask from original matched glyph. */
|
|
context->glyph_infos[idx + i].mask = mask;
|
|
}
|
|
|
|
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 *table = &context->table->table;
|
|
unsigned int idx, coverage_index;
|
|
UINT16 format, coverage, glyph;
|
|
|
|
idx = context->cur;
|
|
glyph = context->u.subst.glyphs[idx];
|
|
|
|
format = table_read_be_word(table, subtable_offset);
|
|
|
|
if (format == 1)
|
|
{
|
|
const struct ot_gsub_altsubst_format1 *format1 = table_read_ensure(table, subtable_offset, sizeof(*format1));
|
|
unsigned int shift, alt_index;
|
|
UINT16 set_offset;
|
|
|
|
coverage = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsub_altsubst_format1, coverage));
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(table, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED) return FALSE;
|
|
|
|
if (!table_read_array_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsub_altsubst_format1, count),
|
|
coverage_index, &set_offset))
|
|
return FALSE;
|
|
|
|
/* Argument is 1-based. */
|
|
BitScanForward(&shift, context->lookup_mask);
|
|
alt_index = (context->lookup_mask & context->glyph_infos[idx].mask) >> shift;
|
|
if (!alt_index) return FALSE;
|
|
|
|
if (!table_read_array_be_word(table, subtable_offset + set_offset, alt_index - 1, &glyph)) return FALSE;
|
|
}
|
|
else
|
|
{
|
|
WARN("Unexpected alternate substitution format %d.\n", format);
|
|
return FALSE;
|
|
}
|
|
|
|
opentype_layout_replace_glyph(context, glyph);
|
|
context->cur++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_context_match_input(const struct match_context *mc, unsigned int count, const UINT16 *input,
|
|
unsigned int *end_offset, unsigned int *match_positions)
|
|
{
|
|
struct match_data match_data = { .mc = mc, .subtable_offset = mc->input_offset };
|
|
struct scriptshaping_context *context = mc->context;
|
|
struct glyph_iterator iter;
|
|
unsigned int i;
|
|
|
|
if (count > GLYPH_CONTEXT_MAX_LENGTH)
|
|
return FALSE;
|
|
|
|
match_positions[0] = context->cur;
|
|
|
|
glyph_iterator_init(context, mc->lookup->flags, context->cur, count - 1, &iter);
|
|
iter.mask = context->lookup_mask;
|
|
iter.match_func = mc->match_func;
|
|
iter.match_data = &match_data;
|
|
iter.glyph_data = input;
|
|
|
|
for (i = 1; i < count; ++i)
|
|
{
|
|
if (!glyph_iterator_next(&iter))
|
|
return FALSE;
|
|
|
|
match_positions[i] = iter.pos;
|
|
}
|
|
|
|
*end_offset = iter.pos - context->cur + 1;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Marks text segment as unsafe to break between [start, end) glyphs. */
|
|
void opentype_layout_unsafe_to_break(struct scriptshaping_context *context, unsigned int start,
|
|
unsigned int end)
|
|
{
|
|
unsigned int i;
|
|
|
|
while (start && !context->u.buffer.glyph_props[start].isClusterStart)
|
|
--start;
|
|
|
|
while (--end && !context->u.buffer.glyph_props[end].isClusterStart)
|
|
;
|
|
|
|
if (start == end)
|
|
{
|
|
context->u.buffer.text_props[context->glyph_infos[start].start_text_idx].canBreakShapingAfter = 0;
|
|
return;
|
|
}
|
|
|
|
for (i = context->glyph_infos[start].start_text_idx; i < context->glyph_infos[end].start_text_idx; ++i)
|
|
{
|
|
context->u.buffer.text_props[i].canBreakShapingAfter = 0;
|
|
}
|
|
}
|
|
|
|
static void opentype_layout_delete_glyph(struct scriptshaping_context *context, unsigned int idx)
|
|
{
|
|
unsigned int shift_len;
|
|
|
|
shift_len = context->glyph_count - context->cur - 1;
|
|
|
|
if (shift_len)
|
|
{
|
|
memmove(&context->u.buffer.glyphs[idx], &context->u.buffer.glyphs[idx + 1],
|
|
shift_len * sizeof(*context->u.buffer.glyphs));
|
|
memmove(&context->u.buffer.glyph_props[idx], &context->u.buffer.glyph_props[idx + 1],
|
|
shift_len * sizeof(*context->u.buffer.glyph_props));
|
|
memmove(&context->glyph_infos[idx], &context->glyph_infos[idx + 1], shift_len * sizeof(*context->glyph_infos));
|
|
}
|
|
|
|
context->glyph_count--;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_ligature(struct scriptshaping_context *context, unsigned int offset,
|
|
const struct lookup *lookup)
|
|
{
|
|
struct match_context mc = { .context = context, .lookup = lookup, .match_func = opentype_match_glyph_func };
|
|
const struct dwrite_fonttable *gsub = &context->table->table;
|
|
unsigned int match_positions[GLYPH_CONTEXT_MAX_LENGTH];
|
|
unsigned int i, j, comp_count, match_length = 0;
|
|
const struct ot_gsub_lig *lig;
|
|
UINT16 lig_glyph;
|
|
|
|
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;
|
|
|
|
lig_glyph = GET_BE_WORD(lig->lig_glyph);
|
|
|
|
if (comp_count == 1)
|
|
{
|
|
opentype_layout_replace_glyph(context, lig_glyph);
|
|
context->cur++;
|
|
return TRUE;
|
|
}
|
|
|
|
if (!opentype_layout_context_match_input(&mc, comp_count, lig->components, &match_length, match_positions))
|
|
return FALSE;
|
|
|
|
opentype_layout_replace_glyph(context, lig_glyph);
|
|
context->u.buffer.glyph_props[context->cur].components = comp_count;
|
|
|
|
/* Positioning against a ligature implies keeping track of ligature component
|
|
glyph should be attached to. Update per-glyph property for interleaving glyphs,
|
|
0 means attaching to last component, n - attaching to n-th glyph before last. */
|
|
for (i = 1; i < comp_count; ++i)
|
|
{
|
|
j = match_positions[i - 1] + 1;
|
|
while (j < match_positions[i])
|
|
{
|
|
context->u.buffer.glyph_props[j++].lig_component = comp_count - i;
|
|
}
|
|
}
|
|
opentype_layout_unsafe_to_break(context, match_positions[0], match_positions[comp_count - 1] + 1);
|
|
|
|
/* Delete ligated glyphs, backwards to preserve index. */
|
|
for (i = 1; i < comp_count; ++i)
|
|
{
|
|
opentype_layout_delete_glyph(context, match_positions[comp_count - i]);
|
|
}
|
|
|
|
/* Skip whole matched sequence, accounting for deleted glyphs. */
|
|
context->cur += match_length - (comp_count - 1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gsub_lig_substitution(struct scriptshaping_context *context, const struct lookup *lookup,
|
|
unsigned int subtable_offset)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
UINT16 format, coverage, glyph, lig_set_offset;
|
|
unsigned int coverage_index;
|
|
|
|
glyph = context->u.subst.glyphs[context->cur];
|
|
|
|
format = table_read_be_word(table, subtable_offset);
|
|
|
|
if (format == 1)
|
|
{
|
|
const struct ot_gsub_ligsubst_format1 *format1 = table_read_ensure(table, subtable_offset, sizeof(*format1));
|
|
unsigned int i;
|
|
const UINT16 *offsets;
|
|
UINT16 lig_count;
|
|
|
|
coverage = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsub_ligsubst_format1, coverage));
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(table, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED) return FALSE;
|
|
|
|
if (!table_read_array_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsub_ligsubst_format1, lig_set_count),
|
|
coverage_index, &lig_set_offset))
|
|
return FALSE;
|
|
|
|
if (!(offsets = table_read_array_be_word(table, subtable_offset + lig_set_offset, ~0u, &lig_count)))
|
|
return FALSE;
|
|
|
|
/* First applicable ligature is used. */
|
|
for (i = 0; i < lig_count; ++i)
|
|
{
|
|
if (opentype_layout_apply_ligature(context, subtable_offset + lig_set_offset + GET_BE_WORD(offsets[i]), lookup))
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
WARN("Unexpected ligature substitution format %d.\n", format);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL opentype_layout_context_match_backtrack(const struct match_context *mc, unsigned int count,
|
|
const UINT16 *backtrack, unsigned int *match_start)
|
|
{
|
|
struct match_data match_data = { .mc = mc, .subtable_offset = mc->backtrack_offset };
|
|
struct scriptshaping_context *context = mc->context;
|
|
struct glyph_iterator iter;
|
|
unsigned int i;
|
|
|
|
glyph_iterator_init(context, mc->lookup->flags, context->cur, count, &iter);
|
|
iter.match_func = mc->match_func;
|
|
iter.match_data = &match_data;
|
|
iter.glyph_data = backtrack;
|
|
iter.ignore_zwnj |= context->auto_zwnj;
|
|
iter.ignore_zwj = 1;
|
|
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
if (!glyph_iterator_prev(&iter))
|
|
return FALSE;
|
|
}
|
|
|
|
*match_start = iter.pos;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_context_match_lookahead(const struct match_context *mc, unsigned int count,
|
|
const UINT16 *lookahead, unsigned int offset, unsigned int *end_index)
|
|
{
|
|
struct match_data match_data = { .mc = mc, .subtable_offset = mc->lookahead_offset };
|
|
struct scriptshaping_context *context = mc->context;
|
|
struct glyph_iterator iter;
|
|
unsigned int i;
|
|
|
|
glyph_iterator_init(context, mc->lookup->flags, context->cur + offset - 1, count, &iter);
|
|
iter.match_func = mc->match_func;
|
|
iter.match_data = &match_data;
|
|
iter.glyph_data = lookahead;
|
|
iter.ignore_zwnj |= context->auto_zwnj;
|
|
iter.ignore_zwj = 1;
|
|
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
if (!glyph_iterator_next(&iter))
|
|
return FALSE;
|
|
}
|
|
|
|
*end_index = iter.pos;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL opentype_layout_context_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)
|
|
{
|
|
unsigned int i, j;
|
|
int end, delta;
|
|
|
|
if (!context->nesting_level_left)
|
|
return TRUE;
|
|
|
|
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]);
|
|
|
|
--context->nesting_level_left;
|
|
context->u.buffer.apply_context_lookup(context, lookup_index);
|
|
++context->nesting_level_left;
|
|
|
|
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 > GLYPH_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_chain_context_match(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, const struct match_context *mc)
|
|
{
|
|
unsigned int start_index = 0, match_length = 0, end_index = 0;
|
|
unsigned int match_positions[GLYPH_CONTEXT_MAX_LENGTH];
|
|
|
|
return opentype_layout_context_match_input(mc, input_count, input, &match_length, match_positions) &&
|
|
opentype_layout_context_match_backtrack(mc, backtrack_count, backtrack, &start_index) &&
|
|
opentype_layout_context_match_lookahead(mc, lookahead_count, lookahead, input_count, &end_index) &&
|
|
opentype_layout_context_apply_lookup(mc->context, input_count, match_positions, lookup_count, lookup_records, match_length);
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_chain_rule_set(const struct match_context *mc, unsigned int offset)
|
|
{
|
|
unsigned int backtrack_count, input_count, lookahead_count, lookup_count;
|
|
const struct dwrite_fonttable *table = &mc->context->table->table;
|
|
const UINT16 *backtrack, *lookahead, *input, *lookup_records;
|
|
const struct ot_gsubgpos_ruleset *ruleset;
|
|
unsigned int i, count;
|
|
|
|
count = table_read_be_word(table, offset);
|
|
ruleset = table_read_ensure(table, offset, count * sizeof(ruleset->offsets));
|
|
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
unsigned int rule_offset = offset + GET_BE_WORD(ruleset->offsets[i]);
|
|
|
|
backtrack_count = table_read_be_word(table, rule_offset);
|
|
rule_offset += 2;
|
|
backtrack = table_read_ensure(table, rule_offset, backtrack_count * sizeof(*backtrack));
|
|
rule_offset += backtrack_count * sizeof(*backtrack);
|
|
|
|
if (!(input_count = table_read_be_word(table, rule_offset)))
|
|
continue;
|
|
|
|
rule_offset += 2;
|
|
input = table_read_ensure(table, rule_offset, (input_count - 1) * sizeof(*input));
|
|
rule_offset += (input_count - 1) * sizeof(*input);
|
|
|
|
lookahead_count = table_read_be_word(table, rule_offset);
|
|
rule_offset += 2;
|
|
lookahead = table_read_ensure(table, rule_offset, lookahead_count * sizeof(*lookahead));
|
|
rule_offset += lookahead_count * sizeof(*lookahead);
|
|
|
|
lookup_count = table_read_be_word(table, rule_offset);
|
|
rule_offset += 2;
|
|
lookup_records = table_read_ensure(table, rule_offset, lookup_count * 2 * sizeof(*lookup_records));
|
|
|
|
/* First applicable rule is used. */
|
|
if (opentype_layout_apply_chain_context_match(backtrack_count, backtrack, input_count, input, lookahead_count,
|
|
lookahead, lookup_count, lookup_records, mc))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_context_match(unsigned int input_count, const UINT16 *input, unsigned int lookup_count,
|
|
const UINT16 *lookup_records, const struct match_context *mc)
|
|
{
|
|
unsigned int match_positions[GLYPH_CONTEXT_MAX_LENGTH];
|
|
unsigned int match_length = 0;
|
|
|
|
return opentype_layout_context_match_input(mc, input_count, input, &match_length, match_positions) &&
|
|
opentype_layout_context_apply_lookup(mc->context, input_count, match_positions, lookup_count,
|
|
lookup_records, match_length);
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_rule_set(const struct match_context *mc, unsigned int offset)
|
|
{
|
|
unsigned int input_count, lookup_count;
|
|
const struct dwrite_fonttable *table = &mc->context->table->table;
|
|
const UINT16 *input, *lookup_records;
|
|
const struct ot_gsubgpos_ruleset *ruleset;
|
|
unsigned int i, count;
|
|
|
|
count = table_read_be_word(table, offset);
|
|
ruleset = table_read_ensure(table, offset, count * sizeof(ruleset->offsets));
|
|
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
unsigned int rule_offset = offset + GET_BE_WORD(ruleset->offsets[i]);
|
|
|
|
if (!(input_count = table_read_be_word(table, rule_offset)))
|
|
continue;
|
|
rule_offset += 2;
|
|
|
|
if (!(lookup_count = table_read_be_word(table, rule_offset)))
|
|
continue;
|
|
rule_offset += 2;
|
|
|
|
if (!(input = table_read_ensure(table, rule_offset, (input_count - 1) * sizeof(*input))))
|
|
continue;
|
|
rule_offset += (input_count - 1) * sizeof(*input);
|
|
|
|
if (!(lookup_records = table_read_ensure(table, rule_offset, lookup_count * 2 * sizeof(*lookup_records))))
|
|
continue;
|
|
|
|
/* First applicable rule is used. */
|
|
if (opentype_layout_apply_context_match(input_count, input, lookup_count, lookup_records, mc))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_context(struct scriptshaping_context *context, const struct lookup *lookup,
|
|
unsigned int subtable_offset)
|
|
{
|
|
struct match_context mc = { .context = context, .lookup = lookup };
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
unsigned int coverage_index = GLYPH_NOT_COVERED, count, offset;
|
|
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_gsubgpos_context_format1, coverage));
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(table, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
count = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsubgpos_context_format1, ruleset_count));
|
|
if (coverage_index >= count)
|
|
return FALSE;
|
|
|
|
offset = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsubgpos_context_format1,
|
|
rulesets[coverage_index]));
|
|
offset += subtable_offset;
|
|
|
|
mc.match_func = opentype_match_glyph_func;
|
|
|
|
ret = opentype_layout_apply_rule_set(&mc, offset);
|
|
}
|
|
else if (format == 2)
|
|
{
|
|
unsigned int input_classdef, rule_set_idx;
|
|
|
|
offset = subtable_offset + 2 /* format */;
|
|
|
|
coverage = table_read_be_word(table, offset);
|
|
offset += 2;
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(table, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
input_classdef = table_read_be_word(table, offset) + subtable_offset;
|
|
offset += 2;
|
|
|
|
count = table_read_be_word(table, offset);
|
|
offset+= 2;
|
|
|
|
rule_set_idx = opentype_layout_get_glyph_class(table, input_classdef, glyph);
|
|
if (rule_set_idx >= count)
|
|
return FALSE;
|
|
|
|
offset = table_read_be_word(table, offset + rule_set_idx * 2);
|
|
offset += subtable_offset;
|
|
|
|
mc.input_offset = input_classdef;
|
|
mc.match_func = opentype_match_class_func;
|
|
|
|
ret = opentype_layout_apply_rule_set(&mc, offset);
|
|
}
|
|
else if (format == 3)
|
|
{
|
|
unsigned int input_count, lookup_count;
|
|
const UINT16 *input, *lookup_records;
|
|
|
|
offset = subtable_offset + 2 /* format */;
|
|
|
|
input_count = table_read_be_word(table, offset);
|
|
offset += 2;
|
|
|
|
if (!input_count)
|
|
return FALSE;
|
|
|
|
lookup_count = table_read_be_word(table, offset);
|
|
offset += 2;
|
|
|
|
if (!(input = table_read_ensure(table, offset, sizeof(*input) * input_count)))
|
|
return FALSE;
|
|
offset += sizeof(*input) * input_count;
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(table, subtable_offset + GET_BE_WORD(input[0]), glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
lookup_records = table_read_ensure(table, offset, lookup_count * 2 * sizeof(*lookup_records));
|
|
|
|
mc.input_offset = subtable_offset;
|
|
mc.match_func = opentype_match_coverage_func;
|
|
|
|
ret = opentype_layout_apply_context_match(input_count, input + 1, lookup_count, lookup_records, &mc);
|
|
}
|
|
else
|
|
WARN("Unknown contextual substitution format %u.\n", format);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_chain_context(struct scriptshaping_context *context, const struct lookup *lookup,
|
|
unsigned int subtable_offset)
|
|
{
|
|
struct match_context mc = { .context = context, .lookup = lookup };
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
unsigned int coverage_index = GLYPH_NOT_COVERED, count, offset;
|
|
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_gsubgpos_context_format1, coverage));
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(table, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
count = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsubgpos_context_format1, ruleset_count));
|
|
if (coverage_index >= count)
|
|
return FALSE;
|
|
|
|
offset = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsubgpos_context_format1,
|
|
rulesets[coverage_index]));
|
|
offset += subtable_offset;
|
|
|
|
mc.match_func = opentype_match_glyph_func;
|
|
|
|
ret = opentype_layout_apply_chain_rule_set(&mc, offset);
|
|
}
|
|
else if (format == 2)
|
|
{
|
|
unsigned int backtrack_classdef, input_classdef, lookahead_classdef, rule_set_idx;
|
|
|
|
offset = subtable_offset + 2 /* format */;
|
|
|
|
coverage = table_read_be_word(table, offset);
|
|
offset += 2;
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(table, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
backtrack_classdef = table_read_be_word(table, offset) + subtable_offset;
|
|
offset += 2;
|
|
|
|
input_classdef = table_read_be_word(table, offset) + subtable_offset;
|
|
offset += 2;
|
|
|
|
lookahead_classdef = table_read_be_word(table, offset) + subtable_offset;
|
|
offset += 2;
|
|
|
|
count = table_read_be_word(table, offset);
|
|
offset+= 2;
|
|
|
|
rule_set_idx = opentype_layout_get_glyph_class(table, input_classdef, glyph);
|
|
if (rule_set_idx >= count)
|
|
return FALSE;
|
|
|
|
offset = table_read_be_word(table, offset + rule_set_idx * 2);
|
|
offset += subtable_offset;
|
|
|
|
mc.backtrack_offset = backtrack_classdef;
|
|
mc.input_offset = input_classdef;
|
|
mc.lookahead_offset = lookahead_classdef;
|
|
mc.match_func = opentype_match_class_func;
|
|
|
|
ret = opentype_layout_apply_chain_rule_set(&mc, offset);
|
|
}
|
|
else if (format == 3)
|
|
{
|
|
unsigned int backtrack_count, input_count, lookahead_count, lookup_count;
|
|
const UINT16 *backtrack, *lookahead, *input, *lookup_records;
|
|
|
|
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(table, subtable_offset + GET_BE_WORD(input[0]), glyph);
|
|
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
mc.backtrack_offset = subtable_offset;
|
|
mc.input_offset = subtable_offset;
|
|
mc.lookahead_offset = subtable_offset;
|
|
mc.match_func = opentype_match_coverage_func;
|
|
|
|
ret = opentype_layout_apply_chain_context_match(backtrack_count, backtrack, input_count, input + 1, lookahead_count,
|
|
lookahead, lookup_count, lookup_records, &mc);
|
|
}
|
|
else
|
|
WARN("Unknown chaining contextual substitution format %u.\n", format);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static BOOL opentype_layout_apply_gsub_reverse_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 offset = subtable_offset;
|
|
UINT16 glyph, format;
|
|
|
|
if (context->nesting_level_left != SHAPE_MAX_NESTING_LEVEL)
|
|
return FALSE;
|
|
|
|
glyph = context->u.subst.glyphs[context->cur];
|
|
|
|
format = table_read_be_word(table, offset);
|
|
offset += 2;
|
|
|
|
if (format == 1)
|
|
{
|
|
struct match_context mc = { .context = context, .lookup = lookup };
|
|
unsigned int start_index = 0, end_index = 0, backtrack_count, lookahead_count;
|
|
unsigned int coverage, coverage_index;
|
|
const UINT16 *backtrack, *lookahead;
|
|
|
|
coverage = table_read_be_word(table, offset);
|
|
offset += 2;
|
|
|
|
coverage_index = opentype_layout_is_glyph_covered(table, subtable_offset + coverage, glyph);
|
|
if (coverage_index == GLYPH_NOT_COVERED)
|
|
return FALSE;
|
|
|
|
backtrack_count = table_read_be_word(table, offset);
|
|
offset += 2;
|
|
|
|
backtrack = table_read_ensure(table, offset, sizeof(*backtrack) * backtrack_count);
|
|
offset += sizeof(*backtrack) * backtrack_count;
|
|
|
|
lookahead_count = table_read_be_word(table, offset);
|
|
offset += 2;
|
|
|
|
lookahead = table_read_ensure(table, offset, sizeof(*lookahead) * lookahead_count);
|
|
offset += sizeof(*lookahead) * lookahead_count;
|
|
|
|
mc.match_func = opentype_match_coverage_func;
|
|
mc.backtrack_offset = subtable_offset;
|
|
mc.lookahead_offset = subtable_offset;
|
|
|
|
if (opentype_layout_context_match_backtrack(&mc, backtrack_count, backtrack, &start_index) &&
|
|
opentype_layout_context_match_lookahead(&mc, lookahead_count, lookahead, 1, &end_index))
|
|
{
|
|
unsigned int glyph_count = table_read_be_word(table, offset);
|
|
if (coverage_index >= glyph_count)
|
|
return FALSE;
|
|
offset += 2;
|
|
|
|
glyph = table_read_be_word(table, offset + coverage_index * sizeof(glyph));
|
|
opentype_layout_replace_glyph(context, glyph);
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
WARN("Unknown reverse chaining contextual substitution format %u.\n", format);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
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, i, &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_CONTEXTUAL_SUBST:
|
|
ret = opentype_layout_apply_context(context, lookup, subtable_offset);
|
|
break;
|
|
case GSUB_LOOKUP_CHAINING_CONTEXTUAL_SUBST:
|
|
ret = opentype_layout_apply_chain_context(context, lookup, subtable_offset);
|
|
break;
|
|
case GSUB_LOOKUP_REVERSE_CHAINING_CONTEXTUAL_SUBST:
|
|
ret = opentype_layout_apply_gsub_reverse_chain_context_substitution(context, lookup, subtable_offset);
|
|
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_zero_width(unsigned int codepoint)
|
|
{
|
|
return codepoint == 0x34f || codepoint == 0x61c || codepoint == 0xfeff ||
|
|
(codepoint >= 0x180b && codepoint <= 0x180e) || (codepoint >= 0x200b && codepoint <= 0x200f);
|
|
}
|
|
|
|
/*
|
|
* 00AD # Cf SOFT HYPHEN
|
|
* 034F # Mn COMBINING GRAPHEME JOINER
|
|
* 061C # Cf ARABIC LETTER MARK
|
|
* 115F..1160 # Lo [2] HANGUL CHOSEONG FILLER..HANGUL JUNGSEONG FILLER
|
|
* 17B4..17B5 # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA
|
|
* 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
|
|
* 202A..202E # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
|
|
* 2060..2064 # Cf [5] WORD JOINER..INVISIBLE PLUS
|
|
* 2065 # Cn <reserved-2065>
|
|
* 2066..206F # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES
|
|
* 3164 # Lo HANGUL FILLER
|
|
* FE00..FE0F # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16
|
|
* FEFF # Cf ZERO WIDTH NO-BREAK SPACE
|
|
* FFA0 # Lo HALFWIDTH HANGUL FILLER
|
|
* FFF0..FFF8 # Cn [9] <reserved-FFF0>..<reserved-FFF8>
|
|
* 1BCA0..1BCA3 # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP
|
|
* 1D173..1D17A # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE
|
|
* E0000 # Cn <reserved-E0000>
|
|
* E0001 # Cf LANGUAGE TAG
|
|
* E0002..E001F # Cn [30] <reserved-E0002>..<reserved-E001F>
|
|
* E0020..E007F # Cf [96] TAG SPACE..CANCEL TAG
|
|
* E0080..E00FF # Cn [128] <reserved-E0080>..<reserved-E00FF>
|
|
* E0100..E01EF # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256
|
|
* E01F0..E0FFF # Cn [3600] <reserved-E01F0>..<reserved-E0FFF>
|
|
*/
|
|
static unsigned int opentype_is_default_ignorable(unsigned int codepoint)
|
|
{
|
|
if (codepoint < 0x80) return 0;
|
|
return codepoint == 0xad ||
|
|
codepoint == 0x34f ||
|
|
codepoint == 0x61c ||
|
|
(codepoint >= 0x17b4 && codepoint <= 0x17b5) ||
|
|
(codepoint >= 0x180b && codepoint <= 0x180e) ||
|
|
(codepoint >= 0x200b && codepoint <= 0x200f) ||
|
|
(codepoint >= 0x202a && codepoint <= 0x202e) ||
|
|
(codepoint >= 0x2060 && codepoint <= 0x206f) ||
|
|
(codepoint >= 0xfe00 && codepoint <= 0xfe0f) ||
|
|
codepoint == 0xfeff ||
|
|
(codepoint >= 0xfff0 && codepoint <= 0xfff8) ||
|
|
(codepoint >= 0x1d173 && codepoint <= 0x1d17a) ||
|
|
(codepoint >= 0xe0000 && codepoint <= 0xe0fff);
|
|
}
|
|
|
|
static unsigned int opentype_is_diacritic(unsigned int codepoint)
|
|
{
|
|
WCHAR ch = codepoint;
|
|
WORD type = 0;
|
|
/* Ignore higher planes for now. */
|
|
if (codepoint > 0xffff) return 0;
|
|
GetStringTypeW(CT_CTYPE3, &ch, 1, &type);
|
|
return !!(type & C3_DIACRITIC);
|
|
}
|
|
|
|
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;
|
|
unsigned int i, g, c, codepoint, cluster_start_idx = 0;
|
|
UINT16 *clustermap = context->u.subst.clustermap;
|
|
const WCHAR *text = context->text;
|
|
BOOL bmp;
|
|
|
|
memset(context->u.subst.glyph_props, 0, context->u.subst.max_glyph_count * sizeof(*context->u.subst.glyph_props));
|
|
memset(context->u.buffer.text_props, 0, context->length * sizeof(*context->u.buffer.text_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;
|
|
}
|
|
|
|
/* Glyph availability is not tested for a replacement digit. */
|
|
if (*context->u.subst.digits && codepoint >= '0' && codepoint <= '9')
|
|
codepoint = context->u.subst.digits[codepoint - '0'];
|
|
|
|
context->glyph_infos[g].codepoint = codepoint;
|
|
context->u.buffer.glyphs[g] = font->get_glyph(context->cache->context, codepoint);
|
|
context->u.buffer.glyph_props[g].justification = SCRIPT_JUSTIFY_CHARACTER;
|
|
opentype_set_subst_glyph_props(context, g);
|
|
if (opentype_is_default_ignorable(codepoint))
|
|
{
|
|
context->glyph_infos[g].props |= GLYPH_PROP_IGNORABLE;
|
|
if (codepoint == 0x200d)
|
|
context->glyph_infos[g].props |= GLYPH_PROP_ZWJ;
|
|
else if (codepoint == 0x200c)
|
|
context->glyph_infos[g].props |= GLYPH_PROP_ZWNJ;
|
|
/* Mongolian FVSs, TAGs, COMBINING GRAPHEME JOINER */
|
|
else if ((codepoint >= 0x180b && codepoint <= 0x180d) ||
|
|
(codepoint >= 0xe0020 && codepoint <= 0xe007f) ||
|
|
codepoint == 0x34f)
|
|
{
|
|
context->glyph_infos[g].props |= GLYPH_PROP_HIDDEN;
|
|
}
|
|
}
|
|
|
|
/* Group diacritics with preceding base. Glyph class is ignored here. */
|
|
if (!g || !opentype_is_diacritic(codepoint))
|
|
{
|
|
context->u.buffer.glyph_props[g].isClusterStart = 1;
|
|
context->glyph_infos[g].start_text_idx = i;
|
|
cluster_start_idx = g;
|
|
}
|
|
if (opentype_is_zero_width(codepoint))
|
|
context->u.buffer.glyph_props[g].isZeroWidthSpace = 1;
|
|
|
|
context->u.buffer.glyph_props[g].components = 1;
|
|
context->glyph_count++;
|
|
|
|
/* Set initial cluster map here, it's used for setting user features masks. */
|
|
clustermap[i] = cluster_start_idx;
|
|
if (bmp)
|
|
context->u.buffer.text_props[i].canBreakShapingAfter = 1;
|
|
else
|
|
{
|
|
clustermap[i + 1] = cluster_start_idx;
|
|
context->u.buffer.text_props[i + 1].canBreakShapingAfter = 1;
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
static BOOL opentype_is_gsub_lookup_reversed(const struct scriptshaping_context *context, const struct lookup *lookup)
|
|
{
|
|
unsigned int lookup_type;
|
|
|
|
opentype_layout_get_gsubgpos_subtable(context, lookup, 0, &lookup_type);
|
|
return lookup_type == GSUB_LOOKUP_REVERSE_CHAINING_CONTEXTUAL_SUBST;
|
|
}
|
|
|
|
static void opentype_layout_apply_gsub_context_lookup(struct scriptshaping_context *context, unsigned int lookup_index)
|
|
{
|
|
struct lookup lookup = { 0 };
|
|
if (opentype_layout_init_lookup(context->table, lookup_index, NULL, &lookup))
|
|
opentype_layout_apply_gsub_lookup(context, &lookup);
|
|
}
|
|
|
|
void opentype_layout_apply_gsub_features(struct scriptshaping_context *context, unsigned int script_index,
|
|
unsigned int language_index, struct shaping_features *features)
|
|
{
|
|
struct lookups lookups = { 0 };
|
|
unsigned int i = 0, j, start_idx;
|
|
BOOL ret;
|
|
|
|
context->nesting_level_left = SHAPE_MAX_NESTING_LEVEL;
|
|
context->u.buffer.apply_context_lookup = opentype_layout_apply_gsub_context_lookup;
|
|
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 (j = 0; j <= features->stage; ++j)
|
|
{
|
|
for (; i < features->stages[j].last_lookup; ++i)
|
|
{
|
|
const struct lookup *lookup = &lookups.lookups[i];
|
|
|
|
context->lookup_mask = lookup->mask;
|
|
context->auto_zwnj = lookup->auto_zwnj;
|
|
context->auto_zwj = lookup->auto_zwj;
|
|
|
|
if (!opentype_is_gsub_lookup_reversed(context, lookup))
|
|
{
|
|
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, context->cur, lookup->flags))
|
|
{
|
|
ret = opentype_layout_apply_gsub_lookup(context, lookup);
|
|
}
|
|
|
|
if (!ret)
|
|
context->cur++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
context->cur = context->glyph_count - 1;
|
|
|
|
for (;;)
|
|
{
|
|
if ((context->glyph_infos[context->cur].mask & lookup->mask) &&
|
|
lookup_is_glyph_match(context, context->cur, lookup->flags))
|
|
{
|
|
opentype_layout_apply_gsub_lookup(context, lookup);
|
|
}
|
|
|
|
if (context->cur == 0) break;
|
|
--context->cur;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (features->stages[j].func)
|
|
features->stages[j].func(context, features);
|
|
}
|
|
|
|
/* For every glyph range of [<last>.isClusterStart, <next>.isClusterStart) set corresponding
|
|
text span to start_idx. */
|
|
start_idx = 0;
|
|
for (i = 1; i < context->glyph_count; ++i)
|
|
{
|
|
if (context->u.buffer.glyph_props[i].isClusterStart)
|
|
{
|
|
unsigned int start_text, end_text;
|
|
|
|
start_text = context->glyph_infos[start_idx].start_text_idx;
|
|
end_text = context->glyph_infos[i].start_text_idx;
|
|
|
|
for (j = start_text; j < end_text; ++j)
|
|
context->u.buffer.clustermap[j] = start_idx;
|
|
|
|
start_idx = i;
|
|
}
|
|
}
|
|
|
|
/* Fill the tail. */
|
|
for (j = context->glyph_infos[start_idx].start_text_idx; j < context->length; ++j)
|
|
context->u.buffer.clustermap[j] = start_idx;
|
|
|
|
free(lookups.lookups);
|
|
}
|
|
|
|
static BOOL opentype_layout_contextual_lookup_is_glyph_covered(struct scriptshaping_context *context, UINT16 glyph,
|
|
unsigned int subtable_offset, unsigned int coverage, unsigned int format)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
const UINT16 *offsets;
|
|
unsigned int count;
|
|
|
|
if (format == 1 || format == 2)
|
|
{
|
|
if (opentype_layout_is_glyph_covered(table, subtable_offset + coverage, glyph) != GLYPH_NOT_COVERED)
|
|
return TRUE;
|
|
}
|
|
else if (format == 3)
|
|
{
|
|
count = table_read_be_word(table, subtable_offset + 2);
|
|
if (!count || !(offsets = table_read_ensure(table, subtable_offset + 6, count * sizeof(*offsets))))
|
|
return FALSE;
|
|
|
|
if (opentype_layout_is_glyph_covered(table, subtable_offset + GET_BE_WORD(offsets[0]), glyph) != GLYPH_NOT_COVERED)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL opentype_layout_chain_contextual_lookup_is_glyph_covered(struct scriptshaping_context *context, UINT16 glyph,
|
|
unsigned int subtable_offset, unsigned int coverage, unsigned int format)
|
|
{
|
|
const struct dwrite_fonttable *table = &context->table->table;
|
|
unsigned int count, backtrack_count;
|
|
const UINT16 *offsets;
|
|
|
|
if (format == 1 || format == 2)
|
|
{
|
|
if (opentype_layout_is_glyph_covered(table, subtable_offset + coverage, glyph) != GLYPH_NOT_COVERED)
|
|
return TRUE;
|
|
}
|
|
else if (format == 3)
|
|
{
|
|
backtrack_count = table_read_be_word(table, subtable_offset + 2);
|
|
|
|
count = table_read_be_word(table, subtable_offset + 4 + backtrack_count * sizeof(*offsets));
|
|
|
|
if (!count || !(offsets = table_read_ensure(table, subtable_offset + 6 + backtrack_count * sizeof(*offsets),
|
|
count * sizeof(*offsets))))
|
|
return FALSE;
|
|
|
|
if (opentype_layout_is_glyph_covered(table, subtable_offset + GET_BE_WORD(offsets[0]), glyph) != GLYPH_NOT_COVERED)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL opentype_layout_gsub_lookup_is_glyph_covered(struct scriptshaping_context *context, UINT16 glyph,
|
|
const struct lookup *lookup)
|
|
{
|
|
const struct dwrite_fonttable *gsub = &context->table->table;
|
|
static const unsigned short gsub_formats[] =
|
|
{
|
|
0, /* Unused */
|
|
1, /* SingleSubst */
|
|
1, /* MultipleSubst */
|
|
1, /* AlternateSubst */
|
|
1, /* LigatureSubst */
|
|
3, /* ContextSubst */
|
|
3, /* ChainContextSubst */
|
|
0, /* Extension, unused */
|
|
1, /* ReverseChainSubst */
|
|
};
|
|
unsigned int i, coverage, lookup_type, format;
|
|
|
|
for (i = 0; i < lookup->subtable_count; ++i)
|
|
{
|
|
unsigned int subtable_offset = opentype_layout_get_gsubgpos_subtable(context, lookup, i, &lookup_type);
|
|
|
|
format = table_read_be_word(gsub, subtable_offset);
|
|
|
|
if (!format || format > ARRAY_SIZE(gsub_formats) || format > gsub_formats[lookup_type])
|
|
break;
|
|
|
|
coverage = table_read_be_word(gsub, subtable_offset + 2);
|
|
|
|
switch (lookup_type)
|
|
{
|
|
case GSUB_LOOKUP_SINGLE_SUBST:
|
|
case GSUB_LOOKUP_MULTIPLE_SUBST:
|
|
case GSUB_LOOKUP_ALTERNATE_SUBST:
|
|
case GSUB_LOOKUP_LIGATURE_SUBST:
|
|
case GSUB_LOOKUP_REVERSE_CHAINING_CONTEXTUAL_SUBST:
|
|
|
|
if (opentype_layout_is_glyph_covered(gsub, subtable_offset + coverage, glyph) != GLYPH_NOT_COVERED)
|
|
return TRUE;
|
|
|
|
break;
|
|
|
|
case GSUB_LOOKUP_CONTEXTUAL_SUBST:
|
|
|
|
if (opentype_layout_contextual_lookup_is_glyph_covered(context, glyph, subtable_offset, coverage, format))
|
|
return TRUE;
|
|
|
|
break;
|
|
|
|
case GSUB_LOOKUP_CHAINING_CONTEXTUAL_SUBST:
|
|
|
|
if (opentype_layout_chain_contextual_lookup_is_glyph_covered(context, glyph, subtable_offset, coverage, format))
|
|
return TRUE;
|
|
|
|
break;
|
|
|
|
default:
|
|
WARN("Unknown lookup type %u.\n", lookup_type);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL opentype_layout_gpos_lookup_is_glyph_covered(struct scriptshaping_context *context, UINT16 glyph,
|
|
const struct lookup *lookup)
|
|
{
|
|
const struct dwrite_fonttable *gpos = &context->table->table;
|
|
static const unsigned short gpos_formats[] =
|
|
{
|
|
0, /* Unused */
|
|
2, /* SinglePos */
|
|
2, /* PairPos */
|
|
1, /* CursivePos */
|
|
1, /* MarkBasePos */
|
|
1, /* MarkLigPos */
|
|
1, /* MarkMarkPos */
|
|
3, /* ContextPos */
|
|
3, /* ChainContextPos */
|
|
0, /* Extension, unused */
|
|
};
|
|
unsigned int i, coverage, lookup_type, format;
|
|
|
|
for (i = 0; i < lookup->subtable_count; ++i)
|
|
{
|
|
unsigned int subtable_offset = opentype_layout_get_gsubgpos_subtable(context, lookup, i, &lookup_type);
|
|
|
|
format = table_read_be_word(gpos, subtable_offset);
|
|
|
|
if (!format || format > ARRAY_SIZE(gpos_formats) || format > gpos_formats[lookup_type])
|
|
break;
|
|
|
|
coverage = table_read_be_word(gpos, subtable_offset + 2);
|
|
|
|
switch (lookup_type)
|
|
{
|
|
case GPOS_LOOKUP_SINGLE_ADJUSTMENT:
|
|
case GPOS_LOOKUP_PAIR_ADJUSTMENT:
|
|
case GPOS_LOOKUP_CURSIVE_ATTACHMENT:
|
|
case GPOS_LOOKUP_MARK_TO_BASE_ATTACHMENT:
|
|
case GPOS_LOOKUP_MARK_TO_LIGATURE_ATTACHMENT:
|
|
case GPOS_LOOKUP_MARK_TO_MARK_ATTACHMENT:
|
|
|
|
if (opentype_layout_is_glyph_covered(gpos, subtable_offset + coverage, glyph) != GLYPH_NOT_COVERED)
|
|
return TRUE;
|
|
|
|
break;
|
|
|
|
case GPOS_LOOKUP_CONTEXTUAL_POSITION:
|
|
|
|
if (opentype_layout_contextual_lookup_is_glyph_covered(context, glyph, subtable_offset, coverage, format))
|
|
return TRUE;
|
|
|
|
break;
|
|
|
|
case GPOS_LOOKUP_CONTEXTUAL_CHAINING_POSITION:
|
|
|
|
if (opentype_layout_chain_contextual_lookup_is_glyph_covered(context, glyph, subtable_offset, coverage, format))
|
|
return TRUE;
|
|
|
|
break;
|
|
|
|
default:
|
|
WARN("Unknown lookup type %u.\n", lookup_type);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
typedef BOOL (*p_lookup_is_glyph_covered_func)(struct scriptshaping_context *context, UINT16 glyph, const struct lookup *lookup);
|
|
|
|
BOOL opentype_layout_check_feature(struct scriptshaping_context *context, unsigned int script_index,
|
|
unsigned int language_index, struct shaping_feature *feature, unsigned int glyph_count,
|
|
const UINT16 *glyphs, UINT8 *feature_applies)
|
|
{
|
|
p_lookup_is_glyph_covered_func func_is_covered;
|
|
struct shaping_features features = { 0 };
|
|
struct lookups lookups = { 0 };
|
|
BOOL ret = FALSE, is_covered;
|
|
unsigned int i, j, applies;
|
|
|
|
features.features = feature;
|
|
features.count = 1;
|
|
|
|
for (i = 0; i < context->glyph_count; ++i)
|
|
opentype_set_glyph_props(context, i);
|
|
|
|
opentype_layout_collect_lookups(context, script_index, language_index, &features, context->table, &lookups);
|
|
|
|
func_is_covered = opentype_layout_is_subst_context(context) ? opentype_layout_gsub_lookup_is_glyph_covered :
|
|
opentype_layout_gpos_lookup_is_glyph_covered;
|
|
|
|
for (i = 0; i < lookups.count; ++i)
|
|
{
|
|
struct lookup *lookup = &lookups.lookups[i];
|
|
|
|
applies = 0;
|
|
for (j = 0; j < context->glyph_count; ++j)
|
|
{
|
|
if (lookup_is_glyph_match(context, j, lookup->flags))
|
|
{
|
|
if ((is_covered = func_is_covered(context, glyphs[i], lookup)))
|
|
++applies;
|
|
feature_applies[j] |= is_covered;
|
|
}
|
|
}
|
|
|
|
if ((ret = (applies == context->glyph_count)))
|
|
break;
|
|
}
|
|
|
|
free(lookups.lookups);
|
|
|
|
return ret;
|
|
}
|
|
|
|
BOOL opentype_has_vertical_variants(struct dwrite_fontface *fontface)
|
|
{
|
|
unsigned int i, j, count = 0, lookup_type, subtable_offset;
|
|
struct shaping_features features = { 0 };
|
|
struct shaping_feature vert_feature = { 0 };
|
|
struct scriptshaping_context context = { 0 };
|
|
struct lookups lookups = { 0 };
|
|
UINT16 format;
|
|
|
|
if (fontface->flags & (FONTFACE_VERTICAL_VARIANTS | FONTFACE_NO_VERTICAL_VARIANTS))
|
|
return !!(fontface->flags & FONTFACE_VERTICAL_VARIANTS);
|
|
|
|
context.cache = fontface_get_shaping_cache(fontface);
|
|
context.table = &context.cache->gsub;
|
|
|
|
vert_feature.tag = DWRITE_MAKE_OPENTYPE_TAG('v','e','r','t');
|
|
vert_feature.flags = FEATURE_GLOBAL | FEATURE_GLOBAL_SEARCH;
|
|
vert_feature.max_value = 1;
|
|
vert_feature.default_value = 1;
|
|
|
|
features.features = &vert_feature;
|
|
features.count = features.capacity = 1;
|
|
|
|
opentype_layout_collect_lookups(&context, ~0u, ~0u, &features, context.table, &lookups);
|
|
|
|
for (i = 0; i < lookups.count && !count; ++i)
|
|
{
|
|
const struct dwrite_fonttable *table = &context.table->table;
|
|
const struct lookup *lookup = &lookups.lookups[i];
|
|
|
|
for (j = 0; j < lookup->subtable_count && !count; ++j)
|
|
{
|
|
subtable_offset = opentype_layout_get_gsubgpos_subtable(&context, lookup, j, &lookup_type);
|
|
|
|
if (lookup_type != GSUB_LOOKUP_SINGLE_SUBST)
|
|
continue;
|
|
|
|
format = table_read_be_word(table, subtable_offset);
|
|
|
|
if (format == 1)
|
|
{
|
|
count = 1;
|
|
}
|
|
else if (format == 2)
|
|
{
|
|
count = table_read_be_word(table, subtable_offset + FIELD_OFFSET(struct ot_gsub_singlesubst_format2, count));
|
|
}
|
|
else
|
|
WARN("Unrecognized single substitution format %u.\n", format);
|
|
}
|
|
}
|
|
|
|
free(lookups.lookups);
|
|
|
|
if (count)
|
|
fontface->flags |= FONTFACE_VERTICAL_VARIANTS;
|
|
else
|
|
fontface->flags |= FONTFACE_NO_VERTICAL_VARIANTS;
|
|
|
|
return !!(fontface->flags & FONTFACE_VERTICAL_VARIANTS);
|
|
}
|
|
|
|
HRESULT opentype_get_vertical_glyph_variants(struct dwrite_fontface *fontface, unsigned int glyph_count,
|
|
const UINT16 *nominal_glyphs, UINT16 *glyphs)
|
|
{
|
|
struct shaping_features features = { 0 };
|
|
struct shaping_feature vert_feature = { 0 };
|
|
struct scriptshaping_context context = { 0 };
|
|
struct lookups lookups = { 0 };
|
|
unsigned int i;
|
|
|
|
memcpy(glyphs, nominal_glyphs, glyph_count * sizeof(*glyphs));
|
|
|
|
if (!opentype_has_vertical_variants(fontface))
|
|
return S_OK;
|
|
|
|
context.cache = fontface_get_shaping_cache(fontface);
|
|
context.u.subst.glyphs = glyphs;
|
|
context.u.subst.glyph_props = calloc(glyph_count, sizeof(*context.u.subst.glyph_props));
|
|
context.u.subst.max_glyph_count = glyph_count;
|
|
context.u.subst.capacity = glyph_count;
|
|
context.glyph_infos = calloc(glyph_count, sizeof(*context.glyph_infos));
|
|
context.table = &context.cache->gsub;
|
|
|
|
vert_feature.tag = DWRITE_MAKE_OPENTYPE_TAG('v','e','r','t');
|
|
vert_feature.flags = FEATURE_GLOBAL | FEATURE_GLOBAL_SEARCH;
|
|
vert_feature.max_value = 1;
|
|
vert_feature.default_value = 1;
|
|
|
|
features.features = &vert_feature;
|
|
features.count = features.capacity = 1;
|
|
|
|
opentype_layout_collect_lookups(&context, ~0u, ~0u, &features, context.table, &lookups);
|
|
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)
|
|
{
|
|
BOOL ret = FALSE;
|
|
|
|
if (lookup_is_glyph_match(&context, context.cur, lookup->flags))
|
|
ret = opentype_layout_apply_gsub_lookup(&context, lookup);
|
|
|
|
if (!ret)
|
|
context.cur++;
|
|
}
|
|
}
|
|
|
|
free(context.u.subst.glyph_props);
|
|
free(context.glyph_infos);
|
|
free(lookups.lookups);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
BOOL opentype_has_kerning_pairs(struct dwrite_fontface *fontface)
|
|
{
|
|
const struct kern_subtable_header *subtable;
|
|
struct file_stream_desc stream_desc;
|
|
const struct kern_header *header;
|
|
unsigned int offset, count, i;
|
|
|
|
if (fontface->flags & (FONTFACE_KERNING_PAIRS | FONTFACE_NO_KERNING_PAIRS))
|
|
return !!(fontface->flags & FONTFACE_KERNING_PAIRS);
|
|
|
|
fontface->flags |= FONTFACE_NO_KERNING_PAIRS;
|
|
|
|
stream_desc.stream = fontface->stream;
|
|
stream_desc.face_type = fontface->type;
|
|
stream_desc.face_index = fontface->index;
|
|
|
|
opentype_get_font_table(&stream_desc, MS_KERN_TAG, &fontface->kern);
|
|
if (fontface->kern.exists)
|
|
{
|
|
if ((header = table_read_ensure(&fontface->kern, 0, sizeof(*header))))
|
|
{
|
|
count = GET_BE_WORD(header->table_count);
|
|
offset = sizeof(*header);
|
|
|
|
/* FreeType limits table count this way. */
|
|
count = min(count, 32);
|
|
|
|
/* Check for presence of format 0 subtable with horizontal coverage. */
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
if (!(subtable = table_read_ensure(&fontface->kern, offset, sizeof(*subtable))))
|
|
break;
|
|
|
|
if (subtable->version == 0 && GET_BE_WORD(subtable->coverage) & 1)
|
|
{
|
|
fontface->flags &= ~FONTFACE_NO_KERNING_PAIRS;
|
|
fontface->flags |= FONTFACE_KERNING_PAIRS;
|
|
break;
|
|
}
|
|
|
|
offset += GET_BE_WORD(subtable->length);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fontface->flags & FONTFACE_NO_KERNING_PAIRS && fontface->kern.data)
|
|
IDWriteFontFileStream_ReleaseFileFragment(stream_desc.stream, fontface->kern.context);
|
|
|
|
return !!(fontface->flags & FONTFACE_KERNING_PAIRS);
|
|
}
|
|
|
|
struct kern_format0_compare_key
|
|
{
|
|
UINT16 left;
|
|
UINT16 right;
|
|
};
|
|
|
|
static int __cdecl kern_format0_compare(const void *a, const void *b)
|
|
{
|
|
const struct kern_format0_compare_key *key = a;
|
|
const WORD *data = b;
|
|
UINT16 left = GET_BE_WORD(data[0]), right = GET_BE_WORD(data[1]);
|
|
int ret;
|
|
|
|
if ((ret = (int)key->left - (int)left)) return ret;
|
|
if ((ret = (int)key->right - (int)right)) return ret;
|
|
return 0;
|
|
}
|
|
|
|
HRESULT opentype_get_kerning_pairs(struct dwrite_fontface *fontface, unsigned int count,
|
|
const UINT16 *glyphs, INT32 *values)
|
|
{
|
|
const struct kern_subtable_header *subtable;
|
|
unsigned int i, s, offset, pair_count, subtable_count;
|
|
struct kern_format0_compare_key key;
|
|
const struct kern_header *header;
|
|
const WORD *data;
|
|
|
|
if (!opentype_has_kerning_pairs(fontface))
|
|
{
|
|
memset(values, 0, count * sizeof(*values));
|
|
return S_OK;
|
|
}
|
|
|
|
subtable_count = table_read_be_word(&fontface->kern, 2);
|
|
subtable_count = min(subtable_count, 32);
|
|
|
|
for (i = 0; i < count - 1; ++i)
|
|
{
|
|
offset = sizeof(*header);
|
|
|
|
key.left = glyphs[i];
|
|
key.right = glyphs[i + 1];
|
|
values[i] = 0;
|
|
|
|
for (s = 0; s < subtable_count; ++s)
|
|
{
|
|
if (!(subtable = table_read_ensure(&fontface->kern, offset, sizeof(*subtable))))
|
|
break;
|
|
|
|
if (subtable->version == 0 && GET_BE_WORD(subtable->coverage) & 1)
|
|
{
|
|
if ((data = table_read_ensure(&fontface->kern, offset, GET_BE_WORD(subtable->length))))
|
|
{
|
|
/* Skip subtable header */
|
|
data += 3;
|
|
pair_count = GET_BE_WORD(*data);
|
|
data += 4;
|
|
/* Move to pair data */
|
|
if ((data = table_read_ensure(&fontface->kern, offset + 7 * sizeof(*data),
|
|
pair_count * 3 * sizeof(*data))))
|
|
{
|
|
if ((data = bsearch(&key, data, pair_count, 3 * sizeof(*data), kern_format0_compare)))
|
|
{
|
|
values[i] = (short)GET_BE_WORD(data[2]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
offset += GET_BE_WORD(subtable->length);
|
|
}
|
|
}
|
|
values[count - 1] = 0;
|
|
|
|
return S_OK;
|
|
}
|