//Apache2, 2017-present, WinterDev
//Apache2, 2014-2016, Samuel Carlsson, WinterDev
using System;
using System.Collections.Generic;
using Typography.OpenFont.Tables;
namespace Typography.OpenFont
{
public partial class Typeface
{
//TODO: implement vertical metrics
HorizontalMetrics _hMetrics;
NameEntry _nameEntry;
Glyph[] _glyphs;
CFF.Cff1FontSet _cff1FontSet;
TableHeader[] _tblHeaders;
bool _hasTtfOutline;
bool _hasCffData;
internal bool _useTypographicMertic;
#if DEBUG
static int s_dbugTotalId;
public readonly int dbugId = ++s_dbugTotalId;
#endif
internal Typeface()
{
//blank typefaces
#if DEBUG
if (dbugId == 5)
{
}
#endif
}
internal void SetTableEntryCollection(TableHeader[] headers) => _tblHeaders = headers;
internal void SetBasicTypefaceTables(OS2Table os2Table,
NameEntry nameEntry,
Head head,
HorizontalMetrics horizontalMetrics)
{
OS2Table = os2Table;
_nameEntry = nameEntry;
Head = head;
Bounds = head.Bounds;
UnitsPerEm = head.UnitsPerEm;
_hMetrics = horizontalMetrics;
}
internal Head Head { get; set; }
internal void SetTtfGlyphs(Glyph[] glyphs)
{
_glyphs = glyphs;
_hasTtfOutline = true;
}
internal void SetBitmapGlyphs(Glyph[] glyphs, BitmapFontGlyphSource bitmapFontGlyphSource)
{
_glyphs = glyphs;
_bitmapFontGlyphSource = bitmapFontGlyphSource;
}
internal void SetCffFontSet(CFF.Cff1FontSet cff1FontSet)
{
_cff1FontSet = cff1FontSet;
_hasCffData = true;
Glyph[] exisitingGlyphs = _glyphs;
_glyphs = cff1FontSet._fonts[0]._glyphs; //TODO: review _fonts[0]
if (exisitingGlyphs != null)
{
//
#if DEBUG
if (_glyphs.Length != exisitingGlyphs.Length)
{
throw new OpenFontNotSupportedException();
}
#endif
for (int i = 0; i < exisitingGlyphs.Length; ++i)
{
Glyph.CopyExistingGlyphInfo(exisitingGlyphs[i], _glyphs[i]);
}
}
}
public Languages Languages { get; } = new Languages();
///
/// control values in Font unit
///
internal int[] ControlValues { get; set; }
internal byte[] PrepProgramBuffer { get; set; }
internal byte[] FpgmProgramBuffer { get; set; }
internal MaxProfile MaxProfile { get; set; }
internal Cmap CmapTable { get; set; }
internal Kern KernTable { get; set; }
internal Gasp GaspTable { get; set; }
internal HorizontalHeader HheaTable { get; set; }
public OS2Table OS2Table { get; set; }
//
public bool HasPrepProgramBuffer => PrepProgramBuffer != null;
///
/// actual font filename (optional)
///
public string Filename { get; set; }
///
/// OS2 sTypoAscender/HheaTable.Ascent, in font designed unit
///
public short Ascender => _useTypographicMertic ? OS2Table.sTypoAscender : HheaTable.Ascent;
///
/// OS2 sTypoDescender, in font designed unit
///
public short Descender => _useTypographicMertic ? OS2Table.sTypoDescender : HheaTable.Descent;
///
/// OS2 usWinAscender
///
public ushort ClipedAscender => OS2Table.usWinAscent;
///
/// OS2 usWinDescender
///
public ushort ClipedDescender => OS2Table.usWinDescent;
///
/// OS2 Linegap
///
public short LineGap => _useTypographicMertic ? OS2Table.sTypoLineGap : HheaTable.LineGap;
//The typographic line gap for this font.
//Remember that this is not the same as the LineGap value in the 'hhea' table,
//which Apple defines in a far different manner.
//The suggested usage for sTypoLineGap is
//that it be used in conjunction with unitsPerEm
//to compute a typographically correct default line spacing.
//
//Typical values average 7 - 10 % of units per em.
//The goal is to free applications from Macintosh or Windows - specific metrics
//which are constrained by backward compatability requirements
//(see chapter, “Recommendations for OpenType Fonts”).
//These new metrics, when combined with the character design widths,
//will allow applications to lay out documents in a typographically correct and portable fashion.
//These metrics will be exposed through Windows APIs.
//Macintosh applications will need to access the 'sfnt' resource and
//parse it to extract this data from the “OS / 2” table
//(unless Apple exposes the 'OS/2' table through a new API)
//---------------
public string Name => _nameEntry.FontName;
public string FontSubFamily => _nameEntry.FontSubFamily;
public string PostScriptName => _nameEntry.PostScriptName;
public string VersionString => _nameEntry.VersionString;
public string UniqueFontIden => _nameEntry.UniqueFontIden;
internal NameEntry NameEntry => _nameEntry;
public int GlyphCount => _glyphs.Length;
///
/// find glyph index by codepoint
///
///
///
///
public ushort GetGlyphIndex(int codepoint, int nextCodepoint, out bool skipNextCodepoint)
{
return CmapTable.GetGlyphIndex(codepoint, nextCodepoint, out skipNextCodepoint);
}
public ushort GetGlyphIndex(int codepoint)
{
return CmapTable.GetGlyphIndex(codepoint, 0, out bool skipNextCodepoint);
}
public void CollectUnicode(List unicodes)
{
CmapTable.CollectUnicode(unicodes);
}
public void CollectUnicode(int platform, List unicodes, List glyphIndexList)
{
CmapTable.CollectUnicode(platform, unicodes, glyphIndexList);
}
public Glyph GetGlyphByName(string glyphName) => GetGlyph(GetGlyphIndexByName(glyphName));
Dictionary _cachedGlyphDicByName;
void UpdateCff1FontSetNamesCache()
{
if (_cff1FontSet != null && _cachedGlyphDicByName == null)
{
//create cache data
_cachedGlyphDicByName = new Dictionary();
for (int i = 1; i < _glyphs.Length; ++i)
{
Glyph glyph = _glyphs[i];
if (glyph._cff1GlyphData != null && glyph._cff1GlyphData.Name != null)
{
_cachedGlyphDicByName.Add(glyph._cff1GlyphData.Name, (ushort)i);
}
else
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("Cff unknown glyphname");
#endif
}
}
}
}
public ushort GetGlyphIndexByName(string glyphName)
{
if (glyphName == null) return 0;
if (_cff1FontSet != null && _cachedGlyphDicByName == null)
{
//we create a dictionary
//create cache data
_cachedGlyphDicByName = new Dictionary();
for (int i = 1; i < _glyphs.Length; ++i)
{
Glyph glyph = _glyphs[i];
if (glyph._cff1GlyphData.Name != null)
{
_cachedGlyphDicByName.Add(glyph._cff1GlyphData.Name, (ushort)i);
}
else
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("Cff unknown glyphname");
#endif
}
}
return _cachedGlyphDicByName.TryGetValue(glyphName, out ushort glyphIndex) ? glyphIndex : (ushort)0;
}
else if (PostTable != null)
{
if (PostTable.Version == 2)
{
return PostTable.GetGlyphIndex(glyphName);
}
else
{
//check data from adobe glyph list
//from the unicode value
//select glyph index
//we use AdobeGlyphList
//from https://github.com/adobe-type-tools/agl-aglfn/blob/master/glyphlist.txt
//but user can provide their own map here...
return GetGlyphIndex(AdobeGlyphList.GetUnicodeValueByGlyphName(glyphName));
}
}
return 0;
}
public IEnumerable GetGlyphNameIter()
{
if (_cachedGlyphDicByName == null && _cff1FontSet != null)
{
UpdateCff1FontSetNamesCache();
}
if (_cachedGlyphDicByName != null)
{
//iter from here
foreach (var kv in _cachedGlyphDicByName)
{
yield return new GlyphNameMap(kv.Value, kv.Key);
}
}
else if (PostTable.Version == 2)
{
foreach (var kp in PostTable.GlyphNames)
{
yield return new GlyphNameMap(kp.Key, kp.Value);
}
}
}
public Glyph GetGlyph(ushort glyphIndex)
{
if (glyphIndex < _glyphs.Length)
{
return _glyphs[glyphIndex];
}
else
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("found unknown glyph:" + glyphIndex);
#endif
return _glyphs[0]; //return empty glyph?;
}
}
public ushort GetAdvanceWidthFromGlyphIndex(ushort glyphIndex) => _hMetrics.GetAdvanceWidth(glyphIndex);
public short GetLeftSideBearing(ushort glyphIndex) => _hMetrics.GetLeftSideBearing(glyphIndex);
public short GetKernDistance(ushort leftGlyphIndex, ushort rightGlyphIndex)
{
//DEPRECATED -> use OpenFont layout instead
return this.KernTable.GetKerningDistance(leftGlyphIndex, rightGlyphIndex);
}
//
public Bounds Bounds { get; private set; }
public ushort UnitsPerEm { get; private set; }
public short UnderlinePosition => PostTable.UnderlinePosition; //TODO: review here
//
const int s_pointsPerInch = 72;//point per inch, fix?
///
/// default dpi
///
public static uint DefaultDpi { get; set; } = 96;
///
/// convert from point-unit value to pixel value
///
///
/// dpi
///
public static float ConvPointsToPixels(float targetPointSize, int resolution = -1)
{
//http://stackoverflow.com/questions/139655/convert-pixels-to-points
//points = pixels * 72 / 96
//------------------------------------------------
//pixels = targetPointSize * 96 /72
//pixels = targetPointSize * resolution / pointPerInch
if (resolution < 0)
{
//use current DefaultDPI
resolution = (int)DefaultDpi;
}
return targetPointSize * resolution / s_pointsPerInch;
}
///
/// calculate scale to target pixel size based on current typeface's UnitsPerEm
///
/// target font size in point unit
///
public float CalculateScaleToPixel(float targetPixelSize)
{
//1. return targetPixelSize / UnitsPerEm
return targetPixelSize / this.UnitsPerEm;
}
///
/// calculate scale to target pixel size based on current typeface's UnitsPerEm
///
/// target font size in point unit
/// dpi
///
public float CalculateScaleToPixelFromPointSize(float targetPointSize, int resolution = -1)
{
//1. var sizeInPixels = ConvPointsToPixels(sizeInPointUnit);
//2. return sizeInPixels / UnitsPerEm
if (resolution < 0)
{
//use current DefaultDPI
resolution = (int)DefaultDpi;
}
return (targetPointSize * resolution / s_pointsPerInch) / this.UnitsPerEm;
}
internal BASE BaseTable { get; set; }
internal GDEF GDEFTable { get; set; }
public COLR COLRTable { get; private set; }
public CPAL CPALTable { get; private set; }
internal bool HasColorAndPal { get; private set; }
internal void SetColorAndPalTable(COLR colr, CPAL cpal)
{
COLRTable = colr;
CPALTable = cpal;
HasColorAndPal = colr != null;
}
public GPOS GPOSTable { get; internal set; }
public GSUB GSUBTable { get; internal set; }
internal void LoadOpenFontLayoutInfo(GDEF gdefTable, GSUB gsubTable, GPOS gposTable, BASE baseTable, COLR colrTable, CPAL cpalTable)
{
//***
this.GDEFTable = gdefTable;
this.GSUBTable = gsubTable;
this.GPOSTable = gposTable;
this.BaseTable = baseTable;
this.COLRTable = colrTable;
this.CPALTable = cpalTable;
//---------------------------
//fill glyph definition
if (gdefTable != null)
{
gdefTable.FillGlyphData(_glyphs);
}
}
internal PostTable PostTable { get; set; }
internal bool _evalCffGlyphBounds;
public bool IsCffFont => _hasCffData;
//Math Table
MathGlyphs.MathGlyphInfo[] _mathGlyphInfos;
internal MathTable _mathTable;
//
public MathGlyphs.MathConstants MathConsts => _mathTable?._mathConstTable;
internal void LoadMathGlyphInfos(MathGlyphs.MathGlyphInfo[] mathGlyphInfos)
{
_mathGlyphInfos = mathGlyphInfos;
if (mathGlyphInfos != null)
{
//fill to original glyph?
for (int glyphIndex = 0; glyphIndex < _glyphs.Length; ++glyphIndex)
{
_glyphs[glyphIndex].MathGlyphInfo = mathGlyphInfos[glyphIndex];
}
}
}
public MathGlyphs.MathGlyphInfo GetMathGlyphInfo(ushort glyphIndex) => _mathGlyphInfos[glyphIndex];
//-------------------------
//svg and bitmap font
SvgTable _svgTable;
internal bool HasSvgTable { get; private set; }
internal void SetSvgTable(SvgTable svgTable)
{
HasSvgTable = (_svgTable = svgTable) != null;
}
public void ReadSvgContent(ushort glyphIndex, System.Text.StringBuilder output) => _svgTable?.ReadSvgContent(glyphIndex, output);
internal BitmapFontGlyphSource _bitmapFontGlyphSource;
public bool IsBitmapFont => _bitmapFontGlyphSource != null;
public void ReadBitmapContent(Glyph glyph, System.IO.Stream output)
{
_bitmapFontGlyphSource.CopyBitmapContent(glyph, output);
}
///
/// undate lang info
///
///
internal void UpdateLangs(Meta metaTable) => Languages.Update(OS2Table, metaTable, CmapTable, this.GSUBTable, this.GPOSTable);
internal ushort _whitespaceWidth; //common used value
internal void UpdateFrequentlyUsedValues()
{
//whitespace
ushort whitespace_glyphIndex = this.GetGlyphIndex(' ');
if (whitespace_glyphIndex > 0)
{
_whitespaceWidth = this.GetAdvanceWidthFromGlyphIndex(whitespace_glyphIndex);
}
}
#if DEBUG
public override string ToString() => Name;
#endif
}
public interface IGlyphPositions
{
int Count { get; }
GlyphClassKind GetGlyphClassKind(int index);
void AppendGlyphOffset(int index, short appendOffsetX, short appendOffsetY);
void AppendGlyphAdvance(int index, short appendAdvX, short appendAdvY);
ushort GetGlyph(int index, out short advW);
ushort GetGlyph(int index, out ushort inputOffset, out short offsetX, out short offsetY, out short advW);
//
void GetOffset(int index, out short offsetX, out short offsetY);
}
public static class StringUtils
{
public static void FillWithCodepoints(List codepoints, char[] str, int startAt = 0, int len = -1)
{
if (len == -1) len = str.Length;
// this is important!
// -----------------------
// from @samhocevar's PR: (https://github.com/LayoutFarm/Typography/pull/56/commits/b71c7cf863531ebf5caa478354d3249bde40b96e)
// In many places, "char" is not a valid type to handle characters, because it
// only supports 16 bits.In order to handle the full range of Unicode characters,
// we need to use "int".
// This allows characters such as 🙌 or 𐐷 or to be treated as single codepoints even
// though they are encoded as two "char"s in a C# string.
for (int i = 0; i < len; ++i)
{
char ch = str[startAt + i];
int codepoint = ch;
if (char.IsHighSurrogate(ch) && i + 1 < len)
{
char nextCh = str[startAt + i + 1];
if (char.IsLowSurrogate(nextCh))
{
++i;
codepoint = char.ConvertToUtf32(ch, nextCh);
}
}
codepoints.Add(codepoint);
}
}
public static IEnumerable GetCodepoints(char[] str, int startAt = 0, int len = -1)
{
if (len == -1) len = str.Length;
// this is important!
// -----------------------
// from @samhocevar's PR: (https://github.com/LayoutFarm/Typography/pull/56/commits/b71c7cf863531ebf5caa478354d3249bde40b96e)
// In many places, "char" is not a valid type to handle characters, because it
// only supports 16 bits.In order to handle the full range of Unicode characters,
// we need to use "int".
// This allows characters such as 🙌 or 𐐷 or to be treated as single codepoints even
// though they are encoded as two "char"s in a C# string.
for (int i = 0; i < len; ++i)
{
char ch = str[startAt + i];
int codepoint = ch;
if (char.IsHighSurrogate(ch) && i + 1 < len)
{
char nextCh = str[startAt + i + 1];
if (char.IsLowSurrogate(nextCh))
{
++i;
codepoint = char.ConvertToUtf32(ch, nextCh);
}
}
yield return codepoint;
}
}
}
public readonly struct GlyphNameMap
{
public readonly ushort glyphIndex;
public readonly string glyphName;
public GlyphNameMap(ushort glyphIndex, string glyphName)
{
this.glyphIndex = glyphIndex;
this.glyphName = glyphName;
}
}
}