| /* |
| * Opentype font interfaces for the Uniscribe Script Processor (usp10.dll) |
| * |
| * Copyright 2012 CodeWeavers, Aric Stewart |
| * |
| * 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 |
| * |
| */ |
| #include <stdarg.h> |
| #include <stdlib.h> |
| |
| #include "windef.h" |
| #include "winbase.h" |
| #include "wingdi.h" |
| #include "winuser.h" |
| #include "winnls.h" |
| #include "usp10.h" |
| #include "winternl.h" |
| |
| #include "usp10_internal.h" |
| |
| #include "wine/debug.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(uniscribe); |
| |
| #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 |
| |
| /* These are all structures needed for the cmap format 12 table */ |
| #define CMAP_TAG MS_MAKE_TAG('c', 'm', 'a', 'p') |
| |
| typedef struct { |
| WORD platformID; |
| WORD encodingID; |
| DWORD offset; |
| } CMAP_EncodingRecord; |
| |
| typedef struct { |
| WORD version; |
| WORD numTables; |
| CMAP_EncodingRecord tables[1]; |
| } CMAP_Header; |
| |
| typedef struct { |
| DWORD startCharCode; |
| DWORD endCharCode; |
| DWORD startGlyphID; |
| } CMAP_SegmentedCoverage_group; |
| |
| typedef struct { |
| WORD format; |
| WORD reserved; |
| DWORD length; |
| DWORD language; |
| DWORD nGroups; |
| CMAP_SegmentedCoverage_group groups[1]; |
| } CMAP_SegmentedCoverage; |
| |
| /* These are all structures needed for the GDEF table */ |
| #define GDEF_TAG MS_MAKE_TAG('G', 'D', 'E', 'F') |
| |
| enum {BaseGlyph=1, LigatureGlyph, MarkGlyph, ComponentGlyph}; |
| |
| typedef struct { |
| DWORD Version; |
| WORD GlyphClassDef; |
| WORD AttachList; |
| WORD LigCaretList; |
| WORD MarkAttachClassDef; |
| } GDEF_Header; |
| |
| typedef struct { |
| WORD ClassFormat; |
| WORD StartGlyph; |
| WORD GlyphCount; |
| WORD ClassValueArray[1]; |
| } GDEF_ClassDefFormat1; |
| |
| typedef struct { |
| WORD Start; |
| WORD End; |
| WORD Class; |
| } GDEF_ClassRangeRecord; |
| |
| typedef struct { |
| WORD ClassFormat; |
| WORD ClassRangeCount; |
| GDEF_ClassRangeRecord ClassRangeRecord[1]; |
| } GDEF_ClassDefFormat2; |
| |
| /* These are all structures needed for the GSUB table */ |
| |
| typedef struct { |
| DWORD version; |
| WORD ScriptList; |
| WORD FeatureList; |
| WORD LookupList; |
| } GSUB_Header; |
| |
| typedef struct { |
| CHAR ScriptTag[4]; |
| WORD Script; |
| } OT_ScriptRecord; |
| |
| typedef struct { |
| WORD ScriptCount; |
| OT_ScriptRecord ScriptRecord[1]; |
| } OT_ScriptList; |
| |
| typedef struct { |
| CHAR LangSysTag[4]; |
| WORD LangSys; |
| } OT_LangSysRecord; |
| |
| typedef struct { |
| WORD DefaultLangSys; |
| WORD LangSysCount; |
| OT_LangSysRecord LangSysRecord[1]; |
| } OT_Script; |
| |
| typedef struct { |
| WORD LookupOrder; /* Reserved */ |
| WORD ReqFeatureIndex; |
| WORD FeatureCount; |
| WORD FeatureIndex[1]; |
| } OT_LangSys; |
| |
| typedef struct { |
| CHAR FeatureTag[4]; |
| WORD Feature; |
| } OT_FeatureRecord; |
| |
| typedef struct { |
| WORD FeatureCount; |
| OT_FeatureRecord FeatureRecord[1]; |
| } OT_FeatureList; |
| |
| typedef struct { |
| WORD FeatureParams; /* Reserved */ |
| WORD LookupCount; |
| WORD LookupListIndex[1]; |
| } OT_Feature; |
| |
| typedef struct { |
| WORD LookupCount; |
| WORD Lookup[1]; |
| } OT_LookupList; |
| |
| typedef struct { |
| WORD LookupType; |
| WORD LookupFlag; |
| WORD SubTableCount; |
| WORD SubTable[1]; |
| } OT_LookupTable; |
| |
| typedef struct { |
| WORD CoverageFormat; |
| WORD GlyphCount; |
| WORD GlyphArray[1]; |
| } OT_CoverageFormat1; |
| |
| typedef struct { |
| WORD Start; |
| WORD End; |
| WORD StartCoverageIndex; |
| } OT_RangeRecord; |
| |
| typedef struct { |
| WORD CoverageFormat; |
| WORD RangeCount; |
| OT_RangeRecord RangeRecord[1]; |
| } OT_CoverageFormat2; |
| |
| typedef struct { |
| WORD SubstFormat; /* = 1 */ |
| WORD Coverage; |
| WORD DeltaGlyphID; |
| } GSUB_SingleSubstFormat1; |
| |
| typedef struct { |
| WORD SubstFormat; /* = 2 */ |
| WORD Coverage; |
| WORD GlyphCount; |
| WORD Substitute[1]; |
| }GSUB_SingleSubstFormat2; |
| |
| typedef struct { |
| WORD SubstFormat; /* = 1 */ |
| WORD Coverage; |
| WORD SequenceCount; |
| WORD Sequence[1]; |
| }GSUB_MultipleSubstFormat1; |
| |
| typedef struct { |
| WORD GlyphCount; |
| WORD Substitute[1]; |
| }GSUB_Sequence; |
| |
| typedef struct { |
| WORD SubstFormat; /* = 1 */ |
| WORD Coverage; |
| WORD LigSetCount; |
| WORD LigatureSet[1]; |
| }GSUB_LigatureSubstFormat1; |
| |
| typedef struct { |
| WORD LigatureCount; |
| WORD Ligature[1]; |
| }GSUB_LigatureSet; |
| |
| typedef struct{ |
| WORD LigGlyph; |
| WORD CompCount; |
| WORD Component[1]; |
| }GSUB_Ligature; |
| |
| typedef struct{ |
| WORD SequenceIndex; |
| WORD LookupListIndex; |
| |
| }GSUB_SubstLookupRecord; |
| |
| typedef struct{ |
| WORD SubstFormat; /* = 1 */ |
| WORD Coverage; |
| WORD ChainSubRuleSetCount; |
| WORD ChainSubRuleSet[1]; |
| }GSUB_ChainContextSubstFormat1; |
| |
| typedef struct { |
| WORD SubstFormat; /* = 3 */ |
| WORD BacktrackGlyphCount; |
| WORD Coverage[1]; |
| }GSUB_ChainContextSubstFormat3_1; |
| |
| typedef struct{ |
| WORD InputGlyphCount; |
| WORD Coverage[1]; |
| }GSUB_ChainContextSubstFormat3_2; |
| |
| typedef struct{ |
| WORD LookaheadGlyphCount; |
| WORD Coverage[1]; |
| }GSUB_ChainContextSubstFormat3_3; |
| |
| typedef struct{ |
| WORD SubstCount; |
| GSUB_SubstLookupRecord SubstLookupRecord[1]; |
| }GSUB_ChainContextSubstFormat3_4; |
| |
| typedef struct { |
| WORD SubstFormat; /* = 1 */ |
| WORD Coverage; |
| WORD AlternateSetCount; |
| WORD AlternateSet[1]; |
| } GSUB_AlternateSubstFormat1; |
| |
| typedef struct{ |
| WORD GlyphCount; |
| WORD Alternate[1]; |
| } GSUB_AlternateSet; |
| |
| /********** |
| * CMAP |
| **********/ |
| |
| static VOID *load_CMAP_format12_table(HDC hdc, ScriptCache *psc) |
| { |
| CMAP_Header *CMAP_Table = NULL; |
| int length; |
| int i; |
| |
| if (!psc->CMAP_Table) |
| { |
| length = GetFontData(hdc, CMAP_TAG , 0, NULL, 0); |
| if (length != GDI_ERROR) |
| { |
| psc->CMAP_Table = HeapAlloc(GetProcessHeap(),0,length); |
| GetFontData(hdc, CMAP_TAG , 0, psc->CMAP_Table, length); |
| TRACE("Loaded cmap table of %i bytes\n",length); |
| } |
| else |
| return NULL; |
| } |
| |
| CMAP_Table = psc->CMAP_Table; |
| |
| for (i = 0; i < GET_BE_WORD(CMAP_Table->numTables); i++) |
| { |
| if ( (GET_BE_WORD(CMAP_Table->tables[i].platformID) == 3) && |
| (GET_BE_WORD(CMAP_Table->tables[i].encodingID) == 10) ) |
| { |
| CMAP_SegmentedCoverage *format = (CMAP_SegmentedCoverage*)(((BYTE*)CMAP_Table) + GET_BE_DWORD(CMAP_Table->tables[i].offset)); |
| if (GET_BE_WORD(format->format) == 12) |
| return format; |
| } |
| } |
| return NULL; |
| } |
| |
| static int compare_group(const void *a, const void* b) |
| { |
| const DWORD *chr = a; |
| const CMAP_SegmentedCoverage_group *group = b; |
| |
| if (*chr < GET_BE_DWORD(group->startCharCode)) |
| return -1; |
| if (*chr > GET_BE_DWORD(group->endCharCode)) |
| return 1; |
| return 0; |
| } |
| |
| DWORD OpenType_CMAP_GetGlyphIndex(HDC hdc, ScriptCache *psc, DWORD utf32c, LPWORD pgi, DWORD flags) |
| { |
| /* BMP: use gdi32 for ease */ |
| if (utf32c < 0x10000) |
| { |
| WCHAR ch = utf32c; |
| return GetGlyphIndicesW(hdc,&ch, 1, pgi, flags); |
| } |
| |
| if (!psc->CMAP_format12_Table) |
| psc->CMAP_format12_Table = load_CMAP_format12_table(hdc, psc); |
| |
| if (flags & GGI_MARK_NONEXISTING_GLYPHS) |
| *pgi = 0xffff; |
| else |
| *pgi = 0; |
| |
| if (psc->CMAP_format12_Table) |
| { |
| CMAP_SegmentedCoverage *format = NULL; |
| CMAP_SegmentedCoverage_group *group = NULL; |
| |
| format = (CMAP_SegmentedCoverage *)psc->CMAP_format12_Table; |
| |
| group = bsearch(&utf32c, format->groups, GET_BE_DWORD(format->nGroups), |
| sizeof(CMAP_SegmentedCoverage_group), compare_group); |
| |
| if (group) |
| { |
| DWORD offset = utf32c - GET_BE_DWORD(group->startCharCode); |
| *pgi = GET_BE_DWORD(group->startGlyphID) + offset; |
| return 0; |
| } |
| } |
| return 0; |
| } |
| |
| /********** |
| * GDEF |
| **********/ |
| |
| static WORD GDEF_get_glyph_class(const GDEF_Header *header, WORD glyph) |
| { |
| int offset; |
| WORD class = 0; |
| const GDEF_ClassDefFormat1 *cf1; |
| |
| if (!header) |
| return 0; |
| |
| offset = GET_BE_WORD(header->GlyphClassDef); |
| if (!offset) |
| return 0; |
| |
| cf1 = (GDEF_ClassDefFormat1*)(((BYTE*)header)+offset); |
| if (GET_BE_WORD(cf1->ClassFormat) == 1) |
| { |
| if (glyph >= GET_BE_WORD(cf1->StartGlyph)) |
| { |
| int index = glyph - GET_BE_WORD(cf1->StartGlyph); |
| if (index < GET_BE_WORD(cf1->GlyphCount)) |
| class = GET_BE_WORD(cf1->ClassValueArray[index]); |
| } |
| } |
| else if (GET_BE_WORD(cf1->ClassFormat) == 2) |
| { |
| const GDEF_ClassDefFormat2 *cf2 = (GDEF_ClassDefFormat2*)cf1; |
| int i, top; |
| top = GET_BE_WORD(cf2->ClassRangeCount); |
| for (i = 0; i < top; i++) |
| { |
| if (glyph >= GET_BE_WORD(cf2->ClassRangeRecord[i].Start) && |
| glyph <= GET_BE_WORD(cf2->ClassRangeRecord[i].End)) |
| { |
| class = GET_BE_WORD(cf2->ClassRangeRecord[i].Class); |
| break; |
| } |
| } |
| } |
| else |
| ERR("Unknown Class Format %i\n",GET_BE_WORD(cf1->ClassFormat)); |
| |
| return class; |
| } |
| |
| static VOID *load_gdef_table(HDC hdc) |
| { |
| VOID* GDEF_Table = NULL; |
| int length = GetFontData(hdc, GDEF_TAG , 0, NULL, 0); |
| if (length != GDI_ERROR) |
| { |
| GDEF_Table = HeapAlloc(GetProcessHeap(),0,length); |
| GetFontData(hdc, GDEF_TAG , 0, GDEF_Table, length); |
| TRACE("Loaded GDEF table of %i bytes\n",length); |
| } |
| return GDEF_Table; |
| } |
| |
| void OpenType_GDEF_UpdateGlyphProps(HDC hdc, ScriptCache *psc, const WORD *pwGlyphs, const WORD cGlyphs, WORD* pwLogClust, const WORD cChars, SCRIPT_GLYPHPROP *pGlyphProp) |
| { |
| int i; |
| |
| if (!psc->GDEF_Table) |
| psc->GDEF_Table = load_gdef_table(hdc); |
| |
| for (i = 0; i < cGlyphs; i++) |
| { |
| WORD class; |
| int char_count = 0; |
| int k; |
| |
| k = USP10_FindGlyphInLogClust(pwLogClust, cChars, i); |
| if (k >= 0) |
| { |
| for (; k < cChars && pwLogClust[k] == i; k++) |
| char_count++; |
| } |
| |
| class = GDEF_get_glyph_class(psc->GDEF_Table, pwGlyphs[i]); |
| |
| switch (class) |
| { |
| case 0: |
| case BaseGlyph: |
| pGlyphProp[i].sva.fClusterStart = 1; |
| pGlyphProp[i].sva.fDiacritic = 0; |
| pGlyphProp[i].sva.fZeroWidth = 0; |
| break; |
| case LigatureGlyph: |
| pGlyphProp[i].sva.fClusterStart = 1; |
| pGlyphProp[i].sva.fDiacritic = 0; |
| pGlyphProp[i].sva.fZeroWidth = 0; |
| break; |
| case MarkGlyph: |
| pGlyphProp[i].sva.fClusterStart = 0; |
| pGlyphProp[i].sva.fDiacritic = 1; |
| pGlyphProp[i].sva.fZeroWidth = 1; |
| break; |
| case ComponentGlyph: |
| pGlyphProp[i].sva.fClusterStart = 0; |
| pGlyphProp[i].sva.fDiacritic = 0; |
| pGlyphProp[i].sva.fZeroWidth = 0; |
| break; |
| default: |
| ERR("Unknown glyph class %i\n",class); |
| pGlyphProp[i].sva.fClusterStart = 1; |
| pGlyphProp[i].sva.fDiacritic = 0; |
| pGlyphProp[i].sva.fZeroWidth = 0; |
| } |
| |
| if (char_count == 0) |
| pGlyphProp[i].sva.fClusterStart = 0; |
| } |
| } |
| |
| /********** |
| * GSUB |
| **********/ |
| static INT GSUB_apply_lookup(const OT_LookupList* lookup, INT lookup_index, WORD *glyphs, INT glyph_index, INT write_dir, INT *glyph_count); |
| |
| static INT GSUB_is_glyph_covered(LPCVOID table , UINT glyph) |
| { |
| const OT_CoverageFormat1* cf1; |
| |
| cf1 = table; |
| |
| if (GET_BE_WORD(cf1->CoverageFormat) == 1) |
| { |
| int count = GET_BE_WORD(cf1->GlyphCount); |
| int i; |
| TRACE("Coverage Format 1, %i glyphs\n",count); |
| for (i = 0; i < count; i++) |
| if (glyph == GET_BE_WORD(cf1->GlyphArray[i])) |
| return i; |
| return -1; |
| } |
| else if (GET_BE_WORD(cf1->CoverageFormat) == 2) |
| { |
| const OT_CoverageFormat2* cf2; |
| int i; |
| int count; |
| cf2 = (const OT_CoverageFormat2*)cf1; |
| |
| count = GET_BE_WORD(cf2->RangeCount); |
| TRACE("Coverage Format 2, %i ranges\n",count); |
| for (i = 0; i < count; i++) |
| { |
| if (glyph < GET_BE_WORD(cf2->RangeRecord[i].Start)) |
| return -1; |
| if ((glyph >= GET_BE_WORD(cf2->RangeRecord[i].Start)) && |
| (glyph <= GET_BE_WORD(cf2->RangeRecord[i].End))) |
| { |
| return (GET_BE_WORD(cf2->RangeRecord[i].StartCoverageIndex) + |
| glyph - GET_BE_WORD(cf2->RangeRecord[i].Start)); |
| } |
| } |
| return -1; |
| } |
| else |
| ERR("Unknown CoverageFormat %i\n",GET_BE_WORD(cf1->CoverageFormat)); |
| |
| return -1; |
| } |
| |
| static INT GSUB_apply_SingleSubst(const OT_LookupTable *look, WORD *glyphs, INT glyph_index, INT write_dir, INT *glyph_count) |
| { |
| int j; |
| TRACE("Single Substitution Subtable\n"); |
| |
| for (j = 0; j < GET_BE_WORD(look->SubTableCount); j++) |
| { |
| int offset; |
| const GSUB_SingleSubstFormat1 *ssf1; |
| offset = GET_BE_WORD(look->SubTable[j]); |
| ssf1 = (const GSUB_SingleSubstFormat1*)((const BYTE*)look+offset); |
| if (GET_BE_WORD(ssf1->SubstFormat) == 1) |
| { |
| int offset = GET_BE_WORD(ssf1->Coverage); |
| TRACE(" subtype 1, delta %i\n", GET_BE_WORD(ssf1->DeltaGlyphID)); |
| if (GSUB_is_glyph_covered((const BYTE*)ssf1+offset, glyphs[glyph_index]) != -1) |
| { |
| TRACE(" Glyph 0x%x ->",glyphs[glyph_index]); |
| glyphs[glyph_index] = glyphs[glyph_index] + GET_BE_WORD(ssf1->DeltaGlyphID); |
| TRACE(" 0x%x\n",glyphs[glyph_index]); |
| return glyph_index + write_dir; |
| } |
| } |
| else |
| { |
| const GSUB_SingleSubstFormat2 *ssf2; |
| INT index; |
| INT offset; |
| |
| ssf2 = (const GSUB_SingleSubstFormat2 *)ssf1; |
| offset = GET_BE_WORD(ssf1->Coverage); |
| TRACE(" subtype 2, glyph count %i\n", GET_BE_WORD(ssf2->GlyphCount)); |
| index = GSUB_is_glyph_covered((const BYTE*)ssf2+offset, glyphs[glyph_index]); |
| TRACE(" Coverage index %i\n",index); |
| if (index != -1) |
| { |
| if (glyphs[glyph_index] == GET_BE_WORD(ssf2->Substitute[index])) |
| return GSUB_E_NOGLYPH; |
| |
| TRACE(" Glyph is 0x%x ->",glyphs[glyph_index]); |
| glyphs[glyph_index] = GET_BE_WORD(ssf2->Substitute[index]); |
| TRACE("0x%x\n",glyphs[glyph_index]); |
| return glyph_index + write_dir; |
| } |
| } |
| } |
| return GSUB_E_NOGLYPH; |
| } |
| |
| static INT GSUB_apply_MultipleSubst(const OT_LookupTable *look, WORD *glyphs, INT glyph_index, INT write_dir, INT *glyph_count) |
| { |
| int j; |
| TRACE("Multiple Substitution Subtable\n"); |
| |
| for (j = 0; j < GET_BE_WORD(look->SubTableCount); j++) |
| { |
| int offset, index; |
| const GSUB_MultipleSubstFormat1 *msf1; |
| offset = GET_BE_WORD(look->SubTable[j]); |
| msf1 = (const GSUB_MultipleSubstFormat1*)((const BYTE*)look+offset); |
| |
| offset = GET_BE_WORD(msf1->Coverage); |
| index = GSUB_is_glyph_covered((const BYTE*)msf1+offset, glyphs[glyph_index]); |
| if (index != -1) |
| { |
| const GSUB_Sequence *seq; |
| int sub_count; |
| int j; |
| offset = GET_BE_WORD(msf1->Sequence[index]); |
| seq = (const GSUB_Sequence*)((const BYTE*)msf1+offset); |
| sub_count = GET_BE_WORD(seq->GlyphCount); |
| TRACE(" Glyph 0x%x (+%i)->",glyphs[glyph_index],(sub_count-1)); |
| |
| for (j = (*glyph_count)+(sub_count-1); j > glyph_index; j--) |
| glyphs[j] =glyphs[j-(sub_count-1)]; |
| |
| for (j = 0; j < sub_count; j++) |
| if (write_dir < 0) |
| glyphs[glyph_index + (sub_count-1) - j] = GET_BE_WORD(seq->Substitute[j]); |
| else |
| glyphs[glyph_index + j] = GET_BE_WORD(seq->Substitute[j]); |
| |
| *glyph_count = *glyph_count + (sub_count - 1); |
| |
| if (TRACE_ON(uniscribe)) |
| { |
| for (j = 0; j < sub_count; j++) |
| TRACE(" 0x%x",glyphs[glyph_index+j]); |
| TRACE("\n"); |
| } |
| |
| return glyph_index + (sub_count * write_dir); |
| } |
| } |
| return GSUB_E_NOGLYPH; |
| } |
| |
| static INT GSUB_apply_AlternateSubst(const OT_LookupTable *look, WORD *glyphs, INT glyph_index, INT write_dir, INT *glyph_count) |
| { |
| int j; |
| TRACE("Alternate Substitution Subtable\n"); |
| |
| for (j = 0; j < GET_BE_WORD(look->SubTableCount); j++) |
| { |
| int offset; |
| const GSUB_AlternateSubstFormat1 *asf1; |
| INT index; |
| |
| offset = GET_BE_WORD(look->SubTable[j]); |
| asf1 = (const GSUB_AlternateSubstFormat1*)((const BYTE*)look+offset); |
| offset = GET_BE_WORD(asf1->Coverage); |
| |
| index = GSUB_is_glyph_covered((const BYTE*)asf1+offset, glyphs[glyph_index]); |
| if (index != -1) |
| { |
| const GSUB_AlternateSet *as; |
| offset = GET_BE_WORD(asf1->AlternateSet[index]); |
| as = (const GSUB_AlternateSet*)((const BYTE*)asf1+offset); |
| FIXME("%i alternates, picking index 0\n",GET_BE_WORD(as->GlyphCount)); |
| if (glyphs[glyph_index] == GET_BE_WORD(as->Alternate[0])) |
| return GSUB_E_NOGLYPH; |
| |
| TRACE(" Glyph 0x%x ->",glyphs[glyph_index]); |
| glyphs[glyph_index] = GET_BE_WORD(as->Alternate[0]); |
| TRACE(" 0x%x\n",glyphs[glyph_index]); |
| return glyph_index + write_dir; |
| } |
| } |
| return GSUB_E_NOGLYPH; |
| } |
| |
| static INT GSUB_apply_LigatureSubst(const OT_LookupTable *look, WORD *glyphs, INT glyph_index, INT write_dir, INT *glyph_count) |
| { |
| int j; |
| |
| TRACE("Ligature Substitution Subtable\n"); |
| for (j = 0; j < GET_BE_WORD(look->SubTableCount); j++) |
| { |
| const GSUB_LigatureSubstFormat1 *lsf1; |
| int offset,index; |
| |
| offset = GET_BE_WORD(look->SubTable[j]); |
| lsf1 = (const GSUB_LigatureSubstFormat1*)((const BYTE*)look+offset); |
| offset = GET_BE_WORD(lsf1->Coverage); |
| index = GSUB_is_glyph_covered((const BYTE*)lsf1+offset, glyphs[glyph_index]); |
| TRACE(" Coverage index %i\n",index); |
| if (index != -1) |
| { |
| const GSUB_LigatureSet *ls; |
| int k, count; |
| |
| offset = GET_BE_WORD(lsf1->LigatureSet[index]); |
| ls = (const GSUB_LigatureSet*)((const BYTE*)lsf1+offset); |
| count = GET_BE_WORD(ls->LigatureCount); |
| TRACE(" LigatureSet has %i members\n",count); |
| for (k = 0; k < count; k++) |
| { |
| const GSUB_Ligature *lig; |
| int CompCount,l,CompIndex; |
| |
| offset = GET_BE_WORD(ls->Ligature[k]); |
| lig = (const GSUB_Ligature*)((const BYTE*)ls+offset); |
| CompCount = GET_BE_WORD(lig->CompCount) - 1; |
| CompIndex = glyph_index+write_dir; |
| for (l = 0; l < CompCount && CompIndex >= 0 && CompIndex < *glyph_count; l++) |
| { |
| int CompGlyph; |
| CompGlyph = GET_BE_WORD(lig->Component[l]); |
| if (CompGlyph != glyphs[CompIndex]) |
| break; |
| CompIndex += write_dir; |
| } |
| if (l == CompCount) |
| { |
| int replaceIdx = glyph_index; |
| if (write_dir < 0) |
| replaceIdx = glyph_index - CompCount; |
| |
| TRACE(" Glyph is 0x%x (+%i) ->",glyphs[glyph_index],CompCount); |
| glyphs[replaceIdx] = GET_BE_WORD(lig->LigGlyph); |
| TRACE("0x%x\n",glyphs[replaceIdx]); |
| if (CompCount > 0) |
| { |
| int j; |
| for (j = replaceIdx + 1; j < *glyph_count; j++) |
| glyphs[j] =glyphs[j+CompCount]; |
| *glyph_count = *glyph_count - CompCount; |
| } |
| return replaceIdx + write_dir; |
| } |
| } |
| } |
| } |
| return GSUB_E_NOGLYPH; |
| } |
| |
| static INT GSUB_apply_ChainContextSubst(const OT_LookupList* lookup, const OT_LookupTable *look, WORD *glyphs, INT glyph_index, INT write_dir, INT *glyph_count) |
| { |
| int j; |
| BOOL done = FALSE; |
| |
| TRACE("Chaining Contextual Substitution Subtable\n"); |
| for (j = 0; j < GET_BE_WORD(look->SubTableCount) && !done; j++) |
| { |
| const GSUB_ChainContextSubstFormat1 *ccsf1; |
| int offset; |
| int dirLookahead = write_dir; |
| int dirBacktrack = -1 * write_dir; |
| |
| offset = GET_BE_WORD(look->SubTable[j]); |
| ccsf1 = (const GSUB_ChainContextSubstFormat1*)((const BYTE*)look+offset); |
| if (GET_BE_WORD(ccsf1->SubstFormat) == 1) |
| { |
| FIXME(" TODO: subtype 1 (Simple context glyph substitution)\n"); |
| continue; |
| } |
| else if (GET_BE_WORD(ccsf1->SubstFormat) == 2) |
| { |
| FIXME(" TODO: subtype 2 (Class-based Chaining Context Glyph Substitution)\n"); |
| continue; |
| } |
| else if (GET_BE_WORD(ccsf1->SubstFormat) == 3) |
| { |
| int k; |
| int indexGlyphs; |
| const GSUB_ChainContextSubstFormat3_1 *ccsf3_1; |
| const GSUB_ChainContextSubstFormat3_2 *ccsf3_2; |
| const GSUB_ChainContextSubstFormat3_3 *ccsf3_3; |
| const GSUB_ChainContextSubstFormat3_4 *ccsf3_4; |
| int newIndex = glyph_index; |
| |
| ccsf3_1 = (const GSUB_ChainContextSubstFormat3_1 *)ccsf1; |
| |
| TRACE(" subtype 3 (Coverage-based Chaining Context Glyph Substitution)\n"); |
| |
| for (k = 0; k < GET_BE_WORD(ccsf3_1->BacktrackGlyphCount); k++) |
| { |
| offset = GET_BE_WORD(ccsf3_1->Coverage[k]); |
| if (GSUB_is_glyph_covered((const BYTE*)ccsf3_1+offset, glyphs[glyph_index + (dirBacktrack * (k+1))]) == -1) |
| break; |
| } |
| if (k != GET_BE_WORD(ccsf3_1->BacktrackGlyphCount)) |
| continue; |
| TRACE("Matched Backtrack\n"); |
| |
| ccsf3_2 = (const GSUB_ChainContextSubstFormat3_2 *)(((LPBYTE)ccsf1)+sizeof(GSUB_ChainContextSubstFormat3_1) + (sizeof(WORD) * (GET_BE_WORD(ccsf3_1->BacktrackGlyphCount)-1))); |
| |
| indexGlyphs = GET_BE_WORD(ccsf3_2->InputGlyphCount); |
| for (k = 0; k < indexGlyphs; k++) |
| { |
| offset = GET_BE_WORD(ccsf3_2->Coverage[k]); |
| if (GSUB_is_glyph_covered((const BYTE*)ccsf3_1+offset, glyphs[glyph_index + (write_dir * k)]) == -1) |
| break; |
| } |
| if (k != indexGlyphs) |
| continue; |
| TRACE("Matched IndexGlyphs\n"); |
| |
| ccsf3_3 = (const GSUB_ChainContextSubstFormat3_3 *)(((LPBYTE)ccsf3_2)+sizeof(GSUB_ChainContextSubstFormat3_2) + (sizeof(WORD) * (GET_BE_WORD(ccsf3_2->InputGlyphCount)-1))); |
| |
| for (k = 0; k < GET_BE_WORD(ccsf3_3->LookaheadGlyphCount); k++) |
| { |
| offset = GET_BE_WORD(ccsf3_3->Coverage[k]); |
| if (GSUB_is_glyph_covered((const BYTE*)ccsf3_1+offset, glyphs[glyph_index + (dirLookahead * (indexGlyphs + k))]) == -1) |
| break; |
| } |
| if (k != GET_BE_WORD(ccsf3_3->LookaheadGlyphCount)) |
| continue; |
| TRACE("Matched LookAhead\n"); |
| |
| ccsf3_4 = (const GSUB_ChainContextSubstFormat3_4 *)(((LPBYTE)ccsf3_3)+sizeof(GSUB_ChainContextSubstFormat3_3) + (sizeof(WORD) * (GET_BE_WORD(ccsf3_3->LookaheadGlyphCount)-1))); |
| |
| if (GET_BE_WORD(ccsf3_4->SubstCount)) |
| { |
| for (k = 0; k < GET_BE_WORD(ccsf3_4->SubstCount); k++) |
| { |
| int lookupIndex = GET_BE_WORD(ccsf3_4->SubstLookupRecord[k].LookupListIndex); |
| int SequenceIndex = GET_BE_WORD(ccsf3_4->SubstLookupRecord[k].SequenceIndex) * write_dir; |
| |
| TRACE("SUBST: %i -> %i %i\n",k, SequenceIndex, lookupIndex); |
| newIndex = GSUB_apply_lookup(lookup, lookupIndex, glyphs, glyph_index + SequenceIndex, write_dir, glyph_count); |
| if (newIndex == -1) |
| { |
| ERR("Chain failed to generate a glyph\n"); |
| continue; |
| } |
| } |
| return newIndex; |
| } |
| else return GSUB_E_NOGLYPH; |
| } |
| } |
| return -1; |
| } |
| |
| static INT GSUB_apply_lookup(const OT_LookupList* lookup, INT lookup_index, WORD *glyphs, INT glyph_index, INT write_dir, INT *glyph_count) |
| { |
| int offset; |
| const OT_LookupTable *look; |
| |
| offset = GET_BE_WORD(lookup->Lookup[lookup_index]); |
| look = (const OT_LookupTable*)((const BYTE*)lookup + offset); |
| TRACE("type %i, flag %x, subtables %i\n",GET_BE_WORD(look->LookupType),GET_BE_WORD(look->LookupFlag),GET_BE_WORD(look->SubTableCount)); |
| switch(GET_BE_WORD(look->LookupType)) |
| { |
| case 1: |
| return GSUB_apply_SingleSubst(look, glyphs, glyph_index, write_dir, glyph_count); |
| case 2: |
| return GSUB_apply_MultipleSubst(look, glyphs, glyph_index, write_dir, glyph_count); |
| case 3: |
| return GSUB_apply_AlternateSubst(look, glyphs, glyph_index, write_dir, glyph_count); |
| case 4: |
| return GSUB_apply_LigatureSubst(look, glyphs, glyph_index, write_dir, glyph_count); |
| case 6: |
| return GSUB_apply_ChainContextSubst(lookup, look, glyphs, glyph_index, write_dir, glyph_count); |
| default: |
| FIXME("We do not handle SubType %i\n",GET_BE_WORD(look->LookupType)); |
| } |
| return GSUB_E_NOGLYPH; |
| } |
| |
| INT OpenType_apply_GSUB_lookup(LPCVOID table, INT lookup_index, WORD *glyphs, INT glyph_index, INT write_dir, INT *glyph_count) |
| { |
| const GSUB_Header *header = (const GSUB_Header *)table; |
| const OT_LookupList *lookup = (const OT_LookupList*)((const BYTE*)header + GET_BE_WORD(header->LookupList)); |
| |
| return GSUB_apply_lookup(lookup, lookup_index, glyphs, glyph_index, write_dir, glyph_count); |
| } |
| |
| static void GSUB_initialize_script_cache(ScriptCache *psc) |
| { |
| int i; |
| |
| if (!psc->script_count) |
| { |
| const OT_ScriptList *script; |
| const GSUB_Header* header = (const GSUB_Header*)psc->GSUB_Table; |
| script = (const OT_ScriptList*)((const BYTE*)header + GET_BE_WORD(header->ScriptList)); |
| psc->script_count = GET_BE_WORD(script->ScriptCount); |
| TRACE("initializing %i scripts in this font\n",psc->script_count); |
| if (psc->script_count) |
| { |
| psc->scripts = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(LoadedScript) * psc->script_count); |
| for (i = 0; i < psc->script_count; i++) |
| { |
| int offset = GET_BE_WORD(script->ScriptRecord[i].Script); |
| psc->scripts[i].tag = MS_MAKE_TAG(script->ScriptRecord[i].ScriptTag[0], script->ScriptRecord[i].ScriptTag[1], script->ScriptRecord[i].ScriptTag[2], script->ScriptRecord[i].ScriptTag[3]); |
| psc->scripts[i].table = ((const BYTE*)script + offset); |
| } |
| } |
| } |
| } |
| |
| HRESULT OpenType_GSUB_GetFontScriptTags(ScriptCache *psc, OPENTYPE_TAG searchingFor, int cMaxTags, OPENTYPE_TAG *pScriptTags, int *pcTags, LPCVOID* script_table) |
| { |
| int i; |
| HRESULT rc = S_OK; |
| |
| GSUB_initialize_script_cache(psc); |
| *pcTags = psc->script_count; |
| |
| if (!searchingFor && cMaxTags < *pcTags) |
| rc = E_OUTOFMEMORY; |
| else if (searchingFor) |
| rc = USP_E_SCRIPT_NOT_IN_FONT; |
| |
| for (i = 0; i < psc->script_count; i++) |
| { |
| if (i < cMaxTags) |
| pScriptTags[i] = psc->scripts[i].tag; |
| |
| if (searchingFor) |
| { |
| if (searchingFor == psc->scripts[i].tag) |
| { |
| pScriptTags[0] = psc->scripts[i].tag; |
| *pcTags = 1; |
| if (script_table) |
| *script_table = psc->scripts[i].table; |
| rc = S_OK; |
| break; |
| } |
| } |
| } |
| return rc; |
| } |
| |
| static void GSUB_initialize_language_cache(LoadedScript *script) |
| { |
| int i; |
| |
| if (!script->language_count) |
| { |
| DWORD offset; |
| const OT_Script* table = script->table; |
| script->language_count = GET_BE_WORD(table->LangSysCount); |
| offset = GET_BE_WORD(table->DefaultLangSys); |
| if (offset) |
| { |
| script->default_language.tag = MS_MAKE_TAG('d','f','l','t'); |
| script->default_language.table = (const BYTE*)table + offset; |
| } |
| |
| if (script->language_count) |
| { |
| TRACE("Deflang %p, LangCount %i\n",script->default_language.table, script->language_count); |
| |
| script->languages = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(LoadedLanguage) * script->language_count); |
| |
| for (i = 0; i < script->language_count; i++) |
| { |
| int offset = GET_BE_WORD(table->LangSysRecord[i].LangSys); |
| script->languages[i].tag = MS_MAKE_TAG(table->LangSysRecord[i].LangSysTag[0], table->LangSysRecord[i].LangSysTag[1], table->LangSysRecord[i].LangSysTag[2], table->LangSysRecord[i].LangSysTag[3]); |
| script->languages[i].table = ((const BYTE*)table + offset); |
| } |
| } |
| } |
| } |
| |
| HRESULT OpenType_GSUB_GetFontLanguageTags(ScriptCache *psc, OPENTYPE_TAG script_tag, OPENTYPE_TAG searchingFor, int cMaxTags, OPENTYPE_TAG *pLanguageTags, int *pcTags, LPCVOID* language_table) |
| { |
| int i; |
| HRESULT rc = S_OK; |
| LoadedScript *script = NULL; |
| |
| GSUB_initialize_script_cache(psc); |
| |
| for (i = 0; i < psc->script_count; i++) |
| { |
| if (psc->scripts[i].tag == script_tag) |
| { |
| script = &psc->scripts[i]; |
| break; |
| } |
| } |
| |
| if (!script) |
| return E_INVALIDARG; |
| |
| GSUB_initialize_language_cache(script); |
| |
| if (!searchingFor && cMaxTags < script->language_count) |
| rc = E_OUTOFMEMORY; |
| else if (searchingFor) |
| rc = E_INVALIDARG; |
| |
| *pcTags = script->language_count; |
| |
| for (i = 0; i < script->language_count; i++) |
| { |
| if (i < cMaxTags) |
| pLanguageTags[i] = script->languages[i].tag; |
| |
| if (searchingFor) |
| { |
| if (searchingFor == script->languages[i].tag) |
| { |
| pLanguageTags[0] = script->languages[i].tag; |
| *pcTags = 1; |
| if (language_table) |
| *language_table = script->languages[i].table; |
| rc = S_OK; |
| break; |
| } |
| } |
| } |
| |
| if (script->default_language.table) |
| { |
| if (i < cMaxTags) |
| pLanguageTags[i] = script->default_language.tag; |
| |
| if (searchingFor && FAILED(rc)) |
| { |
| pLanguageTags[0] = script->default_language.tag; |
| if (language_table) |
| *language_table = script->default_language.table; |
| } |
| i++; |
| *pcTags = (*pcTags) + 1; |
| } |
| |
| return rc; |
| } |
| |
| |
| static void GSUB_initialize_feature_cache(LPCVOID table, LoadedLanguage *language) |
| { |
| int i; |
| |
| if (!language->feature_count) |
| { |
| const OT_LangSys *lang= language->table; |
| const GSUB_Header *header = (const GSUB_Header *)table; |
| const OT_FeatureList *feature_list; |
| |
| language->feature_count = GET_BE_WORD(lang->FeatureCount); |
| TRACE("%i features\n",language->feature_count); |
| |
| if (language->feature_count) |
| { |
| language->features = HeapAlloc(GetProcessHeap(),0,sizeof(LoadedFeature)*language->feature_count); |
| |
| feature_list = (const OT_FeatureList*)((const BYTE*)header + GET_BE_WORD(header->FeatureList)); |
| |
| for (i = 0; i < language->feature_count; i++) |
| { |
| const OT_Feature *feature; |
| int j; |
| int index = GET_BE_WORD(lang->FeatureIndex[i]); |
| |
| language->features[i].tag = MS_MAKE_TAG(feature_list->FeatureRecord[index].FeatureTag[0], feature_list->FeatureRecord[index].FeatureTag[1], feature_list->FeatureRecord[index].FeatureTag[2], feature_list->FeatureRecord[index].FeatureTag[3]); |
| language->features[i].feature = ((const BYTE*)feature_list + GET_BE_WORD(feature_list->FeatureRecord[index].Feature)); |
| feature = (const OT_Feature*)language->features[i].feature; |
| language->features[i].lookup_count = GET_BE_WORD(feature->LookupCount); |
| language->features[i].lookups = HeapAlloc(GetProcessHeap(),0,sizeof(WORD) * language->features[i].lookup_count); |
| for (j = 0; j < language->features[i].lookup_count; j++) |
| language->features[i].lookups[j] = GET_BE_WORD(feature->LookupListIndex[j]); |
| } |
| } |
| } |
| } |
| |
| HRESULT OpenType_GSUB_GetFontFeatureTags(ScriptCache *psc, OPENTYPE_TAG script_tag, OPENTYPE_TAG language_tag, BOOL filtered, OPENTYPE_TAG searchingFor, int cMaxTags, OPENTYPE_TAG *pFeatureTags, int *pcTags, LoadedFeature** feature) |
| { |
| int i; |
| HRESULT rc = S_OK; |
| LoadedScript *script = NULL; |
| LoadedLanguage *language = NULL; |
| |
| GSUB_initialize_script_cache(psc); |
| |
| for (i = 0; i < psc->script_count; i++) |
| { |
| if (psc->scripts[i].tag == script_tag) |
| { |
| script = &psc->scripts[i]; |
| break; |
| } |
| } |
| |
| if (!script) |
| { |
| *pcTags = 0; |
| if (!filtered) |
| return S_OK; |
| else |
| return E_INVALIDARG; |
| } |
| |
| GSUB_initialize_language_cache(script); |
| |
| if (script->default_language.table && script->default_language.tag == language_tag) |
| language = &script->default_language; |
| else |
| { |
| for (i = 0; i < script->language_count; i++) |
| { |
| if (script->languages[i].tag == language_tag) |
| { |
| language = &script->languages[i]; |
| break; |
| } |
| } |
| } |
| |
| if (!language) |
| { |
| *pcTags = 0; |
| return S_OK; |
| } |
| |
| GSUB_initialize_feature_cache(psc->GSUB_Table, language); |
| |
| *pcTags = language->feature_count; |
| |
| if (!searchingFor && cMaxTags < *pcTags) |
| rc = E_OUTOFMEMORY; |
| else if (searchingFor) |
| rc = E_INVALIDARG; |
| |
| for (i = 0; i < language->feature_count; i++) |
| { |
| if (i < cMaxTags) |
| pFeatureTags[i] = language->features[i].tag; |
| |
| if (searchingFor) |
| { |
| if (searchingFor == language->features[i].tag) |
| { |
| pFeatureTags[0] = language->features[i].tag; |
| *pcTags = 1; |
| if (feature) |
| *feature = &language->features[i]; |
| rc = S_OK; |
| break; |
| } |
| } |
| } |
| return rc; |
| } |