/* * 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 #include #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; } GSUB_ScriptRecord; typedef struct { WORD ScriptCount; GSUB_ScriptRecord ScriptRecord[1]; } GSUB_ScriptList; typedef struct { CHAR LangSysTag[4]; WORD LangSys; } GSUB_LangSysRecord; typedef struct { WORD DefaultLangSys; WORD LangSysCount; GSUB_LangSysRecord LangSysRecord[1]; } GSUB_Script; typedef struct { WORD LookupOrder; /* Reserved */ WORD ReqFeatureIndex; WORD FeatureCount; WORD FeatureIndex[1]; } GSUB_LangSys; typedef struct { CHAR FeatureTag[4]; WORD Feature; } GSUB_FeatureRecord; typedef struct { WORD FeatureCount; GSUB_FeatureRecord FeatureRecord[1]; } GSUB_FeatureList; typedef struct { WORD FeatureParams; /* Reserved */ WORD LookupCount; WORD LookupListIndex[1]; } GSUB_Feature; typedef struct { WORD LookupCount; WORD Lookup[1]; } GSUB_LookupList; typedef struct { WORD LookupType; WORD LookupFlag; WORD SubTableCount; WORD SubTable[1]; } GSUB_LookupTable; typedef struct { WORD CoverageFormat; WORD GlyphCount; WORD GlyphArray[1]; } GSUB_CoverageFormat1; typedef struct { WORD Start; WORD End; WORD StartCoverageIndex; } GSUB_RangeRecord; typedef struct { WORD CoverageFormat; WORD RangeCount; GSUB_RangeRecord RangeRecord[1]; } GSUB_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 GSUB_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 GSUB_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 GSUB_CoverageFormat2* cf2; int i; int count; cf2 = (const GSUB_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 GSUB_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 GSUB_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 GSUB_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 GSUB_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 GSUB_LookupList* lookup, const GSUB_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 GSUB_LookupList* lookup, INT lookup_index, WORD *glyphs, INT glyph_index, INT write_dir, INT *glyph_count) { int offset; const GSUB_LookupTable *look; offset = GET_BE_WORD(lookup->Lookup[lookup_index]); look = (const GSUB_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 GSUB_LookupList *lookup = (const GSUB_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 GSUB_ScriptList *script; const GSUB_Header* header = (const GSUB_Header*)psc->GSUB_Table; script = (const GSUB_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) { const GSUB_Script* table = script->table; script->language_count = GET_BE_WORD(table->LangSysCount); script->default_language.tag = MS_MAKE_TAG('d','f','l','t'); script->default_language.table = (const BYTE*)table + GET_BE_WORD(table->DefaultLangSys); 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 GSUB_LangSys *lang= language->table; const GSUB_Header *header = (const GSUB_Header *)table; const GSUB_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 GSUB_FeatureList*)((const BYTE*)header + GET_BE_WORD(header->FeatureList)); for (i = 0; i < language->feature_count; i++) { const GSUB_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 GSUB_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; }