using System; using System.Collections.Generic; using System.Linq; using Unity.UIWidgets.foundation; namespace Unity.UIWidgets.ui { public class Picture { public Picture(List drawCmds, Rect paintBounds) { this.drawCmds = drawCmds; this.paintBounds = paintBounds; } public readonly List drawCmds; public readonly Rect paintBounds; } public class PictureRecorder { readonly List _drawCmds = new List(); readonly List _states = new List(); public PictureRecorder() { this._states.Add(new CanvasState { xform = Matrix3.I(), scissor = null, saveLayer = false, layerOffset = null, paintBounds = Rect.zero, }); } CanvasState _getState() { D.assert(this._states.Count > 0); return this._states.Last(); } public Picture endRecording() { if (this._states.Count > 1) { throw new Exception("unmatched save/restore commands"); } var state = this._getState(); return new Picture(this._drawCmds, state.paintBounds); } public void addDrawCmd(DrawCmd drawCmd) { this._drawCmds.Add(drawCmd); switch (drawCmd) { case DrawSave _: this._states.Add(this._getState().copy()); break; case DrawSaveLayer cmd: { this._states.Add(new CanvasState { xform = Matrix3.I(), scissor = cmd.rect.shift(-cmd.rect.topLeft), saveLayer = true, layerOffset = cmd.rect.topLeft, paintBounds = Rect.zero, }); break; } case DrawRestore _: { var stateToRestore = this._getState(); this._states.RemoveAt(this._states.Count - 1); var state = this._getState(); if (!stateToRestore.saveLayer) { state.paintBounds = stateToRestore.paintBounds; } else { var paintBounds = stateToRestore.paintBounds.shift(stateToRestore.layerOffset); paintBounds = state.xform.mapRect(paintBounds); this._addPaintBounds(paintBounds); } break; } case DrawTranslate cmd: { var state = this._getState(); state.xform.preTranslate((float) cmd.dx, (float) cmd.dy); break; } case DrawScale cmd: { var state = this._getState(); state.xform.preScale((float) cmd.sx, (float) (cmd.sy ?? cmd.sx)); break; } case DrawRotate cmd: { var state = this._getState(); if (cmd.offset == null) { state.xform.preRotate((float) cmd.radians); } else { state.xform.preRotate((float) cmd.radians, (float) cmd.offset.dx, (float) cmd.offset.dy); } break; } case DrawSkew cmd: { var state = this._getState(); state.xform.preSkew((float) cmd.sx, (float) cmd.sy); break; } case DrawConcat cmd: { var state = this._getState(); state.xform.preConcat(cmd.matrix); break; } case DrawResetMatrix _: { var state = this._getState(); state.xform.reset(); break; } case DrawSetMatrix cmd: { var state = this._getState(); state.xform = new Matrix3(cmd.matrix); break; } case DrawClipRect cmd: { var state = this._getState(); var rect = state.xform.mapRect(cmd.rect); state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect); break; } case DrawClipRRect cmd: { var state = this._getState(); var rect = state.xform.mapRect(cmd.rrect.outerRect); state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect); break; } case DrawClipPath cmd: { var state = this._getState(); var scale = XformUtils.getScale(state.xform); var rect = cmd.path.flatten( scale * (float) Window.instance.devicePixelRatio ).getFillMesh(out _).transform(state.xform).bounds; state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect); break; } case DrawPath cmd: { var state = this._getState(); var scale = XformUtils.getScale(state.xform); var path = cmd.path; var paint = cmd.paint; var devicePixelRatio = (float) Window.instance.devicePixelRatio; MeshMesh mesh; if (paint.style == PaintingStyle.fill) { var cache = path.flatten(scale * devicePixelRatio); mesh = cache.getFillMesh(out _).transform(state.xform); } else { float strokeWidth = ((float) paint.strokeWidth * scale).clamp(0, 200.0f); float fringeWidth = 1 / devicePixelRatio; if (strokeWidth < fringeWidth) { strokeWidth = fringeWidth; } var cache = path.flatten(scale * devicePixelRatio); mesh = cache.getStrokeMesh( strokeWidth / scale * 0.5f, paint.strokeCap, paint.strokeJoin, (float) paint.strokeMiterLimit).transform(state.xform); } this._addPaintBounds(mesh.bounds); break; } case DrawImage cmd: { var state = this._getState(); var rect = Rect.fromLTWH(cmd.offset.dx, cmd.offset.dy, cmd.image.width, cmd.image.height); rect = state.xform.mapRect(rect); this._addPaintBounds(rect); break; } case DrawImageRect cmd: { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst); this._addPaintBounds(rect); break; } case DrawImageNine cmd: { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst); this._addPaintBounds(rect); break; } case DrawPicture cmd: { var state = this._getState(); var rect = state.xform.mapRect(cmd.picture.paintBounds); this._addPaintBounds(rect); break; } case DrawTextBlob cmd: { var state = this._getState(); var rect = cmd.textBlob.boundsInText.shift(cmd.offset); rect = state.xform.mapRect(rect); this._addPaintBounds(rect); break; } default: throw new Exception("unknown drawCmd: " + drawCmd); } } void _addPaintBounds(Rect paintBounds) { var state = this._getState(); if (state.scissor != null) { paintBounds = paintBounds.intersect(state.scissor); } if (state.paintBounds.isEmpty) { state.paintBounds = paintBounds; } else { state.paintBounds = state.paintBounds.expandToInclude(paintBounds); } } class CanvasState { public Matrix3 xform; public Rect scissor; public bool saveLayer; public Offset layerOffset; public Rect paintBounds; public CanvasState copy() { return new CanvasState { xform = this.xform, scissor = this.scissor, saveLayer = false, layerOffset = null, paintBounds = this.paintBounds, }; } } } }