@leafer-ui/text 1.0.0-alpha.23 → 1.0.0-alpha.31

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leafer-ui/text",
3
- "version": "1.0.0-alpha.23",
3
+ "version": "1.0.0-alpha.31",
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.23"
22
+ "@leafer-ui/core": "1.0.0-alpha.31"
23
23
  },
24
24
  "devDependencies": {
25
- "@leafer/interface": "1.0.0-alpha.23",
26
- "@leafer-ui/interface": "1.0.0-alpha.23"
25
+ "@leafer/interface": "1.0.0-alpha.31",
26
+ "@leafer-ui/interface": "1.0.0-alpha.31"
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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -1,156 +1,55 @@
1
- import { Platform } from '@leafer/core'
1
+ import { MathHelper, Platform } from '@leafer/core'
2
2
 
3
- import { ITextData, ITextDrawData, ITextRowData } from '@leafer-ui/interface'
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
- getFont(style: ITextData): string {
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
- if (textWidth + wordWidth + spaceWidth + charWidth > width) {
15
+ if (typeof content !== 'string') content = String(content)
46
16
 
47
- if (wordWidth + spaceWidth + charWidth > width) {
48
-
49
- // break word
50
- list.push({ text: text + word })
51
- text = word = space = ''
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, __font } = style
20
+ if (!width) width = 0
75
21
 
22
+ if (padding) {
23
+ const [top, right, bottom, left] = MathHelper.fourNumber(padding)
24
+ if (width) {
25
+ x = left
26
+ width -= (right + left)
76
27
  }
77
-
78
- list.push({ text: text + word + space })
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)
28
+ if (height) {
29
+ y = top
30
+ height -= (top + bottom)
109
31
  }
110
32
  }
111
33
 
34
+ const drawData: ITextDrawData = {
35
+ bounds: { x, y, width, height },
36
+ rows: [],
37
+ paraNumber: 0,
38
+ font: Platform.canvas.font = __font
39
+ }
112
40
 
113
- for (let i = 0, len = list.length; i < len; i++) {
114
- item = list[i]
115
- item.x = 0
41
+ createRows(drawData, content, style) // set rows, paraNumber
116
42
 
117
- if (width) {
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
- }
43
+ layoutText(drawData, style) // set bounds
126
44
 
127
- item.y = starY + lineHeight * i
128
- }
129
-
130
- // decoration
45
+ layoutChar(drawData, style, width, height) // set char.x
131
46
 
132
- let decorationY: number, decorationHeight = fontSize / 11
133
- switch (textDecoration) {
134
- case 'underline':
135
- decorationY = fontSize * 0.1
136
- break
137
- case 'strikethrough':
138
- decorationY = -fontSize * 0.35
139
- }
47
+ if (drawData.overflow) clipText(drawData, textOverflow)
140
48
 
49
+ if (textDecoration !== 'none') decorationText(drawData, style)
141
50
 
142
- return {
143
- list,
144
- x: 0,
145
- y: 0,
146
- width: realWidth,
147
- height: realHeight,
148
- font,
149
- decorationY,
150
- decorationHeight
151
- }
51
+ return drawData
152
52
 
153
53
  }
154
- }
155
54
 
156
- const T = TextConvert
55
+ }
@@ -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
+ }
@@ -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 (width && 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
+ }