/*
 *    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"
#include "winternl.h"

WINE_DEFAULT_DEBUG_CHANNEL(dwrite);

#define MS_HEAD_TAG DWRITE_MAKE_OPENTYPE_TAG('h','e','a','d')
#define MS_HHEA_TAG DWRITE_MAKE_OPENTYPE_TAG('h','h','e','a')
#define MS_OTTO_TAG DWRITE_MAKE_OPENTYPE_TAG('O','T','T','O')
#define MS_OS2_TAG  DWRITE_MAKE_OPENTYPE_TAG('O','S','/','2')
#define MS_POST_TAG DWRITE_MAKE_OPENTYPE_TAG('p','o','s','t')
#define MS_TTCF_TAG DWRITE_MAKE_OPENTYPE_TAG('t','t','c','f')
#define MS_GDEF_TAG DWRITE_MAKE_OPENTYPE_TAG('G','D','E','F')
#define MS_GPOS_TAG DWRITE_MAKE_OPENTYPE_TAG('G','P','O','S')
#define MS_GSUB_TAG DWRITE_MAKE_OPENTYPE_TAG('G','S','U','B')
#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_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')

/* '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')

#ifdef WORDS_BIGENDIAN
#define GET_BE_WORD(x) (x)
#define GET_BE_DWORD(x) (x)
#else
#define GET_BE_WORD(x)  RtlUshortByteSwap(x)
#define GET_BE_DWORD(x) RtlUlongByteSwap(x)
#endif

typedef struct {
    CHAR TTCTag[4];
    DWORD Version;
    DWORD numFonts;
    DWORD OffsetTable[1];
} TTC_Header_V1;

typedef struct {
    DWORD version;
    WORD numTables;
    WORD searchRange;
    WORD entrySelector;
    WORD rangeShift;
} TTC_SFNT_V1;

typedef struct {
    DWORD tag;
    DWORD checkSum;
    DWORD offset;
    DWORD length;
} TT_TableRecord;

struct cmap_encoding_record
{
    WORD platformID;
    WORD encodingID;
    DWORD offset;
};

struct cmap_header
{
    WORD version;
    WORD num_tables;
    struct cmap_encoding_record tables[1];
} CMAP_Header;

typedef struct {
    DWORD startCharCode;
    DWORD endCharCode;
    DWORD startGlyphID;
} CMAP_SegmentedCoverage_group;

struct cmap_segmented_coverage
{
    WORD format;
    WORD reserved;
    DWORD length;
    DWORD language;
    DWORD num_groups;
    CMAP_SegmentedCoverage_group groups[1];
};

struct cmap_segment_mapping
{
    WORD format;
    WORD length;
    WORD language;
    WORD seg_count_x2;
    WORD search_range;
    WORD entry_selector;
    WORD range_shift;
    WORD end_code[1];
};

enum OPENTYPE_CMAP_TABLE_FORMAT
{
    OPENTYPE_CMAP_TABLE_SEGMENT_MAPPING = 4,
    OPENTYPE_CMAP_TABLE_SEGMENTED_COVERAGE = 12
};

/* PANOSE is 10 bytes in size, need to pack the structure properly */
#include "pshpack2.h"
typedef struct
{
    USHORT majorVersion;
    USHORT minorVersion;
    ULONG revision;
    ULONG checksumadj;
    ULONG magic;
    USHORT flags;
    USHORT unitsPerEm;
    ULONGLONG created;
    ULONGLONG modified;
    SHORT xMin;
    SHORT yMin;
    SHORT xMax;
    SHORT yMax;
    USHORT macStyle;
    USHORT lowestRecPPEM;
    SHORT direction_hint;
    SHORT index_format;
    SHORT glyphdata_format;
} TT_HEAD;

enum TT_HEAD_MACSTYLE
{
    TT_HEAD_MACSTYLE_BOLD      = 1 << 0,
    TT_HEAD_MACSTYLE_ITALIC    = 1 << 1,
    TT_HEAD_MACSTYLE_UNDERLINE = 1 << 2,
    TT_HEAD_MACSTYLE_OUTLINE   = 1 << 3,
    TT_HEAD_MACSTYLE_SHADOW    = 1 << 4,
    TT_HEAD_MACSTYLE_CONDENSED = 1 << 5,
    TT_HEAD_MACSTYLE_EXTENDED  = 1 << 6,
};

typedef struct
{
    ULONG Version;
    ULONG italicAngle;
    SHORT underlinePosition;
    SHORT underlineThickness;
    ULONG fixed_pitch;
    ULONG minmemType42;
    ULONG maxmemType42;
    ULONG minmemType1;
    ULONG maxmemType1;
} TT_POST;

typedef struct
{
    USHORT version;
    SHORT xAvgCharWidth;
    USHORT usWeightClass;
    USHORT usWidthClass;
    SHORT fsType;
    SHORT ySubscriptXSize;
    SHORT ySubscriptYSize;
    SHORT ySubscriptXOffset;
    SHORT ySubscriptYOffset;
    SHORT ySuperscriptXSize;
    SHORT ySuperscriptYSize;
    SHORT ySuperscriptXOffset;
    SHORT ySuperscriptYOffset;
    SHORT yStrikeoutSize;
    SHORT yStrikeoutPosition;
    SHORT sFamilyClass;
    PANOSE panose;
    ULONG ulUnicodeRange1;
    ULONG ulUnicodeRange2;
    ULONG ulUnicodeRange3;
    ULONG ulUnicodeRange4;
    CHAR achVendID[4];
    USHORT fsSelection;
    USHORT usFirstCharIndex;
    USHORT usLastCharIndex;
    /* According to the Apple spec, original version didn't have the below fields,
     * version numbers were taken from the OpenType spec.
     */
    /* version 0 (TrueType 1.5) */
    USHORT sTypoAscender;
    USHORT sTypoDescender;
    USHORT sTypoLineGap;
    USHORT usWinAscent;
    USHORT usWinDescent;
    /* version 1 (TrueType 1.66) */
    ULONG ulCodePageRange1;
    ULONG ulCodePageRange2;
    /* version 2 (OpenType 1.2) */
    SHORT sxHeight;
    SHORT sCapHeight;
    USHORT usDefaultChar;
    USHORT usBreakChar;
    USHORT usMaxContext;
} TT_OS2_V2;

typedef struct {
    USHORT majorVersion;
    USHORT minorVersion;
    SHORT  ascender;
    SHORT  descender;
    SHORT  linegap;
    USHORT advanceWidthMax;
    SHORT  minLeftSideBearing;
    SHORT  minRightSideBearing;
    SHORT  xMaxExtent;
    SHORT  caretSlopeRise;
    SHORT  caretSlopeRun;
    SHORT  caretOffset;
    SHORT  reserved[4];
    SHORT  metricDataFormat;
    USHORT numberOfHMetrics;
} TT_HHEA;

struct sbix_header
{
    WORD version;
    WORD flags;
    DWORD num_strikes;
    DWORD strike_offset[1];
};

struct sbix_strike
{
    WORD ppem;
    WORD ppi;
    DWORD glyphdata_offsets[1];
};

struct sbix_glyph_data
{
    WORD originOffsetX;
    WORD originOffsetY;
    DWORD graphic_type;
    BYTE data[1];
};

struct maxp
{
    DWORD version;
    WORD num_glyphs;
};

struct cblc_header
{
    WORD major_version;
    WORD minor_version;
    DWORD num_sizes;
};

typedef struct {
    BYTE res[12];
} sbitLineMetrics;

struct cblc_bitmapsize_table
{
    DWORD indexSubTableArrayOffset;
    DWORD indexTablesSize;
    DWORD numberofIndexSubTables;
    DWORD colorRef;
    sbitLineMetrics hori;
    sbitLineMetrics vert;
    WORD startGlyphIndex;
    WORD endGlyphIndex;
    BYTE ppemX;
    BYTE ppemY;
    BYTE bit_depth;
    BYTE flags;
};

struct gasp_range
{
    WORD max_ppem;
    WORD flags;
};

struct gasp_header
{
    WORD version;
    WORD num_ranges;
    struct gasp_range ranges[1];
};

enum OS2_FSSELECTION {
    OS2_FSSELECTION_ITALIC           = 1 << 0,
    OS2_FSSELECTION_UNDERSCORE       = 1 << 1,
    OS2_FSSELECTION_NEGATIVE         = 1 << 2,
    OS2_FSSELECTION_OUTLINED         = 1 << 3,
    OS2_FSSELECTION_STRIKEOUT        = 1 << 4,
    OS2_FSSELECTION_BOLD             = 1 << 5,
    OS2_FSSELECTION_REGULAR          = 1 << 6,
    OS2_FSSELECTION_USE_TYPO_METRICS = 1 << 7,
    OS2_FSSELECTION_WWS              = 1 << 8,
    OS2_FSSELECTION_OBLIQUE          = 1 << 9
};

typedef struct {
    WORD platformID;
    WORD encodingID;
    WORD languageID;
    WORD nameID;
    WORD length;
    WORD offset;
} TT_NameRecord;

typedef struct {
    WORD format;
    WORD count;
    WORD stringOffset;
    TT_NameRecord nameRecord[1];
} TT_NAME_V0;

struct vdmx_header
{
    WORD version;
    WORD num_recs;
    WORD num_ratios;
};

struct vdmx_ratio
{
    BYTE bCharSet;
    BYTE xRatio;
    BYTE yStartRatio;
    BYTE yEndRatio;
};

struct vdmx_vtable
{
    WORD yPelHeight;
    SHORT yMax;
    SHORT yMin;
};

struct vdmx_group
{
    WORD recs;
    BYTE startsz;
    BYTE endsz;
    struct vdmx_vtable entries[1];
};

struct ot_feature_record
{
    DWORD tag;
    WORD offset;
};

struct ot_feature_list
{
    WORD feature_count;
    struct ot_feature_record features[1];
};

struct ot_langsys
{
    WORD lookup_order; /* Reserved */
    WORD required_feature_index;
    WORD feature_count;
    WORD feature_index[1];
};

struct ot_langsys_record
{
    CHAR tag[4];
    WORD langsys;
};

struct ot_script
{
    WORD default_langsys;
    WORD langsys_count;
    struct ot_langsys_record langsys[1];
} OT_Script;

struct ot_script_record
{
    CHAR tag[4];
    WORD script;
};

struct ot_script_list
{
    WORD script_count;
    struct ot_script_record scripts[1];
};

enum ot_gdef_class
{
    GDEF_CLASS_UNCLASSIFIED = 0,
    GDEF_CLASS_BASE = 1,
    GDEF_CLASS_LIGATURE = 2,
    GDEF_CLASS_MARK = 3,
    GDEF_CLASS_COMPONENT = 4,
    GDEF_CLASS_MAX = GDEF_CLASS_COMPONENT,
};

struct gdef_header
{
    DWORD version;
    WORD classdef;
    WORD attach_list;
    WORD ligcaret_list;
    WORD markattach_classdef;
};

struct ot_gdef_classdef_format1
{
    WORD format;
    WORD start_glyph;
    WORD glyph_count;
    WORD classes[1];
};

struct ot_gdef_class_range
{
    WORD start_glyph;
    WORD end_glyph;
    WORD glyph_class;
};

struct ot_gdef_classdef_format2
{
    WORD format;
    WORD range_count;
    struct ot_gdef_class_range ranges[1];
};

struct gpos_gsub_header
{
    DWORD version;
    WORD script_list;
    WORD feature_list;
    WORD lookup_list;
};

enum gsub_gpos_lookup_flags
{
    LOOKUP_FLAG_RTL = 0x1,
    LOOKUP_FLAG_IGNORE_BASE = 0x2,
    LOOKUP_FLAG_IGNORE_LIGATURES = 0x4,
    LOOKUP_FLAG_IGNORE_MARKS = 0x8,

    LOOKUP_FLAG_IGNORE_MASK = 0xe,
};

enum gpos_lookup_type
{
    GPOS_LOOKUP_SINGLE_ADJUSTMENT = 1,
    GPOS_LOOKUP_PAIR_ADJUSTMENT = 2,
    GPOS_LOOKUP_CURSIVE_ATTACHMENT = 3,
    GPOS_LOOKUP_MARK_TO_BASE_ATTACHMENT = 4,
    GPOS_LOOKUP_MARK_TO_LIGATURE_ATTACHMENT = 5,
    GPOS_LOOKUP_MARK_TO_MARK_ATTACHMENT = 6,
    GPOS_LOOKUP_CONTEXTUAL_POSITION = 7,
    GPOS_LOOKUP_CONTEXTUAL_CHAINING_POSITION = 8,
    GPOS_LOOKUP_EXTENSION_POSITION = 9,
};

enum 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_gpos_extensionpos_format1
{
    WORD format;
    WORD lookup_type;
    DWORD extension_offset;
};

struct ot_feature
{
    WORD feature_params;
    WORD lookup_count;
    WORD lookuplist_index[1];
};

struct ot_lookup_list
{
    WORD lookup_count;
    WORD lookup[1];
};

struct ot_lookup_table
{
    WORD lookup_type;
    WORD flags;
    WORD subtable_count;
    WORD subtable[1];
};

#define GLYPH_NOT_COVERED (~0u)

struct ot_coverage_format1
{
    WORD format;
    WORD glyph_count;
    WORD glyphs[1];
};

struct ot_coverage_range
{
    WORD start_glyph;
    WORD end_glyph;
    WORD startcoverage_index;
};

struct ot_coverage_format2
{
    WORD format;
    WORD range_count;
    struct ot_coverage_range ranges[1];
};

struct ot_gpos_device_table
{
    WORD start_size;
    WORD end_size;
    WORD format;
    WORD values[1];
};

struct ot_gpos_singlepos_format1
{
    WORD format;
    WORD coverage;
    WORD value_format;
    WORD value[1];
};

struct ot_gpos_singlepos_format2
{
    WORD format;
    WORD coverage;
    WORD value_format;
    WORD value_count;
    WORD values[1];
};

struct ot_gpos_pairvalue
{
    WORD second_glyph;
    BYTE data[1];
};

struct ot_gpos_pairset
{
    WORD pairvalue_count;
    struct ot_gpos_pairvalue pairvalues[1];
};

struct ot_gpos_pairpos_format1
{
    WORD format;
    WORD coverage;
    WORD value_format1;
    WORD value_format2;
    WORD pairset_count;
    WORD pairsets[1];
};

struct ot_gpos_pairpos_format2
{
    WORD format;
    WORD coverage;
    WORD value_format1;
    WORD value_format2;
    WORD class_def1;
    WORD class_def2;
    WORD class1_count;
    WORD class2_count;
    WORD values[1];
};

