// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Diagnostics.CodeAnalysis; using System.IO; using SixLabors.Fonts.Tables.AdvancedTypographic.Variations; namespace SixLabors.Fonts.Tables.General.Colr { /// /// Represents the COLR v1 ClipList table, which maps ranges of glyph IDs to clip boxes /// that constrain paint operations. /// /// internal sealed class ClipList { /// /// Initializes a new instance of the class. /// /// The array of clip records mapping glyph ID ranges to clip box offsets. /// The array of resolved clip boxes, one per record. Null entries indicate an unknown format. public ClipList(ClipRecord[] records, ClipBox?[] boxes) { this.Records = records; this.Boxes = boxes; } /// /// Gets the array of clip records, sorted by start glyph ID. /// public ClipRecord[] Records { get; } /// /// Gets the array of resolved clip boxes, one per record. /// A entry indicates a clip box with an unknown format. /// public ClipBox?[] Boxes { get; } /// /// Gets the number of clip records. /// public int Count => this.Records.Length; /// /// Loads a from the given reader at the specified offset. /// /// The binary reader positioned within the COLR table. /// The offset from the beginning of the COLR table to the ClipList. /// The loaded , or if the offset is zero. public static ClipList? Load(BigEndianBinaryReader reader, long offset) { if (offset == 0) { return null; } reader.Seek(offset, SeekOrigin.Begin); _ = reader.ReadByte(); // Version. Always 1. uint count = reader.ReadUInt32(); ClipRecord[] records = new ClipRecord[count]; for (int i = 0; i < count; i++) { ushort start = reader.ReadUInt16(); ushort end = reader.ReadUInt16(); uint boxOffset = reader.ReadOffset24(); records[i] = new ClipRecord(start, end, boxOffset); } // TODO: Should this be nullable? ClipBox?[] boxes = new ClipBox?[count]; for (int i = 0; i < count; i++) { uint boxOffset = records[i].ClipBoxOffset; reader.Seek(offset + boxOffset, SeekOrigin.Begin); byte format = reader.ReadByte(); short xMin = reader.ReadFWORD(); short yMin = reader.ReadFWORD(); short xMax = reader.ReadFWORD(); short yMax = reader.ReadFWORD(); switch (format) { case 1: boxes[i] = new ClipBoxFormat1(xMin, yMin, xMax, yMax); break; case 2: uint varIndexBase = reader.ReadUInt32(); boxes[i] = new ClipBoxFormat2(xMin, yMin, xMax, yMax, varIndexBase); break; default: boxes[i] = null; // Unknown format break; } } return new ClipList(records, boxes); } /// /// Attempts to retrieve the clip box bounds for the specified glyph ID using a binary search /// over the sorted clip records. /// /// The glyph ID to look up. /// The COLR table used for resolving variation deltas. /// The glyph variation processor, or for non-variable fonts. /// /// When this method returns, contains the clip box bounds if found; otherwise, . /// /// if a clip box was found for the glyph; otherwise, . public bool TryGetClipBox( ushort glyphId, ColrTable colr, GlyphVariationProcessor? processor, [NotNullWhen(true)] out Bounds? bounds) { int lo = 0; int hi = this.Records.Length - 1; while (lo <= hi) { int mid = (lo + hi) >> 1; ClipRecord rec = this.Records[mid]; if (glyphId < rec.StartGlyphId) { hi = mid - 1; continue; } if (glyphId > rec.EndGlyphId) { lo = mid + 1; continue; } ClipBox? box = this.Boxes[mid]; if (box is null) { bounds = null; return false; } bounds = box.GetBounds(colr, processor); return true; } bounds = null; return false; } } }