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