struct ot_gpos_anchor_format1
{
    WORD format;
    short x_coord;
    short y_coord;
};

struct ot_gpos_anchor_format2
{
    WORD format;
    short x_coord;
    short y_coord;
    WORD anchor_point;
};

struct ot_gpos_anchor_format3
{
    WORD format;
    short x_coord;
    short y_coord;
    WORD x_dev_offset;
    WORD y_dev_offset;
};

struct ot_gpos_cursive_format1
{
    WORD format;
    WORD coverage;
    WORD count;
    WORD anchors[1];
};

struct ot_gpos_mark_record
{
    WORD mark_class;
    WORD mark_anchor;
};

struct ot_gpos_mark_array
{
    WORD count;
    struct ot_gpos_mark_record records[1];
};

struct ot_gpos_base_array
{
    WORD count;
    WORD offsets[1];
};

struct ot_gpos_mark_to_base_format1
{
    WORD format;
    WORD mark_coverage;
    WORD base_coverage;
    WORD mark_class_count;
    WORD mark_array;
    WORD base_array;
};

struct ot_gpos_mark_to_lig_format1
{
    WORD format;
    WORD mark_coverage;
    WORD lig_coverage;
    WORD mark_class_count;
    WORD mark_array;
    WORD lig_array;
};

struct ot_gpos_mark_to_mark_format1
{
    WORD format;
    WORD mark1_coverage;
    WORD mark2_coverage;
    WORD mark_class_count;
    WORD mark1_array;
    WORD mark2_array;
};

typedef struct {
    WORD SubstFormat;
    WORD Coverage;
    WORD DeltaGlyphID;
} GSUB_SingleSubstFormat1;

typedef struct {
    WORD SubstFormat;
    WORD Coverage;
    WORD GlyphCount;
    WORD Substitute[1];
} GSUB_SingleSubstFormat2;

typedef struct {
    WORD SubstFormat;
    WORD ExtensionLookupType;
    DWORD ExtensionOffset;
} GSUB_ExtensionPosFormat1;

#include "poppack.h"

enum gsub_lookup_type
{
    GSUB_LOOKUP_SINGLE_SUBST = 1,
    GSUB_LOOKUP_EXTENSION_SUBST = 7,
};

enum TT_NAME_WINDOWS_ENCODING_ID
{
    TT_NAME_WINDOWS_ENCODING_SYMBOL = 0,
    TT_NAME_WINDOWS_ENCODING_UCS2,
    TT_NAME_WINDOWS_ENCODING_SJIS,
    TT_NAME_WINDOWS_ENCODING_PRC,
    TT_NAME_WINDOWS_ENCODING_BIG5,
    TT_NAME_WINDOWS_ENCODING_WANSUNG,
    TT_NAME_WINDOWS_ENCODING_JOHAB,
    TT_NAME_WINDOWS_ENCODING_RESERVED1,
    TT_NAME_WINDOWS_ENCODING_RESERVED2,
    TT_NAME_WINDOWS_ENCODING_RESERVED3,
    TT_NAME_WINDOWS_ENCODING_UCS4
};

enum TT_NAME_MAC_ENCODING_ID
{
    TT_NAME_MAC_ENCODING_ROMAN = 0,
    TT_NAME_MAC_ENCODING_JAPANESE,
    TT_NAME_MAC_ENCODING_TRAD_CHINESE,
    TT_NAME_MAC_ENCODING_KOREAN,
    TT_NAME_MAC_ENCODING_ARABIC,
    TT_NAME_MAC_ENCODING_HEBREW,
    TT_NAME_MAC_ENCODING_GREEK,
    TT_NAME_MAC_ENCODING_RUSSIAN,
    TT_NAME_MAC_ENCODING_RSYMBOL,
    TT_NAME_MAC_ENCODING_DEVANAGARI,
    TT_NAME_MAC_ENCODING_GURMUKHI,
    TT_NAME_MAC_ENCODING_GUJARATI,
    TT_NAME_MAC_ENCODING_ORIYA,
    TT_NAME_MAC_ENCODING_BENGALI,
    TT_NAME_MAC_ENCODING_TAMIL,
    TT_NAME_MAC_ENCODING_TELUGU,
    TT_NAME_MAC_ENCODING_KANNADA,
    TT_NAME_MAC_ENCODING_MALAYALAM,
    TT_NAME_MAC_ENCODING_SINHALESE,
    TT_NAME_MAC_ENCODING_BURMESE,
    TT_NAME_MAC_ENCODING_KHMER,
    TT_NAME_MAC_ENCODING_THAI,
    TT_NAME_MAC_ENCODING_LAOTIAN,
    TT_NAME_MAC_ENCODING_GEORGIAN,
    TT_NAME_MAC_ENCODING_ARMENIAN,
    TT_NAME_MAC_ENCODING_SIMPL_CHINESE,
    TT_NAME_MAC_ENCODING_TIBETAN,
    TT_NAME_MAC_ENCODING_MONGOLIAN,
    TT_NAME_MAC_ENCODING_GEEZ,
    TT_NAME_MAC_ENCODING_SLAVIC,
    TT_NAME_MAC_ENCODING_VIETNAMESE,
    TT_NAME_MAC_ENCODING_SINDHI,
    TT_NAME_MAC_ENCODING_UNINTERPRETED
};

enum TT_NAME_MAC_LANGUAGE_ID
{
    TT_NAME_MAC_LANGID_ENGLISH = 0,
    TT_NAME_MAC_LANGID_FRENCH,
    TT_NAME_MAC_LANGID_GERMAN,
    TT_NAME_MAC_LANGID_ITALIAN,
    TT_NAME_MAC_LANGID_DUTCH,
    TT_NAME_MAC_LANGID_SWEDISH,
    TT_NAME_MAC_LANGID_SPANISH,
    TT_NAME_MAC_LANGID_DANISH,
    TT_NAME_MAC_LANGID_PORTUGUESE,
    TT_NAME_MAC_LANGID_NORWEGIAN,
    TT_NAME_MAC_LANGID_HEBREW,
    TT_NAME_MAC_LANGID_JAPANESE,
    TT_NAME_MAC_LANGID_ARABIC,
    TT_NAME_MAC_LANGID_FINNISH,
    TT_NAME_MAC_LANGID_GREEK,
    TT_NAME_MAC_LANGID_ICELANDIC,
    TT_NAME_MAC_LANGID_MALTESE,
    TT_NAME_MAC_LANGID_TURKISH,
    TT_NAME_MAC_LANGID_CROATIAN,
    TT_NAME_MAC_LANGID_TRAD_CHINESE,
    TT_NAME_MAC_LANGID_URDU,
    TT_NAME_MAC_LANGID_HINDI,
    TT_NAME_MAC_LANGID_THAI,
    TT_NAME_MAC_LANGID_KOREAN,
    TT_NAME_MAC_LANGID_LITHUANIAN,
    TT_NAME_MAC_LANGID_POLISH,
    TT_NAME_MAC_LANGID_HUNGARIAN,
    TT_NAME_MAC_LANGID_ESTONIAN,
    TT_NAME_MAC_LANGID_LATVIAN,
    TT_NAME_MAC_LANGID_SAMI,
    TT_NAME_MAC_LANGID_FAROESE,
    TT_NAME_MAC_LANGID_FARSI,
    TT_NAME_MAC_LANGID_RUSSIAN,
    TT_NAME_MAC_LANGID_SIMPL_CHINESE,
    TT_NAME_MAC_LANGID_FLEMISH,
    TT_NAME_MAC_LANGID_GAELIC,
    TT_NAME_MAC_LANGID_ALBANIAN,
    TT_NAME_MAC_LANGID_ROMANIAN,
    TT_NAME_MAC_LANGID_CZECH,
    TT_NAME_MAC_LANGID_SLOVAK,
    TT_NAME_MAC_LANGID_SLOVENIAN,
    TT_NAME_MAC_LANGID_YIDDISH,
    TT_NAME_MAC_LANGID_SERBIAN,
    TT_NAME_MAC_LANGID_MACEDONIAN,
    TT_NAME_MAC_LANGID_BULGARIAN,
    TT_NAME_MAC_LANGID_UKRAINIAN,
    TT_NAME_MAC_LANGID_BYELORUSSIAN,
    TT_NAME_MAC_LANGID_UZBEK,
    TT_NAME_MAC_LANGID_KAZAKH,
    TT_NAME_MAC_LANGID_AZERB_CYR,
    TT_NAME_MAC_LANGID_AZERB_ARABIC,
    TT_NAME_MAC_LANGID_ARMENIAN,
    TT_NAME_MAC_LANGID_GEORGIAN,
    TT_NAME_MAC_LANGID_MOLDAVIAN,
    TT_NAME_MAC_LANGID_KIRGHIZ,
    TT_NAME_MAC_LANGID_TAJIKI,
    TT_NAME_MAC_LANGID_TURKMEN,
    TT_NAME_MAC_LANGID_MONGOLIAN,
    TT_NAME_MAC_LANGID_MONGOLIAN_CYR,
    TT_NAME_MAC_LANGID_PASHTO,
    TT_NAME_MAC_LANGID_KURDISH,
    TT_NAME_MAC_LANGID_KASHMIRI,
    TT_NAME_MAC_LANGID_SINDHI,
    TT_NAME_MAC_LANGID_TIBETAN,
    TT_NAME_MAC_LANGID_NEPALI,
    TT_NAME_MAC_LANGID_SANSKRIT,
    TT_NAME_MAC_LANGID_MARATHI,
    TT_NAME_MAC_LANGID_BENGALI,
    TT_NAME_MAC_LANGID_ASSAMESE,
    TT_NAME_MAC_LANGID_GUJARATI,
    TT_NAME_MAC_LANGID_PUNJABI,
    TT_NAME_MAC_LANGID_ORIYA,
    TT_NAME_MAC_LANGID_MALAYALAM,
    TT_NAME_MAC_LANGID_KANNADA,
    TT_NAME_MAC_LANGID_TAMIL,
    TT_NAME_MAC_LANGID_TELUGU,
    TT_NAME_MAC_LANGID_SINHALESE,
    TT_NAME_MAC_LANGID_BURMESE,
    TT_NAME_MAC_LANGID_KHMER,
    TT_NAME_MAC_LANGID_LAO,
    TT_NAME_MAC_LANGID_VIETNAMESE,
    TT_NAME_MAC_LANGID_INDONESIAN,
    TT_NAME_MAC_LANGID_TAGALOG,
    TT_NAME_MAC_LANGID_MALAY_ROMAN,
    TT_NAME_MAC_LANGID_MALAY_ARABIC,
    TT_NAME_MAC_LANGID_AMHARIC,
    TT_NAME_MAC_LANGID_TIGRINYA,
    TT_NAME_MAC_LANGID_GALLA,
    TT_NAME_MAC_LANGID_SOMALI,
    TT_NAME_MAC_LANGID_SWAHILI,
    TT_NAME_MAC_LANGID_KINYARWANDA,
    TT_NAME_MAC_LANGID_RUNDI,
    TT_NAME_MAC_LANGID_NYANJA,
    TT_NAME_MAC_LANGID_MALAGASY,
    TT_NAME_MAC_LANGID_ESPERANTO,
    TT_NAME_MAC_LANGID_WELSH = 128,
    TT_NAME_MAC_LANGID_BASQUE,
    TT_NAME_MAC_LANGID_CATALAN,
    TT_NAME_MAC_LANGID_LATIN,
    TT_NAME_MAC_LANGID_QUECHUA,
    TT_NAME_MAC_LANGID_GUARANI,
    TT_NAME_MAC_LANGID_AYMARA,
    TT_NAME_MAC_LANGID_TATAR,
    TT_NAME_MAC_LANGID_UIGHUR,
    TT_NAME_MAC_LANGID_DZONGKHA,
    TT_NAME_MAC_LANGID_JAVANESE,
    TT_NAME_MAC_LANGID_SUNDANESE,
    TT_NAME_MAC_LANGID_GALICIAN,
    TT_NAME_MAC_LANGID_AFRIKAANS,
    TT_NAME_MAC_LANGID_BRETON,
    TT_NAME_MAC_LANGID_INUKTITUT,
    TT_NAME_MAC_LANGID_SCOTTISH_GAELIC,
    TT_NAME_MAC_LANGID_MANX_GAELIC,
    TT_NAME_MAC_LANGID_IRISH_GAELIC,
    TT_NAME_MAC_LANGID_TONGAN,
    TT_NAME_MAC_LANGID_GREEK_POLYTONIC,
    TT_NAME_MAC_LANGID_GREENLANDIC,
    TT_NAME_MAC_LANGID_AZER_ROMAN
};

/* Names are indexed with TT_NAME_MAC_LANGUAGE_ID values */
static const char name_mac_langid_to_locale[][10] = {
    "en-US",
    "fr-FR",
    "de-DE",
    "it-IT",
    "nl-NL",
    "sv-SE",
    "es-ES",
    "da-DA",
    "pt-PT",
    "no-NO",
    "he-IL",
    "ja-JP",
    "ar-AR",
    "fi-FI",
    "el-GR",
    "is-IS",
    "mt-MT",
    "tr-TR",
    "hr-HR",
    "zh-HK",
    "ur-PK",
    "hi-IN",
    "th-TH",
    "ko-KR",
    "lt-LT",
    "pl-PL",
    "hu-HU",
    "et-EE",
    "lv-LV",
    "se-NO",
    "fo-FO",
    "fa-IR",
    "ru-RU",
    "zh-CN",
    "nl-BE",
    "gd-GB",
    "sq-AL",
    "ro-RO",
    "cs-CZ",
    "sk-SK",
    "sl-SI",
    "",
    "sr-Latn",
    "mk-MK",
    "bg-BG",
    "uk-UA",
    "be-BY",
    "uz-Latn",
    "kk-KZ",
    "az-Cyrl-AZ",
    "az-AZ",
    "hy-AM",
    "ka-GE",
    "",
    "",
    "tg-TJ",
    "tk-TM",
    "mn-Mong",
    "mn-MN",
    "ps-AF",
    "ku-Arab",
    "",
    "sd-Arab",
    "bo-CN",
    "ne-NP",
    "sa-IN",
    "mr-IN",
    "bn-IN",
    "as-IN",
    "gu-IN",
    "pa-Arab",
    "or-IN",
    "ml-IN",
    "kn-IN",
    "ta-LK",
    "te-IN",
    "si-LK",
    "",
    "km-KH",
    "lo-LA",
    "vi-VN",
    "id-ID",
    "",
    "ms-MY",
    "ms-Arab",
    "am-ET",
    "ti-ET",
    "",
    "",
    "sw-KE",
    "rw-RW",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "cy-GB",
    "eu-ES",
    "ca-ES",
    "",
    "",
    "",
    "",
    "tt-RU",
    "ug-CN",
    "",
    "",
    "",
    "gl-ES",
    "af-ZA",
    "br-FR",
    "iu-Latn-CA",
    "gd-GB",
    "",
    "ga-IE",
    "",
    "",
    "kl-GL",
    "az-Latn"
};

