@leafer-ui/text 1.0.0-beta.8 → 1.0.0-rc.10
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/package.json +9 -6
- package/src/CharLayout.ts +15 -11
- package/src/TextClip.ts +42 -22
- package/src/TextConvert.ts +61 -30
- package/src/TextLayout.ts +34 -16
- package/src/TextRows.ts +34 -9
- package/src/index.ts +7 -1
- package/types/index.d.ts +5 -0
package/package.json
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leafer-ui/text",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-rc.10",
|
|
4
4
|
"description": "@leafer-ui/text",
|
|
5
5
|
"author": "Chao (Leafer) Wan",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "src/index.ts",
|
|
8
|
+
"types": "types/index.d.ts",
|
|
8
9
|
"files": [
|
|
9
|
-
"src"
|
|
10
|
+
"src",
|
|
11
|
+
"types",
|
|
12
|
+
"dist"
|
|
10
13
|
],
|
|
11
14
|
"repository": {
|
|
12
15
|
"type": "git",
|
|
13
16
|
"url": "https://github.com/leaferjs/ui.git"
|
|
14
17
|
},
|
|
15
|
-
"homepage": "https://github.com/leaferjs/ui/tree/main/packages/text",
|
|
18
|
+
"homepage": "https://github.com/leaferjs/ui/tree/main/packages/partner/text",
|
|
16
19
|
"bugs": "https://github.com/leaferjs/ui/issues",
|
|
17
20
|
"keywords": [
|
|
18
21
|
"leafer-ui",
|
|
19
22
|
"leaferjs"
|
|
20
23
|
],
|
|
21
24
|
"dependencies": {
|
|
22
|
-
"@leafer
|
|
25
|
+
"@leafer/core": "1.0.0-rc.10"
|
|
23
26
|
},
|
|
24
27
|
"devDependencies": {
|
|
25
|
-
"@leafer/interface": "1.0.0-
|
|
26
|
-
"@leafer-ui/interface": "1.0.0-
|
|
28
|
+
"@leafer/interface": "1.0.0-rc.10",
|
|
29
|
+
"@leafer-ui/interface": "1.0.0-rc.10"
|
|
27
30
|
}
|
|
28
31
|
}
|
package/src/CharLayout.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { ITextCharData, ITextData, ITextDrawData } from '@leafer-ui/interface'
|
|
1
|
+
import { ITextCharData, ITextData, ITextDrawData, ITextRowData } from '@leafer-ui/interface'
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
const CharMode = 0 // data: [{char:'a', x: 0}, {char:'b', x: 5}, {char:'d', x:20}]
|
|
5
5
|
const WordMode = 1 // data: [{ char:'ab', x: 0}, { char:'d', x:20}]
|
|
6
|
-
const
|
|
6
|
+
const TextMode = 2 // text: 'ab c'
|
|
7
7
|
|
|
8
8
|
export function layoutChar(drawData: ITextDrawData, style: ITextData, width: number, _height: number): void {
|
|
9
9
|
|
|
@@ -16,18 +16,13 @@ export function layoutChar(drawData: ITextDrawData, style: ITextData, width: num
|
|
|
16
16
|
|
|
17
17
|
indentWidth = paraIndent && row.paraStart ? paraIndent : 0
|
|
18
18
|
addWordWidth = (width && textAlign === 'justify' && row.words.length > 1) ? (width - row.width - indentWidth) / (row.words.length - 1) : 0
|
|
19
|
-
mode = (letterSpacing || row.isOverflow) ? CharMode : (addWordWidth > 0.01 ? WordMode :
|
|
19
|
+
mode = (letterSpacing || row.isOverflow) ? CharMode : (addWordWidth > 0.01 ? WordMode : TextMode)
|
|
20
|
+
if (row.isOverflow && !letterSpacing) row.textMode = true
|
|
20
21
|
|
|
21
|
-
if (mode ===
|
|
22
|
+
if (mode === TextMode) {
|
|
22
23
|
|
|
23
|
-
row.text = ''
|
|
24
24
|
row.x += indentWidth
|
|
25
|
-
|
|
26
|
-
row.words.forEach(word => {
|
|
27
|
-
word.data.forEach(char => {
|
|
28
|
-
row.text += char.char
|
|
29
|
-
})
|
|
30
|
-
})
|
|
25
|
+
toTextChar(row)
|
|
31
26
|
|
|
32
27
|
} else {
|
|
33
28
|
|
|
@@ -63,6 +58,15 @@ export function layoutChar(drawData: ITextDrawData, style: ITextData, width: num
|
|
|
63
58
|
|
|
64
59
|
}
|
|
65
60
|
|
|
61
|
+
function toTextChar(row: ITextRowData): void {
|
|
62
|
+
row.text = ''
|
|
63
|
+
row.words.forEach(word => {
|
|
64
|
+
word.data.forEach(char => {
|
|
65
|
+
row.text += char.char
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
66
70
|
function toWordChar(data: ITextCharData[], charX: number, wordChar: ITextCharData): number {
|
|
67
71
|
data.forEach(char => {
|
|
68
72
|
wordChar.char += char.char
|
package/src/TextClip.ts
CHANGED
|
@@ -1,39 +1,59 @@
|
|
|
1
1
|
import { Platform } from '@leafer/core'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ITextCharData, ITextData, ITextDrawData, ITextRowData } from '@leafer-ui/interface'
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
export function clipText(drawData: ITextDrawData,
|
|
6
|
+
export function clipText(drawData: ITextDrawData, style: ITextData): void {
|
|
7
7
|
|
|
8
8
|
const { rows, overflow } = drawData
|
|
9
|
+
let { textOverflow } = style
|
|
9
10
|
rows.splice(overflow)
|
|
10
11
|
|
|
12
|
+
|
|
11
13
|
if (textOverflow !== 'hide') {
|
|
12
14
|
if (textOverflow === 'ellipsis') textOverflow = '...'
|
|
13
15
|
|
|
16
|
+
let char: ITextCharData, charRight: number
|
|
14
17
|
const ellipsisWidth = Platform.canvas.measureText(textOverflow).width
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
const right = style.x + style.width - ellipsisWidth
|
|
19
|
+
const list = style.textWrap === 'none' ? rows : [rows[overflow - 1]]
|
|
20
|
+
|
|
21
|
+
list.forEach(row => {
|
|
22
|
+
|
|
23
|
+
if (row.isOverflow && row.data) {
|
|
24
|
+
|
|
25
|
+
let end = row.data.length - 1
|
|
26
|
+
|
|
27
|
+
for (let i = end; i > -1; i--) {
|
|
28
|
+
char = row.data[i]
|
|
29
|
+
charRight = char.x + char.width
|
|
30
|
+
if (i === end && charRight < right) {
|
|
31
|
+
break
|
|
32
|
+
} else if (charRight < right && char.char !== ' ') {
|
|
33
|
+
row.data.splice(i + 1)
|
|
34
|
+
row.width -= char.width
|
|
35
|
+
break
|
|
36
|
+
}
|
|
37
|
+
row.width -= char.width
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
row.width += ellipsisWidth
|
|
41
|
+
row.data.push({ char: textOverflow, x: charRight })
|
|
42
|
+
|
|
43
|
+
if (row.textMode) toTextChar(row)
|
|
31
44
|
}
|
|
32
|
-
row.width -= char.width
|
|
33
|
-
}
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
|
|
47
|
+
})
|
|
48
|
+
|
|
37
49
|
}
|
|
38
50
|
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function toTextChar(row: ITextRowData): void {
|
|
54
|
+
row.text = ''
|
|
55
|
+
row.data.forEach(char => {
|
|
56
|
+
row.text += char.char
|
|
57
|
+
})
|
|
58
|
+
row.data = null
|
|
39
59
|
}
|
package/src/TextConvert.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Platform, Direction4 } from '@leafer/core'
|
|
2
2
|
|
|
3
3
|
import { ITextData, ITextDrawData } from '@leafer-ui/interface'
|
|
4
4
|
|
|
@@ -9,48 +9,79 @@ import { clipText } from './TextClip'
|
|
|
9
9
|
import { decorationText } from './TextDecoration'
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
const { top, right, bottom, left } = Direction4
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
export function getDrawData(content: string, style: ITextData): ITextDrawData {
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
if (typeof content !== 'string') content = String(content)
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
let { width, height, padding } = style
|
|
20
|
-
const { textDecoration, textOverflow, __font } = style
|
|
21
|
-
if (!width) width = 0
|
|
18
|
+
let x = 0, y = 0
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
width -= (right + left)
|
|
28
|
-
}
|
|
29
|
-
if (height) {
|
|
30
|
-
y = top
|
|
31
|
-
height -= (top + bottom)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
20
|
+
let width = style.__getInput('width') || 0
|
|
21
|
+
let height = style.__getInput('height') || 0
|
|
22
|
+
|
|
23
|
+
const { textDecoration, __font, __padding: padding } = style
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
font: Platform.canvas.font = __font
|
|
25
|
+
if (padding) {
|
|
26
|
+
if (width) {
|
|
27
|
+
x = padding[left]
|
|
28
|
+
width -= (padding[right] + padding[left])
|
|
40
29
|
}
|
|
30
|
+
if (height) {
|
|
31
|
+
y = padding[top]
|
|
32
|
+
height -= (padding[top] + padding[bottom])
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const drawData: ITextDrawData = {
|
|
37
|
+
bounds: { x, y, width, height },
|
|
38
|
+
rows: [],
|
|
39
|
+
paraNumber: 0,
|
|
40
|
+
font: Platform.canvas.font = __font
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
createRows(drawData, content, style) // set rows, paraNumber
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
if (padding) padAutoText(padding, drawData, style, width, height)
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
layoutText(drawData, style) // set bounds
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
layoutChar(drawData, style, width, height) // set char.x
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
if (drawData.overflow) clipText(drawData, style)
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
if (textDecoration !== 'none') decorationText(drawData, style)
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
return drawData
|
|
53
56
|
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
function padAutoText(padding: number[], drawData: ITextDrawData, style: ITextData, width: number, height: number): void {
|
|
61
|
+
if (!width) {
|
|
62
|
+
switch (style.textAlign) {
|
|
63
|
+
case 'left':
|
|
64
|
+
offsetText(drawData, 'x', padding[left])
|
|
65
|
+
break
|
|
66
|
+
case 'right':
|
|
67
|
+
offsetText(drawData, 'x', -padding[right])
|
|
68
|
+
}
|
|
54
69
|
}
|
|
55
70
|
|
|
71
|
+
if (!height) {
|
|
72
|
+
switch (style.verticalAlign) {
|
|
73
|
+
case 'top':
|
|
74
|
+
offsetText(drawData, 'y', padding[top])
|
|
75
|
+
break
|
|
76
|
+
case 'bottom':
|
|
77
|
+
offsetText(drawData, 'y', -padding[bottom])
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
function offsetText(drawData: ITextDrawData, attrName: 'x' | 'y', value: number): void {
|
|
84
|
+
const { bounds, rows } = drawData
|
|
85
|
+
bounds[attrName] += value
|
|
86
|
+
for (let i = 0; i < rows.length; i++) rows[i][attrName] += value
|
|
56
87
|
}
|
package/src/TextLayout.ts
CHANGED
|
@@ -4,32 +4,30 @@ import { ITextData, ITextDrawData, ITextRowData } from '@leafer-ui/interface'
|
|
|
4
4
|
export function layoutText(drawData: ITextDrawData, style: ITextData): void {
|
|
5
5
|
|
|
6
6
|
const { rows, bounds } = drawData
|
|
7
|
-
const { __lineHeight, __baseLine, textAlign, verticalAlign, paraSpacing
|
|
7
|
+
const { __lineHeight, __baseLine, __letterSpacing, __clipText, textAlign, verticalAlign, paraSpacing } = style
|
|
8
8
|
|
|
9
9
|
let { x, y, width, height } = bounds, realHeight = __lineHeight * rows.length + (paraSpacing ? paraSpacing * (drawData.paraNumber - 1) : 0)
|
|
10
10
|
let starY: number = __baseLine
|
|
11
11
|
|
|
12
12
|
// verticalAlign
|
|
13
13
|
|
|
14
|
-
if (height) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
y += (height - realHeight)
|
|
25
|
-
}
|
|
14
|
+
if (__clipText && realHeight > height) {
|
|
15
|
+
realHeight = Math.max(height, __lineHeight)
|
|
16
|
+
drawData.overflow = rows.length
|
|
17
|
+
} else {
|
|
18
|
+
switch (verticalAlign) {
|
|
19
|
+
case 'middle':
|
|
20
|
+
y += (height - realHeight) / 2
|
|
21
|
+
break
|
|
22
|
+
case 'bottom':
|
|
23
|
+
y += (height - realHeight)
|
|
26
24
|
}
|
|
27
|
-
starY += y
|
|
28
25
|
}
|
|
26
|
+
starY += y
|
|
29
27
|
|
|
30
28
|
// textAlign
|
|
31
29
|
|
|
32
|
-
let row: ITextRowData
|
|
30
|
+
let row: ITextRowData, rowX: number, rowWidth: number
|
|
33
31
|
|
|
34
32
|
for (let i = 0, len = rows.length; i < len; i++) {
|
|
35
33
|
row = rows[i]
|
|
@@ -54,7 +52,27 @@ export function layoutText(drawData: ITextDrawData, style: ITextData): void {
|
|
|
54
52
|
drawData.overflow = i + 1
|
|
55
53
|
}
|
|
56
54
|
|
|
57
|
-
|
|
55
|
+
rowX = row.x
|
|
56
|
+
rowWidth = row.width
|
|
57
|
+
|
|
58
|
+
if (__letterSpacing < 0) { // letterSpacing < 0, like -20% -100%
|
|
59
|
+
if (row.width < 0) {
|
|
60
|
+
rowWidth = -row.width + style.fontSize + __letterSpacing
|
|
61
|
+
rowX -= rowWidth
|
|
62
|
+
rowWidth += style.fontSize
|
|
63
|
+
} else {
|
|
64
|
+
rowWidth -= __letterSpacing
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (rowX < bounds.x) bounds.x = rowX
|
|
69
|
+
if (rowWidth > bounds.width) bounds.width = rowWidth
|
|
70
|
+
|
|
71
|
+
// clip nowrap
|
|
72
|
+
if (__clipText && width && width < rowWidth) {
|
|
73
|
+
row.isOverflow = true
|
|
74
|
+
if (!drawData.overflow) drawData.overflow = rows.length
|
|
75
|
+
}
|
|
58
76
|
}
|
|
59
77
|
|
|
60
78
|
bounds.y = y
|
package/src/TextRows.ts
CHANGED
|
@@ -11,7 +11,7 @@ const { trimRight } = TextRowHelper
|
|
|
11
11
|
const { Letter, Single, Before, After, Symbol, Break } = CharType
|
|
12
12
|
|
|
13
13
|
let word: ITextWordData, row: ITextRowData, wordWidth: number, rowWidth: number, realWidth: number
|
|
14
|
-
let char: string, charWidth: number, charType: CharType, lastCharType: CharType, langBreak: boolean, afterBreak: boolean, paraStart: boolean
|
|
14
|
+
let char: string, charWidth: number, startCharSize: number, charSize: number, charType: CharType, lastCharType: CharType, langBreak: boolean, afterBreak: boolean, paraStart: boolean
|
|
15
15
|
let textDrawData: ITextDrawData, rows: ITextRowData[] = [], bounds: IBoundsData
|
|
16
16
|
|
|
17
17
|
export function createRows(drawData: ITextDrawData, content: string, style: ITextData): void {
|
|
@@ -27,9 +27,12 @@ export function createRows(drawData: ITextDrawData, content: string, style: ITex
|
|
|
27
27
|
|
|
28
28
|
if (charMode) {
|
|
29
29
|
|
|
30
|
+
const wrap = style.textWrap !== 'none'
|
|
31
|
+
const breakAll = style.textWrap === 'break'
|
|
32
|
+
|
|
30
33
|
paraStart = true
|
|
31
34
|
lastCharType = null
|
|
32
|
-
wordWidth = rowWidth = 0
|
|
35
|
+
startCharSize = charWidth = charSize = wordWidth = rowWidth = 0
|
|
33
36
|
word = { data: [] }, row = { words: [] }
|
|
34
37
|
|
|
35
38
|
for (let i = 0, len = content.length; i < len; i++) {
|
|
@@ -51,30 +54,44 @@ export function createRows(drawData: ITextDrawData, content: string, style: ITex
|
|
|
51
54
|
if (charType === Letter && textCase !== 'none') char = getTextCase(char, textCase, !wordWidth)
|
|
52
55
|
|
|
53
56
|
charWidth = canvas.measureText(char).width
|
|
54
|
-
if (__letterSpacing)
|
|
57
|
+
if (__letterSpacing) {
|
|
58
|
+
if (__letterSpacing < 0) charSize = charWidth
|
|
59
|
+
charWidth += __letterSpacing
|
|
60
|
+
}
|
|
55
61
|
|
|
56
62
|
langBreak = (charType === Single && (lastCharType === Single || lastCharType === Letter)) || (lastCharType === Single && charType !== After) // break U字 文字 or 字U 字( 字* exclude 字。
|
|
57
63
|
afterBreak = ((charType === Before || charType === Single) && (lastCharType === Symbol || lastCharType === After)) // split >( =文 。哈 ;文
|
|
58
64
|
|
|
59
65
|
realWidth = paraStart && paraIndent ? width - paraIndent : width
|
|
60
66
|
|
|
61
|
-
if (width && rowWidth + wordWidth + charWidth > realWidth) { // wrap
|
|
62
|
-
|
|
63
|
-
if (!afterBreak) afterBreak = charType === Letter && lastCharType == After // split ,S
|
|
67
|
+
if (wrap && (width && rowWidth + wordWidth + charWidth > realWidth)) { // wrap
|
|
64
68
|
|
|
65
|
-
if (
|
|
69
|
+
if (breakAll) {
|
|
66
70
|
|
|
67
71
|
if (wordWidth) addWord() // break
|
|
68
72
|
addRow()
|
|
69
73
|
|
|
70
74
|
} else {
|
|
75
|
+
if (!afterBreak) afterBreak = charType === Letter && lastCharType == After // split ,S
|
|
71
76
|
|
|
72
|
-
|
|
77
|
+
if (langBreak || afterBreak || charType === Break || charType === Before || charType === Single || (wordWidth + charWidth > realWidth)) {
|
|
78
|
+
|
|
79
|
+
if (wordWidth) addWord() // break
|
|
80
|
+
addRow()
|
|
81
|
+
|
|
82
|
+
} else {
|
|
83
|
+
|
|
84
|
+
addRow()
|
|
85
|
+
}
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
}
|
|
76
89
|
|
|
77
|
-
if (
|
|
90
|
+
if (char === ' ' && paraStart !== true && (rowWidth + wordWidth) === 0) {
|
|
91
|
+
|
|
92
|
+
// trim space
|
|
93
|
+
|
|
94
|
+
} else {
|
|
78
95
|
|
|
79
96
|
if (charType === Break) {
|
|
80
97
|
|
|
@@ -117,6 +134,7 @@ export function createRows(drawData: ITextDrawData, content: string, style: ITex
|
|
|
117
134
|
|
|
118
135
|
|
|
119
136
|
function addChar(char: string, width: number): void {
|
|
137
|
+
if (charSize && !startCharSize) startCharSize = charSize
|
|
120
138
|
word.data.push({ char, width })
|
|
121
139
|
wordWidth += width
|
|
122
140
|
}
|
|
@@ -135,6 +153,13 @@ function addRow(): void {
|
|
|
135
153
|
row.paraStart = true
|
|
136
154
|
paraStart = false
|
|
137
155
|
}
|
|
156
|
+
|
|
157
|
+
if (charSize) { // letterSpacing < 0, like -20% -100%
|
|
158
|
+
row.startCharSize = startCharSize
|
|
159
|
+
row.endCharSize = charSize
|
|
160
|
+
startCharSize = 0
|
|
161
|
+
}
|
|
162
|
+
|
|
138
163
|
row.width = rowWidth
|
|
139
164
|
if (bounds.width) trimRight(row)
|
|
140
165
|
rows.push(row)
|
package/src/index.ts
CHANGED