using Azimuth;
namespace Glass.PerformanceTests
{
public static class Engines
{
public struct PlacementTally
{
public int GlyphCount;
public Scalar LastOriginY;
}
public static readonly Engine TextLayoutEngine = new Engine("TextLayout",
measureWidth: static workload =>
new Vector(TextLayout.CalculateTextWidth(workload.Font, workload.Text), 0),
calculateSize: static workload => TextLayout.CalculateSize(workload.Font, workload.Text,
workload.MaxWidth, workload.MaxHeight, workload.Overflow),
placeGlyphs: static workload =>
{
// A callback that does some work per glyph, so placement isn't timed against an empty sink.
PlacementTally tally = default;
TextLayout.PlaceGlyphs(workload.Font, workload.Text,
new Vector(workload.MaxWidth ?? Scalar.PositiveInfinity,
workload.MaxHeight ?? Scalar.PositiveInfinity),
Alignment.Horizontal.Left, Alignment.Vertical.Top, workload.MaxHeight, workload.Overflow,
ref tally, maxLines: null,
static (ref PlacementTally counted, char glyph, Vector origin) =>
{
++counted.GlyphCount;
counted.LastOriginY = origin.Y;
});
return new Vector(tally.GlyphCount, tally.LastOriginY);
});
public static Vector MeasureWords(Workload workload)
{
var measurer = new Typesetting.WordMeasurer(workload.Font,
new Typesetting.LineBreakIterator(workload.Text));
var ends = new Typesetting.WordEndMetrics(workload.Font);
int wordCount = 0;
int width = 0;
while (measurer.MoveNext())
{
++wordCount;
Typesetting.MeasuredWord word = measurer.Current;
width += word.LeadingKerning + word.ContentWidth + ends.TrailingWidthOf(word);
}
return new Vector(wordCount, width);
}
public static readonly Engine WordsEngine = Engine.ForStage("Mk2 words", MeasureWords);
///
/// Fits the words into lines, unlike this genuinely depends on the workload's
/// width, height and overflow, so it isn't a engine.
///
public static Vector FitLines(Workload workload)
{
var words = new Typesetting.WordMeasurer(workload.Font, new Typesetting.LineBreakIterator(workload.Text));
int maxWidth = workload.MaxWidth.HasValue ? (int)workload.MaxWidth.Value : int.MaxValue;
int? maxHeight = workload.MaxHeight.HasValue ? (int)workload.MaxHeight.Value : (int?)null;
var wrapper = new Typesetting.LineWrapper(words, maxWidth, maxHeight, workload.Overflow);
int lineCount = 0;
int widthTotal = 0;
while (wrapper.MoveNext())
{
++lineCount;
widthTotal += wrapper.Current.VisibleWidth;
}
return new Vector(lineCount, widthTotal);
}
///
/// MeasureWidth never wraps, so it skips LineBreakIterator/WordMeasurer/LineWrapper entirely: on a short
/// label, their setup can cost more than the answer.
///
public static Vector MeasureTextWidth(Workload workload)
{
return new Vector(Typesetting.TextWidth.Calculate(workload.Font, workload.Text), 0);
}
public static Vector CalculateLineLayoutSize(Workload workload)
{
return Typesetting.LineLayout.CalculateSize(
workload.Font, workload.Text, workload.MaxWidth, workload.MaxHeight, workload.Overflow);
}
public static readonly Engine LinesEngine =
new Engine("Mk2 lines", MeasureTextWidth, CalculateLineLayoutSize, FitLines);
///
/// Finds every line break, whatever the operation: mk2's first stage alone.
///
public static Vector FindBreaks(Workload workload)
{
var breaks = new Typesetting.LineBreakIterator(workload.Text);
int breakCount = 0;
int positionTotal = 0;
while (breaks.MoveNext())
{
++breakCount;
positionTotal += breaks.Current.Position;
}
return new Vector(breakCount, positionTotal & 0xFFFF);
}
public static readonly Engine BreaksEngine = Engine.ForStage("Mk2 breaks", FindBreaks);
///
/// Every engine under comparison. The first is the baseline every ratio is taken against.
///
public static readonly Engine[] All = { TextLayoutEngine, BreaksEngine, WordsEngine, LinesEngine };
}
}