enum OPENTYPE_STRING_ID
{
    OPENTYPE_STRING_COPYRIGHT_NOTICE = 0,
    OPENTYPE_STRING_FAMILY_NAME,
    OPENTYPE_STRING_SUBFAMILY_NAME,
    OPENTYPE_STRING_UNIQUE_IDENTIFIER,
    OPENTYPE_STRING_FULL_FONTNAME,
    OPENTYPE_STRING_VERSION_STRING,
    OPENTYPE_STRING_POSTSCRIPT_FONTNAME,
    OPENTYPE_STRING_TRADEMARK,
    OPENTYPE_STRING_MANUFACTURER,
    OPENTYPE_STRING_DESIGNER,
    OPENTYPE_STRING_DESCRIPTION,
    OPENTYPE_STRING_VENDOR_URL,
    OPENTYPE_STRING_DESIGNER_URL,
    OPENTYPE_STRING_LICENSE_DESCRIPTION,
    OPENTYPE_STRING_LICENSE_INFO_URL,
    OPENTYPE_STRING_RESERVED_ID15,
    OPENTYPE_STRING_PREFERRED_FAMILY_NAME,
    OPENTYPE_STRING_PREFERRED_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_POSTSCRIPT_CID_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_PREFERRED_FAMILY_NAME,
    OPENTYPE_STRING_PREFERRED_SUBFAMILY_NAME,
    OPENTYPE_STRING_SAMPLE_TEXT,
    OPENTYPE_STRING_FULL_FONTNAME,
    OPENTYPE_STRING_POSTSCRIPT_FONTNAME,
    OPENTYPE_STRING_POSTSCRIPT_CID_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;
};

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_get_font_table(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;
}

/**********
 * CMAP
 **********/

static unsigned int opentype_cmap_get_unicode_ranges_count(const struct dwrite_fonttable *cmap)
{
    unsigned int i, num_tables, count = 0;
    const struct cmap_header *header;

    num_tables = table_read_be_word(cmap, FIELD_OFFSET(struct cmap_header, num_tables));
    header = table_read_ensure(cmap, 0, FIELD_OFFSET(struct cmap_header, tables[num_tables]));

    if (!header)
        return 0;

    for (i = 0; i < num_tables; ++i)
    {
        unsigned int format, offset;

        if (GET_BE_WORD(header->tables[i].platformID) != 3)
            continue;

        offset = GET_BE_DWORD(header->tables[i].offset);
        format = table_read_be_word(cmap, offset);

        switch (format)
        {
            case OPENTYPE_CMAP_TABLE_SEGMENT_MAPPING:
            {
                count += table_read_be_word(cmap, offset + FIELD_OFFSET(struct cmap_segment_mapping, seg_count_x2)) / 2;
                break;
            }
            case OPENTYPE_CMAP_TABLE_SEGMENTED_COVERAGE:
            {
                count += table_read_be_dword(cmap, offset + FIELD_OFFSET(struct cmap_segmented_coverage, num_groups));
                break;
            }
            default:
                FIXME("table format %u is not supported.\n", format);
        }
    }

    return count;
}

HRESULT opentype_cmap_get_unicode_ranges(const struct dwrite_fonttable *cmap, unsigned int max_count,
        DWRITE_UNICODE_RANGE *ranges, unsigned int *count)
{
    unsigned int i, num_tables, k = 0;
    const struct cmap_header *header;

    if (!cmap->exists)
        return E_FAIL;

    *count = opentype_cmap_get_unicode_ranges_count(cmap);

    num_tables = table_read_be_word(cmap, FIELD_OFFSET(struct cmap_header, num_tables));
    header = table_read_ensure(cmap, 0, FIELD_OFFSET(struct cmap_header, tables[num_tables]));

    if (!header)
        return S_OK;

    for (i = 0; i < num_tables && k < max_count; ++i)
    {
        unsigned int j, offset, format;

        if (GET_BE_WORD(header->tables[i].platformID) != 3)
            continue;

        offset = GET_BE_DWORD(header->tables[i].offset);

        format = table_read_be_word(cmap, offset);
        switch (format)
        {
            case OPENTYPE_CMAP_TABLE_SEGMENT_MAPPING:
            {
                unsigned int segment_count = table_read_be_word(cmap, offset +
                        FIELD_OFFSET(struct cmap_segment_mapping, seg_count_x2)) / 2;
                const UINT16 *start_code = table_read_ensure(cmap, offset,
                        FIELD_OFFSET(struct cmap_segment_mapping, end_code[segment_count]) +
                        2 /* reservedPad */ +
                        2 * segment_count /* start code array */);
                const UINT16 *end_code = table_read_ensure(cmap, offset,
                        FIELD_OFFSET(struct cmap_segment_mapping, end_code[segment_count]));

                if (!start_code || !end_code)
                    continue;

                for (j = 0; j < segment_count && GET_BE_WORD(end_code[j]) != 0xffff && k < max_count; ++j, ++k)
                {
                    ranges[k].first = GET_BE_WORD(start_code[j]);
                    ranges[k].last = GET_BE_WORD(end_code[j]);
                }
                break;
            }
            case OPENTYPE_CMAP_TABLE_SEGMENTED_COVERAGE:
            {
                unsigned int num_groups = table_read_be_dword(cmap, offset +
                        FIELD_OFFSET(struct cmap_segmented_coverage, num_groups));
                const struct cmap_segmented_coverage *coverage;

                coverage = table_read_ensure(cmap, offset,
                        FIELD_OFFSET(struct cmap_segmented_coverage, groups[num_groups]));

                for (j = 0; j < num_groups && k < max_count; j++, k++)
                {
                    ranges[k].first = GET_BE_DWORD(coverage->groups[j].startCharCode);
                    ranges[k].last = GET_BE_DWORD(coverage->groups[j].endCharCode);
                }
                break;
            }
            default:
                FIXME("table format %u unhandled.\n", format);
        }
    }

    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)
{
    const TT_OS2_V2 *data;
    unsigned int size;
    void *context;

    opentype_get_font_table(stream_desc, MS_OS2_TAG, (const void **)&data, &context, &size, NULL);

    *ascent = *descent = 0;

    if (size >= FIELD_OFFSET(TT_OS2_V2, sTypoLineGap))
    {
        SHORT value = GET_BE_WORD(data->sTypoDescender);
        *ascent = GET_BE_WORD(data->sTypoAscender);
        *descent = value < 0 ? -value : 0;
    }

    if (data)
        IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, context);
}

void opentype_get_font_metrics(struct file_stream_desc *stream_desc, DWRITE_FONT_METRICS1 *metrics, DWRITE_CARET_METRICS *caret)
{
    void *os2_context, *head_context, *post_context, *hhea_context;
    const TT_OS2_V2 *tt_os2;
    const TT_HEAD *tt_head;
    const TT_POST *tt_post;
    const TT_HHEA *tt_hhea;

    memset(metrics, 0, sizeof(*metrics));

    opentype_get_font_table(stream_desc, MS_OS2_TAG,  (const void**)&tt_os2, &os2_context, NULL, NULL);
    opentype_get_font_table(stream_desc, MS_HEAD_TAG, (const void**)&tt_head, &head_context, NULL, NULL);
    opentype_get_font_table(stream_desc, MS_POST_TAG, (const void**)&tt_post, &post_context, NULL, NULL);
    opentype_get_font_table(stream_desc, MS_HHEA_TAG, (const void**)&tt_hhea, &hhea_context, NULL, NULL);

    if (tt_head) {
        metrics->designUnitsPerEm = GET_BE_WORD(tt_head->unitsPerEm);
        metrics->glyphBoxLeft = GET_BE_WORD(tt_head->xMin);
        metrics->glyphBoxTop = GET_BE_WORD(tt_head->yMax);
        metrics->glyphBoxRight = GET_BE_WORD(tt_head->xMax);
        metrics->glyphBoxBottom = GET_BE_WORD(tt_head->yMin);
    }

    if (caret) {
        if (tt_hhea) {
            caret->slopeRise = GET_BE_WORD(tt_hhea->caretSlopeRise);
            caret->slopeRun = GET_BE_WORD(tt_hhea->caretSlopeRun);
            caret->offset = GET_BE_WORD(tt_hhea->caretOffset);
        }
        else {
            caret->slopeRise = 0;
            caret->slopeRun = 0;
            caret->offset = 0;
        }
    }

    if (tt_os2) {
        USHORT version = GET_BE_WORD(tt_os2->version);

        metrics->ascent  = GET_BE_WORD(tt_os2->usWinAscent);
        /* Some fonts have usWinDescent value stored as signed short, which could be wrongly
           interpreted as large unsigned value. */
        metrics->descent = abs((SHORT)GET_BE_WORD(tt_os2->usWinDescent));

        /* line gap is estimated using two sets of ascender/descender values and 'hhea' line gap */
        if (tt_hhea) {
            SHORT descender = (SHORT)GET_BE_WORD(tt_hhea->descender);
            INT32 linegap;

            linegap = GET_BE_WORD(tt_hhea->ascender) + abs(descender) + GET_BE_WORD(tt_hhea->linegap) -
                metrics->ascent - metrics->descent;
            metrics->lineGap = linegap > 0 ? linegap : 0;
        }

        metrics->strikethroughPosition  = GET_BE_WORD(tt_os2->yStrikeoutPosition);
        metrics->strikethroughThickness = GET_BE_WORD(tt_os2->yStrikeoutSize);
        metrics->subscriptPositionX = GET_BE_WORD(tt_os2->ySubscriptXOffset);
        /* Y offset is stored as positive offset below baseline */
        metrics->subscriptPositionY = -GET_BE_WORD(tt_os2->ySubscriptYOffset);
        metrics->subscriptSizeX = GET_BE_WORD(tt_os2->ySubscriptXSize);
        metrics->subscriptSizeY = GET_BE_WORD(tt_os2->ySubscriptYSize);
        metrics->superscriptPositionX = GET_BE_WORD(tt_os2->ySuperscriptXOffset);
        metrics->superscriptPositionY = GET_BE_WORD(tt_os2->ySuperscriptYOffset);
        metrics->superscriptSizeX = GET_BE_WORD(tt_os2->ySuperscriptXSize);
        metrics->superscriptSizeY = GET_BE_WORD(tt_os2->ySuperscriptYSize);

        /* version 2 fields */
        if (version >= 2) {
            metrics->capHeight = GET_BE_WORD(tt_os2->sCapHeight);
            metrics->xHeight   = GET_BE_WORD(tt_os2->sxHeight);
        }

        if (GET_BE_WORD(tt_os2->fsSelection) & OS2_FSSELECTION_USE_TYPO_METRICS) {
            SHORT descent = GET_BE_WORD(tt_os2->sTypoDescender);
            metrics->ascent = GET_BE_WORD(tt_os2->sTypoAscender);
            metrics->descent = descent < 0 ? -descent : 0;
            metrics->lineGap = GET_BE_WORD(tt_os2->sTypoLineGap);
            metrics->hasTypographicMetrics = TRUE;
        }
    }
    else {
        metrics->strikethroughPosition = metrics->designUnitsPerEm / 3;
        if (tt_hhea) {
            metrics->ascent = GET_BE_WORD(tt_hhea->ascender);
            metrics->descent = abs((SHORT)GET_BE_WORD(tt_hhea->descender));
        }
    }

    if (tt_post) {
        metrics->underlinePosition = GET_BE_WORD(tt_post->underlinePosition);
        metrics->underlineThickness = GET_BE_WORD(tt_post->underlineThickness);
    }

    if (metrics->underlineThickness == 0)
        metrics->underlineThickness = metrics->designUnitsPerEm / 14;
    if (metrics->strikethroughThickness == 0)
        metrics->strikethroughThickness = metrics->underlineThickness;

    /* estimate missing metrics */
    if (metrics->xHeight == 0)
        metrics->xHeight = metrics->designUnitsPerEm / 2;
    if (metrics->capHeight == 0)
        metrics->capHeight = metrics->designUnitsPerEm * 7 / 10;

    if (tt_os2)
        IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, os2_context);
    if (tt_head)
        IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, head_context);
    if (tt_post)
        IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, post_context);
    if (tt_hhea)
        IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, hhea_context);
}

