// 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 { /// /// Contains CFF specific methods. /// 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(); HorizontalHeadTable hhea = reader.GetTable(); MaximumProfileTable maxp = reader.GetTable(); OS2Table os2 = reader.GetTable(); NameTable name = reader.GetTable(); CMapTable cmap = reader.GetTable(); PostTable post = reader.GetTable(); ICffTable? cff = (reader.TryGetTable() ?? (ICffTable?)reader.TryGetTable()) ?? throw new InvalidFontFileException("Missing required CFF table."); // TODO: VORG HorizontalMetricsTable htmx = reader.GetTable(); VerticalHeadTable? vhea = reader.TryGetTable(); VerticalMetricsTable? vmtx = null; if (vhea is not null) { vmtx = reader.TryGetTable(); } KerningTable? kern = reader.TryGetTable(); GlyphDefinitionTable? gdef = reader.TryGetTable(); GSubTable? gSub = reader.TryGetTable(); GPosTable? gPos = reader.TryGetTable(); ColrTable? colr = reader.TryGetTable(); CpalTable? cpal = reader.TryGetTable(); SvgTable? svg = reader.TryGetTable(); // Variations related tables. FVarTable? fVar = reader.TryGetTable(); AVarTable? aVar = reader.TryGetTable(); GVarTable? gVar = reader.TryGetTable(); HVarTable? hVar = reader.TryGetTable(); VVarTable? vVar = reader.TryGetTable(); MVarTable? mVar = reader.TryGetTable(); 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); } } }