@leafer-in/stroke-sides 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/package.json +40 -0
- package/src/decorator.ts +19 -0
- package/src/fillSroke.ts +50 -0
- package/src/index.ts +27 -0
- package/src/path.ts +106 -0
- package/types/index.d.ts +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-present, Chao (Leafer) Wan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @leafer-in/stroke-sides
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leafer-in/stroke-sides",
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "@leafer-in/stroke-sides",
|
|
5
|
+
"author": "Chao (Leafer) Wan",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/stroke-sides.esm.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
"import": "./dist/stroke-sides.esm.js",
|
|
11
|
+
"require": "./dist/stroke-sides.cjs",
|
|
12
|
+
"types": "./types/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"types": "types/index.d.ts",
|
|
15
|
+
"unpkg": "dist/stroke-sides.js",
|
|
16
|
+
"jsdelivr": "dist/stroke-sides.js",
|
|
17
|
+
"files": [
|
|
18
|
+
"src",
|
|
19
|
+
"types",
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/leaferjs/leafer-in.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/leaferjs/leafer-in/tree/main/packages/stroke-sides",
|
|
27
|
+
"bugs": "https://github.com/leaferjs/leafer-in/issues",
|
|
28
|
+
"keywords": [
|
|
29
|
+
"leafer stroke-sides",
|
|
30
|
+
"leafer-stroke-sides",
|
|
31
|
+
"leafer-in",
|
|
32
|
+
"stroke-sides",
|
|
33
|
+
"leafer-ui",
|
|
34
|
+
"leaferjs"
|
|
35
|
+
],
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@leafer-ui/draw": "^2.0.0",
|
|
38
|
+
"@leafer-ui/interface": "^2.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/decorator.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { IUIData, IValue } from '@leafer-ui/interface'
|
|
2
|
+
import { decorateLeafAttr, attr, doStrokeType, doBoundsType, isArray, DataHelper } from '@leafer-ui/core'
|
|
3
|
+
|
|
4
|
+
export function strokeWidthType(defaultValue?: IValue) {
|
|
5
|
+
return decorateLeafAttr(defaultValue, (key: string) => attr({
|
|
6
|
+
set(value: IValue) {
|
|
7
|
+
if (this.__setAttr(key, value)) {
|
|
8
|
+
doStrokeType(this)
|
|
9
|
+
const data = this.__
|
|
10
|
+
data.__useStroke = true
|
|
11
|
+
let hasStrokeSides
|
|
12
|
+
DataHelper.stintSet(data as IUIData, '__hasStrokeSides', hasStrokeSides = isArray(value) && Math.max(...value))
|
|
13
|
+
if (hasStrokeSides) {
|
|
14
|
+
doBoundsType(this)
|
|
15
|
+
} else if ((data as IUIData).__pathForStroke) (data as IUIData).__pathForStroke = undefined
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}))
|
|
19
|
+
}
|
package/src/fillSroke.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ILeaferCanvas, IRenderOptions } from '@leafer/interface'
|
|
2
|
+
import { isObject, PathDrawer } from "@leafer/core"
|
|
3
|
+
|
|
4
|
+
import { IUI, ILeafPaint, ILeafStrokePaint } from '@leafer-ui/interface'
|
|
5
|
+
import { PaintImage } from "@leafer-ui/draw"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export function fillStroke(stroke: string | ILeafPaint[], ui: IUI, canvas: ILeaferCanvas, renderOptions: IRenderOptions): void {
|
|
9
|
+
|
|
10
|
+
canvas.beginPath()
|
|
11
|
+
const { data, windingRule } = ui.__.__pathForStroke
|
|
12
|
+
PathDrawer.drawPathByData(canvas, data)
|
|
13
|
+
|
|
14
|
+
if (isObject(stroke)) {
|
|
15
|
+
drawStrokesStyle(stroke, ui, canvas, renderOptions)
|
|
16
|
+
} else {
|
|
17
|
+
|
|
18
|
+
canvas.fillStyle = stroke
|
|
19
|
+
windingRule ? canvas.fill(windingRule) : canvas.fill()
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (ui.__.__fillAfterStroke) ui.__drawRenderPath(canvas)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
function drawStrokesStyle(strokes: ILeafStrokePaint[], ui: IUI, canvas: ILeaferCanvas, renderOptions: IRenderOptions): void {
|
|
28
|
+
let item: ILeafStrokePaint
|
|
29
|
+
|
|
30
|
+
for (let i = 0, len = strokes.length; i < len; i++) {
|
|
31
|
+
item = strokes[i]
|
|
32
|
+
|
|
33
|
+
if (item.image && PaintImage.checkImage(item, false, ui, canvas, renderOptions)) continue
|
|
34
|
+
|
|
35
|
+
if (item.style) {
|
|
36
|
+
|
|
37
|
+
const { windingRule } = ui.__.__pathForStroke
|
|
38
|
+
|
|
39
|
+
if (item.originPaint.blendMode) {
|
|
40
|
+
canvas.saveBlendMode(item.originPaint.blendMode)
|
|
41
|
+
canvas.fillStyle = item.style
|
|
42
|
+
windingRule ? canvas.fill(windingRule) : canvas.fill()
|
|
43
|
+
canvas.restoreBlendMode()
|
|
44
|
+
} else {
|
|
45
|
+
canvas.fillStyle = item.style
|
|
46
|
+
windingRule ? canvas.fill(windingRule) : canvas.fill()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Paint, Plugin, Rect, Box, RectData, DataHelper, MathHelper, BoxData } from '@leafer-ui/draw'
|
|
2
|
+
import { fillStroke } from './fillSroke'
|
|
3
|
+
import { createRectStrokePath } from './path'
|
|
4
|
+
import { strokeWidthType } from './decorator'
|
|
5
|
+
import { IRectData } from '@leafer-ui/interface'
|
|
6
|
+
|
|
7
|
+
Plugin.add('stroke-sides')
|
|
8
|
+
|
|
9
|
+
Rect.addAttr('strokeWidth', 1, strokeWidthType)
|
|
10
|
+
Box.addAttr('strokeWidth', 1, strokeWidthType)
|
|
11
|
+
|
|
12
|
+
Paint.fillStroke = fillStroke
|
|
13
|
+
|
|
14
|
+
const { stintSet } = DataHelper
|
|
15
|
+
|
|
16
|
+
Rect.prototype.__updatePath = Box.prototype.__updatePath = function (): void {
|
|
17
|
+
const data = this.__
|
|
18
|
+
if (data.__hasStrokeSides) {
|
|
19
|
+
const { width, height, cornerRadius, strokeWidth, strokeAlign } = data
|
|
20
|
+
data.__pathForStroke = data.__pathInputed ? undefined : createRectStrokePath(width, height, cornerRadius && MathHelper.fourNumber(cornerRadius), MathHelper.fourNumber(strokeWidth), strokeAlign)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
BoxData.prototype.__checkComplex = RectData.prototype.__checkComplex = function (): void {
|
|
25
|
+
const t = this as IRectData
|
|
26
|
+
stintSet(t, '__complex', (t.__isFills || t.__isStrokes || t.cornerRadius || t.__hasStrokeSides || t.__useEffect) as boolean)
|
|
27
|
+
}
|
package/src/path.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { IPathCommandData, IPathCommandDataWithWindingRule } from '@leafer/interface'
|
|
2
|
+
import { PathCommandDataHelper } from "@leafer/core"
|
|
3
|
+
import { IStrokeAlign } from '@leafer-ui/interface'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 矩形描边路径生成器
|
|
7
|
+
* 支持非均匀描边(上右下左宽度不同)与圆角逻辑
|
|
8
|
+
*/
|
|
9
|
+
export function createRectStrokePath(width: number, height: number, cornerRadius: number[] | undefined, strokeWidth: number[], strokeAlign: IStrokeAlign): IPathCommandDataWithWindingRule {
|
|
10
|
+
|
|
11
|
+
if (cornerRadius) {
|
|
12
|
+
const [r0, r1, r2, r3] = cornerRadius
|
|
13
|
+
const [t, r_w, b, l] = strokeWidth
|
|
14
|
+
|
|
15
|
+
const hasRadius = (r0 + r1 + r2 + r3) > 0
|
|
16
|
+
|
|
17
|
+
// 场景 A: 包含圆角,必须使用 evenodd 挖空法
|
|
18
|
+
if (hasRadius) {
|
|
19
|
+
const data: IPathCommandData = []
|
|
20
|
+
const expand = strokeAlign === 'inside' ? 0 : (strokeAlign === 'center' ? 0.5 : 1)
|
|
21
|
+
const shrink = 1 - expand
|
|
22
|
+
|
|
23
|
+
// 外边界参数
|
|
24
|
+
const ox = -l * expand, oy = -t * expand
|
|
25
|
+
const ow = width + (l + r_w) * expand
|
|
26
|
+
const oh = height + (t + b) * expand
|
|
27
|
+
const outerR = [
|
|
28
|
+
r0 + Math.max(t, l) * expand,
|
|
29
|
+
r1 + Math.max(t, r_w) * expand,
|
|
30
|
+
r2 + Math.max(b, r_w) * expand,
|
|
31
|
+
r3 + Math.max(b, l) * expand
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
// 内边界参数
|
|
35
|
+
const ix = l * shrink, iy = t * shrink
|
|
36
|
+
const iw = width - (l + r_w) * shrink
|
|
37
|
+
const ih = height - (t + b) * shrink
|
|
38
|
+
|
|
39
|
+
// 绘制外框
|
|
40
|
+
PathCommandDataHelper.roundRect(data, ox, oy, ow, oh, outerR)
|
|
41
|
+
|
|
42
|
+
// 只有内框有空间时才绘制内框(进行掏空)
|
|
43
|
+
if (iw > 0 && ih > 0) {
|
|
44
|
+
const innerR = [
|
|
45
|
+
Math.max(0, r0 - Math.max(t, l) * shrink),
|
|
46
|
+
Math.max(0, r1 - Math.max(t, r_w) * shrink),
|
|
47
|
+
Math.max(0, r2 - Math.max(b, r_w) * shrink),
|
|
48
|
+
Math.max(0, r3 - Math.max(b, l) * shrink)
|
|
49
|
+
]
|
|
50
|
+
PathCommandDataHelper.roundRect(data, ix, iy, iw, ih, innerR)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { data, windingRule: 'evenodd' }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
// 场景 B: 纯直角,采用四边拼接法 (性能最优,无 WindingRule 依赖)
|
|
60
|
+
return createRectStrokePathNoRadius(width, height, strokeWidth, strokeAlign)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 纯直角矩形描边生成器 (拼接法)
|
|
65
|
+
*/
|
|
66
|
+
function createRectStrokePathNoRadius(
|
|
67
|
+
width: number,
|
|
68
|
+
height: number,
|
|
69
|
+
strokeWidth: number[],
|
|
70
|
+
strokeAlign: IStrokeAlign
|
|
71
|
+
): IPathCommandDataWithWindingRule {
|
|
72
|
+
const data: IPathCommandData = []
|
|
73
|
+
const [t, r_w, b, l] = strokeWidth
|
|
74
|
+
|
|
75
|
+
const ext = strokeAlign === 'inside' ? 0 : (strokeAlign === 'center' ? 0.5 : 1)
|
|
76
|
+
const ins = 1 - ext
|
|
77
|
+
|
|
78
|
+
// 计算外部极值边界坐标
|
|
79
|
+
const xL = -l * ext
|
|
80
|
+
const xR = width + r_w * ext
|
|
81
|
+
const yT = -t * ext
|
|
82
|
+
const yB = height + b * ext
|
|
83
|
+
|
|
84
|
+
// 计算内部裁剪边界坐标 (用于侧边填充高度)
|
|
85
|
+
const innerYStart = t * ins
|
|
86
|
+
const innerYEnd = height - b * ins
|
|
87
|
+
const innerHeight = Math.max(0, innerYEnd - innerYStart)
|
|
88
|
+
|
|
89
|
+
// 1. Top Bar: 覆盖全宽
|
|
90
|
+
if (t > 0) PathCommandDataHelper.rect(data, xL, yT, xR - xL, t)
|
|
91
|
+
|
|
92
|
+
// 2. Bottom Bar: 覆盖全宽
|
|
93
|
+
if (b > 0) PathCommandDataHelper.rect(data, xL, yB - b, xR - xL, b)
|
|
94
|
+
|
|
95
|
+
// 3. Left Bar: 填充中间高度,避免与 Top/Bottom 重合
|
|
96
|
+
if (l > 0 && innerHeight > 0) {
|
|
97
|
+
PathCommandDataHelper.rect(data, xL, innerYStart, l, innerHeight)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 4. Right Bar: 填充中间高度
|
|
101
|
+
if (r_w > 0 && innerHeight > 0) {
|
|
102
|
+
PathCommandDataHelper.rect(data, xR - r_w, innerYStart, r_w, innerHeight)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { data }
|
|
106
|
+
}
|
package/types/index.d.ts
ADDED