void opentype_get_font_properties(struct file_stream_desc *stream_desc, struct dwrite_font_props *props)
{
    void *os2_context, *head_context;
    const TT_OS2_V2 *tt_os2;
    const TT_HEAD *tt_head;

    opentype_get_font_table(stream_desc, MS_OS2_TAG,  (const void**)&tt_os2, &os2_context, NULL, NULL);
    opentype_get_font_table(stream_desc, MS_HEAD_TAG, (const void**)&tt_head, &head_context, NULL, NULL);

    /* 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));

    /* DWRITE_FONT_STRETCH enumeration values directly match font data values */
    if (tt_os2) {
        USHORT version = GET_BE_WORD(tt_os2->version);
        USHORT fsSelection = GET_BE_WORD(tt_os2->fsSelection);
        USHORT usWeightClass = GET_BE_WORD(tt_os2->usWeightClass);
        USHORT usWidthClass = GET_BE_WORD(tt_os2->usWidthClass);

        if (usWidthClass > DWRITE_FONT_STRETCH_UNDEFINED && usWidthClass <= DWRITE_FONT_STRETCH_ULTRA_EXPANDED)
            props->stretch = usWidthClass;

        if (usWeightClass >= 1 && usWeightClass <= 9)
            usWeightClass *= 100;

        if (usWeightClass > DWRITE_FONT_WEIGHT_ULTRA_BLACK)
            props->weight = DWRITE_FONT_WEIGHT_ULTRA_BLACK;
        else if (usWeightClass > 0)
            props->weight = usWeightClass;

        if (version >= 4 && (fsSelection & OS2_FSSELECTION_OBLIQUE))
            props->style = DWRITE_FONT_STYLE_OBLIQUE;
        else if (fsSelection & OS2_FSSELECTION_ITALIC)
            props->style = DWRITE_FONT_STYLE_ITALIC;
        props->lf.lfItalic = !!(fsSelection & OS2_FSSELECTION_ITALIC);

        memcpy(&props->panose, &tt_os2->panose, sizeof(props->panose));

        /* FONTSIGNATURE */
        props->fontsig.fsUsb[0] = GET_BE_DWORD(tt_os2->ulUnicodeRange1);
        props->fontsig.fsUsb[1] = GET_BE_DWORD(tt_os2->ulUnicodeRange2);
        props->fontsig.fsUsb[2] = GET_BE_DWORD(tt_os2->ulUnicodeRange3);
        props->fontsig.fsUsb[3] = GET_BE_DWORD(tt_os2->ulUnicodeRange4);

        if (GET_BE_WORD(tt_os2->version) == 0) {
            props->fontsig.fsCsb[0] = 0;
            props->fontsig.fsCsb[1] = 0;
        }
        else {
            props->fontsig.fsCsb[0] = GET_BE_DWORD(tt_os2->ulCodePageRange1);
            props->fontsig.fsCsb[1] = GET_BE_DWORD(tt_os2->ulCodePageRange2);
        }
    }
    else if (tt_head) {
        USHORT macStyle = GET_BE_WORD(tt_head->macStyle);

        if (macStyle & TT_HEAD_MACSTYLE_CONDENSED)
            props->stretch = DWRITE_FONT_STRETCH_CONDENSED;
        else if (macStyle & TT_HEAD_MACSTYLE_EXTENDED)
            props->stretch = DWRITE_FONT_STRETCH_EXPANDED;

        if (macStyle & TT_HEAD_MACSTYLE_BOLD)
            props->weight = DWRITE_FONT_WEIGHT_BOLD;

        if (macStyle & TT_HEAD_MACSTYLE_ITALIC) {
            props->style = DWRITE_FONT_STYLE_ITALIC;
            props->lf.lfItalic = 1;
        }
    }

    props->lf.lfWeight = props->weight;

    TRACE("stretch=%d, weight=%d, style %d\n", props->stretch, props->weight, props->style);

    if (tt_os2)
        IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, os2_context);
    if (tt_head)
        IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, head_context);
}

static UINT get_name_record_codepage(enum OPENTYPE_PLATFORM_ID platform, USHORT encoding)
{
    UINT codepage = 0;

    switch (platform) {
    case OPENTYPE_PLATFORM_UNICODE:
        break;
    case OPENTYPE_PLATFORM_MAC:
        switch (encoding)
        {
            case TT_NAME_MAC_ENCODING_ROMAN:
                codepage = 10000;
                break;
            case TT_NAME_MAC_ENCODING_JAPANESE:
                codepage = 10001;
                break;
            case TT_NAME_MAC_ENCODING_TRAD_CHINESE:
                codepage = 10002;
                break;
            case TT_NAME_MAC_ENCODING_KOREAN:
                codepage = 10003;
                break;
            case TT_NAME_MAC_ENCODING_ARABIC:
                codepage = 10004;
                break;
            case TT_NAME_MAC_ENCODING_HEBREW:
                codepage = 10005;
                break;
            case TT_NAME_MAC_ENCODING_GREEK:
                codepage = 10006;
                break;
            case TT_NAME_MAC_ENCODING_RUSSIAN:
                codepage = 10007;
                break;
            case TT_NAME_MAC_ENCODING_SIMPL_CHINESE:
                codepage = 10008;
                break;
            case TT_NAME_MAC_ENCODING_THAI:
                codepage = 10021;
                break;
            default:
                FIXME("encoding %u not handled, platform %d.\n", encoding, platform);
                break;
        }
        break;
    case OPENTYPE_PLATFORM_WIN:
        switch (encoding)
        {
            case TT_NAME_WINDOWS_ENCODING_SYMBOL:
            case TT_NAME_WINDOWS_ENCODING_UCS2:
                break;
            case TT_NAME_WINDOWS_ENCODING_SJIS:
                codepage = 932;
                break;
            case TT_NAME_WINDOWS_ENCODING_PRC:
                codepage = 936;
                break;
            case TT_NAME_WINDOWS_ENCODING_BIG5:
                codepage = 950;
                break;
            case TT_NAME_WINDOWS_ENCODING_WANSUNG:
                codepage = 20949;
                break;
            case TT_NAME_WINDOWS_ENCODING_JOHAB:
                codepage = 1361;
                break;
            default:
                FIXME("encoding %u not handled, platform %d.\n", encoding, platform);
                break;
        }
        break;
    default:
        FIXME("unknown platform %d\n", platform);
    }

    return codepage;
}

static void get_name_record_locale(enum OPENTYPE_PLATFORM_ID platform, USHORT lang_id, WCHAR *locale, USHORT locale_len)
{
    static const WCHAR enusW[] = {'e','n','-','U','S',0};

    switch (platform) {
    case OPENTYPE_PLATFORM_MAC:
    {
        const char *locale_name = NULL;

        if (lang_id > TT_NAME_MAC_LANGID_AZER_ROMAN)
            WARN("invalid mac lang id %d\n", lang_id);
        else if (!name_mac_langid_to_locale[lang_id][0])
            FIXME("failed to map mac lang id %d to locale name\n", lang_id);
        else
            locale_name = name_mac_langid_to_locale[lang_id];

        if (locale_name)
            MultiByteToWideChar(CP_ACP, 0, name_mac_langid_to_locale[lang_id], -1, locale, locale_len);
        else
            strcpyW(locale, enusW);
        break;
    }
    case OPENTYPE_PLATFORM_WIN:
        if (!LCIDToLocaleName(MAKELCID(lang_id, SORT_DEFAULT), locale, locale_len, 0)) {
            FIXME("failed to get locale name for lcid=0x%08x\n", MAKELCID(lang_id, SORT_DEFAULT));
            strcpyW(locale, enusW);
        }
        break;
    case OPENTYPE_PLATFORM_UNICODE:
        strcpyW(locale, enusW);
        break;
    default:
        FIXME("unknown platform %d\n", platform);
    }
}

static BOOL opentype_decode_namerecord(const TT_NAME_V0 *header, BYTE *storage_area, USHORT recid, IDWriteLocalizedStrings *strings)
{
    const TT_NameRecord *record = &header->nameRecord[recid];
    USHORT lang_id, length, offset, encoding, platform;
    BOOL ret = FALSE;

    platform = GET_BE_WORD(record->platformID);
    lang_id = GET_BE_WORD(record->languageID);
    length = GET_BE_WORD(record->length);
    offset = GET_BE_WORD(record->offset);
    encoding = GET_BE_WORD(record->encodingID);

    if (lang_id < 0x8000) {
        WCHAR locale[LOCALE_NAME_MAX_LENGTH];
        WCHAR *name_string;
        UINT codepage;

        codepage = get_name_record_codepage(platform, encoding);
        get_name_record_locale(platform, lang_id, locale, ARRAY_SIZE(locale));

        if (codepage) {
            DWORD len = MultiByteToWideChar(codepage, 0, (LPSTR)(storage_area + offset), length, NULL, 0);
            name_string = heap_alloc(sizeof(WCHAR) * (len+1));
            MultiByteToWideChar(codepage, 0, (LPSTR)(storage_area + offset), length, name_string, len);
            name_string[len] = 0;
        }
        else {
            int i;

            length /= sizeof(WCHAR);
            name_string = heap_strdupnW((LPWSTR)(storage_area + offset), length);
            for (i = 0; i < length; i++)
                name_string[i] = GET_BE_WORD(name_string[i]);
        }

        TRACE("string %s for locale %s found\n", debugstr_w(name_string), debugstr_w(locale));
        add_localizedstring(strings, locale, name_string);
        heap_free(name_string);
        ret = TRUE;
    }
    else
        FIXME("handle NAME format 1\n");

    return ret;
}

static HRESULT opentype_get_font_strings_from_id(const void *table_data, enum OPENTYPE_STRING_ID id, IDWriteLocalizedStrings **strings)
{
    const TT_NAME_V0 *header;
    BYTE *storage_area = 0;
    USHORT count = 0;
    int i, candidate;
    WORD format;
    BOOL exists;
    HRESULT hr;

    if (!table_data)
        return E_FAIL;

    hr = create_localizedstrings(strings);
    if (FAILED(hr)) return hr;

    header = table_data;
    format = GET_BE_WORD(header->format);

    switch (format) {
    case 0:
    case 1:
        break;
    default:
        FIXME("unsupported NAME format %d\n", format);
    }

    storage_area = (LPBYTE)table_data + GET_BE_WORD(header->stringOffset);
    count = GET_BE_WORD(header->count);

    exists = FALSE;
    candidate = -1;
    for (i = 0; i < count; i++) {
        const TT_NameRecord *record = &header->nameRecord[i];
        USHORT platform;

        if (GET_BE_WORD(record->nameID) != id)
            continue;

        /* Right now only accept unicode and windows encoded fonts */
        platform = GET_BE_WORD(record->platformID);
        if (platform != OPENTYPE_PLATFORM_UNICODE &&
            platform != OPENTYPE_PLATFORM_MAC &&
            platform != OPENTYPE_PLATFORM_WIN)
        {
            FIXME("platform %i not supported\n", platform);
            continue;
        }

        /* Skip such entries for now, fonts tend to duplicate those strings as
           WIN platform entries. If font does not have WIN or MAC entry for this id, we will
           use this Unicode platform entry while assuming en-US locale. */
        if (platform == OPENTYPE_PLATFORM_UNICODE) {
            candidate = i;
            continue;
        }

        if (!opentype_decode_namerecord(header, storage_area, i, *strings))
            continue;

        exists = TRUE;
    }

    if (!exists) {
        if (candidate != -1)
            exists = opentype_decode_namerecord(header, storage_area, candidate, *strings);
        else {
            IDWriteLocalizedStrings_Release(*strings);
            *strings = NULL;
        }
    }

    return exists ? S_OK : E_FAIL;
}

/* Provides a conversion from DWRITE to OpenType name ids, input id should be valid, it's not checked. */
HRESULT opentype_get_font_info_strings(const void *table_data, DWRITE_INFORMATIONAL_STRING_ID id, IDWriteLocalizedStrings **strings)
{
    return opentype_get_font_strings_from_id(table_data, dwriteid_to_opentypeid[id], strings);
}

/* 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)
{
    const TT_OS2_V2 *tt_os2;
    void *os2_context, *name_context;
    const void *name_table;
    HRESULT hr;

    opentype_get_font_table(stream_desc, MS_OS2_TAG,  (const void**)&tt_os2, &os2_context, NULL, NULL);
    opentype_get_font_table(stream_desc, MS_NAME_TAG, &name_table, &name_context, NULL, NULL);

    *names = NULL;

    /* if Preferred Family doesn't conform to WWS model try WWS name */
    if (tt_os2 && !(GET_BE_WORD(tt_os2->fsSelection) & OS2_FSSELECTION_WWS))
        hr = opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_WWS_FAMILY_NAME, names);
    else
        hr = E_FAIL;

    if (FAILED(hr))
        hr = opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_PREFERRED_FAMILY_NAME, names);
    if (FAILED(hr))
        hr = opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_FAMILY_NAME, names);

    if (tt_os2)
        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)
{
    IDWriteLocalizedStrings *lfnames;
    void *os2_context, *name_context;
    const TT_OS2_V2 *tt_os2;
    const void *name_table;
    HRESULT hr;

    opentype_get_font_table(stream_desc, MS_OS2_TAG,  (const void**)&tt_os2, &os2_context, NULL, NULL);
    opentype_get_font_table(stream_desc, MS_NAME_TAG, &name_table, &name_context, NULL, NULL);

    *names = NULL;

    /* if Preferred Family doesn't conform to WWS model try WWS name */
    if (tt_os2 && !(GET_BE_WORD(tt_os2->fsSelection) & OS2_FSSELECTION_WWS))
        hr = opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_WWS_SUBFAMILY_NAME, names);
    else
        hr = E_FAIL;

    if (FAILED(hr))
        hr = opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_PREFERRED_SUBFAMILY_NAME, names);
    if (FAILED(hr))
        hr = opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_SUBFAMILY_NAME, names);

    /* User locale is preferred, with fallback to en-us. */
    *lfname = 0;
    if (SUCCEEDED(opentype_get_font_strings_from_id(name_table, OPENTYPE_STRING_FAMILY_NAME, &lfnames))) {
        static const WCHAR enusW[] = {'e','n','-','u','s',0};
        WCHAR localeW[LOCALE_NAME_MAX_LENGTH];
        UINT32 index;
        BOOL exists;

        exists = FALSE;
        if (GetSystemDefaultLocaleName(localeW, ARRAY_SIZE(localeW)))
            IDWriteLocalizedStrings_FindLocaleName(lfnames, localeW, &index, &exists);

        if (!exists)
            IDWriteLocalizedStrings_FindLocaleName(lfnames, enusW, &index, &exists);

        if (exists) {
            UINT32 length = 0;
            WCHAR *nameW;

            IDWriteLocalizedStrings_GetStringLength(lfnames, index, &length);
            nameW = heap_alloc((length + 1) * sizeof(WCHAR));
            if (nameW) {
                *nameW = 0;
                IDWriteLocalizedStrings_GetString(lfnames, index, nameW, length + 1);
                lstrcpynW(lfname, nameW, LF_FACESIZE);
                heap_free(nameW);
            }
        }

        IDWriteLocalizedStrings_Release(lfnames);
    }

    if (tt_os2)
        IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, os2_context);
    if (name_context)
        IDWriteFontFileStream_ReleaseFileFragment(stream_desc->stream, name_context);

    return hr;
}

static inline const struct ot_script *opentype_get_script(const struct ot_script_list *scriptlist, UINT32 scripttag)
{
    UINT16 j;

    for (j = 0; j < GET_BE_WORD(scriptlist->script_count); j++) {
        const char *tag = scriptlist->scripts[j].tag;
        if (scripttag == DWRITE_MAKE_OPENTYPE_TAG(tag[0], tag[1], tag[2], tag[3]))
            return (struct ot_script*)((BYTE*)scriptlist + GET_BE_WORD(scriptlist->scripts[j].script));
    }

    return NULL;
}

static inline const struct ot_langsys *opentype_get_langsys(const struct ot_script *script, UINT32 languagetag)
{
    UINT16 j;

    for (j = 0; j < GET_BE_WORD(script->langsys_count); j++) {
        const char *tag = script->langsys[j].tag;
        if (languagetag == DWRITE_MAKE_OPENTYPE_TAG(tag[0], tag[1], tag[2], tag[3]))
            return (struct ot_langsys *)((BYTE*)script + GET_BE_WORD(script->langsys[j].langsys));
    }

    return NULL;
}

