@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 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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+
2
+ export { };