ImageSharp/SixLabors.Fonts/StreamFontMetrics.Cff.cs
2026-08-03 22:31:27 +02:00

194 lines
7.5 KiB
C#

// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using SixLabors.Fonts.Rendering;
using SixLabors.Fonts.Tables.AdvancedTypographic;
using SixLabors.Fonts.Tables.AdvancedTypographic.Variations;
using SixLabors.Fonts.Tables.Cff;
using SixLabors.Fonts.Tables.General;
using SixLabors.Fonts.Tables.General.Colr;
using SixLabors.Fonts.Tables.General.Kern;
using SixLabors.Fonts.Tables.General.Name;
using SixLabors.Fonts.Tables.General.Post;
using SixLabors.Fonts.Tables.General.Svg;
using SixLabors.Fonts.Unicode;
namespace SixLabors.Fonts {
/// <content>
/// Contains CFF specific methods.
/// </content>
internal partial class StreamFontMetrics
{
private static StreamFontMetrics LoadCompactFont(FontReader reader)
{
// Load using recommended order for best performance.
// https://learn.microsoft.com/en-gb/typography/opentype/spec/recom#optimized-table-ordering
// 'head', 'hhea', 'maxp', OS/2, 'name', 'cmap', 'post', 'CFF ' / 'CFF2'
HeadTable head = reader.GetTable<HeadTable>();
HorizontalHeadTable hhea = reader.GetTable<HorizontalHeadTable>();
MaximumProfileTable maxp = reader.GetTable<MaximumProfileTable>();
OS2Table os2 = reader.GetTable<OS2Table>();
NameTable name = reader.GetTable<NameTable>();
CMapTable cmap = reader.GetTable<CMapTable>();
PostTable post = reader.GetTable<PostTable>();
ICffTable? cff =
(reader.TryGetTable<Cff1Table>() ?? (ICffTable?)reader.TryGetTable<Cff2Table>())
?? throw new InvalidFontFileException("Missing required CFF table.");
// TODO: VORG
HorizontalMetricsTable htmx = reader.GetTable<HorizontalMetricsTable>();
VerticalHeadTable? vhea = reader.TryGetTable<VerticalHeadTable>();
VerticalMetricsTable? vmtx = null;
if (vhea is not null)
{
vmtx = reader.TryGetTable<VerticalMetricsTable>();
}
KerningTable? kern = reader.TryGetTable<KerningTable>();
GlyphDefinitionTable? gdef = reader.TryGetTable<GlyphDefinitionTable>();
GSubTable? gSub = reader.TryGetTable<GSubTable>();
GPosTable? gPos = reader.TryGetTable<GPosTable>();
ColrTable? colr = reader.TryGetTable<ColrTable>();
CpalTable? cpal = reader.TryGetTable<CpalTable>();
SvgTable? svg = reader.TryGetTable<SvgTable>();
// Variations related tables.
FVarTable? fVar = reader.TryGetTable<FVarTable>();
AVarTable? aVar = reader.TryGetTable<AVarTable>();
GVarTable? gVar = reader.TryGetTable<GVarTable>();
HVarTable? hVar = reader.TryGetTable<HVarTable>();
VVarTable? vVar = reader.TryGetTable<VVarTable>();
MVarTable? mVar = reader.TryGetTable<MVarTable>();
GlyphVariationProcessor? glyphVariationProcessor = null;
if (cff.ItemVariationStore != null)
{
if (fVar is null)
{
throw new InvalidFontFileException("missing fvar table required for glyph variations processing");
}
glyphVariationProcessor = new GlyphVariationProcessor(cff.ItemVariationStore, fVar, aVar, gVar, hVar, vVar, mVar);
}
CompactFontTables tables = new(cmap, head, hhea, htmx, maxp, name, os2, post, cff)
{
Kern = kern,
Vhea = vhea,
Vmtx = vmtx,
Gdef = gdef,
GSub = gSub,
GPos = gPos,
Colr = colr,
Cpal = cpal,
FVar = fVar,
AVar = aVar,
GVar = gVar,
HVar = hVar,
VVar = vVar,
MVar = mVar,
Svg = svg
};
return new StreamFontMetrics(tables, glyphVariationProcessor);
}
private FontGlyphMetrics CreateCffGlyphMetrics(
in CodePoint codePoint,
ushort glyphId,
GlyphType glyphType,
TextAttributes textAttributes,
TextDecorations textDecorations,
ColorFontSupport colorSupport,
bool isVerticalLayout,
ushort paletteIndex = 0)
{
// TODO: When do we require and how do we use the palette index?
CompactFontTables tables = this.compactFontTables!;
ICffTable cff = tables.Cff;
HorizontalMetricsTable htmx = tables.Htmx;
VerticalMetricsTable? vtmx = tables.Vmtx;
FVarTable? fVar = tables.FVar;
AVarTable? aVar = tables.AVar;
GVarTable? gVar = tables.GVar;
CffGlyphData vector = cff.GetGlyph(glyphId);
vector.FVar = fVar;
vector.AVar = aVar;
vector.GVar = gVar;
Bounds bounds = vector.GetBounds();
// Apply the CFF FontMatrix to transform bounds from charstring space to design units.
if (vector.FontMatrix is double[] fm)
{
float upm = this.UnitsPerEm;
Vector2 fmScale = new((float)(fm[0] * upm), (float)(fm[3] * upm));
bounds = new Bounds(bounds.Min * fmScale, bounds.Max * fmScale);
}
ushort advanceWidth = htmx.GetAdvancedWidth(glyphId);
short lsb = htmx.GetLeftSideBearing(glyphId);
// Apply HVAR advance width adjustment if available.
if (this.GlyphVariationProcessor is not null)
{
advanceWidth = (ushort)(advanceWidth + MathF.Round(this.GlyphVariationProcessor.AdvanceAdjustment(glyphId)));
}
IMetricsHeader metrics = isVerticalLayout ? this.VerticalMetrics : this.HorizontalMetrics;
ushort advancedHeight = (ushort)(metrics.Ascender - metrics.Descender);
short tsb = (short)(metrics.Ascender - bounds.Max.Y);
if (vtmx != null)
{
advancedHeight = vtmx.GetAdvancedHeight(glyphId);
tsb = vtmx.GetTopSideBearing(glyphId);
}
// Apply VVAR advance height adjustment if available.
if (this.GlyphVariationProcessor is not null)
{
advancedHeight = (ushort)(advancedHeight + MathF.Round(this.GlyphVariationProcessor.VerticalAdvanceAdjustment(glyphId)));
}
// TODO: Support CFF based COLR glyphs.
// This requires parsing the CFF charstrings to extract the glyph vectors.
SvgTable? svg = tables.Svg;
if ((colorSupport & ColorFontSupport.Svg) == ColorFontSupport.Svg && svg?.ContainsGlyph(glyphId) == true)
{
return new PaintedGlyphMetrics(
this,
glyphId,
codePoint,
this.GetOrCreateSvgGlyphSource(svg),
bounds,
advanceWidth,
advancedHeight,
lsb,
tsb,
this.UnitsPerEm,
textAttributes,
textDecorations);
}
return new CffGlyphMetrics(
this,
glyphId,
codePoint,
vector,
bounds,
advanceWidth,
advancedHeight,
lsb,
tsb,
this.UnitsPerEm,
textAttributes,
textDecorations,
glyphType);
}
}
}