static void opentype_add_font_features(const struct gpos_gsub_header *header, const struct ot_langsys *langsys,
    UINT32 max_tagcount, UINT32 *count, DWRITE_FONT_FEATURE_TAG *tags)
{
    const struct ot_feature_list *features = (const struct ot_feature_list *)((const BYTE*)header + GET_BE_WORD(header->feature_list));
    UINT16 j;

    for (j = 0; j < GET_BE_WORD(langsys->feature_count); j++) {
        const struct ot_feature_record *feature = &features->features[langsys->feature_index[j]];

        if (*count < max_tagcount)
            tags[*count] = GET_BE_DWORD(feature->tag);

        (*count)++;
    }
}

HRESULT opentype_get_typographic_features(IDWriteFontFace *fontface, UINT32 scripttag, UINT32 languagetag, UINT32 max_tagcount,
    UINT32 *count, DWRITE_FONT_FEATURE_TAG *tags)
{
    UINT32 tables[2] = { MS_GSUB_TAG, MS_GPOS_TAG };
    HRESULT hr;
    UINT8 i;

    *count = 0;
    for (i = 0; i < ARRAY_SIZE(tables); i++) {
        const struct ot_script_list *scriptlist;
        const struct gpos_gsub_header *header;
        const struct ot_script *script;
        const void *ptr;
        void *context;
        UINT32 size;
        BOOL exists;

        exists = FALSE;
        hr = IDWriteFontFace_TryGetFontTable(fontface, tables[i], &ptr, &size, &context, &exists);
        if (FAILED(hr))
            return hr;

        if (!exists)
            continue;

        header = (const struct gpos_gsub_header *)ptr;
        scriptlist = (const struct ot_script_list *)((const BYTE*)header + GET_BE_WORD(header->script_list));

        script = opentype_get_script(scriptlist, scripttag);
        if (script) {
            const struct ot_langsys *langsys = opentype_get_langsys(script, languagetag);
            if (langsys)
                opentype_add_font_features(header, langsys, max_tagcount, count, tags);
        }

        IDWriteFontFace_ReleaseFontTable(fontface, context);
    }

    return *count > max_tagcount ? E_NOT_SUFFICIENT_BUFFER : S_OK;
}

static unsigned int find_vdmx_group(const struct vdmx_header *hdr)
{
    WORD num_ratios, i;
    const struct vdmx_ratio *ratios = (struct vdmx_ratio *)(hdr + 1);
    BYTE dev_x_ratio = 1, dev_y_ratio = 1;
    unsigned int group_offset = 0;

    num_ratios = GET_BE_WORD(hdr->num_ratios);

    for (i = 0; i < num_ratios; i++) {

        if (!ratios[i].bCharSet) continue;

        if ((ratios[i].xRatio == 0 && ratios[i].yStartRatio == 0 &&
             ratios[i].yEndRatio == 0) ||
	   (ratios[i].xRatio == dev_x_ratio && ratios[i].yStartRatio <= dev_y_ratio &&
            ratios[i].yEndRatio >= dev_y_ratio))
        {
            group_offset = GET_BE_WORD(*((WORD *)(ratios + num_ratios) + i));
            break;
        }
    }

    return group_offset;
}

BOOL opentype_get_vdmx_size(const struct dwrite_fonttable *vdmx, INT emsize, UINT16 *ascent, UINT16 *descent)
{
    unsigned int num_ratios, num_recs, group_offset, i;
    const struct vdmx_header *header;
    const struct vdmx_group *group;

    if (!vdmx->exists)
        return FALSE;

    num_ratios = table_read_be_word(vdmx, FIELD_OFFSET(struct vdmx_header, num_ratios));
    num_recs = table_read_be_word(vdmx, FIELD_OFFSET(struct vdmx_header, num_recs));

    header = table_read_ensure(vdmx, 0, sizeof(*header) + num_ratios * sizeof(struct vdmx_ratio) +
            num_recs * sizeof(*group));

    if (!header)
        return FALSE;

    group_offset = find_vdmx_group(header);
    if (!group_offset)
        return FALSE;

    num_recs = table_read_be_word(vdmx, group_offset);
    group = table_read_ensure(vdmx, group_offset, FIELD_OFFSET(struct vdmx_group, entries[num_recs]));

    if (!group)
        return FALSE;

    if (emsize < group->startsz || emsize >= group->endsz)
        return FALSE;

    for (i = 0; i < num_recs; ++i)
    {
        WORD ppem = GET_BE_WORD(group->entries[i].yPelHeight);
        if (ppem > emsize) {
            FIXME("interpolate %d\n", emsize);
            return FALSE;
        }

        if (ppem == emsize) {
            *ascent = (SHORT)GET_BE_WORD(group->entries[i].yMax);
            *descent = -(SHORT)GET_BE_WORD(group->entries[i].yMin);
            return TRUE;
        }
    }

    return FALSE;
}

unsigned int opentype_get_gasp_flags(const struct dwrite_fonttable *gasp, float emsize)
{
    unsigned int version, num_ranges, i;
    const struct gasp_header *table;
    WORD flags = 0;

    if (!gasp->exists)
        return 0;

    num_ranges = table_read_be_word(gasp, FIELD_OFFSET(struct gasp_header, num_ranges));

    table = table_read_ensure(gasp, 0, FIELD_OFFSET(struct gasp_header, ranges[num_ranges]));
    if (!table)
        return 0;

    version = GET_BE_WORD(table->version);
    if (version > 1)
    {
        ERR("Unsupported gasp table format version %u.\n", version);
        goto done;
    }

    for (i = 0; i < num_ranges; ++i)
    {
        flags = GET_BE_WORD(table->ranges[i].flags);
        if (emsize <= GET_BE_WORD(table->ranges[i].max_ppem)) break;
    }

done:
    return flags;
}

unsigned int opentype_get_cpal_palettecount(const struct dwrite_fonttable *cpal)
{
    return table_read_be_word(cpal, FIELD_OFFSET(struct cpal_header_0, num_palettes));
}

unsigned int opentype_get_cpal_paletteentrycount(const struct dwrite_fonttable *cpal)
{
    return table_read_be_word(cpal, FIELD_OFFSET(struct cpal_header_0, num_palette_entries));
}

HRESULT opentype_get_cpal_entries(const struct dwrite_fonttable *cpal, unsigned int palette,
        unsigned int first_entry_index, unsigned int entry_count, DWRITE_COLOR_F *entries)
{
    unsigned int num_palettes, num_palette_entries, i;
    const struct cpal_color_record *records;
    const struct cpal_header_0 *header;

    header = table_read_ensure(cpal, 0, sizeof(*header));

    if (!cpal->exists || !header)
        return DWRITE_E_NOCOLOR;

    num_palettes = GET_BE_WORD(header->num_palettes);
    if (palette >= num_palettes)
        return DWRITE_E_NOCOLOR;

    header = table_read_ensure(cpal, 0, FIELD_OFFSET(struct cpal_header_0, color_record_indices[palette]));
    if (!header)
        return DWRITE_E_NOCOLOR;

    num_palette_entries = GET_BE_WORD(header->num_palette_entries);
    if (first_entry_index + entry_count > num_palette_entries)
        return E_INVALIDARG;

    records = table_read_ensure(cpal, GET_BE_DWORD(header->offset_first_color_record),
            sizeof(*records) * GET_BE_WORD(header->num_color_records));
    if (!records)
        return DWRITE_E_NOCOLOR;

    first_entry_index += GET_BE_WORD(header->color_record_indices[palette]);

    for (i = 0; i < entry_count; i++) {
        entries[i].u1.r = records[first_entry_index + i].red   / 255.0f;
        entries[i].u2.g = records[first_entry_index + i].green / 255.0f;
        entries[i].u3.b = records[first_entry_index + i].blue  / 255.0f;
        entries[i].u4.a = records[first_entry_index + i].alpha / 255.0f;
    }

    return S_OK;
}

static int colr_compare_gid(const void *g, const void *r)
{
    const struct colr_baseglyph_record *record = r;
    UINT16 glyph = *(UINT16*)g, GID = GET_BE_WORD(record->glyph);
    int ret = 0;

    if (glyph > GID)
        ret = 1;
    else if (glyph < GID)
        ret = -1;

    return ret;
}

HRESULT opentype_get_colr_glyph(const struct dwrite_fonttable *colr, UINT16 glyph, struct dwrite_colorglyph *ret)
{
    unsigned int num_baseglyph_records, offset_baseglyph_records;
    const struct colr_baseglyph_record *record;
    const struct colr_layer_record *layer;
    const struct colr_header *header;

    memset(ret, 0, sizeof(*ret));
    ret->glyph = glyph;
    ret->palette_index = 0xffff;

    header = table_read_ensure(colr, 0, sizeof(*header));
    if (!header)
        return S_FALSE;

    num_baseglyph_records = GET_BE_WORD(header->num_baseglyph_records);
    offset_baseglyph_records = GET_BE_DWORD(header->offset_baseglyph_records);
    if (!table_read_ensure(colr, offset_baseglyph_records, num_baseglyph_records * sizeof(*record)))
    {
        return S_FALSE;
    }

    record = bsearch(&glyph, colr->data + offset_baseglyph_records, num_baseglyph_records,
            sizeof(*record), colr_compare_gid);
    if (!record)
        return S_FALSE;

    ret->first_layer = GET_BE_WORD(record->first_layer_index);
    ret->num_layers = GET_BE_WORD(record->num_layers);

    if ((layer = table_read_ensure(colr, GET_BE_DWORD(header->offset_layer_records),
            (ret->first_layer + ret->layer) * sizeof(*layer))))
    {
        layer += ret->first_layer + ret->layer;
        ret->glyph = GET_BE_WORD(layer->glyph);
        ret->palette_index = GET_BE_WORD(layer->palette_index);
    }

    return S_OK;
}

void opentype_colr_next_glyph(const struct dwrite_fonttable *colr, struct dwrite_colorglyph *glyph)
{
    const struct colr_layer_record *layer;
    const struct colr_header *header;

    /* iterated all the way through */
    if (glyph->layer == glyph->num_layers)
        return;

    if (!(header = table_read_ensure(colr, 0, sizeof(*header))))
        return;

    glyph->layer++;

    if ((layer = table_read_ensure(colr, GET_BE_DWORD(header->offset_layer_records),
            (glyph->first_layer + glyph->layer) * sizeof(*layer))))
    {
        layer += glyph->first_layer + glyph->layer;
        glyph->glyph = GET_BE_WORD(layer->glyph);
        glyph->palette_index = GET_BE_WORD(layer->palette_index);
    }
}

BOOL opentype_has_vertical_variants(IDWriteFontFace4 *fontface)
{
    const struct gpos_gsub_header *header;
    const struct ot_feature_list *featurelist;
    const struct ot_lookup_list *lookup_list;
    BOOL exists = FALSE, ret = FALSE;
    unsigned int i, j;
    const void *data;
    void *context;
    UINT32 size;
    HRESULT hr;

    hr = IDWriteFontFace4_TryGetFontTable(fontface, MS_GSUB_TAG, &data, &size, &context, &exists);
    if (FAILED(hr) || !exists)
        return FALSE;

    header = data;
    featurelist = (struct ot_feature_list *)((BYTE*)header + GET_BE_WORD(header->feature_list));
    lookup_list = (const struct ot_lookup_list *)((BYTE*)header + GET_BE_WORD(header->lookup_list));

    for (i = 0; i < GET_BE_WORD(featurelist->feature_count); i++) {
        if (featurelist->features[i].tag == DWRITE_FONT_FEATURE_TAG_VERTICAL_WRITING) {
            const struct ot_feature *feature = (const struct ot_feature*)((BYTE*)featurelist + GET_BE_WORD(featurelist->features[i].offset));
            UINT16 lookup_count = GET_BE_WORD(feature->lookup_count), index, count, type;
            const GSUB_SingleSubstFormat2 *subst2;
            const struct ot_lookup_table *lookup_table;
            UINT32 offset;

            if (lookup_count == 0)
                continue;

            for (j = 0; j < lookup_count; ++j) {
                /* check if lookup is empty */
                index = GET_BE_WORD(feature->lookuplist_index[j]);
                lookup_table = (const struct ot_lookup_table *)((BYTE*)lookup_list + GET_BE_WORD(lookup_list->lookup[index]));

                type = GET_BE_WORD(lookup_table->lookup_type);
                if (type != GSUB_LOOKUP_SINGLE_SUBST && type != GSUB_LOOKUP_EXTENSION_SUBST)
                    continue;

                count = GET_BE_WORD(lookup_table->subtable_count);
                if (count == 0)
                    continue;

                offset = GET_BE_WORD(lookup_table->subtable[0]);
                if (type == GSUB_LOOKUP_EXTENSION_SUBST) {
                    const GSUB_ExtensionPosFormat1 *ext = (const GSUB_ExtensionPosFormat1 *)((const BYTE *)lookup_table + offset);
                    if (GET_BE_WORD(ext->SubstFormat) == 1)
                        offset += GET_BE_DWORD(ext->ExtensionOffset);
                    else
                        FIXME("Unhandled Extension Substitution Format %u\n", GET_BE_WORD(ext->SubstFormat));
                }

                subst2 = (const GSUB_SingleSubstFormat2*)((BYTE*)lookup_table + offset);
                index = GET_BE_WORD(subst2->SubstFormat);
                if (index == 1)
                    FIXME("Validate Single Substitution Format 1\n");
                else if (index == 2) {
                    /* SimSun-ExtB has 0 glyph count for this substitution */
                    if (GET_BE_WORD(subst2->GlyphCount) > 0) {
                        ret = TRUE;
                        break;
                    }
                }
                else
                    WARN("Unknown Single Substitution Format, %u\n", index);
            }
        }
    }

    IDWriteFontFace4_ReleaseFontTable(fontface, context);

    return ret;
}

static BOOL opentype_has_font_table(IDWriteFontFace4 *fontface, UINT32 tag)
{
    BOOL exists = FALSE;
    const void *data;
    void *context;
    UINT32 size;
    HRESULT hr;

    hr = IDWriteFontFace4_TryGetFontTable(fontface, tag, &data, &size, &context, &exists);
    if (FAILED(hr))
        return FALSE;

    if (exists)
        IDWriteFontFace4_ReleaseFontTable(fontface, context);

    return exists;
}

static unsigned int opentype_get_sbix_formats(IDWriteFontFace4 *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));

    IDWriteFontFace4_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)));
                }
            }
        }
    }

    IDWriteFontFace4_ReleaseFontTable(fontface, table.context);

    return ret;
}

