// 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;
}
}
}