-
Notifications
You must be signed in to change notification settings - Fork 86
/
Copy pathTypeface.cs
569 lines (492 loc) · 20 KB
/
Typeface.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
//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();
/// <summary>
/// control values in Font unit
/// </summary>
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;
/// <summary>
/// actual font filename (optional)
/// </summary>
public string Filename { get; set; }
/// <summary>
/// OS2 sTypoAscender/HheaTable.Ascent, in font designed unit
/// </summary>
public short Ascender => _useTypographicMertic ? OS2Table.sTypoAscender : HheaTable.Ascent;
/// <summary>
/// OS2 sTypoDescender, in font designed unit
/// </summary>
public short Descender => _useTypographicMertic ? OS2Table.sTypoDescender : HheaTable.Descent;
/// <summary>
/// OS2 usWinAscender
/// </summary>
public ushort ClipedAscender => OS2Table.usWinAscent;
/// <summary>
/// OS2 usWinDescender
/// </summary>
public ushort ClipedDescender => OS2Table.usWinDescent;
/// <summary>
/// OS2 Linegap
/// </summary>
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;
/// <summary>
/// find glyph index by codepoint
/// </summary>
/// <param name="codepoint"></param>
/// <param name="nextCodepoint"></param>
/// <returns></returns>
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<uint> unicodes)
{
CmapTable.CollectUnicode(unicodes);
}
public void CollectUnicode(int platform, List<uint> unicodes, List<ushort> glyphIndexList)
{
CmapTable.CollectUnicode(platform, unicodes, glyphIndexList);
}
public Glyph GetGlyphByName(string glyphName) => GetGlyph(GetGlyphIndexByName(glyphName));
Dictionary<string, ushort> _cachedGlyphDicByName;
void UpdateCff1FontSetNamesCache()
{
if (_cff1FontSet != null && _cachedGlyphDicByName == null)
{
//create cache data
_cachedGlyphDicByName = new Dictionary<string, ushort>();
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<string, ushort>();
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<GlyphNameMap> 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?
/// <summary>
/// default dpi
/// </summary>
public static uint DefaultDpi { get; set; } = 96;
/// <summary>
/// convert from point-unit value to pixel value
/// </summary>
/// <param name="targetPointSize"></param>
/// <param name="resolution">dpi</param>
/// <returns></returns>
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;
}
/// <summary>
/// calculate scale to target pixel size based on current typeface's UnitsPerEm
/// </summary>
/// <param name="targetPixelSize">target font size in point unit</param>
/// <returns></returns>
public float CalculateScaleToPixel(float targetPixelSize)
{
//1. return targetPixelSize / UnitsPerEm
return targetPixelSize / this.UnitsPerEm;
}
/// <summary>
/// calculate scale to target pixel size based on current typeface's UnitsPerEm
/// </summary>
/// <param name="targetPointSize">target font size in point unit</param>
/// <param name="resolution">dpi</param>
/// <returns></returns>
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);
}
/// <summary>
/// undate lang info
/// </summary>
/// <param name="metaTable"></param>
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<int> 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<int> 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;
}
}
}