static unsigned int opentype_get_cblc_formats(IDWriteFontFace4 *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;
        }
    }

    IDWriteFontFace4_ReleaseFontTable(fontface, cblc.context);

    return ret;
}

UINT32 opentype_get_glyph_image_formats(IDWriteFontFace4 *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_GPOS_TAG, &cache->gpos.table.data, &cache->gpos.table.size,
            &cache->gpos.table.context);

    if (cache->gpos.table.data)
    {
        cache->gpos.script_list = table_read_be_word(&cache->gpos.table,
                FIELD_OFFSET(struct gpos_gsub_header, script_list));
        cache->gpos.feature_list = table_read_be_word(&cache->gpos.table,
                FIELD_OFFSET(struct gpos_gsub_header, feature_list));
        cache->gpos.lookup_list = table_read_be_word(&cache->gpos.table,
                FIELD_OFFSET(struct gpos_gsub_header, lookup_list));
    }

    cache->font->grab_font_table(cache->context, MS_GDEF_TAG, &cache->gdef.table.data, &cache->gdef.table.size,
            &cache->gdef.table.context);

    if (cache->gdef.table.data)
        cache->gdef.classdef = table_read_be_word(&cache->gdef.table, FIELD_OFFSET(struct gdef_header, classdef));
}

DWORD opentype_layout_find_script(const struct scriptshaping_cache *cache, DWORD kind, DWORD script,
        unsigned int *script_index)
{
    WORD script_count;
    unsigned int i;

    *script_index = ~0u;

    if (kind != MS_GPOS_TAG)
        return 0;

    script_count = table_read_be_word(&cache->gpos.table, cache->gpos.script_list);
    if (!script_count)
        return 0;

    for (i = 0; i < script_count; i++)
    {
        DWORD tag = table_read_dword(&cache->gpos.table, cache->gpos.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;
}

DWORD opentype_layout_find_language(const struct scriptshaping_cache *cache, DWORD kind, DWORD language,
        unsigned int script_index, unsigned int *language_index)
{
    WORD table_offset, lang_count;
    unsigned int i;

    *language_index = ~0u;

    if (kind != MS_GPOS_TAG)
        return 0;

    table_offset = table_read_be_word(&cache->gpos.table, cache->gpos.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(&cache->gpos.table, cache->gpos.script_list + table_offset +
            FIELD_OFFSET(struct ot_script, langsys_count));
    for (i = 0; i < lang_count; i++)
    {
        DWORD tag = table_read_dword(&cache->gpos.table, cache->gpos.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(&cache->gpos.table, cache->gpos.script_list + table_offset))
        return ~0u;

    return 0;
}

static int gdef_class_compare_format2(const void *g, const void *r)
{
    const struct ot_gdef_class_range *range = r;
    UINT16 glyph = *(UINT16 *)g;

    if (glyph < GET_BE_WORD(range->start_glyph))
        return -1;
    else if (glyph > GET_BE_WORD(range->end_glyph))
        return 1;
    else
        return 0;
}

static unsigned int opentype_layout_get_glyph_class(const struct dwrite_fonttable *table,
        unsigned int offset, UINT16 glyph)
{
    WORD format = table_read_be_word(table, offset), count;
    unsigned int glyph_class = GDEF_CLASS_UNCLASSIFIED;

    if (format == 1)
    {
        const struct ot_gdef_classdef_format1 *format1;

        count = table_read_be_word(table, offset + FIELD_OFFSET(struct ot_gdef_classdef_format1, glyph_count));
        format1 = table_read_ensure(table, offset, FIELD_OFFSET(struct ot_gdef_classdef_format1, classes[count]));
        if (format1)
        {
            WORD start_glyph = GET_BE_WORD(format1->start_glyph);
            if (glyph >= start_glyph && (glyph - start_glyph) < count)
            {
                glyph_class = GET_BE_WORD(format1->classes[glyph - start_glyph]);
                if (glyph_class > GDEF_CLASS_MAX)
                     glyph_class = GDEF_CLASS_UNCLASSIFIED;
            }
        }
    }
    else if (format == 2)
    {
        const struct ot_gdef_classdef_format2 *format2;

        count = table_read_be_word(table, offset + FIELD_OFFSET(struct ot_gdef_classdef_format2, range_count));
        format2 = table_read_ensure(table, offset, FIELD_OFFSET(struct ot_gdef_classdef_format2, ranges[count]));
        if (format2)
        {
            const struct ot_gdef_class_range *range = bsearch(&glyph, format2->ranges, count,
                    sizeof(struct ot_gdef_class_range), gdef_class_compare_format2);
            glyph_class = range && glyph <= GET_BE_WORD(range->end_glyph) ?
                    GET_BE_WORD(range->glyph_class) : GDEF_CLASS_UNCLASSIFIED;
            if (glyph_class > GDEF_CLASS_MAX)
                 glyph_class = GDEF_CLASS_UNCLASSIFIED;
        }
    }
    else
        WARN("Unknown GDEF format %u.\n", format);

    return glyph_class;
}

struct coverage_compare_format1_context
{
    UINT16 glyph;
    const UINT16 *table_base;
    unsigned int *coverage_index;
};

static int coverage_compare_format1(const void *left, const void *right)
{
    const struct coverage_compare_format1_context *context = left;
    UINT16 glyph = GET_BE_WORD(*(UINT16 *)right);
    int ret;

    ret = context->glyph - glyph;
    if (!ret)
        *context->coverage_index = (UINT16 *)right - context->table_base;

    return ret;
}

static int coverage_compare_format2(const void *g, const void *r)
{
    const struct ot_coverage_range *range = r;
    UINT16 glyph = *(UINT16 *)g;

    if (glyph < GET_BE_WORD(range->start_glyph))
        return -1;
    else if (glyph > GET_BE_WORD(range->end_glyph))
        return 1;
    else
        return 0;
}

static unsigned int opentype_layout_is_glyph_covered(const struct dwrite_fonttable *table, DWORD coverage,
        UINT16 glyph)
{
    WORD format = table_read_be_word(table, coverage), count;

    count = table_read_be_word(table, coverage + 2);

    if (format == 1)
    {
        const struct ot_coverage_format1 *format1 = table_read_ensure(table, coverage,
                FIELD_OFFSET(struct ot_coverage_format1, glyphs[count]));
        struct coverage_compare_format1_context context;
        unsigned int coverage_index = GLYPH_NOT_COVERED;

        if (format1)
        {
            context.glyph = glyph;
            context.table_base = format1->glyphs;
            context.coverage_index = &coverage_index;

            bsearch(&context, format1->glyphs, count, sizeof(glyph), coverage_compare_format1);
        }

        return coverage_index;
    }
    else if (format == 2)
    {
        const struct ot_coverage_format2 *format2 = table_read_ensure(table, coverage,
                FIELD_OFFSET(struct ot_coverage_format2, ranges[count]));
        if (format2)
        {
            const struct ot_coverage_range *range = bsearch(&glyph, format2->ranges, count,
                    sizeof(struct ot_coverage_range), coverage_compare_format2);
            return range && glyph <= GET_BE_WORD(range->end_glyph) ?
                    GET_BE_WORD(range->startcoverage_index) + glyph - GET_BE_WORD(range->start_glyph) :
                    GLYPH_NOT_COVERED;
        }
    }
    else
        WARN("Unknown coverage format %u.\n", format);

    return -1;
}

static inline unsigned int dwrite_popcount(unsigned int x)
{
#ifdef HAVE___BUILTIN_POPCOUNT
    return __builtin_popcount(x);
#else
    x -= x >> 1 & 0x55555555;
    x = (x & 0x33333333) + (x >> 2 & 0x33333333);
    return ((x + (x >> 4)) & 0x0f0f0f0f) * 0x01010101 >> 24;
#endif
}

static float opentype_scale_gpos_be_value(WORD value, float emsize, UINT16 upem)
{
    return (short)GET_BE_WORD(value) * emsize / upem;
}

static int opentype_layout_gpos_get_dev_value(const struct scriptshaping_context *context, unsigned int offset)
{
    const struct scriptshaping_cache *cache = context->cache;
    unsigned int start_size, end_size, format, value_word;
    unsigned int index, ppem, mask;
    int value;

    if (!offset)
        return 0;

    start_size = table_read_be_word(&cache->gpos.table, offset);
    end_size = table_read_be_word(&cache->gpos.table, offset + FIELD_OFFSET(struct ot_gpos_device_table, end_size));

    ppem = context->emsize;
    if (ppem < start_size || ppem > end_size)
        return 0;

    format = table_read_be_word(&cache->gpos.table, offset + FIELD_OFFSET(struct ot_gpos_device_table, format));

    if (format < 1 || format > 3)
        return 0;

    index = ppem - start_size;

    value_word = table_read_be_word(&cache->gpos.table, offset +
            FIELD_OFFSET(struct ot_gpos_device_table, values[index >> (4 - format)]));
    mask = 0xffff >> (16 - (1 << format));

    value = (value_word >> ((index % (4 - format)) * (1 << format))) & mask;

    if ((unsigned int)value >= ((mask + 1) >> 1))
        value -= mask + 1;

    return value;
}

static void opentype_layout_apply_gpos_value(struct scriptshaping_context *context, unsigned int table_offset,
        WORD value_format, const WORD *values, unsigned int glyph)
{
    const struct scriptshaping_cache *cache = context->cache;
    DWRITE_GLYPH_OFFSET *offset = &context->offsets[glyph];
    float *advance = &context->advances[glyph];

    if (!value_format)
        return;

    if (value_format & GPOS_VALUE_X_PLACEMENT)
    {
        offset->advanceOffset += opentype_scale_gpos_be_value(*values, context->emsize, cache->upem);
        values++;
    }
    if (value_format & GPOS_VALUE_Y_PLACEMENT)
    {
        offset->ascenderOffset += opentype_scale_gpos_be_value(*values, context->emsize, cache->upem);
        values++;
    }
    if (value_format & GPOS_VALUE_X_ADVANCE)
    {
        *advance += opentype_scale_gpos_be_value(*values, context->emsize, cache->upem);
        values++;
    }
    if (value_format & GPOS_VALUE_Y_ADVANCE)
    {
        values++;
    }
    if (value_format & GPOS_VALUE_X_PLACEMENT_DEVICE)
    {
        offset->advanceOffset += opentype_layout_gpos_get_dev_value(context, table_offset + GET_BE_WORD(*values));
        values++;
    }
    if (value_format & GPOS_VALUE_Y_PLACEMENT_DEVICE)
    {
        offset->ascenderOffset += opentype_layout_gpos_get_dev_value(context, table_offset + GET_BE_WORD(*values));
        values++;
    }
    if (value_format & GPOS_VALUE_X_ADVANCE_DEVICE)
    {
        *advance += opentype_layout_gpos_get_dev_value(context, table_offset + GET_BE_WORD(*values));
        values++;
    }
    if (value_format & GPOS_VALUE_Y_ADVANCE_DEVICE)
    {
        values++;
    }
}

static unsigned int opentype_layout_get_gpos_subtable(const struct scriptshaping_cache *cache,
        unsigned int lookup_offset, unsigned int subtable)
{
    WORD lookup_type = table_read_be_word(&cache->gpos.table, lookup_offset);
    unsigned int subtable_offset = table_read_be_word(&cache->gpos.table, lookup_offset +
            FIELD_OFFSET(struct ot_lookup_table, subtable[subtable]));
    if (lookup_type == GPOS_LOOKUP_EXTENSION_POSITION)
    {
        const struct ot_gpos_extensionpos_format1 *format1 = table_read_ensure(&cache->gpos.table,
                lookup_offset + subtable_offset, sizeof(*format1));
        subtable_offset += GET_BE_DWORD(format1->extension_offset);
    }

    return lookup_offset + subtable_offset;
}

struct lookup
{
    unsigned int offset;
    unsigned int subtable_count;
    unsigned int flags;
};

struct glyph_iterator
{
    const struct scriptshaping_context *context;
    unsigned int flags;
    unsigned int pos;
    unsigned int len;
};

static void glyph_iterator_init(const struct scriptshaping_context *context, unsigned int flags, unsigned int pos,
        unsigned int len, struct glyph_iterator *iter)
{
    iter->context = context;
    iter->flags = flags;
    iter->pos = pos;
    iter->len = len;
}

static BOOL glyph_iterator_match(const struct glyph_iterator *iter)
{
    struct scriptshaping_cache *cache = iter->context->cache;

    if (cache->gdef.classdef)
    {
        unsigned int glyph_class = opentype_layout_get_glyph_class(&cache->gdef.table, cache->gdef.classdef,
                iter->context->u.pos.glyphs[iter->pos]);
        if ((1 << glyph_class) & iter->flags & LOOKUP_FLAG_IGNORE_MASK)
            return FALSE;
    }

    return TRUE;
}

static BOOL glyph_iterator_next(struct glyph_iterator *iter)
{
    while (iter->pos + iter->len < iter->context->glyph_count)
    {
        ++iter->pos;
        if (glyph_iterator_match(iter))
        {
            --iter->len;
            return TRUE;
        }
    }

    return FALSE;
}

static BOOL glyph_iterator_prev(struct glyph_iterator *iter)
{
    if (!iter->pos)
        return FALSE;

    while (iter->pos > iter->len - 1)
    {
        --iter->pos;
        if (glyph_iterator_match(iter))
        {
            --iter->len;
            return TRUE;
        }
    }

    return FALSE;
}

static BOOL opentype_layout_apply_gpos_single_adjustment(struct scriptshaping_context *context,
        struct glyph_iterator *iter, const struct lookup *lookup)
{
    struct scriptshaping_cache *cache = context->cache;
    WORD format, value_format, value_len, coverage;
    unsigned int i;

    for (i = 0; i < lookup->subtable_count; ++i)
    {
        unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i);
        unsigned int coverage_index;

        format = table_read_be_word(&cache->gpos.table, subtable_offset);

        coverage = table_read_be_word(&cache->gpos.table, subtable_offset +
                FIELD_OFFSET(struct ot_gpos_singlepos_format1, coverage));
        value_format = table_read_be_word(&cache->gpos.table, subtable_offset +
                FIELD_OFFSET(struct ot_gpos_singlepos_format1, value_format));
        value_len = dwrite_popcount(value_format);

        if (format == 1)
        {
            const struct ot_gpos_singlepos_format1 *format1 = table_read_ensure(&cache->gpos.table, subtable_offset,
                    FIELD_OFFSET(struct ot_gpos_singlepos_format1, value[value_len]));

            coverage_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + coverage,
                    context->u.pos.glyphs[iter->pos]);
            if (coverage_index == GLYPH_NOT_COVERED)
                continue;

            opentype_layout_apply_gpos_value(context, subtable_offset, value_format, format1->value, iter->pos);
            break;
        }
        else if (format == 2)
        {
            WORD value_count = table_read_be_word(&cache->gpos.table, subtable_offset +
                    FIELD_OFFSET(struct ot_gpos_singlepos_format2, value_count));
            const struct ot_gpos_singlepos_format2 *format2 = table_read_ensure(&cache->gpos.table, subtable_offset,
                    FIELD_OFFSET(struct ot_gpos_singlepos_format2, values) + value_count * value_len * sizeof(WORD));

            coverage_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset + coverage,
                    context->u.pos.glyphs[iter->pos]);
            if (coverage_index == GLYPH_NOT_COVERED || coverage_index >= value_count)
                continue;

            opentype_layout_apply_gpos_value(context, subtable_offset, value_format,
                    &format2->values[coverage_index * value_len], iter->pos);
            break;
        }
        else
            WARN("Unknown single adjustment format %u.\n", format);
    }

    return FALSE;
}

static int gpos_pair_adjustment_compare_format1(const void *g, const void *r)
{
    const struct ot_gpos_pairvalue *pairvalue = r;
    UINT16 second_glyph = GET_BE_WORD(pairvalue->second_glyph);
    return *(UINT16 *)g - second_glyph;
}

static BOOL opentype_layout_apply_gpos_pair_adjustment(struct scriptshaping_context *context,
        struct glyph_iterator *iter, const struct lookup *lookup)
{
    struct scriptshaping_cache *cache = context->cache;
    unsigned int i, first_glyph, second_glyph;
    struct glyph_iterator iter_pair;
    WORD format, coverage;

    glyph_iterator_init(context, iter->flags, iter->pos, 1, &iter_pair);
    if (!glyph_iterator_next(&iter_pair))
        return FALSE;

    if (context->is_rtl)
    {
        first_glyph = iter_pair.pos;
        second_glyph = iter->pos;
    }
    else
    {
        first_glyph = iter->pos;
        second_glyph = iter_pair.pos;
    }

    for (i = 0; i < lookup->subtable_count; ++i)
    {
        unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i);
        WORD value_format1, value_format2, value_len1, value_len2;
        unsigned int coverage_index;

        format = table_read_be_word(&cache->gpos.table, subtable_offset);

        coverage = table_read_be_word(&cache->gpos.table, subtable_offset +
                FIELD_OFFSET(struct ot_gpos_pairpos_format1, coverage));
        if (!coverage)
            continue;

        coverage_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset +
                    coverage, context->u.pos.glyphs[first_glyph]);
        if (coverage_index == GLYPH_NOT_COVERED)
            continue;

        if (format == 1)
        {
            const struct ot_gpos_pairpos_format1 *format1;
            WORD pairset_count = table_read_be_word(&cache->gpos.table, subtable_offset +
                    FIELD_OFFSET(struct ot_gpos_pairpos_format1, pairset_count));
            unsigned int pairvalue_len, pairset_offset;
            const struct ot_gpos_pairset *pairset;
            const WORD *pairvalue;
            WORD pairvalue_count;

            if (!pairset_count || coverage_index >= pairset_count)
                continue;

            format1 = table_read_ensure(&cache->gpos.table, subtable_offset,
                    FIELD_OFFSET(struct ot_gpos_pairpos_format1, pairsets[pairset_count]));
            if (!format1)
                continue;

            /* Ordered paired values. */
            pairvalue_count = table_read_be_word(&cache->gpos.table, subtable_offset +
                    GET_BE_WORD(format1->pairsets[coverage_index]));
            if (!pairvalue_count)
                continue;

            /* Structure length is variable, but does not change across the subtable. */
            value_format1 = GET_BE_WORD(format1->value_format1) & 0xff;
            value_format2 = GET_BE_WORD(format1->value_format2) & 0xff;

            value_len1 = dwrite_popcount(value_format1);
            value_len2 = dwrite_popcount(value_format2);
            pairvalue_len = FIELD_OFFSET(struct ot_gpos_pairvalue, data) + value_len1 * sizeof(WORD) +
                    value_len2 * sizeof(WORD);

            pairset_offset = subtable_offset + GET_BE_WORD(format1->pairsets[coverage_index]);
            pairset = table_read_ensure(&cache->gpos.table, subtable_offset + pairset_offset,
                    pairvalue_len * pairvalue_count);
            if (!pairset)
                continue;

            pairvalue = bsearch(&context->u.pos.glyphs[second_glyph], pairset->pairvalues, pairvalue_count,
                    pairvalue_len, gpos_pair_adjustment_compare_format1);
            if (!pairvalue)
                continue;

            pairvalue += 1; /* Skip SecondGlyph. */
            opentype_layout_apply_gpos_value(context, pairset_offset, value_format1, pairvalue, first_glyph);
            opentype_layout_apply_gpos_value(context, pairset_offset, value_format2, pairvalue + value_len1,
                    second_glyph);

            iter->pos = iter_pair.pos;
            if (value_len2)
                iter->pos++;

            return TRUE;
        }
        else if (format == 2)
        {
            const struct ot_gpos_pairpos_format2 *format2;
            WORD class1_count, class2_count;
            unsigned int class1, class2;

            value_format1 = table_read_be_word(&cache->gpos.table, subtable_offset +
                    FIELD_OFFSET(struct ot_gpos_pairpos_format2, value_format1)) & 0xff;
            value_format2 = table_read_be_word(&cache->gpos.table, subtable_offset +
                    FIELD_OFFSET(struct ot_gpos_pairpos_format2, value_format2)) & 0xff;

            class1_count = table_read_be_word(&cache->gpos.table, subtable_offset +
                    FIELD_OFFSET(struct ot_gpos_pairpos_format2, class1_count));
            class2_count = table_read_be_word(&cache->gpos.table, subtable_offset +
                    FIELD_OFFSET(struct ot_gpos_pairpos_format2, class2_count));

            value_len1 = dwrite_popcount(value_format1);
            value_len2 = dwrite_popcount(value_format2);

            format2 = table_read_ensure(&cache->gpos.table, subtable_offset,
                    FIELD_OFFSET(struct ot_gpos_pairpos_format2,
                    values[class1_count * class2_count * (value_len1 + value_len2)]));
            if (!format2)
                continue;

            class1 = opentype_layout_get_glyph_class(&cache->gpos.table, subtable_offset + GET_BE_WORD(format2->class_def1),
                    context->u.pos.glyphs[first_glyph]);
            class2 = opentype_layout_get_glyph_class(&cache->gpos.table, subtable_offset + GET_BE_WORD(format2->class_def2),
                    context->u.pos.glyphs[second_glyph]);

            if (class1 < class1_count && class2 < class2_count)
            {
                const WCHAR *values = &format2->values[(class1 * class2_count + class2) * (value_len1 + value_len2)];
                opentype_layout_apply_gpos_value(context, subtable_offset, value_format1, values, first_glyph);
                opentype_layout_apply_gpos_value(context, subtable_offset, value_format2, values + value_len1,
                        second_glyph);

                iter->pos = iter_pair.pos;
                if (value_len2)
                    iter->pos++;

                return TRUE;
            }
        }
        else
        {
            WARN("Unknown pair adjustment format %u.\n", format);
            continue;
        }
    }

    return FALSE;
}

