@next2d/text 1.17.4 → 1.18.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/dist/TextData.js CHANGED
@@ -67,9 +67,6 @@ export class TextData {
67
67
  this._$textHeight = 0;
68
68
  for (let idx = 0; idx < this._$heightTable.length; ++idx) {
69
69
  this._$textHeight += this._$heightTable[idx];
70
- if (idx) {
71
- this._$textHeight += 1;
72
- }
73
70
  }
74
71
  }
75
72
  return this._$textHeight;
@@ -191,6 +191,18 @@ export declare class TextFormat {
191
191
  */
192
192
  get underline(): boolean | null;
193
193
  set underline(underline: boolean | null);
194
+ /**
195
+ * @return {string}
196
+ * @method
197
+ * @private
198
+ */
199
+ _$toStyleString(): string;
200
+ /**
201
+ * @return {boolean}
202
+ * @method
203
+ * @private
204
+ */
205
+ _$isSame(text_format: TextFormat): boolean;
194
206
  /**
195
207
  * @return {next2d.text.TextFormat}
196
208
  * @method
@@ -1,4 +1,4 @@
1
- import { $clamp, $toColorInt } from "@next2d/share";
1
+ import { $clamp, $intToRGBA, $toColorInt } from "@next2d/share";
2
2
  /**
3
3
  * TextFormat クラスは、文字フォーマット情報を表します。
4
4
  * TextFormat クラスを使用して、テキストフィールドに特定のテキストフォーマットを作成します。
@@ -304,6 +304,93 @@ export class TextFormat {
304
304
  set underline(underline) {
305
305
  this._$underline = underline !== null ? !!underline : null;
306
306
  }
307
+ /**
308
+ * @return {string}
309
+ * @method
310
+ * @private
311
+ */
312
+ _$toStyleString() {
313
+ let style = "";
314
+ if (this._$font) {
315
+ style += `font-family: ${this._$font};`;
316
+ }
317
+ if (this._$size) {
318
+ style += `font-size: ${this._$size}px;`;
319
+ }
320
+ if (this._$color) {
321
+ const color = $intToRGBA($toColorInt(this._$color));
322
+ const R = color.R.toString(16).padStart(2, "0");
323
+ const G = color.G.toString(16).padStart(2, "0");
324
+ const B = color.B.toString(16).padStart(2, "0");
325
+ style += `color: #${R}${G}${B};`;
326
+ }
327
+ if (this._$bold) {
328
+ style += "font-weight: bold;";
329
+ }
330
+ if (this._$italic) {
331
+ style += "font-style: italic;";
332
+ }
333
+ if (this._$underline) {
334
+ style += "text-decoration: underline;";
335
+ }
336
+ if (this._$align) {
337
+ style += `text-align: ${this._$align};`;
338
+ }
339
+ if (this._$leftMargin) {
340
+ style += `margin-left: ${this._$leftMargin}px;`;
341
+ }
342
+ if (this._$rightMargin) {
343
+ style += `margin-right: ${this._$rightMargin}px;`;
344
+ }
345
+ if (this._$leading) {
346
+ style += `margin-bottom: ${this._$leading}px;`;
347
+ }
348
+ if (this._$letterSpacing) {
349
+ style += `letter-spacing: ${this._$letterSpacing}px;`;
350
+ }
351
+ return style;
352
+ }
353
+ /**
354
+ * @return {boolean}
355
+ * @method
356
+ * @private
357
+ */
358
+ _$isSame(text_format) {
359
+ if (this._$font !== text_format.font) {
360
+ return false;
361
+ }
362
+ if (this._$size !== text_format.size) {
363
+ return false;
364
+ }
365
+ if (this._$color !== text_format.color) {
366
+ return false;
367
+ }
368
+ if (this._$bold !== text_format.bold) {
369
+ return false;
370
+ }
371
+ if (this._$italic !== text_format.italic) {
372
+ return false;
373
+ }
374
+ if (this._$underline !== text_format.underline) {
375
+ return false;
376
+ }
377
+ if (this._$align !== text_format.align) {
378
+ return false;
379
+ }
380
+ if (this._$leftMargin !== text_format.leftMargin) {
381
+ return false;
382
+ }
383
+ if (this._$rightMargin !== text_format.rightMargin) {
384
+ return false;
385
+ }
386
+ if (this._$leading !== text_format.leading) {
387
+ return false;
388
+ }
389
+ if (this._$letterSpacing !== text_format.letterSpacing) {
390
+ return false;
391
+ }
392
+ return true;
393
+ }
307
394
  /**
308
395
  * @return {next2d.text.TextFormat}
309
396
  * @method
@@ -24,6 +24,9 @@ const _$parseText = (texts, text_format, text_data, options) => {
24
24
  let line = text_data.lineTable.length - 1;
25
25
  const maxWidth = options.width - text_format._$widthMargin() - 4;
26
26
  for (let idx = 0; idx < texts.length; ++idx) {
27
+ const textFormat = options.textFormats === null
28
+ ? text_format
29
+ : options.textFormats.shift();
27
30
  const text = texts[idx];
28
31
  const object = {
29
32
  "mode": "text",
@@ -33,58 +36,192 @@ const _$parseText = (texts, text_format, text_data, options) => {
33
36
  "w": 0,
34
37
  "h": 0,
35
38
  "line": line,
36
- "textFormat": text_format._$clone()
39
+ "textFormat": textFormat._$clone()
37
40
  };
38
- const breakCode = options.multiline
39
- && text === "\n"
40
- || text === "\r"
41
- || text === "\n\r";
42
- $context.font = text_format._$generateFontStyle();
41
+ $context.font = textFormat._$generateFontStyle();
43
42
  const mesure = $context.measureText(text || "");
44
43
  let width = mesure.width;
45
- if (text_format.letterSpacing) {
46
- width += text_format.letterSpacing;
44
+ if (textFormat.letterSpacing) {
45
+ width += textFormat.letterSpacing;
47
46
  }
48
47
  let height = mesure.fontBoundingBoxAscent + mesure.fontBoundingBoxDescent;
49
- if (text_format.leading) {
50
- height += text_format.leading;
48
+ if (textFormat.leading) {
49
+ height += textFormat.leading;
51
50
  }
52
51
  // setup
53
- object.x = mesure.actualBoundingBoxLeft;
54
- object.y = mesure.actualBoundingBoxAscent;
52
+ object.x = 0;
53
+ object.y = mesure.fontBoundingBoxAscent;
55
54
  object.w = width;
56
55
  object.h = height;
57
56
  $currentWidth += width;
58
- if (breakCode || options.wordWrap && $currentWidth > maxWidth) {
57
+ if (options.wordWrap && $currentWidth > maxWidth) {
59
58
  $currentWidth = width;
60
59
  // update
61
60
  line++;
62
61
  object.line = line;
63
62
  // break object
64
63
  const wrapObject = {
65
- "mode": breakCode ? "break" : "wrap",
64
+ "mode": "wrap",
66
65
  "text": "",
67
66
  "x": 0,
68
67
  "y": 0,
69
68
  "w": 0,
70
69
  "h": 0,
71
70
  "line": line,
72
- "textFormat": text_format._$clone()
71
+ "textFormat": textFormat._$clone()
73
72
  };
73
+ let chunkLength = 1;
74
+ let isSeparated = true;
75
+ const pattern = /[0-9a-zA-Z?!;:.,?!。、;:〜]/g;
76
+ for (;;) {
77
+ const index = text_data.textTable.length - chunkLength;
78
+ if (0 >= index) {
79
+ isSeparated = false;
80
+ chunkLength = 0;
81
+ break;
82
+ }
83
+ const prevObj = text_data.textTable[index];
84
+ if (!prevObj) {
85
+ isSeparated = false;
86
+ chunkLength = 0;
87
+ break;
88
+ }
89
+ if (prevObj.mode !== "text") {
90
+ isSeparated = false;
91
+ break;
92
+ }
93
+ if (prevObj.text === " ") {
94
+ chunkLength--;
95
+ break;
96
+ }
97
+ if (!prevObj.text.match(pattern)) {
98
+ chunkLength--;
99
+ break;
100
+ }
101
+ chunkLength++;
102
+ }
74
103
  // new line
75
104
  text_data.widthTable[line] = 0;
76
105
  text_data.heightTable[line] = 0;
77
106
  text_data.ascentTable[line] = 0;
78
- text_data.textTable.push(wrapObject);
79
- text_data.lineTable.push(wrapObject);
107
+ if (chunkLength > 0 && isSeparated) {
108
+ const insertIdx = text_data.textTable.length - chunkLength;
109
+ text_data.textTable.splice(insertIdx, 0, wrapObject);
110
+ text_data.lineTable.push(wrapObject);
111
+ const prevLine = line - 1;
112
+ // reset
113
+ text_data.widthTable[prevLine] = 0;
114
+ text_data.heightTable[prevLine] = 0;
115
+ text_data.ascentTable[prevLine] = 0;
116
+ for (let idx = 0; idx < insertIdx; ++idx) {
117
+ const textObject = text_data.textTable[idx];
118
+ if (textObject.line !== prevLine) {
119
+ continue;
120
+ }
121
+ if (textObject.mode !== "text") {
122
+ continue;
123
+ }
124
+ text_data.widthTable[prevLine] += textObject.w;
125
+ text_data.heightTable[prevLine] = Math.max(text_data.heightTable[prevLine], textObject.h);
126
+ text_data.ascentTable[prevLine] = Math.max(text_data.ascentTable[prevLine], textObject.y);
127
+ }
128
+ // reset
129
+ $currentWidth = 0;
130
+ for (let idx = insertIdx + 1; idx < text_data.textTable.length; ++idx) {
131
+ const textObject = text_data.textTable[idx];
132
+ textObject.line = line;
133
+ $currentWidth += textObject.w;
134
+ }
135
+ }
136
+ else {
137
+ text_data.textTable.push(wrapObject);
138
+ text_data.lineTable.push(wrapObject);
139
+ }
80
140
  }
81
- if (!breakCode) {
82
- text_data.widthTable[line] = $currentWidth;
83
- text_data.heightTable[line] = Math.max(text_data.heightTable[line], height);
84
- text_data.ascentTable[line] = Math.max(text_data.ascentTable[line], object.y);
85
- text_data.textTable.push(object);
141
+ text_data.widthTable[line] = $currentWidth;
142
+ text_data.heightTable[line] = Math.max(text_data.heightTable[line], height);
143
+ text_data.ascentTable[line] = Math.max(text_data.ascentTable[line], object.y);
144
+ text_data.textTable.push(object);
145
+ }
146
+ };
147
+ /**
148
+ * @param {string} value
149
+ * @param {TextFormat} text_format
150
+ * @return {void}
151
+ * @method
152
+ * @private
153
+ */
154
+ const _$parseStyle = (value, text_format, options) => {
155
+ const values = value
156
+ .trim()
157
+ .split(";");
158
+ const attributes = [];
159
+ for (let idx = 0; idx < values.length; ++idx) {
160
+ const styleValue = values[idx];
161
+ if (!styleValue) {
162
+ continue;
163
+ }
164
+ const styles = styleValue.split(":");
165
+ const name = styles[0].trim();
166
+ const value = styles[1].trim();
167
+ switch (name) {
168
+ case "font-size":
169
+ attributes.push({
170
+ "name": "size",
171
+ "value": parseFloat(value)
172
+ });
173
+ break;
174
+ case "font-family":
175
+ attributes.push({
176
+ "name": "face",
177
+ "value": value.replace(/'|"/g, "")
178
+ });
179
+ break;
180
+ case "letter-spacing":
181
+ attributes.push({
182
+ "name": "letterSpacing",
183
+ "value": value
184
+ });
185
+ break;
186
+ case "margin-bottom":
187
+ attributes.push({
188
+ "name": "leading",
189
+ "value": parseFloat(value)
190
+ });
191
+ break;
192
+ case "margin-left":
193
+ attributes.push({
194
+ "name": "leftMargin",
195
+ "value": parseFloat(value)
196
+ });
197
+ break;
198
+ case "margin-right":
199
+ attributes.push({
200
+ "name": "rightMargin",
201
+ "value": parseFloat(value)
202
+ });
203
+ break;
204
+ case "color":
205
+ case "align":
206
+ attributes.push({
207
+ "name": name,
208
+ "value": value
209
+ });
210
+ break;
211
+ case "text-decoration":
212
+ case "font-weight":
213
+ case "font-style":
214
+ attributes.push({
215
+ "name": value,
216
+ "value": true
217
+ });
218
+ break;
219
+ default:
220
+ break;
86
221
  }
87
222
  }
223
+ // eslint-disable-next-line no-use-before-define
224
+ _$setAttributes(attributes, text_format, options);
88
225
  };
89
226
  /**
90
227
  * @param {array} attributes
@@ -98,6 +235,9 @@ const _$setAttributes = (attributes, text_format, options) => {
98
235
  for (let idx = 0; idx < attributes.length; ++idx) {
99
236
  const object = attributes[idx];
100
237
  switch (object.name) {
238
+ case "style":
239
+ _$parseStyle(object.value, text_format, options);
240
+ break;
101
241
  case "align":
102
242
  text_format.align = object.value;
103
243
  break;
@@ -128,6 +268,15 @@ const _$setAttributes = (attributes, text_format, options) => {
128
268
  case "rightMargin":
129
269
  text_format.rightMargin = +object.value;
130
270
  break;
271
+ case "underline":
272
+ text_format.underline = true;
273
+ break;
274
+ case "bold":
275
+ text_format.bold = true;
276
+ break;
277
+ case "italic":
278
+ text_format.italic = true;
279
+ break;
131
280
  default:
132
281
  break;
133
282
  }
@@ -244,21 +393,28 @@ const _$adjustmentHeight = (text_data) => {
244
393
  * @public
245
394
  */
246
395
  export const parsePlainText = (text, text_format, options) => {
396
+ const textData = new TextData();
397
+ if (!text) {
398
+ return textData;
399
+ }
247
400
  const lineText = options.multiline
248
401
  ? text.split("\n")
249
402
  : [text.replace("\n", "")];
250
- const textData = new TextData();
251
- // clone
252
- const textFormat = text_format._$clone();
253
- if (options.subFontSize
254
- && options.subFontSize > 0 && textFormat.size) {
255
- textFormat.size -= options.subFontSize;
256
- if (1 > textFormat.size) {
257
- textFormat.size = 1;
258
- }
259
- }
260
403
  for (let idx = 0; idx < lineText.length; ++idx) {
261
- if (options.wordWrap || options.multiline) {
404
+ let textFormat = text_format._$clone();
405
+ if (options.textFormats) {
406
+ textFormat = idx === 0
407
+ ? options.textFormats[0]
408
+ : options.textFormats.shift();
409
+ }
410
+ if (options.subFontSize
411
+ && options.subFontSize > 0 && textFormat.size) {
412
+ textFormat.size -= options.subFontSize;
413
+ if (1 > textFormat.size) {
414
+ textFormat.size = 1;
415
+ }
416
+ }
417
+ if (idx === 0 || options.wordWrap || options.multiline) {
262
418
  _$createNewLine(textData, textFormat);
263
419
  }
264
420
  const texts = lineText[idx];
@@ -280,11 +436,14 @@ export const parsePlainText = (text, text_format, options) => {
280
436
  * @public
281
437
  */
282
438
  export const parseHtmlText = (html_text, text_format, options) => {
439
+ const textData = new TextData();
440
+ if (!html_text) {
441
+ return textData;
442
+ }
283
443
  const htmlText = html_text
284
444
  .trim()
285
445
  .replace(/\r?\n/g, "")
286
446
  .replace(/\t/g, "");
287
- const textData = new TextData();
288
447
  const textFormat = text_format._$clone();
289
448
  if (options.subFontSize && options.subFontSize > 0 && textFormat.size) {
290
449
  textFormat.size -= options.subFontSize;
@@ -1,6 +1,8 @@
1
+ import { TextFormat } from "../TextFormat";
1
2
  export interface OptionsImpl {
2
3
  width: number;
3
4
  multiline: boolean;
4
5
  wordWrap: boolean;
6
+ textFormats: TextFormat[] | null;
5
7
  subFontSize?: number;
6
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next2d/text",
3
- "version": "1.17.4",
3
+ "version": "1.18.0",
4
4
  "description": "Next2D Text Packages",
5
5
  "author": "Toshiyuki Ienaga<ienaga@tvon.jp> (https://github.com/ienaga/)",
6
6
  "license": "MIT",
@@ -32,6 +32,6 @@
32
32
  "url": "git+https://github.com/Next2D/Player.git"
33
33
  },
34
34
  "peerDependencies": {
35
- "@next2d/share": "1.17.4"
35
+ "@next2d/share": "1.18.0"
36
36
  }
37
37
  }