using Age.Engine.Model; namespace Age.Engine.Text; /// /// Transient live-layout presentation state. History persistence owns neither this list nor its Gfx /// bindings; saved-frame script reconstruction rebuilds both. /// public sealed class RetainedAdvTextLayoutPresentation { private readonly List _glyphs = new(); public RetainedAdvTextLayoutPresentation(AdvTextLayoutPresentationBinding binding) { if (binding.LayoutSlot <= 0) throw new ArgumentOutOfRangeException(nameof(binding)); if (binding.SourceSurfaceSlot < 0) throw new ArgumentOutOfRangeException(nameof(binding)); if (binding.FirstObjectHandle < 0 || binding.ObjectCapacity <= 0) throw new ArgumentException( "A retained ADV layout requires a nonnegative first handle and positive capacity.", nameof(binding)); Binding = binding; } public AdvTextLayoutPresentationBinding Binding { get; } public IReadOnlyList Glyphs => _glyphs; public int PublishedGlyphCount { get; private set; } public int PublishableGlyphCount => (int)Math.Min(_glyphs.Count, Binding.ObjectCapacity); public int Append( IReadOnlyList records, int layoutOriginX, int layoutOriginY) { ArgumentNullException.ThrowIfNull(records); int first = _glyphs.Count; foreach (AdvRetainedGlyphRecord record in records) _glyphs.Add(new AdvRetainedGlyphPlacement( record, new AdvRetainedGlyphPresentationRect( record.Left, record.Top, record.Right, record.Bottom), layoutOriginX, layoutOriginY)); return first; } public int Append( IReadOnlyList records, IReadOnlyList presentationRects, int layoutOriginX, int layoutOriginY) { ArgumentNullException.ThrowIfNull(records); ArgumentNullException.ThrowIfNull(presentationRects); if (presentationRects.Count != records.Count) throw new ArgumentException( "Every retained glyph record requires one presentation rectangle.", nameof(presentationRects)); int first = _glyphs.Count; for (int index = 0; index < records.Count; index++) _glyphs.Add(new AdvRetainedGlyphPlacement( records[index], presentationRects[index], layoutOriginX, layoutOriginY)); return first; } /// Publish newly revealed glyphs through a layout-global target count. public int PublishThrough(GfxState gfx, int targetGlyphCount) { ArgumentNullException.ThrowIfNull(gfx); int target = Math.Clamp(targetGlyphCount, 0, PublishableGlyphCount); for (int index = PublishedGlyphCount; index < target; index++) Bind(gfx, index); PublishedGlyphCount = Math.Max(PublishedGlyphCount, target); return PublishedGlyphCount; } /// Recreate every currently revealed binding after op 0x20a or temporary suspension. public int Republish(GfxState gfx) { ArgumentNullException.ThrowIfNull(gfx); for (int index = 0; index < PublishedGlyphCount; index++) Bind(gfx, index); return PublishedGlyphCount; } public void ErasePublished(GfxState gfx) { ArgumentNullException.ThrowIfNull(gfx); gfx.EraseRange(Binding.FirstObjectHandle, Binding.ObjectCapacity); } private void Bind(GfxState gfx, int index) { AdvRetainedGlyphPlacement placement = _glyphs[index]; AdvRetainedGlyphPresentationRect rect = placement.PresentationRect; if (rect.Width <= 0 || rect.Height <= 0) return; gfx.BindDraw( checked(Binding.FirstObjectHandle + index), Binding.SourceSurfaceSlot, rect.Left, rect.Top, rect.Width, rect.Height, checked(placement.LayoutOriginX + rect.Left), checked(placement.LayoutOriginY + rect.Top)); } }