@leafer-ui/text 1.0.0-alpha.23 → 1.0.0-alpha.30
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 +4 -4
- package/src/CharLayout.ts +83 -0
- package/src/CharType.ts +57 -0
- package/src/TextCase.ts +14 -0
- package/src/TextClip.ts +37 -0
- package/src/TextConvert.ts +35 -135
- package/src/TextDecoration.ts +13 -0
- package/src/TextLayout.ts +61 -0
- package/src/TextRowHelper.ts +22 -0
- package/src/TextRows.ts +144 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leafer-ui/text",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.30",
|
|
4
4
|
"description": "@leafer-ui/text",
|
|
5
5
|
"author": "Chao (Leafer) Wan",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
"leaferjs"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@leafer-ui/core": "1.0.0-alpha.
|
|
22
|
+
"@leafer-ui/core": "1.0.0-alpha.30"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@leafer/interface": "1.0.0-alpha.
|
|
26
|
-
"@leafer-ui/interface": "1.0.0-alpha.
|
|
25
|
+
"@leafer/interface": "1.0.0-alpha.30",
|
|
26
|
+
"@leafer-ui/interface": "1.0.0-alpha.30"
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ITextCharData, ITextData, ITextDrawData } from '@leafer-ui/interface'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const CharMode = 0 // data: [{char:'a', x: 0}, {char:'b', x: 5}, {char:'d', x:20}]
|
|
5
|
+
const WordMode = 1 // data: [{ char:'ab', x: 0}, { char:'d', x:20}]
|
|
6
|
+
const RowMode = 2 // text: 'ab c'
|
|
7
|
+
|
|
8
|
+
export function layoutChar(drawData: ITextDrawData, style: ITextData, width: number, _height: number): void {
|
|
9
|
+
|
|
10
|
+
const { rows } = drawData
|
|
11
|
+
const { textAlign, paraIndent, letterSpacing } = style
|
|
12
|
+
let charX: number, addWordWidth: number, indentWidth: number, mode: number, wordChar: ITextCharData
|
|
13
|
+
|
|
14
|
+
rows.forEach(row => {
|
|
15
|
+
if (row.words) {
|
|
16
|
+
|
|
17
|
+
indentWidth = paraIndent && row.paraStart ? paraIndent : 0
|
|
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 : RowMode)
|
|
20
|
+
|
|
21
|
+
if (mode === RowMode) {
|
|
22
|
+
|
|
23
|
+
row.text = ''
|
|
24
|
+
row.x += indentWidth
|
|
25
|
+
|
|
26
|
+
row.words.forEach(word => {
|
|
27
|
+
word.data.forEach(char => {
|
|
28
|
+
row.text += char.char
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
} else {
|
|
33
|
+
|
|
34
|
+
row.x += indentWidth
|
|
35
|
+
charX = row.x
|
|
36
|
+
row.data = []
|
|
37
|
+
|
|
38
|
+
row.words.forEach(word => {
|
|
39
|
+
|
|
40
|
+
if (mode === WordMode) {
|
|
41
|
+
|
|
42
|
+
wordChar = { char: '', x: charX }
|
|
43
|
+
charX = toWordChar(word.data, charX, wordChar)
|
|
44
|
+
if (wordChar.char !== ' ') row.data.push(wordChar)
|
|
45
|
+
|
|
46
|
+
} else {
|
|
47
|
+
|
|
48
|
+
charX = toChar(word.data, charX, row.data)
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!row.paraEnd && addWordWidth) {
|
|
53
|
+
charX += addWordWidth
|
|
54
|
+
row.width += addWordWidth
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
row.words = null
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function toWordChar(data: ITextCharData[], charX: number, wordChar: ITextCharData): number {
|
|
67
|
+
data.forEach(char => {
|
|
68
|
+
wordChar.char += char.char
|
|
69
|
+
charX += char.width
|
|
70
|
+
})
|
|
71
|
+
return charX
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function toChar(data: ITextCharData[], charX: number, rowData: ITextCharData[]): number {
|
|
75
|
+
data.forEach(char => {
|
|
76
|
+
if (char.char !== ' ') {
|
|
77
|
+
char.x = charX
|
|
78
|
+
rowData.push(char)
|
|
79
|
+
}
|
|
80
|
+
charX += char.width
|
|
81
|
+
})
|
|
82
|
+
return charX
|
|
83
|
+
}
|
package/src/CharType.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { IBooleanMap } from '@leafer-ui/interface'
|
|
2
|
+
|
|
3
|
+
const money = '¥¥$€££¢¢'
|
|
4
|
+
const letter = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'
|
|
5
|
+
|
|
6
|
+
const langBefore = '《(「〈『〖【〔{┌<’“=' + money
|
|
7
|
+
const langAfter = '》)」〉』〗】〕}┐>’“!?,、。:;‰'
|
|
8
|
+
const langSymbol = '≮≯≈≠=…'
|
|
9
|
+
const langBreak = '—/~|┆·'
|
|
10
|
+
|
|
11
|
+
const beforeChar = '{[(<\'"' + langBefore
|
|
12
|
+
const afterChar = '>)]}%!?,.:;\'"' + langAfter
|
|
13
|
+
const symbolChar = afterChar + '_#~&*+\\=|' + langSymbol
|
|
14
|
+
const breakChar = '- ' + langBreak
|
|
15
|
+
|
|
16
|
+
function mapChar(str: string): IBooleanMap {
|
|
17
|
+
const map: IBooleanMap = {}
|
|
18
|
+
str.split('').forEach(char => map[char] = true)
|
|
19
|
+
return map
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const letterMap = mapChar(letter)
|
|
23
|
+
const beforeMap = mapChar(beforeChar)
|
|
24
|
+
const afterMap = mapChar(afterChar)
|
|
25
|
+
const symbolMap = mapChar(symbolChar)
|
|
26
|
+
const breakMap = mapChar(breakChar)
|
|
27
|
+
|
|
28
|
+
export enum CharType {
|
|
29
|
+
Letter, // en... number
|
|
30
|
+
Single, // cjk
|
|
31
|
+
Before, // (
|
|
32
|
+
After, // )
|
|
33
|
+
Symbol, // *
|
|
34
|
+
Break, // space
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { Letter, Single, Before, After, Symbol, Break } = CharType
|
|
38
|
+
|
|
39
|
+
export function getCharType(char: string): CharType {
|
|
40
|
+
|
|
41
|
+
if (letterMap[char]) {
|
|
42
|
+
return Letter
|
|
43
|
+
} else if (breakMap[char]) {
|
|
44
|
+
return Break
|
|
45
|
+
} else if (/[\u3400-\u9FBF]/.test(char)) { // 4e00-\u9FBF cjk
|
|
46
|
+
return Single
|
|
47
|
+
} else if (beforeMap[char]) {
|
|
48
|
+
return Before
|
|
49
|
+
} else if (afterMap[char]) {
|
|
50
|
+
return After
|
|
51
|
+
} else if (symbolMap[char]) {
|
|
52
|
+
return Symbol
|
|
53
|
+
} else {
|
|
54
|
+
return Letter
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}
|
package/src/TextCase.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ITextCase } from '@leafer-ui/interface'
|
|
2
|
+
|
|
3
|
+
export function getTextCase(char: string, textCase: ITextCase, firstChar?: boolean): string {
|
|
4
|
+
switch (textCase) {
|
|
5
|
+
case 'title':
|
|
6
|
+
return firstChar ? char.toUpperCase() : char
|
|
7
|
+
case 'upper':
|
|
8
|
+
return char.toUpperCase()
|
|
9
|
+
case 'lower':
|
|
10
|
+
return char.toLowerCase()
|
|
11
|
+
default:
|
|
12
|
+
return char
|
|
13
|
+
}
|
|
14
|
+
}
|
package/src/TextClip.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { IOverflow, ITextCharData, ITextDrawData } from '@leafer-ui/interface'
|
|
2
|
+
import { Platform } from '@leafer/core'
|
|
3
|
+
|
|
4
|
+
export function clipText(drawData: ITextDrawData, textOverflow: IOverflow | string): void {
|
|
5
|
+
|
|
6
|
+
const { rows, overflow } = drawData
|
|
7
|
+
rows.splice(overflow)
|
|
8
|
+
|
|
9
|
+
if (textOverflow !== 'hide') {
|
|
10
|
+
if (textOverflow === 'ellipsis') textOverflow = '...'
|
|
11
|
+
|
|
12
|
+
const ellipsisWidth = Platform.canvas.measureText(textOverflow).width
|
|
13
|
+
const row = rows[overflow - 1]
|
|
14
|
+
|
|
15
|
+
let char: ITextCharData, end = row.data.length - 1, charRight: number
|
|
16
|
+
|
|
17
|
+
const { x, width } = drawData.bounds
|
|
18
|
+
const right = x + width - ellipsisWidth
|
|
19
|
+
|
|
20
|
+
for (let i = end; i > -1; i--) {
|
|
21
|
+
char = row.data[i]
|
|
22
|
+
charRight = char.x + char.width
|
|
23
|
+
if (i === end && charRight < right) {
|
|
24
|
+
break
|
|
25
|
+
} else if (charRight < right && char.char !== ' ') {
|
|
26
|
+
row.data.splice(i + 1)
|
|
27
|
+
row.width -= char.width
|
|
28
|
+
break
|
|
29
|
+
}
|
|
30
|
+
row.width -= char.width
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
row.width += ellipsisWidth
|
|
34
|
+
row.data.push({ char: textOverflow, x: charRight })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|
package/src/TextConvert.ts
CHANGED
|
@@ -1,156 +1,56 @@
|
|
|
1
|
-
import { Platform } from '@leafer/core'
|
|
1
|
+
import { MathHelper, Platform } from '@leafer/core'
|
|
2
2
|
|
|
3
|
-
import { ITextData, ITextDrawData
|
|
3
|
+
import { ITextData, ITextDrawData } from '@leafer-ui/interface'
|
|
4
|
+
import { createRows } from './TextRows'
|
|
5
|
+
import { layoutChar } from './CharLayout'
|
|
6
|
+
import { layoutText } from './TextLayout'
|
|
7
|
+
import { clipText } from './TextClip'
|
|
8
|
+
import { decorationText } from './TextDecoration'
|
|
4
9
|
|
|
5
|
-
export const TextConvert = {
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
const { fontFamily, fontSize, fontWeight, italic, textCase } = style
|
|
9
|
-
return `${italic ? 'italic' : ''} ${textCase === 'small-caps' ? 'small-caps' : 'normal'} ${fontWeight ? fontWeight : 'normal'} ${fontSize}px/${fontSize}px ${fontFamily}`
|
|
10
|
-
},
|
|
11
|
+
export const TextConvert = {
|
|
11
12
|
|
|
12
13
|
getDrawData(content: string, style: ITextData): ITextDrawData {
|
|
13
|
-
const { width, height, fontSize, textDecoration, textAlign, verticalAlign } = style
|
|
14
|
-
const { canvas } = Platform
|
|
15
|
-
|
|
16
|
-
const list: ITextRowData[] = []
|
|
17
|
-
const lineHeight = fontSize * style.lineHeight
|
|
18
|
-
const font = canvas.font = T.getFont(style)
|
|
19
|
-
|
|
20
|
-
if (width) {
|
|
21
|
-
let char: string, charWidth: number, space = '', spaceWidth = 0, spaceCharWidth: number, word = '', wordWidth = 0, text = '', textWidth = 0
|
|
22
|
-
for (let i = 0, len = content.length; i < len; i++) {
|
|
23
|
-
char = content[i]
|
|
24
|
-
switch (char) {
|
|
25
|
-
case '\n':
|
|
26
|
-
|
|
27
|
-
//list.push({ text: text + word + space })
|
|
28
|
-
list.push({ text: text + word })
|
|
29
|
-
text = word = space = ''
|
|
30
|
-
textWidth = wordWidth = spaceWidth = 0
|
|
31
|
-
|
|
32
|
-
break
|
|
33
|
-
case ' ':
|
|
34
|
-
|
|
35
|
-
if (!spaceCharWidth) spaceCharWidth = canvas.measureText(char).width
|
|
36
|
-
space += char
|
|
37
|
-
spaceWidth += spaceCharWidth
|
|
38
|
-
|
|
39
|
-
break
|
|
40
|
-
|
|
41
|
-
default:
|
|
42
|
-
|
|
43
|
-
charWidth = canvas.measureText(char).width
|
|
44
14
|
|
|
45
|
-
|
|
15
|
+
if (typeof content !== 'string') content = String(content)
|
|
46
16
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
textWidth = wordWidth = spaceWidth = 0
|
|
53
|
-
|
|
54
|
-
} else {
|
|
55
|
-
|
|
56
|
-
list.push({ text })
|
|
57
|
-
text = ''
|
|
58
|
-
textWidth = 0
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (space) {
|
|
64
|
-
text += word + space
|
|
65
|
-
textWidth += wordWidth + spaceWidth
|
|
66
|
-
|
|
67
|
-
word = space = ''
|
|
68
|
-
wordWidth = spaceWidth = 0
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
word += char
|
|
72
|
-
wordWidth += charWidth
|
|
73
|
-
|
|
74
|
-
}
|
|
17
|
+
let x = 0, y = 0
|
|
18
|
+
let { width, height, padding } = style
|
|
19
|
+
const { textDecoration, textOverflow, fontSize, __font } = style
|
|
20
|
+
if (!width) width = 0
|
|
21
|
+
height = Math.max(height, fontSize)
|
|
75
22
|
|
|
23
|
+
if (padding) {
|
|
24
|
+
const [top, right, bottom, left] = MathHelper.fourNumber(padding)
|
|
25
|
+
if (width) {
|
|
26
|
+
x = left
|
|
27
|
+
width -= (right + left)
|
|
76
28
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
} else {
|
|
81
|
-
|
|
82
|
-
content.split('\n').map(text => {
|
|
83
|
-
list.push({ text })
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// measure
|
|
89
|
-
|
|
90
|
-
let realWidth = 0, realHeight = lineHeight * list.length
|
|
91
|
-
|
|
92
|
-
list.forEach(item => {
|
|
93
|
-
item.width = canvas.measureText(item.text).width
|
|
94
|
-
realWidth = Math.max(realWidth, item.width)
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// align
|
|
99
|
-
|
|
100
|
-
let item: ITextRowData, starY: number = lineHeight - (lineHeight - fontSize * 0.7) / 2
|
|
101
|
-
|
|
102
|
-
if (height) {
|
|
103
|
-
switch (verticalAlign) {
|
|
104
|
-
case 'middle':
|
|
105
|
-
starY += (height - realHeight) / 2
|
|
106
|
-
break
|
|
107
|
-
case 'bottom':
|
|
108
|
-
starY += (height - realHeight)
|
|
29
|
+
if (height) {
|
|
30
|
+
y = top
|
|
31
|
+
height -= (top + bottom)
|
|
109
32
|
}
|
|
110
33
|
}
|
|
111
34
|
|
|
35
|
+
const drawData: ITextDrawData = {
|
|
36
|
+
bounds: { x, y, width, height },
|
|
37
|
+
rows: [],
|
|
38
|
+
paraNumber: 0,
|
|
39
|
+
font: Platform.canvas.font = __font
|
|
40
|
+
}
|
|
112
41
|
|
|
113
|
-
|
|
114
|
-
item = list[i]
|
|
115
|
-
item.x = 0
|
|
42
|
+
createRows(drawData, content, style) // set rows, paraNumber
|
|
116
43
|
|
|
117
|
-
|
|
118
|
-
switch (textAlign) {
|
|
119
|
-
case 'center':
|
|
120
|
-
item.x = (width - item.width) / 2
|
|
121
|
-
break
|
|
122
|
-
case 'right':
|
|
123
|
-
item.x = width - item.width
|
|
124
|
-
}
|
|
125
|
-
}
|
|
44
|
+
layoutText(drawData, style) // set bounds
|
|
126
45
|
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// decoration
|
|
46
|
+
layoutChar(drawData, style, width, height) // set char.x
|
|
131
47
|
|
|
132
|
-
|
|
133
|
-
switch (textDecoration) {
|
|
134
|
-
case 'underline':
|
|
135
|
-
decorationY = fontSize * 0.1
|
|
136
|
-
break
|
|
137
|
-
case 'strikethrough':
|
|
138
|
-
decorationY = -fontSize * 0.35
|
|
139
|
-
}
|
|
48
|
+
if (drawData.overflow) clipText(drawData, textOverflow)
|
|
140
49
|
|
|
50
|
+
if (textDecoration !== 'none') decorationText(drawData, style)
|
|
141
51
|
|
|
142
|
-
return
|
|
143
|
-
list,
|
|
144
|
-
x: 0,
|
|
145
|
-
y: 0,
|
|
146
|
-
width: realWidth,
|
|
147
|
-
height: realHeight,
|
|
148
|
-
font,
|
|
149
|
-
decorationY,
|
|
150
|
-
decorationHeight
|
|
151
|
-
}
|
|
52
|
+
return drawData
|
|
152
53
|
|
|
153
54
|
}
|
|
154
|
-
}
|
|
155
55
|
|
|
156
|
-
|
|
56
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ITextData, ITextDrawData } from '@leafer-ui/interface'
|
|
2
|
+
|
|
3
|
+
export function decorationText(drawData: ITextDrawData, style: ITextData): void {
|
|
4
|
+
const { fontSize } = style
|
|
5
|
+
drawData.decorationHeight = fontSize / 11
|
|
6
|
+
switch (style.textDecoration) {
|
|
7
|
+
case 'underline':
|
|
8
|
+
drawData.decorationY = fontSize * 0.15
|
|
9
|
+
break
|
|
10
|
+
case 'delete':
|
|
11
|
+
drawData.decorationY = -fontSize * 0.35
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ITextData, ITextDrawData, ITextRowData } from '@leafer-ui/interface'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export function layoutText(drawData: ITextDrawData, style: ITextData): void {
|
|
5
|
+
|
|
6
|
+
const { rows, bounds } = drawData
|
|
7
|
+
const { __lineHeight, __baseLine, textAlign, verticalAlign, paraSpacing, textOverflow } = style
|
|
8
|
+
|
|
9
|
+
let { x, y, width, height } = bounds, realHeight = __lineHeight * rows.length + (paraSpacing ? paraSpacing * (drawData.paraNumber - 1) : 0)
|
|
10
|
+
let starY: number = __baseLine
|
|
11
|
+
|
|
12
|
+
// verticalAlign
|
|
13
|
+
|
|
14
|
+
if (height) {
|
|
15
|
+
if (textOverflow !== 'show' && realHeight > height) {
|
|
16
|
+
realHeight = Math.max(height, __lineHeight)
|
|
17
|
+
drawData.overflow = rows.length
|
|
18
|
+
} else {
|
|
19
|
+
switch (verticalAlign) {
|
|
20
|
+
case 'middle':
|
|
21
|
+
y += (height - realHeight) / 2
|
|
22
|
+
break
|
|
23
|
+
case 'bottom':
|
|
24
|
+
y += (height - realHeight)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
starY += y
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// textAlign
|
|
31
|
+
|
|
32
|
+
let row: ITextRowData
|
|
33
|
+
|
|
34
|
+
for (let i = 0, len = rows.length; i < len; i++) {
|
|
35
|
+
row = rows[i]
|
|
36
|
+
row.x = x
|
|
37
|
+
|
|
38
|
+
switch (textAlign) {
|
|
39
|
+
case 'center':
|
|
40
|
+
row.x += (width - row.width) / 2
|
|
41
|
+
break
|
|
42
|
+
case 'right':
|
|
43
|
+
row.x += width - row.width
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (row.paraStart && paraSpacing && i > 0) starY += paraSpacing
|
|
47
|
+
|
|
48
|
+
row.y = starY
|
|
49
|
+
|
|
50
|
+
starY += __lineHeight
|
|
51
|
+
|
|
52
|
+
if (drawData.overflow > i && starY > realHeight) {
|
|
53
|
+
row.isOverflow = true
|
|
54
|
+
drawData.overflow = i + 1
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
bounds.y = y
|
|
59
|
+
bounds.height = realHeight
|
|
60
|
+
|
|
61
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ITextRowData, ITextCharData } from '@leafer-ui/interface'
|
|
2
|
+
|
|
3
|
+
export const TextRowHelper = {
|
|
4
|
+
|
|
5
|
+
trimRight(row: ITextRowData): void {
|
|
6
|
+
const { words } = row
|
|
7
|
+
let trimRight = 0, len = words.length, char: ITextCharData
|
|
8
|
+
|
|
9
|
+
for (let i = len - 1; i > -1; i--) {
|
|
10
|
+
char = words[i].data[0]
|
|
11
|
+
if (char.char === ' ') {
|
|
12
|
+
trimRight++
|
|
13
|
+
row.width -= char.width
|
|
14
|
+
} else {
|
|
15
|
+
break
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (trimRight) words.splice(len - trimRight, trimRight)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
}
|
package/src/TextRows.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Platform } from '@leafer/core'
|
|
2
|
+
|
|
3
|
+
import { IBoundsData, ITextData, ITextDrawData, ITextRowData, ITextWordData } from '@leafer-ui/interface'
|
|
4
|
+
|
|
5
|
+
import { CharType, getCharType } from './CharType'
|
|
6
|
+
import { TextRowHelper } from './TextRowHelper'
|
|
7
|
+
import { getTextCase } from './TextCase'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const { trimRight } = TextRowHelper
|
|
11
|
+
const { Letter, Single, Before, After, Symbol, Break } = CharType
|
|
12
|
+
|
|
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
|
|
15
|
+
let textDrawData: ITextDrawData, rows: ITextRowData[] = [], bounds: IBoundsData
|
|
16
|
+
|
|
17
|
+
export function createRows(drawData: ITextDrawData, content: string, style: ITextData): void {
|
|
18
|
+
|
|
19
|
+
textDrawData = drawData
|
|
20
|
+
rows = drawData.rows
|
|
21
|
+
bounds = drawData.bounds
|
|
22
|
+
|
|
23
|
+
const { __letterSpacing, paraIndent, textCase } = style
|
|
24
|
+
const { canvas } = Platform
|
|
25
|
+
const { width, height } = bounds
|
|
26
|
+
const charMode = width || height || __letterSpacing || textCase
|
|
27
|
+
|
|
28
|
+
if (charMode) {
|
|
29
|
+
|
|
30
|
+
paraStart = true
|
|
31
|
+
lastCharType = null
|
|
32
|
+
wordWidth = rowWidth = 0
|
|
33
|
+
word = { data: [] }, row = { words: [] }
|
|
34
|
+
|
|
35
|
+
for (let i = 0, len = content.length; i < len; i++) {
|
|
36
|
+
|
|
37
|
+
char = content[i]
|
|
38
|
+
|
|
39
|
+
if (char === '\n') {
|
|
40
|
+
|
|
41
|
+
if (wordWidth) addWord()
|
|
42
|
+
row.paraEnd = true
|
|
43
|
+
addRow()
|
|
44
|
+
|
|
45
|
+
paraStart = true
|
|
46
|
+
|
|
47
|
+
} else {
|
|
48
|
+
|
|
49
|
+
charType = getCharType(char)
|
|
50
|
+
|
|
51
|
+
if (charType === Letter && textCase !== 'none') char = getTextCase(char, textCase, !wordWidth)
|
|
52
|
+
|
|
53
|
+
charWidth = canvas.measureText(char).width
|
|
54
|
+
if (__letterSpacing) charWidth += __letterSpacing
|
|
55
|
+
|
|
56
|
+
langBreak = (charType === Single && (lastCharType === Single || lastCharType === Letter)) || (lastCharType === Single && charType !== After) // break U字 文字 or 字U 字( 字* exclude 字。
|
|
57
|
+
afterBreak = ((charType === Before || charType === Single) && (lastCharType === Symbol || lastCharType === After)) // split >( =文 。哈 ;文
|
|
58
|
+
|
|
59
|
+
realWidth = paraStart && paraIndent ? width - paraIndent : width
|
|
60
|
+
|
|
61
|
+
if (rowWidth + wordWidth + charWidth > realWidth) { // wrap
|
|
62
|
+
|
|
63
|
+
if (!afterBreak) afterBreak = charType === Letter && lastCharType == After // split ,S
|
|
64
|
+
|
|
65
|
+
if (langBreak || afterBreak || charType === Break || charType === Before || charType === Single || (wordWidth + charWidth > realWidth)) {
|
|
66
|
+
|
|
67
|
+
if (wordWidth) addWord() // break
|
|
68
|
+
addRow()
|
|
69
|
+
|
|
70
|
+
} else {
|
|
71
|
+
|
|
72
|
+
addRow()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!(char === ' ' && (rowWidth + wordWidth) === 0)) {
|
|
78
|
+
|
|
79
|
+
if (charType === Break) {
|
|
80
|
+
|
|
81
|
+
if (char === ' ' && wordWidth) addWord()
|
|
82
|
+
addChar(char, charWidth)
|
|
83
|
+
addWord()
|
|
84
|
+
|
|
85
|
+
} else if (langBreak || afterBreak) {
|
|
86
|
+
|
|
87
|
+
if (wordWidth) addWord()
|
|
88
|
+
addChar(char, charWidth)
|
|
89
|
+
|
|
90
|
+
} else {
|
|
91
|
+
|
|
92
|
+
addChar(char, charWidth)
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
lastCharType = charType
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (wordWidth) addWord()
|
|
104
|
+
if (rowWidth) addRow()
|
|
105
|
+
|
|
106
|
+
rows[rows.length - 1].paraEnd = true
|
|
107
|
+
|
|
108
|
+
} else {
|
|
109
|
+
|
|
110
|
+
content.split('\n').forEach(content => {
|
|
111
|
+
textDrawData.paraNumber++
|
|
112
|
+
rows.push({ x: paraIndent || 0, text: content, width: canvas.measureText(content).width, paraStart: true })
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
function addChar(char: string, width: number): void {
|
|
120
|
+
word.data.push({ char, width })
|
|
121
|
+
wordWidth += width
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function addWord(): void {
|
|
125
|
+
rowWidth += wordWidth
|
|
126
|
+
word.width = wordWidth
|
|
127
|
+
row.words.push(word)
|
|
128
|
+
word = { data: [] }
|
|
129
|
+
wordWidth = 0
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function addRow(): void {
|
|
133
|
+
if (paraStart) {
|
|
134
|
+
textDrawData.paraNumber++
|
|
135
|
+
row.paraStart = true
|
|
136
|
+
paraStart = false
|
|
137
|
+
}
|
|
138
|
+
row.width = rowWidth
|
|
139
|
+
trimRight(row)
|
|
140
|
+
rows.push(row)
|
|
141
|
+
if (row.width > bounds.width) bounds.width = row.width
|
|
142
|
+
row = { words: [] }
|
|
143
|
+
rowWidth = 0
|
|
144
|
+
}
|