|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Security;
using System.IO;
using System.Diagnostics;
using MS.Internal.FontCache;
namespace MS.Internal.Shaping
{
internal struct LayoutOffset
{
public LayoutOffset(int dx, int dy) { this.dx=dx; this.dy=dy; }
public int dx;
public int dy;
}
/// <summary>
/// Tags used in OpenTypeLayout
/// </summary>
internal enum OpenTypeTags :uint
{
Null = 0x00000000,
GSUB = 0x47535542,
GPOS = 0x47504F53,
GDEF = 0x47444546,
BASE = 0x42415345,
name = 0x6e616D65,
post = 0x706F7374,
dflt = 0x64666c74,
head = 0x68656164,
//GSUB feature tags
locl = 0x6c6f636c,
ccmp = 0x63636d70,
rlig = 0x726c6967,
liga = 0x6c696761,
clig = 0x636c6967,
pwid = 0x70776964,
init = 0x696e6974,
medi = 0x6d656469,
fina = 0x66696e61,
isol = 0x69736f6c,
calt = 0x63616c74,
//Indic subst
nukt = 0x6e756b74,
akhn = 0x616b686e,
rphf = 0x72706866,
blwf = 0x626c7766,
half = 0x68616c66,
vatu = 0x76617475,
pres = 0x70726573,
abvs = 0x61627673,
blws = 0x626c7773,
psts = 0x70737473,
haln = 0x68616c6e,
//GPOS feature tags
kern = 0x6b65726e,
mark = 0x6d61726b,
mkmk = 0x6d6b6d6b,
curs = 0x63757273,
//Indic pos
abvm = 0x6162766d,
blwm = 0x626c776d,
dist = 0x64697374,
//script tags
latn = 0x6c61746e
}
/// <summary>
/// FeatureInfo flags, describing actions implemented in OT feature
/// </summary>
[Flags]
internal enum TagInfoFlags : uint
{
Substitution = 0x01, // does glyph substitution
Positioning = 0x02, // does glyph positioning
Both = 0x03, // does both substitution and positioning
None = 0x00 // neither of them
}
/* Used by commented code below
/// <summary>
/// OpenType feature information. Returned from GetFeatureList method
/// </summary>
internal struct TagInfo
{
public uint Tag;
public TagInfoFlags TagFlags;
public static bool IsNewTag(TagInfo[] Tags, uint Tag)
{
for(int i=0; i<Tags.Length; i++)
{
if (Tags[i].Tag==Tag) return false;
}
return true;
}
public static int GetTagIndex(TagInfo[] Tags, uint Tag)
{
for(ushort i=0; i<Tags.Length; i++)
{
if (Tags[i].Tag==Tag) return i;
}
return ushort.MaxValue;
}
}
*/
/// <summary>
/// Table pointer wrapper. Checking table boundaries
/// </summary>
internal unsafe class FontTable
{
public FontTable(byte[] data)
{
m_data = data;
if (data != null)
{
m_length = (uint)data.Length;
}
else
{
m_length = 0;
}
}
public const int InvalidOffset = int.MaxValue;
public const int NullOffset = 0;
public bool IsPresent
{
get
{
return (m_data!=null);
}
}
public ushort GetUShort(int offset)
{
Invariant.Assert(m_data!= null);
if ((offset + 1) >= m_length) throw new FileFormatException();
return (ushort)((m_data[offset]<<8) + m_data[offset+1]);
}
public short GetShort(int offset)
{
Invariant.Assert(m_data != null);
if ((offset + 1) >= m_length) throw new FileFormatException();
return (short)((m_data[offset]<<8) + m_data[offset+1]);
}
public uint GetUInt(int offset)
{
Invariant.Assert(m_data != null);
if ((offset + 3) >= m_length) throw new FileFormatException();
return (uint)((m_data[offset]<<24) + (m_data[offset+1]<<16) + (m_data[offset+2]<<8) + m_data[offset+3]);
}
public ushort GetOffset(int offset)
{
Invariant.Assert(m_data != null);
if ((offset+1)>=m_length) throw new FileFormatException();
return (ushort)((m_data[offset]<<8) + m_data[offset+1]);
}
private byte[] m_data;
private uint m_length;
}
/// <summary>
/// Font file access callbacks
/// </summary>
internal interface IOpenTypeFont
{
/// <summary>
/// Returns array containing font table data
/// Return empty array if table does not exist.
/// </summary>
FontTable GetFontTable(OpenTypeTags TableTag);
/// <summary>
/// Returns glyph coordinate
/// </summary>
LayoutOffset GetGlyphPointCoord(ushort Glyph, ushort PointIndex);
/// <summary>
/// Returns cache for layout table. If cache not found, return null Checked pointer
/// </summary>
byte[] GetTableCache(OpenTypeTags tableTag);
/// <summary>
/// Allocate space for layout table cache. If space is not available
/// client should return null checked pointer.
/// Only font cache implementation need to implement this interface.
/// Normal layout funcitons will not call it.
/// </summary>
byte[] AllocateTableCache(OpenTypeTags tableTag, int size);
}
/// <summary>
/// Text direction
/// </summary>
internal enum TextFlowDirection : ushort
{
LTR,
RTL,
TTB,
BTT
}
/// <summary>
/// Layout metrics
/// </summary>
internal struct LayoutMetrics
{
public TextFlowDirection Direction;
//if DesignEmHeight==0, result requested in design units
public ushort DesignEmHeight; // font design units per Em
public ushort PixelsEmWidth; // Em width in pixels
public ushort PixelsEmHeight; // Em height in pixels
public LayoutMetrics(TextFlowDirection Direction,
ushort DesignEmHeight,
ushort PixelsEmWidth,
ushort PixelsEmHeight)
{
this.Direction=Direction;
this.DesignEmHeight=DesignEmHeight;
this.PixelsEmWidth=PixelsEmWidth;
this.PixelsEmHeight=PixelsEmHeight;
}
}
internal class Feature
{
public Feature(
ushort startIndex,
ushort length,
uint tag,
uint parameter //0 if disabled
)
{
_startIndex = startIndex;
_length = length;
_tag = tag;
_parameter = parameter;
}
public uint Tag
{
get { return _tag; }
set { _tag = value; }
}
public uint Parameter
{
get { return _parameter; }
set { _parameter = value; }
}
public ushort StartIndex
{
get { return _startIndex; }
set { _startIndex = value; }
}
public ushort Length
{
get { return _length; }
set { _length = value; }
}
private ushort _startIndex; // first to be applied
private ushort _length; // length to be applied
private uint _tag; // OpenType feature tag
private uint _parameter; // feature parameter
}
/// <summary>
/// OpenTypeLayout class provides access to OpenType Layout services
/// </summary>
internal static unsafe class OpenTypeLayout
{
/// <summary>
/// </summary>
/// <param name="Font">Font</param>
/// <param name="ScriptTag">Script to find</param>
/// <returns>TagInfo, if script not present flags == None</returns>
internal static TagInfoFlags FindScript(
IOpenTypeFont Font, // In: Font access interface
uint ScriptTag // In
)
{
TagInfoFlags flags = TagInfoFlags.None;
try
{
FontTable gsubTable = Font.GetFontTable(OpenTypeTags.GSUB);
if (gsubTable.IsPresent)
{
GSUBHeader gsubHeader = new GSUBHeader(0);
if (!gsubHeader.GetScriptList(gsubTable).FindScript(gsubTable,ScriptTag).IsNull)
{
flags |= TagInfoFlags.Substitution;
}
}
}
catch (FileFormatException)
{
return TagInfoFlags.None;
}
try
{
FontTable gposTable = Font.GetFontTable(OpenTypeTags.GPOS);
if (gposTable.IsPresent)
{
GPOSHeader gposHeader = new GPOSHeader(0);
if (!gposHeader.GetScriptList(gposTable).FindScript(gposTable,ScriptTag).IsNull)
{
flags |= TagInfoFlags.Positioning;
}
}
}
catch (FileFormatException)
{
return TagInfoFlags.None;
}
return flags;
}
/// <summary>
///
/// </summary>
/// <param name="Font">Font</param>
/// <param name="ScriptTag">Script to search in</param>
/// <param name="LangSysTag">LangGys to search for</param>
/// <returns>TagInfoFlags, if script not present == None</returns>
internal static TagInfoFlags FindLangSys(
IOpenTypeFont Font,
uint ScriptTag,
uint LangSysTag
)
{
TagInfoFlags flags = TagInfoFlags.None;
try
{
FontTable gsubTable = Font.GetFontTable(OpenTypeTags.GSUB);
if (gsubTable.IsPresent)
{
GSUBHeader gsubHeader = new GSUBHeader(0);
ScriptTable gsubScript = gsubHeader.GetScriptList(gsubTable).FindScript(gsubTable,ScriptTag);
if (!gsubScript.IsNull && !gsubScript.FindLangSys(gsubTable,LangSysTag).IsNull)
{
flags |= TagInfoFlags.Substitution;
}
}
}
catch (FileFormatException)
{
return TagInfoFlags.None;
}
try
{
FontTable gposTable = Font.GetFontTable(OpenTypeTags.GPOS);
if (gposTable.IsPresent)
{
GPOSHeader gposHeader = new GPOSHeader(0);
ScriptTable gposScript = gposHeader.GetScriptList(gposTable).FindScript(gposTable,ScriptTag);
if (!gposScript.IsNull && !gposScript.FindLangSys(gposTable,LangSysTag).IsNull)
{
flags |= TagInfoFlags.Positioning;
}
}
}
catch (FileFormatException)
{
return TagInfoFlags.None;
}
return flags;
}
/* This is unused code, but will be used later so it is just commented out for now.
/// <summary>
/// Enumerates scripts in a font
/// </summary>
internal static OpenTypeLayoutResult GetScriptList (
IOpenTypeFont Font, // In: Font access interface
out TagInfo[] Scripts // Out: Array of scripts supported
)
{
ushort i;
ushort GposNewTags;
Scripts=null; // Assignment required, because of out attribute.
// This value should be owerwritten later.
try
{
FontTable GsubTable = Font.GetFontTable(OpenTypeTags.GSUB);
FontTable GposTable = Font.GetFontTable(OpenTypeTags.GPOS);
GSUBHeader GsubHeader = new GSUBHeader(0);
GPOSHeader GposHeader = new GPOSHeader(0);
ScriptList GsubScriptList;
ScriptList GposScriptList;
ushort GsubScriptCount;
ushort GposScriptCount;
if (GsubTable.IsNotPresent && GposTable.IsNotPresent)
{
Scripts = Array.Empty<TagInfo>();
return OpenTypeLayoutResult.Success;
}
if (GsubTable.IsPresent)
{
GsubScriptList = GsubHeader.GetScriptList(GsubTable);
GsubScriptCount = GsubScriptList.GetScriptCount(GsubTable);
}
else
{
GsubScriptList = new ScriptList(FontTable.InvalidOffset);
GsubScriptCount = 0;
}
if (GposTable.IsPresent)
{
GposScriptList = GposHeader.GetScriptList(GposTable);
GposScriptCount = GposScriptList.GetScriptCount(GposTable);
}
else
{
GposScriptList = new ScriptList(FontTable.InvalidOffset);
GposScriptCount = 0;
}
//This is true in most cases that there is no new tags in GPOS.
//So, we allocate this array then check GPOS for new tags
Scripts = new TagInfo[GsubScriptCount];
for(i=0; i<GsubScriptCount; i++)
{
Scripts[i].Tag = GsubScriptList.GetScriptTag(GsubTable,i);
Scripts[i].TagFlags = TagInfoFlags.Substitution;
}
//Check GPOS for tags that is not in GSUB
GposNewTags=0;
for(i=0;i<GposScriptCount;i++)
{
uint GposTag = GsubScriptList.GetScriptTag(GposTable,i);
if (TagInfo.IsNewTag(Scripts,GposTag)) GposNewTags++;
}
//append new tags to ScriptTags if any exists
if (GposNewTags>0)
{
int CurrentScriptIndex=GposScriptCount;
//Allocate new array to fit all tags
TagInfo[] tmp = Scripts;
Scripts = new TagInfo[GsubScriptCount+GposNewTags];
Array.Copy(tmp,0,Scripts,0,tmp.Length);
for(i=0;i<GposScriptCount;i++)
{
uint GposTag = GsubScriptList.GetScriptTag(GposTable,i);
if (TagInfo.IsNewTag(Scripts,GposTag))
{
Scripts[CurrentScriptIndex].Tag=GposTag;
Scripts[CurrentScriptIndex].TagFlags
= TagInfoFlags.Positioning;
++CurrentScriptIndex;
}
else
{
int ScriptIndex = TagInfo.GetTagIndex(Scripts,GposTag);
Scripts[ScriptIndex].TagFlags |= TagInfoFlags.Positioning;
}
}
Debug.Assert(CurrentScriptIndex==Scripts.Length);
}
}
catch (FileFormatException)
{
return OpenTypeLayoutResult.BadFontTable;
}
return OpenTypeLayoutResult.Success;
}
///<summary>
/// Enumerates language systems for script
/// </summary>
internal static OpenTypeLayoutResult GetLangSysList (
IOpenTypeFont Font, // In: Font access interface
uint ScriptTag, // In: Script tag
out TagInfo[] LangSystems // Out: Array of LangSystems for Script
)
{
ushort i;
ushort GposNewTags;
LangSystems=null; // Assignment required, because of out attribute.
// This value should be owerwritten later.
try
{
FontTable GsubTable = Font.GetFontTable(OpenTypeTags.GSUB);
FontTable GposTable = Font.GetFontTable(OpenTypeTags.GPOS);
GSUBHeader GsubHeader = new GSUBHeader(0);
GPOSHeader GposHeader = new GPOSHeader(0);
ScriptList GsubScriptList;
ScriptList GposScriptList;
ScriptTable GsubScript;
ScriptTable GposScript;
ushort GsubLangSysCount;
ushort GposLangSysCount;
if (GsubTable.IsNotPresent && GposTable.IsNotPresent)
{
return OpenTypeLayoutResult.ScriptNotFound;
}
if (GsubTable.IsPresent)
{
GsubScriptList = GsubHeader.GetScriptList(GsubTable);
GsubScript = GsubScriptList.FindScript(GsubTable,ScriptTag);
}
else
{
GsubScript = new ScriptTable(FontTable.InvalidOffset);
}
if (GposTable.IsPresent)
{
GposScriptList = GposHeader.GetScriptList(GposTable);
GposScript = GposScriptList.FindScript(GposTable,ScriptTag);
}
else
{
GposScript = new ScriptTable(FontTable.InvalidOffset);
}
if (GsubScript.IsNull && GposScript.IsNull)
{
return OpenTypeLayoutResult.ScriptNotFound;
}
if (!GsubScript.IsNull)
{
GsubLangSysCount = GsubScript.GetLangSysCount(GsubTable);
}
else
{
GsubLangSysCount = 0;
}
if (!GposScript.IsNull)
{
GposLangSysCount = GposScript.GetLangSysCount(GposTable);
}
else
{
GposLangSysCount = 0;
}
//This is true in most cases that there is no new tags in GPOS.
//So, we allocate this array then check GPOS for new tags
ushort CurrentLangSysIndex;
if (GsubScript.IsDefaultLangSysExists(GsubTable))
{
LangSystems = new TagInfo[GsubLangSysCount+1];
LangSystems[0].Tag = (uint)OpenTypeTags.dflt;
LangSystems[0].TagFlags = TagInfoFlags.Substitution;
CurrentLangSysIndex = 1;
}
else
{
LangSystems = new TagInfo[GsubLangSysCount];
CurrentLangSysIndex = 0;
}
for(i=0; i<GsubLangSysCount; i++)
{
LangSystems[CurrentLangSysIndex].Tag = GsubScript.GetLangSysTag(GsubTable,i);
LangSystems[CurrentLangSysIndex].TagFlags = TagInfoFlags.Substitution;
++CurrentLangSysIndex;
}
//Check GPOS for tags that is not in GSUB
GposNewTags=0;
if (!GposScript.IsNull)
{
if (GposScript.IsDefaultLangSysExists(GposTable) &&
TagInfo.IsNewTag(LangSystems,(uint)OpenTypeTags.dflt))
{
++GposNewTags;
}
for(i=0;i<GposLangSysCount;i++)
{
uint GposTag = GsubScript.GetLangSysTag(GposTable,i);
if (TagInfo.IsNewTag(LangSystems,GposTag))
{
++GposNewTags;
}
}
}
Debug.Assert(CurrentLangSysIndex==LangSystems.Length);
//append new tags to ScriptTags if any exists
if (GposNewTags>0)
{
//Allocate new array to fit all tags
TagInfo[] tmp = LangSystems;
LangSystems = new TagInfo[GsubLangSysCount+GposNewTags];
Array.Copy(tmp,0,LangSystems,0,tmp.Length);
if (GposScript.IsDefaultLangSysExists(GposTable))
{
if (TagInfo.IsNewTag(LangSystems,(uint)OpenTypeTags.dflt))
{
LangSystems[CurrentLangSysIndex].Tag = (uint)OpenTypeTags.dflt;
LangSystems[CurrentLangSysIndex].TagFlags = TagInfoFlags.Positioning;
++CurrentLangSysIndex;
}
else
{
int LangSysIndex = TagInfo.GetTagIndex(LangSystems,(uint)OpenTypeTags.dflt);
LangSystems[LangSysIndex].TagFlags |= TagInfoFlags.Positioning;
}
}
for(i=0;i<GposLangSysCount;i++)
{
uint GposTag = GposScript.GetLangSysTag(GposTable,i);
if (TagInfo.IsNewTag(LangSystems,GposTag))
{
LangSystems[CurrentLangSysIndex].Tag = GposTag;
LangSystems[CurrentLangSysIndex].TagFlags = TagInfoFlags.Positioning;
++CurrentLangSysIndex;
}
else
{
int LangSysIndex = TagInfo.GetTagIndex(LangSystems,GposTag);
LangSystems[LangSysIndex].TagFlags |= TagInfoFlags.Positioning;
}
}
Debug.Assert(CurrentLangSysIndex==LangSystems.Length);
}
}
catch (FileFormatException)
{
return OpenTypeLayoutResult.BadFontTable;
}
return OpenTypeLayoutResult.Success;
}
/// <summary>
/// Enumerates features in a language system
/// </summary>
internal static OpenTypeLayoutResult GetFeatureList (
IOpenTypeFont Font, // In: Font access interface
uint ScriptTag, // In: Script tag
uint LangSysTag, // In: LangSys tag
out TagInfo[] Features // Out: Array of features
)
{
ushort i;
ushort GposNewTags;
Features=null; // Assignment required, because of out attribute.
// This value should be owerwritten later.
try
{
FontTable GsubTable = Font.GetFontTable(OpenTypeTags.GSUB);
FontTable GposTable = Font.GetFontTable(OpenTypeTags.GPOS);
GSUBHeader GsubHeader = new GSUBHeader(0);
GPOSHeader GposHeader = new GPOSHeader(0);
ScriptList GsubScriptList;
ScriptList GposScriptList;
ScriptTable GsubScript;
ScriptTable GposScript;
LangSysTable GsubLangSys;
LangSysTable GposLangSys;
ushort GsubFeatureCount;
ushort GposFeatureCount;
FeatureList GsubFeatureList;
FeatureList GposFeatureList;
if (GsubTable.IsNotPresent && GposTable.IsNotPresent)
{
return OpenTypeLayoutResult.ScriptNotFound;
}
if (GsubTable.IsPresent)
{
GsubScriptList = GsubHeader.GetScriptList(GsubTable);
GsubScript = GsubScriptList.FindScript(GsubTable,ScriptTag);
GsubLangSys = GsubScript.FindLangSys(GsubTable,LangSysTag);
GsubFeatureList = GsubHeader.GetFeatureList(GsubTable);
}
else
{
GsubScript = new ScriptTable(FontTable.InvalidOffset);
GsubLangSys = new LangSysTable(FontTable.InvalidOffset);
GsubFeatureList = new FeatureList(FontTable.InvalidOffset);
}
if (GposTable.IsPresent)
{
GposScriptList = GposHeader.GetScriptList(GposTable);
GposScript = GposScriptList.FindScript(GposTable,ScriptTag);
GposLangSys = GposScript.FindLangSys(GposTable,LangSysTag);
GposFeatureList = GposHeader.GetFeatureList(GposTable);
}
else
{
GposScript = new ScriptTable(FontTable.InvalidOffset);
GposLangSys = new LangSysTable(FontTable.InvalidOffset);
GposFeatureList = new FeatureList(FontTable.InvalidOffset);
}
if (GsubScript.IsNull && GposScript.IsNull)
{
return OpenTypeLayoutResult.ScriptNotFound;
}
if (GsubLangSys.IsNull && GposLangSys.IsNull)
{
return OpenTypeLayoutResult.LangSysNotFound;
}
if (!GsubLangSys.IsNull)
{
GsubFeatureCount = GsubLangSys.FeatureCount(GsubTable);
}
else
{
GsubFeatureCount = 0;
}
if (!GposLangSys.IsNull)
{
GposFeatureCount = GposLangSys.FeatureCount(GposTable);
}
else
{
GposFeatureCount = 0;
}
Features = new TagInfo[GsubFeatureCount];
int CurrentFeatureIndex = 0;
for(i=0; i<GsubFeatureCount; i++)
{
ushort FeatureIndex = GsubLangSys.GetFeatureIndex(GsubTable,i);
Features[CurrentFeatureIndex].Tag = GsubFeatureList.FeatureTag(GsubTable,FeatureIndex);
Features[CurrentFeatureIndex].TagFlags = TagInfoFlags.Substitution;
++CurrentFeatureIndex;
}
Debug.Assert(CurrentFeatureIndex==Features.Length);
//Check GPOS for tags that is not in GSUB
GposNewTags=0;
if (!GposLangSys.IsNull)
{
for(i=0;i<GposFeatureCount;i++)
{
ushort FeatureIndex = GposLangSys.GetFeatureIndex(GposTable,i);
uint GposTag = GposFeatureList.FeatureTag(GposTable,FeatureIndex);
if (TagInfo.IsNewTag(Features,GposTag))
{
++GposNewTags;
}
}
}
//append new tags to ScriptTags if any exists
if (GposNewTags>0)
{
//Allocate new array to fit all tags
TagInfo[] tmp = Features;
Features = new TagInfo[GsubFeatureCount+GposNewTags];
Array.Copy(tmp,0,Features,0,tmp.Length);
for(i=0;i<GposFeatureCount;i++)
{
ushort FeatureIndex = GposLangSys.GetFeatureIndex(GposTable,i);
uint GposTag = GposFeatureList.FeatureTag(GposTable,FeatureIndex);
if (TagInfo.IsNewTag(Features,GposTag))
{
Features[CurrentFeatureIndex].Tag = GposTag;
Features[CurrentFeatureIndex].TagFlags = TagInfoFlags.Positioning;
++CurrentFeatureIndex;
}
else
{
int Index = TagInfo.GetTagIndex(Features,GposTag);
Features[Index].TagFlags |= TagInfoFlags.Positioning;
}
}
Debug.Assert(CurrentFeatureIndex==Features.Length);
}
}
catch (FileFormatException)
{
return OpenTypeLayoutResult.BadFontTable;
}
return OpenTypeLayoutResult.Success;
}
*/
/// <summary>
/// Substitutes glyphs according to features defined in the font.
/// </summary>
/// <param name="Font">In: Font access interface</param>
/// <param name="workspace">In: Workspace for layout engine</param>
/// <param name="ScriptTag">In: Script tag</param>
/// <param name="LangSysTag">In: LangSys tag</param>
/// <param name="FeatureSet">In: List of features to apply</param>
/// <param name="featureCount">In: Actual number of features in <paramref name="FeatureSet"/></param>
/// <param name="featureSetOffset">In: offset of input characters inside FeatureSet</param>
/// <param name="CharCount">In: Characters count (i.e. <paramref name="Charmap"/>.Length);</param>
/// <param name="Charmap">In/out: Char to glyph mapping</param>
/// <param name="Glyphs">In/out: List of GlyphInfo structs</param>
/// <returns>Substitution result</returns>
internal static OpenTypeLayoutResult SubstituteGlyphs(
IOpenTypeFont Font, // In: Font access interface
OpenTypeLayoutWorkspace workspace, // In: Workspace for layout engine
uint ScriptTag, // In: Script tag
uint LangSysTag, // In: LangSys tag
Feature[] FeatureSet, // In: List of features to apply
int featureCount, // In: Actual number of features in FeatureSet
int featureSetOffset,
int CharCount, // In: Characters count (i.e. Charmap.Length);
UshortList Charmap, // In/out: Char to glyph mapping
GlyphInfoList Glyphs // In/out: List of GlyphInfo structs
)
{
try
{
FontTable GsubTable = Font.GetFontTable(OpenTypeTags.GSUB);
if (!GsubTable.IsPresent) {return OpenTypeLayoutResult.ScriptNotFound;}
GSUBHeader GsubHeader = new GSUBHeader(0);
ScriptList ScriptList = GsubHeader.GetScriptList(GsubTable);
ScriptTable Script = ScriptList.FindScript(GsubTable,ScriptTag);
if (Script.IsNull) {return OpenTypeLayoutResult.ScriptNotFound;}
LangSysTable LangSys = Script.FindLangSys(GsubTable,LangSysTag);
if (LangSys.IsNull) {return OpenTypeLayoutResult.LangSysNotFound;}
FeatureList FeatureList = GsubHeader.GetFeatureList(GsubTable);
LookupList LookupList = GsubHeader.GetLookupList(GsubTable);
LayoutEngine.ApplyFeatures(
Font,
workspace,
OpenTypeTags.GSUB,
GsubTable,
new LayoutMetrics(), //it is not needed for substitution
LangSys,
FeatureList,
LookupList,
FeatureSet,
featureCount,
featureSetOffset,
CharCount,
Charmap,
Glyphs,
null,
null
);
}
catch (FileFormatException)
{
return OpenTypeLayoutResult.BadFontTable;
}
return OpenTypeLayoutResult.Success;
}
/// <summary>
/// Position glyphs according to features defined in the font.
/// </summary>
/// <param name="Font">In: Font access interface</param>
/// <param name="workspace">In: Workspace for layout engine</param>
/// <param name="ScriptTag">In: Script tag</param>
/// <param name="LangSysTag">In: LangSys tag</param>
/// <param name="Metrics">In: LayoutMetrics</param>
/// <param name="FeatureSet">In: List of features to apply</param>
/// <param name="featureCount">In: Actual number of features in <paramref name="FeatureSet"/></param>
/// <param name="featureSetOffset">In: offset of input characters inside FeatureSet</param>
/// <param name="CharCount">In: Characters count (i.e. <paramref name="Charmap"/>.Length);</param>
/// <param name="Charmap">In: Char to glyph mapping</param>
/// <param name="Glyphs">In/out: List of GlyphInfo structs</param>
/// <param name="Advances">In/out: Glyphs adv.widths</param>
/// <param name="Offsets">In/out: Glyph offsets</param>
/// <returns>Substitution result</returns>
internal static OpenTypeLayoutResult PositionGlyphs(
IOpenTypeFont Font,
OpenTypeLayoutWorkspace workspace,
uint ScriptTag,
uint LangSysTag,
LayoutMetrics Metrics,
Feature[] FeatureSet,
int featureCount,
int featureSetOffset,
int CharCount,
UshortList Charmap,
GlyphInfoList Glyphs,
int* Advances,
LayoutOffset* Offsets
)
{
try
{
FontTable GposTable = Font.GetFontTable(OpenTypeTags.GPOS);
if (!GposTable.IsPresent) {return OpenTypeLayoutResult.ScriptNotFound;}
GPOSHeader GposHeader = new GPOSHeader(0);
ScriptList ScriptList = GposHeader.GetScriptList(GposTable);
ScriptTable Script = ScriptList.FindScript(GposTable,ScriptTag);
if (Script.IsNull) {return OpenTypeLayoutResult.ScriptNotFound;}
LangSysTable LangSys = Script.FindLangSys(GposTable,LangSysTag);
if (LangSys.IsNull) {return OpenTypeLayoutResult.LangSysNotFound;}
FeatureList FeatureList = GposHeader.GetFeatureList(GposTable);
LookupList LookupList = GposHeader.GetLookupList(GposTable);
LayoutEngine.ApplyFeatures(
Font,
workspace,
OpenTypeTags.GPOS,
GposTable,
Metrics,
LangSys,
FeatureList,
LookupList,
FeatureSet,
featureCount,
featureSetOffset,
CharCount,
Charmap,
Glyphs,
Advances,
Offsets
);
}
catch (FileFormatException)
{
return OpenTypeLayoutResult.BadFontTable;
}
return OpenTypeLayoutResult.Success;
}
///<summary>
///
///</summary>
internal static OpenTypeLayoutResult CreateLayoutCache (
IOpenTypeFont font, // In: Font access interface
int maxCacheSize // In: Maximum cache size allowed
)
{
OpenTypeLayoutCache.CreateCache(font, maxCacheSize);
return OpenTypeLayoutResult.Success;
}
///<summary>
/// Internal method to test layout tables if they are uitable for fast path.
/// Returns list of script-langauge pairs that are not optimizable.
///</summary>
internal static OpenTypeLayoutResult GetComplexLanguageList (
IOpenTypeFont Font, //In: Font access interface
uint[] featureList, //In: Feature to look in
uint[] glyphBits,
ushort minGlyphId,
ushort maxGlyphId,
out WritingSystem[] complexLanguages
// Out: List of script/langauge pair
// that are not optimizable
)
{
try
{
WritingSystem[] gsubComplexLanguages = null;
WritingSystem[] gposComplexLanguages = null;
int gsubComplexLanguagesCount = 0;
int gposComplexLanguagesCount = 0;
FontTable GsubTable = Font.GetFontTable(OpenTypeTags.GSUB);
FontTable GposTable = Font.GetFontTable(OpenTypeTags.GPOS);
if (GsubTable.IsPresent)
{
LayoutEngine.GetComplexLanguageList(
OpenTypeTags.GSUB,
GsubTable,
featureList,
glyphBits,
minGlyphId,
maxGlyphId,
out gsubComplexLanguages,
out gsubComplexLanguagesCount
);
}
if (GposTable.IsPresent)
{
LayoutEngine.GetComplexLanguageList(
OpenTypeTags.GPOS,
GposTable,
featureList,
glyphBits,
minGlyphId,
maxGlyphId,
out gposComplexLanguages,
out gposComplexLanguagesCount
);
}
if (gsubComplexLanguages == null && gposComplexLanguages == null)
{
complexLanguages = null;
return OpenTypeLayoutResult.Success;
}
// Both tables have complex scrips, merge results
// Count gpos unique Languages
// and pack them at the same time
// so we do not research them again.
int gposNewLanguages=0, i, j;
for(i = 0; i < gposComplexLanguagesCount ;i++)
{
bool foundInGsub = false;
for(j = 0; j < gsubComplexLanguagesCount ;j++)
{
if (gsubComplexLanguages[j].scriptTag == gposComplexLanguages[i].scriptTag &&
gsubComplexLanguages[j].langSysTag == gposComplexLanguages[i].langSysTag
)
{
foundInGsub = true;
break;
};
}
if (!foundInGsub)
{
if (gposNewLanguages < i)
{
gposComplexLanguages[gposNewLanguages] = gposComplexLanguages[i];
}
gposNewLanguages++;
}
}
//realloc array for merged results, merge both arrays
complexLanguages = new WritingSystem[gsubComplexLanguagesCount + gposNewLanguages];
for(i = 0; i < gsubComplexLanguagesCount; i++)
{
complexLanguages[i] = gsubComplexLanguages[i];
}
for(i = 0; i < gposNewLanguages; i++)
{
complexLanguages[gsubComplexLanguagesCount + i] = gposComplexLanguages[i];
}
return OpenTypeLayoutResult.Success;
}
catch (FileFormatException)
{
complexLanguages = null;
return OpenTypeLayoutResult.BadFontTable;
}
}
}
internal struct WritingSystem
{
internal uint scriptTag;
internal uint langSysTag;
}
/// <summary>
/// </summary>
internal enum OpenTypeLayoutResult
{
Success,
InvalidParameter,
TableNotFound,
ScriptNotFound,
LangSysNotFound,
BadFontTable,
UnderConstruction
}
/// <summary>
/// Class for internal OpenType use to store per font
/// information and temporary buffers.
///
/// We do not use fontcache now, so this information
/// will be recreated every time shaping engine
/// will be called, so, in the future, design interfaces to use
/// fontcache and define what information can be stored there.
/// </summary>
internal class OpenTypeLayoutWorkspace
{
/// <summary>
/// Init buffers to initial values.
/// </summary>
internal unsafe OpenTypeLayoutWorkspace()
{
_bytesPerLookup = 0;
_lookupUsageFlags = null;
_cachePointers = null;
}
/// <summary>
/// Reset all structures to the new font/OTTable/script/langsys.
///
/// Client need to call it only once per shaping engine call.
/// This is client's responsibility to ensure that workspace is
/// used for single font/OTTable/script/langsys between Init() calls
/// </summary>
///<param name="font">In: Font access interface</param>
///<param name="tableTag">In: Font table tag</param>
///<param name="scriptTag">In: Script tag</param>
///<param name="langSysTag">In: Language System tag</param>
///<returns>Success if workspace is initialized succesfully, specific error if failed</returns>
internal OpenTypeLayoutResult Init(
IOpenTypeFont font,
OpenTypeTags tableTag,
uint scriptTag,
uint langSysTag
)
{
// Currently all buffers are per call,
// no need to do anything.
return OpenTypeLayoutResult.Success;
}
#region Lookup flags
//lookup usage flags access
private const byte AggregatedFlagMask = 0x01;
private const byte RequiredFeatureFlagMask = 0x02;
private const int FeatureFlagsStartBit = 2;
public void InitLookupUsageFlags(int lookupCount, int featureCount)
{
_bytesPerLookup = (featureCount + FeatureFlagsStartBit + 7) >> 3;
int requiredLookupUsageArraySize = lookupCount * _bytesPerLookup;
if ( _lookupUsageFlags == null ||
_lookupUsageFlags.Length < requiredLookupUsageArraySize)
{
_lookupUsageFlags = new byte[requiredLookupUsageArraySize];
}
Array.Clear(_lookupUsageFlags, 0, requiredLookupUsageArraySize);
}
public bool IsAggregatedFlagSet(int lookupIndex)
{
return ((_lookupUsageFlags[lookupIndex * _bytesPerLookup] & AggregatedFlagMask) != 0);
}
public bool IsFeatureFlagSet(int lookupIndex, int featureIndex)
{
int flagIndex = featureIndex + FeatureFlagsStartBit;
int flagByte = (lookupIndex * _bytesPerLookup) + (flagIndex >> 3);
byte flagMask = (byte)(1 << (flagIndex % 8));
return ((_lookupUsageFlags[flagByte] & flagMask) != 0);
}
public bool IsRequiredFeatureFlagSet(int lookupIndex)
{
return ((_lookupUsageFlags[lookupIndex * _bytesPerLookup] & RequiredFeatureFlagMask) != 0);
}
public void SetFeatureFlag(int lookupIndex, int featureIndex)
{
int startLookupByte = lookupIndex * _bytesPerLookup;
int flagIndex = featureIndex + FeatureFlagsStartBit;
int flagByte = startLookupByte + (flagIndex >> 3);
byte flagMask = (byte)(1 << (flagIndex % 8));
if (flagByte >= _lookupUsageFlags.Length)
{
//This should be invalid font. Lookup associated with the feature is not in lookup array.
throw new FileFormatException();
}
_lookupUsageFlags[flagByte] |= flagMask;
// Also set agregated usage flag
_lookupUsageFlags[startLookupByte] |= AggregatedFlagMask;
}
public void SetRequiredFeatureFlag(int lookupIndex)
{
int flagByte = lookupIndex * _bytesPerLookup;
if (flagByte >= _lookupUsageFlags.Length)
{
//This should be invalid font. Lookup associated with the feature is not in lookup array.
throw new FileFormatException();
}
//set RequiredFeature and aggregated flag at the same time
_lookupUsageFlags[flagByte] |= (AggregatedFlagMask | RequiredFeatureFlagMask);
}
// Define cache which lookup is enabled by which feature.
// Buffer grows with number of features applied
private int _bytesPerLookup;
private byte[] _lookupUsageFlags;
#endregion Lookup flags
#region Layout cache pointers
/// <summary>
/// Allocate enough memory for array of cache pointers, parallel to glyph run.
///
/// These method should not be used directly, it is only called by OpenTypeLayputCache.
///
/// </summary>
///<param name="glyphRunLength">In: Size of a glyph run</param>
public unsafe void AllocateCachePointers(int glyphRunLength)
{
if (_cachePointers != null && _cachePointers.Length >= glyphRunLength) return;
_cachePointers = new ushort[glyphRunLength];
}
/// <summary>
/// If glyph run is cahnged, update pointers according to the change. Reallocate array if necessary.
///
/// These method should not be used directly, it is only called by OpenTypeLayputCache.
///
/// </summary>
///<param name="oldLength">In: Number of glyphs in the run before change</param>
///<param name="newLength">In: Number of glyphs in the run after change</param>
///<param name="firstGlyphChanged">In: Index of the first changed glyph</param>
///<param name="afterLastGlyphChanged">In: Index of the glyph after last changed</param>
public unsafe void UpdateCachePointers(
int oldLength,
int newLength,
int firstGlyphChanged,
int afterLastGlyphChanged
)
{
if (oldLength != newLength)
{
int oldAfterLastGlyphChanged = afterLastGlyphChanged - (newLength - oldLength);
if (_cachePointers.Length < newLength)
{
ushort[] tmp = new ushort[newLength];
Array.Copy(_cachePointers, tmp, firstGlyphChanged);
Array.Copy(_cachePointers, oldAfterLastGlyphChanged, tmp, afterLastGlyphChanged, oldLength - oldAfterLastGlyphChanged);
_cachePointers = tmp;
}
else
{
Array.Copy(_cachePointers, oldAfterLastGlyphChanged, _cachePointers, afterLastGlyphChanged, oldLength - oldAfterLastGlyphChanged);
}
}
}
public unsafe ushort[] CachePointers
{
get { return _cachePointers; }
}
public byte[] TableCacheData
{
get { return _tableCache; }
set { _tableCache = value; }
}
// Array of cache pointers, per glyph
private unsafe ushort[] _cachePointers;
// Pointer to the table cache
private byte[] _tableCache;
#endregion Layout cache pointers
}
}
|