static void opentype_layout_gpos_get_anchor(const struct scriptshaping_context *context, unsigned int anchor_offset,
        unsigned int glyph_index, float *x, float *y)
{
    const struct scriptshaping_cache *cache = context->cache;

    WORD format = table_read_be_word(&cache->gpos.table, anchor_offset);

    *x = *y = 0.0f;

    if (format == 1)
    {
        const struct ot_gpos_anchor_format1 *format1 = table_read_ensure(&cache->gpos.table, anchor_offset,
                sizeof(*format1));

        if (format1)
        {
            *x = opentype_scale_gpos_be_value(format1->x_coord, context->emsize, cache->upem);
            *y = opentype_scale_gpos_be_value(format1->y_coord, context->emsize, cache->upem);
        }
    }
    else if (format == 2)
    {
        const struct ot_gpos_anchor_format2 *format2 = table_read_ensure(&cache->gpos.table, anchor_offset,
                sizeof(*format2));

        if (format2)
        {
            if (context->measuring_mode != DWRITE_MEASURING_MODE_NATURAL)
                FIXME("Use outline anchor point for glyph %u.\n", context->u.pos.glyphs[glyph_index]);

            *x = opentype_scale_gpos_be_value(format2->x_coord, context->emsize, cache->upem);
            *y = opentype_scale_gpos_be_value(format2->y_coord, context->emsize, cache->upem);
        }
    }
    else if (format == 3)
    {
        const struct ot_gpos_anchor_format3 *format3 = table_read_ensure(&cache->gpos.table, anchor_offset,
                sizeof(*format3));

        if (format3)
        {
            *x = opentype_scale_gpos_be_value(format3->x_coord, context->emsize, cache->upem);
            *y = opentype_scale_gpos_be_value(format3->y_coord, context->emsize, cache->upem);

            if (context->measuring_mode != DWRITE_MEASURING_MODE_NATURAL)
            {
                if (format3->x_dev_offset)
                    *x += opentype_layout_gpos_get_dev_value(context, anchor_offset + GET_BE_WORD(format3->x_dev_offset));
                if (format3->y_dev_offset)
                    *y += opentype_layout_gpos_get_dev_value(context, anchor_offset + GET_BE_WORD(format3->y_dev_offset));
            }
        }
    }
    else
        WARN("Unknown anchor format %u.\n", format);
}

static BOOL opentype_layout_apply_gpos_cursive_attachment(struct scriptshaping_context *context,
        struct glyph_iterator *iter, const struct lookup *lookup)
{
    struct scriptshaping_cache *cache = context->cache;
    unsigned int i;

    for (i = 0; i < lookup->subtable_count; ++i)
    {
        unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i);
        WORD format;

        format = table_read_be_word(&cache->gpos.table, subtable_offset);

        if (format == 1)
        {
            WORD coverage_offset = table_read_be_word(&cache->gpos.table, subtable_offset +
                    FIELD_OFFSET(struct ot_gpos_cursive_format1, coverage));
            unsigned int glyph_index, entry_count, entry_anchor, exit_anchor;
            float entry_x, entry_y, exit_x, exit_y, delta;
            struct glyph_iterator prev_iter;

            if (!coverage_offset)
                continue;

            entry_count = table_read_be_word(&cache->gpos.table, subtable_offset +
                    FIELD_OFFSET(struct ot_gpos_cursive_format1, count));

            glyph_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset +
                    coverage_offset, context->u.pos.glyphs[iter->pos]);
            if (glyph_index == GLYPH_NOT_COVERED || glyph_index >= entry_count)
                continue;

            entry_anchor = table_read_be_word(&cache->gpos.table, subtable_offset +
                    FIELD_OFFSET(struct ot_gpos_cursive_format1, anchors[glyph_index * 2]));
            if (!entry_anchor)
                continue;

            glyph_iterator_init(context, iter->flags, iter->pos, 1, &prev_iter);
            if (!glyph_iterator_prev(&prev_iter))
                continue;

            glyph_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset +
                    coverage_offset, context->u.pos.glyphs[prev_iter.pos]);
            if (glyph_index == GLYPH_NOT_COVERED || glyph_index >= entry_count)
                continue;

            exit_anchor = table_read_be_word(&cache->gpos.table, subtable_offset +
                    FIELD_OFFSET(struct ot_gpos_cursive_format1, anchors[glyph_index * 2 + 1]));
            if (!exit_anchor)
                continue;

            opentype_layout_gpos_get_anchor(context, subtable_offset + exit_anchor, prev_iter.pos, &exit_x, &exit_y);
            opentype_layout_gpos_get_anchor(context, subtable_offset + entry_anchor, iter->pos, &entry_x, &entry_y);

            if (context->is_rtl)
            {
                delta = exit_x + context->offsets[prev_iter.pos].advanceOffset;
                context->advances[prev_iter.pos] -= delta;
                context->advances[iter->pos] = entry_x + context->offsets[iter->pos].advanceOffset;
                context->offsets[prev_iter.pos].advanceOffset -= delta;
            }
            else
            {
                delta = entry_x + context->offsets[iter->pos].advanceOffset;
                context->advances[prev_iter.pos] = exit_x + context->offsets[prev_iter.pos].advanceOffset;
                context->advances[iter->pos] -= delta;
                context->offsets[iter->pos].advanceOffset -= delta;
            }

            if (lookup->flags & LOOKUP_FLAG_RTL)
                context->offsets[prev_iter.pos].ascenderOffset = entry_y - exit_y;
            else
                context->offsets[iter->pos].ascenderOffset = exit_y - entry_y;

            break;
        }
        else
            WARN("Unknown cursive attachment format %u.\n", format);

    }

    return FALSE;
}

static BOOL opentype_layout_apply_gpos_mark_to_base_attachment(const struct scriptshaping_context *context,
        struct glyph_iterator *iter, const struct lookup *lookup)
{
    struct scriptshaping_cache *cache = context->cache;
    unsigned int i;
    WORD format;

    for (i = 0; i < lookup->subtable_count; ++i)
    {
        unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i);

        format = table_read_be_word(&cache->gpos.table, subtable_offset);

        if (format == 1)
        {
            const struct ot_gpos_mark_to_base_format1 *format1 = table_read_ensure(&cache->gpos.table, subtable_offset,
                    sizeof(*format1));
            unsigned int mark_class_count, count, mark_array_offset, base_array_offset;
            const struct ot_gpos_mark_array *mark_array;
            const struct ot_gpos_base_array *base_array;
            float mark_x, mark_y, base_x, base_y;
            unsigned int base_index, mark_index;
            struct glyph_iterator base_iter;
            unsigned int base_anchor;

            if (!format1)
                continue;

            mark_array_offset = subtable_offset + GET_BE_WORD(format1->mark_array);
            if (!(count = table_read_be_word(&cache->gpos.table, mark_array_offset)))
                continue;

            mark_array = table_read_ensure(&cache->gpos.table, mark_array_offset,
                    FIELD_OFFSET(struct ot_gpos_mark_array, records[count]));
            if (!mark_array)
                continue;

            base_array_offset = subtable_offset + GET_BE_WORD(format1->base_array);
            if (!(count = table_read_be_word(&cache->gpos.table, base_array_offset)))
                continue;

            base_array = table_read_ensure(&cache->gpos.table, base_array_offset,
                    FIELD_OFFSET(struct ot_gpos_base_array, offsets[count * GET_BE_WORD(format1->mark_class_count)]));
            if (!base_array)
                continue;

            mark_class_count = GET_BE_WORD(format1->mark_class_count);

            mark_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset +
                    GET_BE_WORD(format1->mark_coverage), context->u.pos.glyphs[iter->pos]);

