import { SObject } from "@saga-web/base/lib"; import { SPaintEngine } from "./engines/SPaintEngine"; import { SBrush, SCanvasPaintEngine, SCanvasView, SFont, SLine, SPath2D, SPen, SPoint, SRect, SSize } from "./"; import { SCompositeType } from "./enums/SCompositeType"; import { SArrow } from "./types/SArrow"; import { SArrowStyleType } from "./enums/SArrowStyleType"; /** * Painter * * @author 庞利祥(sybotan@126.com) */ export class SPainter extends SObject { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 属性定义 /** 绘制引擎 */ private readonly engine: SPaintEngine; /** 画笔 */ get pen(): SPen { return this.engine.state.pen; } // Get pen set pen(value: SPen) { this.engine.state.pen = value; } // Set pen /** 画刷 */ get brush(): SBrush { return this.engine.state.brush; } // Get brush set brush(value: SBrush) { this.engine.state.brush = value; } // Set brush /** 字体属性 */ get font(): SFont { return this.engine.state.font; } // Get font set font(value: SFont) { this.engine.state.font = value; } // Set font /** 融合属性 */ get composite(): number { return this.engine.state._composite; } // Get composite set composite(value: number) { this.engine.state._composite = value; } // Set composite /** 变换矩阵 */ get worldTransform(): DOMMatrix { return this.engine.state.matrix; } // Get worldTransform /** * 构造函数 * * @param engine 绘制引擎 */ constructor(engine: SCanvasView | SPaintEngine) { super(); if (engine instanceof SCanvasView) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.engine = new SCanvasPaintEngine(engine.canvas!!); } else { this.engine = engine; } this.pen = new SPen(); this.brush = new SBrush(); this.font = new SFont(); this.composite = SCompositeType.SourceOver; } // Constructor() /** * 保存painter状态 */ save(): void { this.engine.save(); } // Function save() /** * 恢复painter状态 */ restore(): void { this.engine.restore(); } // Function restore() // ================================================================================================================= // 变换相关 /** * 平移变换 * * @param x X轴方向平移 * @param y Y辆方向平移 */ translate(x: number, y: number): void { this.engine.translate(x, y); } // Function translate() /** * 缩放 * * @param x X轴方向缩放 * @param y Y辆方向缩放 */ scale(x: number, y: number): void { this.engine.scale(x, y); } // Function scale() /** * 旋转 * * @param angle 旋转角度(单位弧度) */ rotate(angle: number): void { this.engine.rotate(angle); } // Function rotate() /** * 将当前的变形矩阵乘上一个基于自身参数的矩阵 * * @param m11 水平方向的缩放 * @param m12 水平方向的倾斜偏移 * @param m21 竖直方向的倾斜偏移 * @param m22 竖直方向的缩放 * @param dx 水平方向的移动 * @param dy 竖直方向的移动 */ transform( m11: number, m12: number, m21: number, m22: number, dx: number, dy: number ): void { this.engine.transform(m11, m12, m21, m22, dx, dy); } // Function transform() /** * 将当前的变形矩阵重置为单位矩阵,再将当前的变形矩阵乘上一个基于自身参数的矩阵 * * @param m11 水平方向的缩放 * @param m12 水平方向的倾斜偏移 * @param m21 竖直方向的倾斜偏移 * @param m22 竖直方向的缩放 * @param dx 水平方向的移动 * @param dy 竖直方向的移动 */ setTransform( m11: number, m12: number, m21: number, m22: number, dx: number, dy: number ): void { this.engine.setTransform(m11, m12, m21, m22, dx, dy); } // Function transform() /** * 重置当前变形为单位矩阵。等价于调用setTransform(1, 0, 0, 1, 0, 0) */ resetTransform(): void { this.engine.resetTransform(); } // Function resetTransform() // ================================================================================================================= // 绘制相关 /** * 设置裁剪路径 * * @param path 裁剪路径 */ setClip(path: Path2D): void { this.engine.setClip(path); } // Function setClip() /** * 清空矩形区域 * * @param rect 矩形 */ clearRect(rect: SRect): void; /** * 清空矩形区域 * * @param leftTop 左上角坐标 * @param rightBottom 右下角坐标 */ clearRect(leftTop: SPoint, rightBottom: SPoint): void; /** * 清空矩形区域 * * @param leftTop 左上角坐标 * @param size 大小 */ clearRect(leftTop: SPoint, size: SSize): void; /** * 清空矩形区域 * * @param x X坐标 * @param y Y坐标 * @param w 宽度 * @param h 高度 */ clearRect(x: number, y: number, w: number, h: number): void; /** * 清空矩形区域(重载实现) * * @param x X坐标 | 左上角坐标 | 矩形 * @param y Y坐标 | 右下角坐标 | 大小 * @param w 宽度 * @param h 高度 */ clearRect( x: number | SPoint | SRect, y?: number | SPoint | SSize, w?: number, h?: number ): void { if (x instanceof SRect) { this.engine.clearRect(x); } else if (x instanceof SPoint && y instanceof SPoint) { this.engine.clearRect(new SRect(x, y)); } else if (x instanceof SPoint && y instanceof SSize) { this.engine.clearRect(new SRect(x, y)); } else { this.engine.clearRect( new SRect(x as number, y as number, w as number, h as number) ); } } // Function clearRect() /** * 绘制矩形 * * @param rect 矩形 */ drawRect(rect: SRect): void; /** * 绘制矩形 * * @param leftTop 左上角坐标 * @param rightBottom 右下角坐标 */ drawRect(leftTop: SPoint, rightBottom: SPoint): void; /** * 绘制矩形 * * @param leftTop 左上角坐标 * @param size 大小 */ drawRect(leftTop: SPoint, size: SSize): void; /** * 绘制矩形 * * @param x X坐标 * @param y Y坐标 * @param w 宽度 * @param h 高度 */ drawRect(x: number, y: number, w: number, h: number): void; /** * 绘制矩形 * * @param x X坐标 | 左上角坐标 | 矩形 * @param y Y坐标 | 右下角坐标 | 大小 * @param w 宽度 * @param h 高度 */ drawRect( x: number | SPoint | SRect, y?: number | SPoint | SSize, w?: number, h?: number ): void { if (x instanceof SRect) { this.engine.drawRect(x); } else if (x instanceof SPoint && y instanceof SPoint) { this.engine.drawRect(new SRect(x, y)); } else if (x instanceof SPoint && y instanceof SSize) { this.engine.drawRect(new SRect(x, y)); } else { this.engine.drawRect( new SRect(x as number, y as number, w as number, h as number) ); } } // Function drawRect() // /** // * 绘制带导角空心矩形 // * // * @param x X坐标 // * @param y Y坐标 // * @param w 宽度 // * @param h 高度 // * @param r 导角半径 // */ // drawRoundedRect( // x: number, // y: number, // w: number, // h: number, // r: number // ): void {} // Function drawRoundedRect() /** * 绘制圆形 * * @param cx 圆心X坐标 * @param cy 圆心X坐标 * @param r 圆半径 */ drawCircle(cx: number, cy: number, r: number): void { this.engine.drawCircle(cx, cy, r); } // Function drawCircle() /** * 绘制椭圆 * * @param cx 圆点X坐标 * @param cy 圆点Y坐标 * @param rx 水平半径 * @param ry 垂直半径 */ drawEllipse(cx: number, cy: number, rx: number, ry: number): void { this.engine.drawEllipse(cx, cy, rx, ry); } // Function drawEllipse() /** * 绘制椭圆弧 * * @param rect 椭圆所在矩形 * @param startAngle 开始角度(单位弧度) * @param endAngle 结束角度(单位弧度) */ drawArc(rect: SRect, startAngle: number, endAngle: number): void; /** * 绘制椭圆弧 * * @param x 椭圆所在矩形X坐标 * @param y 椭圆所在矩形Y坐标 * @param width 椭圆所在矩形宽度 * @param height 椭圆所在矩形高度 * @param startAngle 开始角度(单位弧度) * @param endAngle 结束角度(单位弧度) */ drawArc( x: number, y: number, width: number, height: number, startAngle: number, endAngle: number ): void; /** * 绘制椭圆弧(重载实现) * * @param x 椭圆所在矩形X坐标 * @param y 椭圆所在矩形Y坐标 * @param width 椭圆所在矩形宽度 * @param height 椭圆所在矩形高度 * @param startAngle 开始角度(单位弧度) * @param endAngle 结束角度(单位弧度) */ drawArc( x: SRect | number, y: number, width: number, height?: number, startAngle?: number, endAngle?: number ): void { if (x instanceof SRect) { this.engine.drawArc(x.x, x.y, x.width, x.height, y, width); } else { this.engine.drawArc( x, y, width, height as number, startAngle as number, endAngle as number ); } } // Function drawArc() /** * 绘制椭圆弦弧 * * @param rect 椭圆所在矩形 * @param startAngle 开始角度(单位弧度) * @param endAngle 结束角度(单位弧度) */ drawChord(rect: SRect, startAngle: number, endAngle: number): void; /** * 绘制椭圆弦弧 * * @param x 椭圆所在矩形X坐标 * @param y 椭圆所在矩形Y坐标 * @param width 椭圆所在矩形宽度 * @param height 椭圆所在矩形高度 * @param startAngle 开始角度(单位弧度) * @param endAngle 结束角度(单位弧度) */ drawChord( x: number, y: number, width: number, height: number, startAngle: number, endAngle: number ): void; /** * 绘制椭圆弦弧(重载实现) * * @param x 椭圆所在矩形X坐标 * @param y 椭圆所在矩形Y坐标 * @param width 椭圆所在矩形宽度 * @param height 椭圆所在矩形高度 * @param startAngle 开始角度(单位弧度) * @param endAngle 结束角度(单位弧度) */ drawChord( x: SRect | number, y: number, width: number, height?: number, startAngle?: number, endAngle?: number ): void { if (x instanceof SRect) { this.engine.drawChord(x.x, x.y, x.width, x.height, y, width); } else { this.engine.drawChord( x, y, width, height as number, startAngle as number, endAngle as number ); } } // Function drawChord() /** * 绘制椭圆饼 * * @param rect 椭圆所在矩形 * @param startAngle 开始角度(单位弧度) * @param endAngle 结束角度(单位弧度) */ drawPie(rect: SRect, startAngle: number, endAngle: number): void; /** * 绘制椭圆饼 * * @param x 椭圆所在矩形X坐标 * @param y 椭圆所在矩形Y坐标 * @param width 椭圆所在矩形宽度 * @param height 椭圆所在矩形高度 * @param startAngle 开始角度(单位弧度) * @param endAngle 结束角度(单位弧度) */ drawPie( x: number, y: number, width: number, height: number, startAngle: number, endAngle: number ): void; /** * 绘制椭圆饼 * * @param x 椭圆所在矩形X坐标 * @param y 椭圆所在矩形Y坐标 * @param width 椭圆所在矩形宽度 * @param height 椭圆所在矩形高度 * @param startAngle 开始角度(单位弧度) * @param endAngle 结束角度(单位弧度) */ drawPie( x: SRect | number, y: number, width: number, height?: number, startAngle?: number, endAngle?: number ): void { if (x instanceof SRect) { this.engine.drawPie(x.x, x.y, x.width, x.height, y, width); } else { this.engine.drawPie( x, y, width, height as number, startAngle as number, endAngle as number ); } } // Function drawPie() /** * 绘制一条线段 * * @param line 线段 */ drawLine(line: SLine): void; /** * 绘制一条线段 * * @param p1 启点坐标 * @param p2 终点坐标 */ drawLine(p1: SPoint, p2: SPoint): void; /** * 绘制一条线段 * * @param x1 启点X坐标 * @param y1 启点Y坐标 * @param x2 终点X坐标 * @param y2 终点Y坐标 */ drawLine(x1: number, y1: number, x2: number, y2: number): void; /** * 绘制一条线段(重载实现) * * @param x1 启点X坐标 | 起点1坐标 | 线段 | * @param y1 启点Y坐标 | 终点坐标 * @param x2 终点X坐标 * @param y2 终点Y坐标 */ drawLine( x1: number | SPoint | SLine, y1?: number | SPoint, x2?: number, y2?: number ): void { if (x1 instanceof SLine) { this.engine.drawLine(x1); } else if (x1 instanceof SPoint && y1 instanceof SPoint) { this.engine.drawLine(new SLine(x1, y1)); } else { this.engine.drawLine( new SLine( x1 as number, y1 as number, x2 as number, y2 as number ) ); } } // Function drawLine() /** * 绘制折线 * * @param points 折线折点 */ drawPolyline(points: SPoint[]): void { this.engine.drawPolyline(points); } // Function drawPolyline() /** * 绘制多边形 * * @param points 多边形顶点 */ drawPolygon(points: SPoint[]): void { this.engine.drawPolygon(points); } // Functin drawPolygon() /** * 绘制路径 * * @param path 路径 */ drawPath(path: SPath2D): void { this.engine.drawPath(path); } // Function drawPath() /** * 绘制文本 * * @param text 文本内容 * @param x X坐标 * @param y Y坐标 * @param maxWidth 最大宽度 */ drawText(text: string, x: number, y: number, maxWidth?: number): void { this.engine.drawText(text, x, y, maxWidth); } // Function drawText() /** * 绘制图片 * * @param img 图片 * @param x X坐标 * @param y Y坐标 * @param width 宽度 * @param height 高度 */ drawImage( img: CanvasImageSource, x: number, y: number, width?: number, height?: number ): void { this.engine.drawImage(img, x, y, width, height); } // Function drawImage() /** * painter转实现view象素 * * @param p 绘制坐标 */ toPx(p: number): number { return p / this.engine.state.matrix.a; } // Function painterToView() /** * 绘制带箭头的线段 * * @param line 线段 * @param style 末端样式 */ drawArrowLine(line: SLine, style?: SArrow): void; /** * 绘制带箭头的线段 * * @param p1 启点坐标 * @param p2 终点坐标 * @param style 末端样式 */ drawArrowLine(p1: SPoint, p2: SPoint, style?: SArrow): void; /** * 绘制带箭头的线段 * * @param x1 启点X坐标 * @param y1 启点Y坐标 * @param x2 终点X坐标 * @param y2 终点Y坐标 * @param style 末端样式 */ drawArrowLine( x1: number, y1: number, x2: number, y2: number, style?: SArrow ): void; /** * 绘制带箭头的线段 * * @param x1 启点X坐标 * @param y1 启点Y坐标 * @param x2 终点X坐标 * @param y2 终点Y坐标 * @param st 线段两端样式 */ drawArrowLine( x1: number | SPoint | SLine, y1?: number | SPoint | SArrow, x2?: number | SArrow, y2?: number, st?: SArrow ): void { let line: SLine, style: SArrow; if (x1 instanceof SLine) { line = x1; style = y1 as SArrow; } else if (x1 instanceof SPoint && y1 instanceof SPoint) { line = new SLine(x1, y1); style = x2 as SArrow; } else { line = new SLine( x1 as number, y1 as number, x2 as number, y2 as number ); style = st as SArrow; } this.engine.drawLine(line); if (style) { if (style.begin) { if (style.begin == SArrowStyleType.Basic) { this.drawBasicArrow(line, false); } else if (style.begin == SArrowStyleType.Triangle) { this.drawTriangleArrow(line, false); } else if (style.begin == SArrowStyleType.Diamond) { this.drawDiamondArrow(line, false); } else if (style.begin == SArrowStyleType.Square) { this.drawSquareArrow(line, false); } else if (style.begin == SArrowStyleType.Circle) { this.drawCircleArrow(line, false); } } if (style.end) { if (style.end == SArrowStyleType.Basic) { this.drawBasicArrow(line, true); } else if (style.end == SArrowStyleType.Triangle) { this.drawTriangleArrow(line, true); } else if (style.end == SArrowStyleType.Diamond) { this.drawDiamondArrow(line, true); } else if (style.end == SArrowStyleType.Square) { this.drawSquareArrow(line, true); } else if (style.end == SArrowStyleType.Circle) { this.drawCircleArrow(line, true); } } } } // Function drawArrowLine() /** * 私有计算方法-绘制线段末端标准箭头 * * @param line 要加末端样式的线段 * @param isEnd 是否为结束位置 * */ private drawBasicArrow(line: SLine, isEnd: boolean = true): void { // 定义箭头长度 const d = 5; // 箭头横坐标 const x1 = d * Math.cos(Math.PI / 4); // 箭头纵坐标 const y1 = d * Math.sin(Math.PI / 4); // 线段与x轴夹角 const fo = Math.atan(line.dy / line.dx); const ang = line.dx >= 0 ? fo : fo + Math.PI; // 是否为终点画箭头 if (isEnd) { this.save(); this.translate(line.x2, line.y2); this.rotate(ang); this.engine.drawPolyline([ new SPoint(-x1, y1), new SPoint(0, 0), new SPoint(-x1, -y1) ]); this.restore(); } else { this.save(); this.translate(line.x1, line.y1); this.rotate(ang); this.engine.drawPolyline([ new SPoint(x1, y1), new SPoint(0, 0), new SPoint(x1, -y1) ]); this.restore(); } } // Function drawArrow() /** * 私有计算方法-绘制线段末端三角形箭头 * * @param line 要加末端样式的线段 * @param isEnd 是否为结束位置 * */ private drawTriangleArrow(line: SLine, isEnd: boolean = true): void { // 定义箭头长度 const d = 5; // 箭头横坐标 const x1 = d * Math.cos(Math.PI / 12); // 箭头纵坐标 const y1 = d * Math.sin(Math.PI / 12); // 线段与x轴夹角 const fo = Math.atan(line.dy / line.dx); const ang = line.dx >= 0 ? fo : fo + Math.PI; // 是否为终点画箭头 if (isEnd) { this.save(); this.translate(line.x2, line.y2); this.rotate(ang); this.engine.drawPolygon([ new SPoint(-x1, y1), new SPoint(0, 0), new SPoint(-x1, -y1) ]); this.restore(); } else { this.save(); this.translate(line.x1, line.y1); this.rotate(ang); this.engine.drawPolygon([ new SPoint(x1, y1), new SPoint(0, 0), new SPoint(x1, -y1) ]); this.restore(); } } // Function drawTriangleArrow() /** * 私有计算方法-绘制线段末端菱形箭头 * * @param line 要加末端样式的线段 * @param isEnd 是否为结束位置 * */ private drawDiamondArrow(line: SLine, isEnd: boolean = true): void { // 定义箭头长度 const d = 5; // 箭头横坐标 const x1 = d * Math.cos(Math.PI / 4); // 箭头纵坐标 const y1 = d * Math.sin(Math.PI / 4); // 线段与x轴夹角 const fo = Math.atan(line.dy / line.dx); const ang = line.dx >= 0 ? fo : fo + Math.PI; // 是否为终点画箭头 if (isEnd) { this.save(); this.translate(line.x2, line.y2); this.rotate(ang); this.engine.drawPolygon([ new SPoint(-x1, y1), new SPoint(0, 0), new SPoint(-x1, -y1), new SPoint(-Math.sqrt(2) * d, 0) ]); this.restore(); } else { this.save(); this.translate(line.x1, line.y1); this.rotate(ang); this.engine.drawPolygon([ new SPoint(x1, y1), new SPoint(0, 0), new SPoint(x1, -y1), new SPoint(Math.sqrt(2) * d, 0) ]); this.restore(); } } // Function drawDiamondArrow() /** * 私有计算方法-绘制线段末端方形箭头 * * @param line 要加末端样式的线段 * @param isEnd 是否为结束位置 * */ private drawSquareArrow(line: SLine, isEnd: boolean = true): void { // 定义箭头长度 const d = 5; // 线段与x轴夹角 const fo = Math.atan(line.dy / line.dx); const ang = line.dx >= 0 ? fo : fo + Math.PI; // 是否为终点画箭头 if (isEnd) { this.save(); this.translate(line.x2, line.y2); this.rotate(ang); this.engine.drawPolygon([ new SPoint(-d, d / 2), new SPoint(0, d / 2), new SPoint(0, -d / 2), new SPoint(-d, -d / 2) ]); this.restore(); } else { this.save(); this.translate(line.x1, line.y1); this.rotate(ang); this.engine.drawPolygon([ new SPoint(0, d / 2), new SPoint(d, d / 2), new SPoint(d, -d / 2), new SPoint(0, -d / 2) ]); this.restore(); } } // Function drawSquareArrow() /** * 私有计算方法-绘制线段末端圆形箭头 * * @param line 要加末端样式的线段 * @param isEnd 是否为结束位置 * */ private drawCircleArrow(line: SLine, isEnd: boolean = true): void { // 定义箭头长度 const d = 5; // 线段与x轴夹角 const fo = Math.atan(line.dy / line.dx); const ang = line.dx >= 0 ? fo : fo + Math.PI; // 是否为终点画箭头 if (isEnd) { this.save(); this.translate(line.x2, line.y2); this.rotate(ang); this.engine.drawCircle(-d / 2, 0, d / 2); this.restore(); } else { this.save(); this.translate(line.x1, line.y1); this.rotate(ang); this.engine.drawCircle(d / 2, 0, d / 2); this.restore(); } } // Function drawCircleArrow() } // Class SPainter