/* * 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; } 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; /* These are all structures needed for the GPOS table */ typedef struct { DWORD version; WORD ScriptList; WORD FeatureList; WORD LookupList; } GPOS_Header; typedef struct { WORD StartSize; WORD EndSize; WORD DeltaFormat; WORD DeltaValue[1]; } OT_DeviceTable; typedef struct { WORD AnchorFormat; WORD XCoordinate; WORD YCoordinate; } GPOS_AnchorFormat1; typedef struct { WORD AnchorFormat; WORD XCoordinate; WORD YCoordinate; WORD AnchorPoint; } GPOS_AnchorFormat2; typedef struct { WORD AnchorFormat; WORD XCoordinate; WORD YCoordinate; WORD XDeviceTable; WORD YDeviceTable; } GPOS_AnchorFormat3; typedef struct { WORD XPlacement; WORD YPlacement; WORD XAdvance; WORD YAdvance; WORD XPlaDevice; WORD YPlaDevice; WORD XAdvDevice; WORD YAdvDevice; } GPOS_ValueRecord; typedef struct { WORD PosFormat; WORD Coverage; WORD ValueFormat; WORD Value[1]; } GPOS_SinglePosFormat1; typedef struct { WORD PosFormat; WORD Coverage; WORD ValueFormat; WORD ValueCount; WORD Value[1]; } GPOS_SinglePosFormat2; typedef struct { WORD PosFormat; WORD Coverage; WORD ValueFormat1; WORD ValueFormat2; WORD PairSetCount; WORD PairSetOffset[1]; } GPOS_PairPosFormat1; typedef struct { WORD SecondGlyph; WORD Value1[1]; WORD Value2[1]; } GPOS_PairValueRecord; typedef struct { WORD PairValueCount; GPOS_PairValueRecord PairValueRecord[1]; } GPOS_PairSet; typedef struct { WORD PosFormat; WORD MarkCoverage; WORD BaseCoverage; WORD ClassCount; WORD MarkArray; WORD BaseArray; } GPOS_MarkBasePosFormat1; typedef struct { WORD BaseAnchor[1]; } GPOS_BaseRecord; typedef struct { WORD BaseCount; GPOS_BaseRecord BaseRecord[1]; } GPOS_BaseArray; typedef struct { WORD Class; WORD MarkAnchor; } GPOS_MarkRecord; typedef struct { WORD MarkCount; GPOS_MarkRecord MarkRecord[1]; } GPOS_MarkArray; typedef struct { WORD SequenceIndex; WORD LookupListIndex; } GPOS_PosLookupRecord; typedef struct { WORD PosFormat; WORD BacktrackGlyphCount; WORD Coverage[1]; } GPOS_ChainContextPosFormat3_1; typedef struct { WORD InputGlyphCount; WORD Coverage[1]; } GPOS_ChainContextPosFormat3_2; typedef struct { WORD LookaheadGlyphCount; WORD Coverage[1]; } GPOS_ChainContextPosFormat3_3; typedef struct { WORD PosCount; GPOS_PosLookupRecord PosLookupRecord[1]; } GPOS_ChainContextPosFormat3_4; /********** * 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); } /********** * GPOS **********/ static INT GPOS_apply_lookup(LPOUTLINETEXTMETRICW lpotm, LPLOGFONTW lplogfont, INT* piAdvance, const OT_LookupList* lookup, INT lookup_index, const WORD *glyphs, INT glyph_index, INT write_dir, INT glyph_count, GOFFSET *pGoffset); static INT GPOS_get_device_table_value(const OT_DeviceTable *DeviceTable, WORD ppem) { static const WORD mask[3] = {3,0xf,0xff}; if (DeviceTable && ppem >= GET_BE_WORD(DeviceTable->StartSize) && ppem <= GET_BE_WORD(DeviceTable->EndSize)) { int format = GET_BE_WORD(DeviceTable->DeltaFormat); int index = ppem - GET_BE_WORD(DeviceTable->StartSize); int value; TRACE("device table, format %i, index %i\n",format, index); index = index << format; value = (DeviceTable->DeltaValue[index/sizeof(WORD)] << (index%sizeof(WORD)))&mask[format-1]; TRACE("offset %i, value %i\n",index, value); if (value > mask[format-1]/2) value = -1 * ((mask[format-1]+1) - value); return value; } return 0; } static VOID GPOS_get_anchor_values(LPCVOID table, LPPOINT pt, WORD ppem) { const GPOS_AnchorFormat1* anchor1 = (const GPOS_AnchorFormat1*)table; switch (GET_BE_WORD(anchor1->AnchorFormat)) { case 1: { TRACE("Anchor Format 1\n"); pt->x = (short)GET_BE_WORD(anchor1->XCoordinate); pt->y = (short)GET_BE_WORD(anchor1->YCoordinate); break; } case 2: { const GPOS_AnchorFormat2* anchor2 = (const GPOS_AnchorFormat2*)table; TRACE("Anchor Format 2\n"); pt->x = (short)GET_BE_WORD(anchor2->XCoordinate); pt->y = (short)GET_BE_WORD(anchor2->YCoordinate); break; } case 3: { int offset; const GPOS_AnchorFormat3* anchor3 = (const GPOS_AnchorFormat3*)table; TRACE("Anchor Format 3\n"); pt->x = (short)GET_BE_WORD(anchor3->XCoordinate); pt->y = (short)GET_BE_WORD(anchor3->YCoordinate); offset = GET_BE_WORD(anchor3->XDeviceTable); TRACE("ppem %i\n",ppem); if (offset) { const OT_DeviceTable* DeviceTableX = NULL; DeviceTableX = (const OT_DeviceTable*)((const BYTE*)anchor3 + offset); pt->x += GPOS_get_device_table_value(DeviceTableX, ppem); } offset = GET_BE_WORD(anchor3->YDeviceTable); if (offset) { const OT_DeviceTable* DeviceTableY = NULL; DeviceTableY = (const OT_DeviceTable*)((const BYTE*)anchor3 + offset); pt->y += GPOS_get_device_table_value(DeviceTableY, ppem); } break; } default: ERR("Unknown Anchor Format %i\n",GET_BE_WORD(anchor1->AnchorFormat)); pt->x = 0; pt->y = 0; } } static void GPOS_convert_design_units_to_device(LPOUTLINETEXTMETRICW lpotm, LPLOGFONTW lplogfont, int desX, int desY, double *devX, double *devY) { int emHeight = lpotm->otmTextMetrics.tmAscent + lpotm->otmTextMetrics.tmDescent - lpotm->otmTextMetrics.tmInternalLeading; TRACE("emHeight %i lfWidth %i\n",emHeight, lplogfont->lfWidth); *devX = (desX * emHeight) / (double)lpotm->otmEMSquare; *devY = (desY * emHeight) / (double)lpotm->otmEMSquare; if (lplogfont->lfWidth) FIXME("Font with lfWidth set no handled properly\n"); } static INT GPOS_get_value_record(WORD ValueFormat, const WORD data[], GPOS_ValueRecord *record) { INT offset = 0; if (ValueFormat & 0x0001) record->XPlacement = GET_BE_WORD(data[offset++]); if (ValueFormat & 0x0002) record->YPlacement = GET_BE_WORD(data[offset++]); if (ValueFormat & 0x0004) record->XAdvance = GET_BE_WORD(data[offset++]); if (ValueFormat & 0x0008) record->YAdvance = GET_BE_WORD(data[offset++]); if (ValueFormat & 0x0010) record->XPlaDevice = GET_BE_WORD(data[offset++]); if (ValueFormat & 0x0020) record->YPlaDevice = GET_BE_WORD(data[offset++]); if (ValueFormat & 0x0040) record->XAdvDevice = GET_BE_WORD(data[offset++]); if (ValueFormat & 0x0080) record->YAdvDevice = GET_BE_WORD(data[offset++]); return offset; } static VOID GPOS_get_value_record_offsets(const BYTE* head, GPOS_ValueRecord *ValueRecord, WORD ValueFormat, INT ppem, LPPOINT ptPlacement, LPPOINT ptAdvance) { if (ValueFormat & 0x0001) ptPlacement->x += (short)ValueRecord->XPlacement; if (ValueFormat & 0x0002) ptPlacement->y += (short)ValueRecord->YPlacement; if (ValueFormat & 0x0004) ptAdvance->x += (short)ValueRecord->XAdvance; if (ValueFormat & 0x0008) ptAdvance->y += (short)ValueRecord->YAdvance; if (ValueFormat & 0x0010) ptPlacement->x += GPOS_get_device_table_value((const OT_DeviceTable*)(head + ValueRecord->XPlaDevice), ppem); if (ValueFormat & 0x0020) ptPlacement->y += GPOS_get_device_table_value((const OT_DeviceTable*)(head + ValueRecord->YPlaDevice), ppem); if (ValueFormat & 0x0040) ptAdvance->x += GPOS_get_device_table_value((const OT_DeviceTable*)(head + ValueRecord->XAdvDevice), ppem); if (ValueFormat & 0x0080) ptAdvance->y += GPOS_get_device_table_value((const OT_DeviceTable*)(head + ValueRecord->YAdvDevice), ppem); if (ValueFormat & 0xFF00) FIXME("Unhandled Value Format %x\n",ValueFormat&0xFF00); } static VOID GPOS_apply_SingleAdjustment(const OT_LookupTable *look, const WORD *glyphs, INT glyph_index, INT write_dir, INT glyph_count, INT ppem, LPPOINT ptAdjust, LPPOINT ptAdvance) { int j; TRACE("Single Adjustment Positioning Subtable\n"); for (j = 0; j < GET_BE_WORD(look->SubTableCount); j++) { const GPOS_SinglePosFormat1 *spf1; WORD offset = GET_BE_WORD(look->SubTable[j]); spf1 = (const GPOS_SinglePosFormat1*)((const BYTE*)look+offset); if (GET_BE_WORD(spf1->PosFormat) == 1) { offset = GET_BE_WORD(spf1->Coverage); if (GSUB_is_glyph_covered((const BYTE*)spf1+offset, glyphs[glyph_index]) != -1) { GPOS_ValueRecord ValueRecord = {0,0,0,0,0,0,0,0}; WORD ValueFormat = GET_BE_WORD(spf1->ValueFormat); GPOS_get_value_record(ValueFormat, spf1->Value, &ValueRecord); GPOS_get_value_record_offsets((const BYTE*)spf1, &ValueRecord, ValueFormat, ppem, ptAdjust, ptAdvance); TRACE("Glyph Adjusted by %i,%i\n",ValueRecord.XPlacement,ValueRecord.YPlacement); } } else if (GET_BE_WORD(spf1->PosFormat) == 2) { int index; const GPOS_SinglePosFormat2 *spf2; spf2 = (const GPOS_SinglePosFormat2*)spf1; offset = GET_BE_WORD(spf2->Coverage); index = GSUB_is_glyph_covered((const BYTE*)spf2+offset, glyphs[glyph_index]); if (index != -1) { int size; GPOS_ValueRecord ValueRecord = {0,0,0,0,0,0,0,0}; WORD ValueFormat = GET_BE_WORD(spf2->ValueFormat); size = GPOS_get_value_record(ValueFormat, spf2->Value, &ValueRecord); if (index > 0) { offset = size * index; GPOS_get_value_record(ValueFormat, &spf2->Value[offset], &ValueRecord); } GPOS_get_value_record_offsets((const BYTE*)spf2, &ValueRecord, ValueFormat, ppem, ptAdjust, ptAdvance); TRACE("Glyph Adjusted by %i,%i\n",ValueRecord.XPlacement,ValueRecord.YPlacement); } } else FIXME("Single Adjustment Positioning: Format %i Unhandled\n",GET_BE_WORD(spf1->PosFormat)); } } static INT GPOS_apply_PairAdjustment(const OT_LookupTable *look, const WORD *glyphs, INT glyph_index, INT write_dir, INT glyph_count, INT ppem, LPPOINT ptAdjust, LPPOINT ptAdvance) { int j; TRACE("Pair Adjustment Positioning Subtable\n"); for (j = 0; j < GET_BE_WORD(look->SubTableCount); j++) { const GPOS_PairPosFormat1 *ppf1; WORD offset = GET_BE_WORD(look->SubTable[j]); ppf1 = (const GPOS_PairPosFormat1*)((const BYTE*)look+offset); if (GET_BE_WORD(ppf1->PosFormat) == 1) { int index; offset = GET_BE_WORD(ppf1->Coverage); index = GSUB_is_glyph_covered((const BYTE*)ppf1+offset, glyphs[glyph_index]); if (index != -1 && index < GET_BE_WORD(ppf1->PairSetCount)) { int k; int pair_count; const GPOS_PairSet *ps; offset = GET_BE_WORD(ppf1->PairSetOffset[index]); ps = (const GPOS_PairSet*)((const BYTE*)ppf1+offset); pair_count = GET_BE_WORD(ps->PairValueCount); for (k = 0; k < pair_count; k++) { WORD second_glyph = GET_BE_WORD(ps->PairValueRecord[k].SecondGlyph); if (glyphs[glyph_index+write_dir] == second_glyph) { int next = 1; GPOS_ValueRecord ValueRecord1 = {0,0,0,0,0,0,0,0}; GPOS_ValueRecord ValueRecord2 = {0,0,0,0,0,0,0,0}; WORD ValueFormat1 = GET_BE_WORD(ppf1->ValueFormat1); WORD ValueFormat2 = GET_BE_WORD(ppf1->ValueFormat2); TRACE("Format 1: Found Pair %x,%x\n",glyphs[glyph_index],glyphs[glyph_index+write_dir]); offset = GPOS_get_value_record(ValueFormat1, ps->PairValueRecord[k].Value1, &ValueRecord1); GPOS_get_value_record(ValueFormat2, (WORD*)((const BYTE*)(ps->PairValueRecord[k].Value2)+offset), &ValueRecord2); if (ValueFormat1) { GPOS_get_value_record_offsets((const BYTE*)ppf1, &ValueRecord1, ValueFormat1, ppem, &ptAdjust[0], &ptAdvance[0]); TRACE("Glyph 1 resulting cumulative offset is %i,%i design units\n",ptAdjust[0].x,ptAdjust[0].y); TRACE("Glyph 1 resulting cumulative advance is %i,%i design units\n",ptAdvance[0].x,ptAdvance[0].y); } if (ValueFormat2) { GPOS_get_value_record_offsets((const BYTE*)ppf1, &ValueRecord2, ValueFormat2, ppem, &ptAdjust[1], &ptAdvance[1]); TRACE("Glyph 2 resulting cumulative offset is %i,%i design units\n",ptAdjust[1].x,ptAdjust[1].y); TRACE("Glyph 2 resulting cumulative advance is %i,%i design units\n",ptAdvance[1].x,ptAdvance[1].y); next++; } if (next) return glyph_index + next; } } } } else FIXME("Pair Adjustment Positioning: Format %i Unhandled\n",GET_BE_WORD(ppf1->PosFormat)); } return glyph_index+1; } static VOID GPOS_apply_MarkToBase(const OT_LookupTable *look, const WORD *glyphs, INT glyph_index, INT write_dir, INT glyph_count, INT ppem, LPPOINT pt) { int j; TRACE("MarkToBase Attachment Positioning Subtable\n"); for (j = 0; j < GET_BE_WORD(look->SubTableCount); j++) { int offset; const GPOS_MarkBasePosFormat1 *mbpf1; offset = GET_BE_WORD(look->SubTable[j]); mbpf1 = (const GPOS_MarkBasePosFormat1*)((const BYTE*)look+offset); if (GET_BE_WORD(mbpf1->PosFormat) == 1) { int offset = GET_BE_WORD(mbpf1->MarkCoverage); int mark_index; mark_index = GSUB_is_glyph_covered((const BYTE*)mbpf1+offset, glyphs[glyph_index]); if (mark_index != -1) { int base_index; offset = GET_BE_WORD(mbpf1->BaseCoverage); base_index = GSUB_is_glyph_covered((const BYTE*)mbpf1+offset, glyphs[glyph_index - write_dir]); if (base_index != -1) { const GPOS_MarkArray *ma; const GPOS_MarkRecord *mr; const GPOS_BaseArray *ba; const GPOS_BaseRecord *br; int mark_class; int class_count = GET_BE_WORD(mbpf1->ClassCount); int baserecord_size; POINT base_pt; POINT mark_pt; TRACE("Mark %x(%i) and base %x(%i)\n",glyphs[glyph_index], mark_index, glyphs[glyph_index - write_dir], base_index); offset = GET_BE_WORD(mbpf1->MarkArray); ma = (const GPOS_MarkArray*)((const BYTE*)mbpf1 + offset); if (mark_index > GET_BE_WORD(ma->MarkCount)) { ERR("Mark index exeeded mark count\n"); return; } mr = &ma->MarkRecord[mark_index]; mark_class = GET_BE_WORD(mr->Class); TRACE("Mark Class %i total classes %i\n",mark_class,class_count); offset = GET_BE_WORD(mbpf1->BaseArray); ba = (const GPOS_BaseArray*)((const BYTE*)mbpf1 + offset); baserecord_size = class_count * sizeof(WORD); br = (const GPOS_BaseRecord*)((const BYTE*)ba + sizeof(WORD) + (baserecord_size * base_index)); offset = GET_BE_WORD(br->BaseAnchor[mark_class]); GPOS_get_anchor_values((const BYTE*)ba + offset, &base_pt, ppem); offset = GET_BE_WORD(mr->MarkAnchor); GPOS_get_anchor_values((const BYTE*)ma + offset, &mark_pt, ppem); TRACE("Offset on base is %i,%i design units\n",base_pt.x,base_pt.y); TRACE("Offset on mark is %i,%i design units\n",mark_pt.x, mark_pt.y); pt->x += base_pt.x - mark_pt.x; pt->y += base_pt.y - mark_pt.y; TRACE("Resulting cumulative offset is %i,%i design units\n",pt->x,pt->y); } } } else FIXME("Unhandled Mark To Base Format %i\n",GET_BE_WORD(mbpf1->PosFormat)); } } static INT GPOS_apply_ChainContextPos(LPOUTLINETEXTMETRICW lpotm, LPLOGFONTW lplogfont, INT* piAdvance, const OT_LookupList *lookup, const OT_LookupTable *look, const WORD *glyphs, INT glyph_index, INT write_dir, INT glyph_count, INT ppem, GOFFSET *pGoffset) { int j; TRACE("Chaining Contextual Positioning Subtable\n"); for (j = 0; j < GET_BE_WORD(look->SubTableCount); j++) { int offset; const GPOS_ChainContextPosFormat3_1 *ccpf3; int dirLookahead = write_dir; int dirBacktrack = -1 * write_dir; offset = GET_BE_WORD(look->SubTable[j]); ccpf3 = (const GPOS_ChainContextPosFormat3_1*)((const BYTE*)look+offset); if (GET_BE_WORD(ccpf3->PosFormat) == 1) { FIXME(" TODO: subtype 1 (Simple Chaining Context Glyph Positioning)\n"); continue; } else if (GET_BE_WORD(ccpf3->PosFormat) == 2) { FIXME(" TODO: subtype 2 (Class-based Chaining Context Glyph Positioning)\n"); continue; } else if (GET_BE_WORD(ccpf3->PosFormat) == 3) { int k; int indexGlyphs; const GPOS_ChainContextPosFormat3_2 *ccpf3_2; const GPOS_ChainContextPosFormat3_3 *ccpf3_3; const GPOS_ChainContextPosFormat3_4 *ccpf3_4; TRACE(" subtype 3 (Coverage-based Chaining Context Glyph Positioning)\n"); for (k = 0; k < GET_BE_WORD(ccpf3->BacktrackGlyphCount); k++) { offset = GET_BE_WORD(ccpf3->Coverage[k]); if (GSUB_is_glyph_covered((const BYTE*)ccpf3+offset, glyphs[glyph_index + (dirBacktrack * (k+1))]) == -1) break; } if (k != GET_BE_WORD(ccpf3->BacktrackGlyphCount)) continue; TRACE("Matched Backtrack\n"); ccpf3_2 = (const GPOS_ChainContextPosFormat3_2*)(((LPBYTE)ccpf3)+sizeof(GPOS_ChainContextPosFormat3_1) + (sizeof(WORD) * (GET_BE_WORD(ccpf3->BacktrackGlyphCount)-1))); indexGlyphs = GET_BE_WORD(ccpf3_2->InputGlyphCount); for (k = 0; k < indexGlyphs; k++) { offset = GET_BE_WORD(ccpf3_2->Coverage[k]); if (GSUB_is_glyph_covered((const BYTE*)ccpf3+offset, glyphs[glyph_index + (write_dir * k)]) == -1) break; } if (k != indexGlyphs) continue; TRACE("Matched IndexGlyphs\n"); ccpf3_3 = (const GPOS_ChainContextPosFormat3_3*)(((LPBYTE)ccpf3_2)+sizeof(GPOS_ChainContextPosFormat3_2) + (sizeof(WORD) * (GET_BE_WORD(ccpf3_2->InputGlyphCount)-1))); for (k = 0; k < GET_BE_WORD(ccpf3_3->LookaheadGlyphCount); k++) { offset = GET_BE_WORD(ccpf3_3->Coverage[k]); if (GSUB_is_glyph_covered((const BYTE*)ccpf3+offset, glyphs[glyph_index + (dirLookahead * (indexGlyphs + k))]) == -1) break; } if (k != GET_BE_WORD(ccpf3_3->LookaheadGlyphCount)) continue; TRACE("Matched LookAhead\n"); ccpf3_4 = (const GPOS_ChainContextPosFormat3_4*)(((LPBYTE)ccpf3_3)+sizeof(GPOS_ChainContextPosFormat3_3) + (sizeof(WORD) * (GET_BE_WORD(ccpf3_3->LookaheadGlyphCount)-1))); if (GET_BE_WORD(ccpf3_4->PosCount)) { for (k = 0; k < GET_BE_WORD(ccpf3_4->PosCount); k++) { int lookupIndex = GET_BE_WORD(ccpf3_4->PosLookupRecord[k].LookupListIndex); int SequenceIndex = GET_BE_WORD(ccpf3_4->PosLookupRecord[k].SequenceIndex) * write_dir; TRACE("Position: %i -> %i %i\n",k, SequenceIndex, lookupIndex); GPOS_apply_lookup(lpotm, lplogfont, piAdvance, lookup, lookupIndex, glyphs, glyph_index + SequenceIndex, write_dir, glyph_count, pGoffset); } return glyph_index + indexGlyphs + GET_BE_WORD(ccpf3_3->LookaheadGlyphCount); } else return glyph_index + 1; } else FIXME("Unhandled Chaining Contextual Positioning Format %i\n",GET_BE_WORD(ccpf3->PosFormat)); } return glyph_index + 1; } static INT GPOS_apply_lookup(LPOUTLINETEXTMETRICW lpotm, LPLOGFONTW lplogfont, INT* piAdvance, const OT_LookupList* lookup, INT lookup_index, const WORD *glyphs, INT glyph_index, INT write_dir, INT glyph_count, GOFFSET *pGoffset) { int offset; const OT_LookupTable *look; int ppem = lpotm->otmTextMetrics.tmAscent + lpotm->otmTextMetrics.tmDescent - lpotm->otmTextMetrics.tmInternalLeading; 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: { double devX, devY; POINT adjust = {0,0}; POINT advance = {0,0}; GPOS_apply_SingleAdjustment(look, glyphs, glyph_index, write_dir, glyph_count, ppem, &adjust, &advance); if (adjust.x || adjust.y) { GPOS_convert_design_units_to_device(lpotm, lplogfont, adjust.x, adjust.y, &devX, &devY); pGoffset[glyph_index].du += (int)(devX+0.5); pGoffset[glyph_index].dv += (int)(devY+0.5); } if (advance.x || advance.y) { GPOS_convert_design_units_to_device(lpotm, lplogfont, advance.x, advance.y, &devX, &devY); piAdvance[glyph_index] += (int)(devX+0.5); if (advance.y) FIXME("Unhandled adjustment to Y advancement\n"); } } case 2: { POINT advance[2]= {{0,0},{0,0}}; POINT adjust[2]= {{0,0},{0,0}}; double devX, devY; int index; index = GPOS_apply_PairAdjustment(look, glyphs, glyph_index, write_dir, glyph_count, ppem, adjust, advance); if (adjust[0].x || adjust[0].y) { GPOS_convert_design_units_to_device(lpotm, lplogfont, adjust[0].x, adjust[0].y, &devX, &devY); pGoffset[glyph_index].du += (int)(devX+0.5); pGoffset[glyph_index].dv += (int)(devY+0.5); } if (advance[0].x || advance[0].y) { GPOS_convert_design_units_to_device(lpotm, lplogfont, advance[0].x, advance[0].y, &devX, &devY); piAdvance[glyph_index] += (int)(devX+0.5); } if (adjust[1].x || adjust[1].y) { GPOS_convert_design_units_to_device(lpotm, lplogfont, adjust[1].x, adjust[1].y, &devX, &devY); pGoffset[glyph_index + write_dir].du += (int)(devX+0.5); pGoffset[glyph_index + write_dir].dv += (int)(devY+0.5); } if (advance[1].x || advance[1].y) { GPOS_convert_design_units_to_device(lpotm, lplogfont, advance[1].x, advance[1].y, &devX, &devY); piAdvance[glyph_index + write_dir] += (int)(devX+0.5); } return index; } case 4: { double devX, devY; POINT desU = {0,0}; GPOS_apply_MarkToBase(look, glyphs, glyph_index, write_dir, glyph_count, ppem, &desU); if (desU.x || desU.y) { GPOS_convert_design_units_to_device(lpotm, lplogfont, desU.x, desU.y, &devX, &devY); pGoffset[glyph_index].du += ((int)(devX+0.5) - piAdvance[glyph_index-1]); pGoffset[glyph_index].dv += (int)(devY+0.5); } break; } case 8: { return GPOS_apply_ChainContextPos(lpotm, lplogfont, piAdvance, lookup, look, glyphs, glyph_index, write_dir, glyph_count, ppem, pGoffset); } default: FIXME("We do not handle SubType %i\n",GET_BE_WORD(look->LookupType)); } return glyph_index+1; } INT OpenType_apply_GPOS_lookup(LPOUTLINETEXTMETRICW lpotm, LPLOGFONTW lplogfont, INT* piAdvance, LPCVOID table, INT lookup_index, const WORD *glyphs, INT glyph_index, INT write_dir, INT glyph_count, GOFFSET *pGoffset) { const GPOS_Header *header = (const GPOS_Header *)table; const OT_LookupList *lookup = (const OT_LookupList*)((const BYTE*)header + GET_BE_WORD(header->LookupList)); return GPOS_apply_lookup(lpotm, lplogfont, piAdvance, lookup, lookup_index, glyphs, glyph_index, write_dir, glyph_count, pGoffset); } static void GSUB_initialize_script_cache(ScriptCache *psc) { int i; if (psc->GSUB_Table) { 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].gsub_table = ((const BYTE*)script + offset); } } } } static void GPOS_expand_script_cache(ScriptCache *psc) { int i, count; const OT_ScriptList *script; const GPOS_Header* header = (const GPOS_Header*)psc->GPOS_Table; if (!header) return; script = (const OT_ScriptList*)((const BYTE*)header + GET_BE_WORD(header->ScriptList)); count = GET_BE_WORD(script->ScriptCount); if (!psc->script_count) { psc->script_count = count; 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].gpos_table = ((const BYTE*)script + offset); } } } else { for (i = 0; i < count; i++) { int j; int offset = GET_BE_WORD(script->ScriptRecord[i].Script); OPENTYPE_TAG tag = MS_MAKE_TAG(script->ScriptRecord[i].ScriptTag[0], script->ScriptRecord[i].ScriptTag[1], script->ScriptRecord[i].ScriptTag[2], script->ScriptRecord[i].ScriptTag[3]); for (j = 0; j < psc->script_count; j++) { if (psc->scripts[j].tag == tag) { psc->scripts[j].gpos_table = ((const BYTE*)script + offset); break; } } if (j == psc->script_count) { psc->script_count++; psc->scripts = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,psc->scripts, sizeof(LoadedScript) * psc->script_count); psc->scripts[j].tag = tag; psc->scripts[j].gpos_table = ((const BYTE*)script + offset); } } } } static void _initialize_script_cache(ScriptCache *psc) { if (!psc->script_count) { GSUB_initialize_script_cache(psc); GPOS_expand_script_cache(psc); } } HRESULT OpenType_GetFontScriptTags(ScriptCache *psc, OPENTYPE_TAG searchingFor, int cMaxTags, OPENTYPE_TAG *pScriptTags, int *pcTags) { int i; HRESULT rc = S_OK; _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; rc = S_OK; break; } } } return rc; } static void GSUB_initialize_language_cache(LoadedScript *script) { int i; if (script->gsub_table) { DWORD offset; const OT_Script* table = script->gsub_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.gsub_table = (const BYTE*)table + offset; } if (script->language_count) { TRACE("Deflang %p, LangCount %i\n",script->default_language.gsub_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].gsub_table = ((const BYTE*)table + offset); } } } } static void GPOS_expand_language_cache(LoadedScript *script) { int count; const OT_Script* table = script->gpos_table; DWORD offset; if (!table) return; offset = GET_BE_WORD(table->DefaultLangSys); if (offset) script->default_language.gpos_table = (const BYTE*)table + offset; count = GET_BE_WORD(table->LangSysCount); TRACE("Deflang %p, LangCount %i\n",script->default_language.gpos_table, count); if (!script->language_count) { int i; script->language_count = 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].gpos_table = ((const BYTE*)table + offset); } } else if (count) { int i,j; for (i = 0; i < count; i++) { int offset = GET_BE_WORD(table->LangSysRecord[i].LangSys); OPENTYPE_TAG tag = MS_MAKE_TAG(table->LangSysRecord[i].LangSysTag[0], table->LangSysRecord[i].LangSysTag[1], table->LangSysRecord[i].LangSysTag[2], table->LangSysRecord[i].LangSysTag[3]); for (j = 0; j < script->language_count; j++) { if (script->languages[j].tag == tag) { script->languages[j].gpos_table = ((const BYTE*)table + offset); break; } } if (j == script->language_count) { script->language_count++; script->languages = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,script->languages, sizeof(LoadedLanguage) * script->language_count); script->languages[j].tag = tag; script->languages[j].gpos_table = ((const BYTE*)table + offset); } } } } static void _initialize_language_cache(LoadedScript *script) { if (!script->language_count) { GSUB_initialize_language_cache(script); GPOS_expand_language_cache(script); } } HRESULT OpenType_GetFontLanguageTags(ScriptCache *psc, OPENTYPE_TAG script_tag, OPENTYPE_TAG searchingFor, int cMaxTags, OPENTYPE_TAG *pLanguageTags, int *pcTags) { int i; HRESULT rc = S_OK; LoadedScript *script = NULL; _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; _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; rc = S_OK; break; } } } if (script->default_language.gsub_table) { if (i < cMaxTags) pLanguageTags[i] = script->default_language.tag; if (searchingFor && FAILED(rc)) { pLanguageTags[0] = script->default_language.tag; } i++; *pcTags = (*pcTags) + 1; } return rc; } static void GSUB_initialize_feature_cache(LPCVOID table, LoadedLanguage *language) { int i; if (language->gsub_table) { const OT_LangSys *lang = language->gsub_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]); } } } } static void GPOS_expand_feature_cache(LPCVOID table, LoadedLanguage *language) { int i, count; const OT_LangSys *lang = language->gpos_table; const GPOS_Header *header = (const GPOS_Header *)table; const OT_FeatureList *feature_list; if (!lang) return; count = GET_BE_WORD(lang->FeatureCount); feature_list = (const OT_FeatureList*)((const BYTE*)header + GET_BE_WORD(header->FeatureList)); TRACE("%i features\n",count); if (!language->feature_count) { language->feature_count = count; if (language->feature_count) { language->features = HeapAlloc(GetProcessHeap(),0,sizeof(LoadedFeature)*language->feature_count); 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]); } } } else if (count) { language->features = HeapReAlloc(GetProcessHeap(),0,language->features, sizeof(LoadedFeature)*(language->feature_count + count)); for (i = 0; i < count; i++) { const OT_Feature *feature; int j; int index = GET_BE_WORD(lang->FeatureIndex[i]); int idx = language->feature_count + i; language->features[idx].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[idx].feature = ((const BYTE*)feature_list + GET_BE_WORD(feature_list->FeatureRecord[index].Feature)); feature = (const OT_Feature*)language->features[idx].feature; language->features[idx].lookup_count = GET_BE_WORD(feature->LookupCount); language->features[idx].lookups = HeapAlloc(GetProcessHeap(),0,sizeof(WORD) * language->features[idx].lookup_count); for (j = 0; j < language->features[idx].lookup_count; j++) language->features[idx].lookups[j] = GET_BE_WORD(feature->LookupListIndex[j]); } language->feature_count += count; } } static void _initialize_feature_cache(ScriptCache *psc, LoadedLanguage *language) { if (!language->feature_count) { GSUB_initialize_feature_cache(psc->GSUB_Table, language); GPOS_expand_feature_cache(psc->GPOS_Table, language); } } HRESULT OpenType_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; _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; } _initialize_language_cache(script); if ((script->default_language.gsub_table || script->default_language.gpos_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; } _initialize_feature_cache(psc, 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; }