            if (mark_index == GLYPH_NOT_COVERED || mark_index >= GET_BE_WORD(mark_array->count))
                continue;

            /* Look back for first base glyph. */
            glyph_iterator_init(context, LOOKUP_FLAG_IGNORE_MARKS, iter->pos, 1, &base_iter);
            if (!glyph_iterator_prev(&base_iter))
                continue;

            base_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset +
                    GET_BE_WORD(format1->base_coverage), context->u.pos.glyphs[base_iter.pos]);
            if (base_index == GLYPH_NOT_COVERED || base_index >= GET_BE_WORD(base_array->count))
                continue;

            base_anchor = GET_BE_WORD(base_array->offsets[base_index * mark_class_count +
                    GET_BE_WORD(mark_array->records[mark_index].mark_class)]);

            opentype_layout_gpos_get_anchor(context, mark_array_offset +
                    GET_BE_WORD(mark_array->records[mark_index].mark_anchor), iter->pos, &mark_x, &mark_y);
            opentype_layout_gpos_get_anchor(context, base_array_offset + base_anchor, base_iter.pos, &base_x, &base_y);

            context->offsets[iter->pos].advanceOffset = (context->is_rtl ? -1.0f : 1.0f) * (base_x - mark_x);
            context->offsets[iter->pos].ascenderOffset = base_y - mark_y;

            break;
        }
        else
            WARN("Unknown mark-to-base format %u.\n", format);
    }

    return FALSE;
}

static BOOL opentype_layout_apply_gpos_mark_to_lig_attachment(const struct scriptshaping_context *context,
        struct glyph_iterator *iter, const struct lookup *lookup)
{
    struct scriptshaping_cache *cache = context->cache;
    unsigned int i;
    WORD format;

    for (i = 0; i < lookup->subtable_count; ++i)
    {
        unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i);

        format = table_read_be_word(&cache->gpos.table, subtable_offset);

        if (format == 1)
        {
            const struct ot_gpos_mark_to_lig_format1 *format1 = table_read_ensure(&cache->gpos.table,
                    subtable_offset, sizeof(*format1));
            unsigned int mark_index, lig_index;
            struct glyph_iterator lig_iter;

            if (!format1)
                continue;

            mark_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset +
                    GET_BE_WORD(format1->mark_coverage), context->u.pos.glyphs[iter->pos]);
            if (mark_index == GLYPH_NOT_COVERED)
                continue;

            glyph_iterator_init(context, LOOKUP_FLAG_IGNORE_MARKS, iter->pos, 1, &lig_iter);
            if (!glyph_iterator_prev(&lig_iter))
                continue;

            lig_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset +
                    GET_BE_WORD(format1->lig_coverage), context->u.pos.glyphs[lig_iter.pos]);
            if (lig_index == GLYPH_NOT_COVERED)
                continue;

            FIXME("Unimplemented.\n");
        }
        else
            WARN("Unknown mark-to-ligature format %u.\n", format);
    }

    return FALSE;
}

static BOOL opentype_layout_apply_gpos_mark_to_mark_attachment(const struct scriptshaping_context *context,
        struct glyph_iterator *iter, const struct lookup *lookup)
{
    struct scriptshaping_cache *cache = context->cache;
    unsigned int i;
    WORD format;

    for (i = 0; i < lookup->subtable_count; ++i)
    {
        unsigned int subtable_offset = opentype_layout_get_gpos_subtable(cache, lookup->offset, i);

        format = table_read_be_word(&cache->gpos.table, subtable_offset);

        if (format == 1)
        {
            const struct ot_gpos_mark_to_mark_format1 *format1 = table_read_ensure(&cache->gpos.table,
                    subtable_offset, sizeof(*format1));
            unsigned int count, mark1_array_offset, mark2_array_offset, mark_class_count;
            unsigned int mark1_index, mark2_index, mark2_anchor;
            const struct ot_gpos_mark_array *mark1_array;
            const struct ot_gpos_base_array *mark2_array;
            float mark1_x, mark1_y, mark2_x, mark2_y;
            struct glyph_iterator mark_iter;

            if (!format1)
                continue;

            mark1_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset +
                    GET_BE_WORD(format1->mark1_coverage), context->u.pos.glyphs[iter->pos]);

            mark1_array_offset = subtable_offset + GET_BE_WORD(format1->mark1_array);
            if (!(count = table_read_be_word(&cache->gpos.table, mark1_array_offset)))
                continue;

            mark1_array = table_read_ensure(&cache->gpos.table, mark1_array_offset,
                    FIELD_OFFSET(struct ot_gpos_mark_array, records[count]));
            if (!mark1_array)
                continue;

            if (mark1_index == GLYPH_NOT_COVERED || mark1_index >= count)
                continue;

            glyph_iterator_init(context, lookup->flags & ~LOOKUP_FLAG_IGNORE_MASK, iter->pos, 1, &mark_iter);
            if (!glyph_iterator_prev(&mark_iter))
                continue;

            if (!context->u.pos.glyph_props[mark_iter.pos].isDiacritic)
                continue;

            mark2_array_offset = subtable_offset + GET_BE_WORD(format1->mark2_array);
            if (!(count = table_read_be_word(&cache->gpos.table, mark2_array_offset)))
                continue;

            mark_class_count = GET_BE_WORD(format1->mark_class_count);

            mark2_array = table_read_ensure(&cache->gpos.table, mark2_array_offset,
                    FIELD_OFFSET(struct ot_gpos_base_array, offsets[count * mark_class_count]));
            if (!mark2_array)
                continue;

            mark2_index = opentype_layout_is_glyph_covered(&cache->gpos.table, subtable_offset +
                    GET_BE_WORD(format1->mark2_coverage), context->u.pos.glyphs[mark_iter.pos]);

            if (mark2_index == GLYPH_NOT_COVERED || mark2_index >= count)
                continue;

            mark2_anchor = GET_BE_WORD(mark2_array->offsets[mark2_index * mark_class_count +
                    GET_BE_WORD(mark1_array->records[mark1_index].mark_class)]);
            opentype_layout_gpos_get_anchor(context, mark1_array_offset +
                    GET_BE_WORD(mark1_array->records[mark1_index].mark_anchor), iter->pos, &mark1_x, &mark1_y);
            opentype_layout_gpos_get_anchor(context, mark2_array_offset + mark2_anchor, mark_iter.pos,
                    &mark2_x, &mark2_y);

            context->offsets[iter->pos].advanceOffset = mark2_x - mark1_x;
            context->offsets[iter->pos].ascenderOffset = mark2_y - mark1_y;

            break;
        }
        else
            WARN("Unknown mark-to-mark format %u.\n", format);
    }

    return FALSE;
}

static BOOL opentype_layout_apply_gpos_contextual_positioning(const struct scriptshaping_context *context,
        struct glyph_iterator *iter, const struct lookup *lookup)
{
    return FALSE;
}

static BOOL opentype_layout_apply_gpos_chaining_contextual_positioning(const struct scriptshaping_context *context,
        struct glyph_iterator *iter, const struct lookup *lookup)
{
    return FALSE;
}

static void opentype_layout_apply_gpos_lookup(struct scriptshaping_context *context, int lookup_index)
{
    struct scriptshaping_cache *cache = context->cache;
    const struct ot_lookup_table *lookup_table;
    struct glyph_iterator iter;
    struct lookup lookup;
    WORD lookup_type;

    lookup.offset = table_read_be_word(&cache->gpos.table, cache->gpos.lookup_list +
            FIELD_OFFSET(struct ot_lookup_list, lookup[lookup_index]));
    if (!lookup.offset)
        return;

    lookup.offset += cache->gpos.lookup_list;

    if (!(lookup_table = table_read_ensure(&cache->gpos.table, lookup.offset, sizeof(*lookup_table))))
        return;

    lookup.subtable_count = GET_BE_WORD(lookup_table->subtable_count);
    if (!lookup.subtable_count)
        return;

    lookup_type = GET_BE_WORD(lookup_table->lookup_type);
    if (lookup_type == GPOS_LOOKUP_EXTENSION_POSITION)
    {
        const struct ot_gpos_extensionpos_format1 *extension = table_read_ensure(&cache->gpos.table,
                lookup.offset + GET_BE_WORD(lookup_table->subtable[0]), sizeof(*extension));
        WORD format;

        if (!extension)
            return;

        format = GET_BE_WORD(extension->format);
        if (format != 1)
        {
            WARN("Unexpected extension table format %u.\n", format);
            return;
        }

        lookup_type = GET_BE_WORD(extension->lookup_type);
    }
    lookup.flags = GET_BE_WORD(lookup_table->flags);

    glyph_iterator_init(context, lookup.flags, 0, context->glyph_count, &iter);

    while (iter.pos < context->glyph_count)
    {
        BOOL ret;

        if (!glyph_iterator_match(&iter))
        {
            ++iter.pos;
            continue;
        }

        switch (lookup_type)
        {
            case GPOS_LOOKUP_SINGLE_ADJUSTMENT:
                ret = opentype_layout_apply_gpos_single_adjustment(context, &iter, &lookup);
                break;
            case GPOS_LOOKUP_PAIR_ADJUSTMENT:
                ret = opentype_layout_apply_gpos_pair_adjustment(context, &iter, &lookup);
                break;
            case GPOS_LOOKUP_CURSIVE_ATTACHMENT:
                ret = opentype_layout_apply_gpos_cursive_attachment(context, &iter, &lookup);
                break;
            case GPOS_LOOKUP_MARK_TO_BASE_ATTACHMENT:
                ret = opentype_layout_apply_gpos_mark_to_base_attachment(context, &iter, &lookup);
                break;
            case GPOS_LOOKUP_MARK_TO_LIGATURE_ATTACHMENT:
                ret = opentype_layout_apply_gpos_mark_to_lig_attachment(context, &iter, &lookup);
                break;
            case GPOS_LOOKUP_MARK_TO_MARK_ATTACHMENT:
                ret = opentype_layout_apply_gpos_mark_to_mark_attachment(context, &iter, &lookup);
                break;
            case GPOS_LOOKUP_CONTEXTUAL_POSITION:
                ret = opentype_layout_apply_gpos_contextual_positioning(context, &iter, &lookup);
                break;
            case GPOS_LOOKUP_CONTEXTUAL_CHAINING_POSITION:
                ret = opentype_layout_apply_gpos_chaining_contextual_positioning(context, &iter, &lookup);
                break;
            case GPOS_LOOKUP_EXTENSION_POSITION:
                WARN("Recursive extension lookup.\n");
                ret = FALSE;
                break;
            default:
                WARN("Unknown lookup type %u.\n", lookup_type);
                return;
        }

        /* Some lookups update position after making changes. */
        if (!ret)
            ++iter.pos;
    }
}

struct lookups
{
    int *indexes;
    size_t capacity;
    size_t count;
};

static int lookups_sorting_compare(const void *left, const void *right)
{
    return *(int *)left - *(int *)right;
};

void opentype_layout_apply_gpos_features(struct scriptshaping_context *context,
        unsigned int script_index, unsigned int language_index, const struct shaping_features *features)
{
    WORD table_offset, langsys_offset, script_feature_count, total_feature_count, total_lookup_count;
    struct scriptshaping_cache *cache = context->cache;
    const struct ot_feature_list *feature_list;
    struct lookups lookups = { 0 };
    unsigned int i, j, l;

    /* ScriptTable offset. */
    table_offset = table_read_be_word(&cache->gpos.table, cache->gpos.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(&cache->gpos.table, cache->gpos.script_list + table_offset);
    else
        langsys_offset = table_read_be_word(&cache->gpos.table, cache->gpos.script_list + table_offset +
                FIELD_OFFSET(struct ot_script, langsys) + language_index * sizeof(struct ot_langsys_record) +
                FIELD_OFFSET(struct ot_langsys_record, langsys));

    script_feature_count = table_read_be_word(&cache->gpos.table, cache->gpos.script_list + table_offset +
            langsys_offset + FIELD_OFFSET(struct ot_langsys, feature_count));
    if (!script_feature_count)
        return;

    total_feature_count = table_read_be_word(&cache->gpos.table, cache->gpos.feature_list);
    if (!total_feature_count)
        return;

    total_lookup_count = table_read_be_word(&cache->gpos.table, cache->gpos.lookup_list);
    if (!total_lookup_count)
        return;

    feature_list = table_read_ensure(&cache->gpos.table, cache->gpos.feature_list,
            FIELD_OFFSET(struct ot_feature_list, features[total_feature_count]));
    if (!feature_list)
        return;

    /* Collect lookups for all given features. */
    for (i = 0; i < features->count; ++i)
    {
        for (j = 0; j < script_feature_count; ++j)
        {
            WORD feature_index = table_read_be_word(&cache->gpos.table, cache->gpos.script_list + table_offset +
                    langsys_offset + FIELD_OFFSET(struct ot_langsys, feature_index[j]));
            if (feature_index >= total_feature_count)
                continue;

            if (feature_list->features[feature_index].tag == features->tags[i])
            {
                WORD feature_offset = GET_BE_WORD(feature_list->features[feature_index].offset);
                WORD lookup_count;

                lookup_count = table_read_be_word(&cache->gpos.table, cache->gpos.feature_list + feature_offset +
                        FIELD_OFFSET(struct ot_feature, lookup_count));
                if (!lookup_count)
                    continue;

                if (!dwrite_array_reserve((void **)&lookups.indexes, &lookups.capacity, lookups.count + lookup_count,
                        sizeof(*lookups.indexes)))
                {
                    heap_free(lookups.indexes);
                    return;
                }

                for (l = 0; l < lookup_count; ++l)
                {
                    WORD lookup_index = table_read_be_word(&cache->gpos.table, cache->gpos.feature_list +
                            feature_offset + FIELD_OFFSET(struct ot_feature, lookuplist_index[l]));

                    if (lookup_index >= total_lookup_count)
                        continue;

                    lookups.indexes[lookups.count++] = lookup_index;
                }
            }
        }
    }

    /* Sort lookups. */
    qsort(lookups.indexes, lookups.count, sizeof(*lookups.indexes), lookups_sorting_compare);

    for (l = 0; l < lookups.count; ++l)
    {
        /* Skip duplicates. */
        if (l && lookups.indexes[l] == lookups.indexes[l - 1])
            continue;

        opentype_layout_apply_gpos_lookup(context, lookups.indexes[l]);
    }

    heap_free(lookups.indexes);
}