1 | import { __decorate, __metadata, __read, __spreadArray } from "tslib";
|
2 | import { singleton, inject } from 'mana-syringe';
|
3 | import { toFontString } from '../utils';
|
4 | import { Rectangle } from '../shapes';
|
5 | import { OffscreenCanvasCreator } from './OffscreenCanvasCreator';
|
6 | var TEXT_METRICS = {
|
7 | MetricsString: '|ÉqÅ',
|
8 | BaselineSymbol: 'M',
|
9 | BaselineMultiplier: 1.4,
|
10 | HeightMultiplier: 2,
|
11 | Newlines: [0x000a, 0x000d
|
12 | ],
|
13 | BreakingSpaces: [0x0009, 0x0020, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2008, 0x2009, 0x200a, 0x205f, 0x3000
|
14 | ]
|
15 | };
|
16 | var LATIN_REGEX = /[a-zA-Z0-9\u00C0-\u00D6\u00D8-\u00f6\u00f8-\u00ff!"#$%&'()*+,-./:;]/;
|
17 |
|
18 |
|
19 | var regexCannotStartZhCn = /[!%),.:;?\]}¢°·'""†‡›℃∶、。〃〆〕〗〞﹚﹜!"%'),.:;?!]}~]/;
|
20 | var regexCannotEndZhCn = /[$(£¥·'"〈《「『【〔〖〝﹙﹛$(.[{£¥]/;
|
21 | var regexCannotStartZhTw = /[!),.:;?\]}¢·–—'"•"、。〆〞〕〉》」︰︱︲︳﹐﹑﹒﹔﹕﹖﹘﹚﹜!),.:;?︶︸︺︼︾﹀﹂﹗]|}、]/;
|
22 | var regexCannotEndZhTw = /[([{£¥'"‵〈《「『〔〝︴﹙﹛({︵︷︹︻︽︿﹁﹃﹏]/;
|
23 | var regexCannotStartJaJp = /[)\]}〕〉》」』】〙〗〟'"⦆»ヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻‐゠–〜?!‼⁇⁈⁉・、:;,。.]/;
|
24 | var regexCannotEndJaJp = /[([{〔〈《「『【〘〖〝'"⦅«—...‥〳〴〵]/;
|
25 | var regexCannotStartKoKr = /[!%),.:;?\]}¢°'"†‡℃〆〈《「『〕!%),.:;?]}]/;
|
26 | var regexCannotEndKoKr = /[$([{£¥'"々〇〉》」〔$([{⦆¥₩#]/;
|
27 | var regexCannotStart = new RegExp("".concat(regexCannotStartZhCn.source, "|").concat(regexCannotStartZhTw.source, "|").concat(regexCannotStartJaJp.source, "|").concat(regexCannotStartKoKr.source));
|
28 | var regexCannotEnd = new RegExp("".concat(regexCannotEndZhCn.source, "|").concat(regexCannotEndZhTw.source, "|").concat(regexCannotEndJaJp.source, "|").concat(regexCannotEndKoKr.source));
|
29 |
|
30 | var TextService =
|
31 |
|
32 | function () {
|
33 | function TextService() {
|
34 | var _this = this;
|
35 |
|
36 | this.cache = {};
|
37 |
|
38 | this.shouldBreakByKinsokuShorui = function (char, nextChar) {
|
39 | if (_this.isBreakingSpace(nextChar)) return false;
|
40 |
|
41 | if (char) {
|
42 |
|
43 | if (regexCannotEnd.exec(nextChar) || regexCannotStart.exec(char)) {
|
44 | return true;
|
45 | }
|
46 | }
|
47 |
|
48 | return false;
|
49 | };
|
50 |
|
51 | this.trimByKinsokuShorui = function (prev) {
|
52 | var next = __spreadArray([], __read(prev), false);
|
53 |
|
54 | var prevLine = next[next.length - 2];
|
55 |
|
56 | if (!prevLine) {
|
57 | return prev;
|
58 | }
|
59 |
|
60 | var lastChar = prevLine[prevLine.length - 1];
|
61 | next[next.length - 2] = prevLine.slice(0, -1);
|
62 | next[next.length - 1] = lastChar + next[next.length - 1];
|
63 | return next;
|
64 | };
|
65 | }
|
66 |
|
67 | TextService.prototype.measureFont = function (font, offscreenCanvas) {
|
68 |
|
69 | if (this.cache[font]) {
|
70 | return this.cache[font];
|
71 | }
|
72 |
|
73 | var properties = {
|
74 | ascent: 0,
|
75 | descent: 0,
|
76 | fontSize: 0
|
77 | };
|
78 | var canvas = this.offscreenCanvas.getOrCreateCanvas(offscreenCanvas);
|
79 | var context = this.offscreenCanvas.getOrCreateContext(offscreenCanvas);
|
80 | context.font = font;
|
81 | var metricsString = TEXT_METRICS.MetricsString + TEXT_METRICS.BaselineSymbol;
|
82 | var width = Math.ceil(context.measureText(metricsString).width);
|
83 | var baseline = Math.ceil(context.measureText(TEXT_METRICS.BaselineSymbol).width);
|
84 | var height = TEXT_METRICS.HeightMultiplier * baseline;
|
85 | baseline = baseline * TEXT_METRICS.BaselineMultiplier | 0;
|
86 |
|
87 | canvas.width = width;
|
88 |
|
89 | canvas.height = height;
|
90 | context.fillStyle = '#f00';
|
91 | context.fillRect(0, 0, width, height);
|
92 | context.font = font;
|
93 | context.textBaseline = 'alphabetic';
|
94 | context.fillStyle = '#000';
|
95 | context.fillText(metricsString, 0, baseline);
|
96 | var imagedata = context.getImageData(0, 0, width || 1, height || 1).data;
|
97 | var pixels = imagedata.length;
|
98 | var line = width * 4;
|
99 | var i = 0;
|
100 | var idx = 0;
|
101 | var stop = false;
|
102 |
|
103 | for (i = 0; i < baseline; ++i) {
|
104 | for (var j = 0; j < line; j += 4) {
|
105 | if (imagedata[idx + j] !== 255) {
|
106 | stop = true;
|
107 | break;
|
108 | }
|
109 | }
|
110 |
|
111 | if (!stop) {
|
112 | idx += line;
|
113 | } else {
|
114 | break;
|
115 | }
|
116 | }
|
117 |
|
118 | properties.ascent = baseline - i;
|
119 | idx = pixels - line;
|
120 | stop = false;
|
121 |
|
122 | for (i = height; i > baseline; --i) {
|
123 | for (var j = 0; j < line; j += 4) {
|
124 | if (imagedata[idx + j] !== 255) {
|
125 | stop = true;
|
126 | break;
|
127 | }
|
128 | }
|
129 |
|
130 | if (!stop) {
|
131 | idx -= line;
|
132 | } else {
|
133 | break;
|
134 | }
|
135 | }
|
136 |
|
137 | properties.descent = i - baseline;
|
138 | properties.fontSize = properties.ascent + properties.descent;
|
139 | this.cache[font] = properties;
|
140 | return properties;
|
141 | };
|
142 |
|
143 | TextService.prototype.measureText = function (text, parsedStyle, offscreenCanvas) {
|
144 | var fontSize = parsedStyle.fontSize,
|
145 | wordWrap = parsedStyle.wordWrap,
|
146 | _a = parsedStyle.lineHeight,
|
147 | strokeHeight = _a === void 0 ? 0 : _a,
|
148 | lineWidth = parsedStyle.lineWidth,
|
149 | textBaseline = parsedStyle.textBaseline,
|
150 | textAlign = parsedStyle.textAlign,
|
151 | _b = parsedStyle.letterSpacing,
|
152 | letterSpacing = _b === void 0 ? 0 : _b,
|
153 |
|
154 |
|
155 | _c = parsedStyle.leading,
|
156 |
|
157 |
|
158 | leading = _c === void 0 ? 0 : _c;
|
159 | var font = toFontString(parsedStyle);
|
160 | var fontProperties = this.measureFont(font, offscreenCanvas);
|
161 |
|
162 |
|
163 | if (fontProperties.fontSize === 0) {
|
164 | fontProperties.fontSize = fontSize.value;
|
165 | fontProperties.ascent = fontSize.value;
|
166 | }
|
167 |
|
168 | var context = this.offscreenCanvas.getOrCreateContext(offscreenCanvas);
|
169 | context.font = font;
|
170 | var outputText = wordWrap ? this.wordWrap(text, parsedStyle, offscreenCanvas) : text;
|
171 | var lines = outputText.split(/(?:\r\n|\r|\n)/);
|
172 | var lineWidths = new Array(lines.length);
|
173 | var maxLineWidth = 0;
|
174 |
|
175 | for (var i = 0; i < lines.length; i++) {
|
176 | var lineWidth_1 = context.measureText(lines[i]).width + (lines[i].length - 1) * letterSpacing;
|
177 | lineWidths[i] = lineWidth_1;
|
178 | maxLineWidth = Math.max(maxLineWidth, lineWidth_1);
|
179 | }
|
180 |
|
181 | var width = maxLineWidth + lineWidth.value;
|
182 |
|
183 |
|
184 |
|
185 | var lineHeight = strokeHeight || fontProperties.fontSize + lineWidth.value;
|
186 | var height = Math.max(lineHeight, fontProperties.fontSize + lineWidth.value) + (lines.length - 1) * (lineHeight + leading);
|
187 |
|
188 |
|
189 |
|
190 | lineHeight += leading;
|
191 |
|
192 | var offsetY = 0;
|
193 |
|
194 | if (textBaseline.value === 'middle') {
|
195 | offsetY = -height / 2;
|
196 | } else if (textBaseline.value === 'bottom' || textBaseline.value === 'alphabetic' || textBaseline.value === 'ideographic') {
|
197 | offsetY = -height;
|
198 | } else if (textBaseline.value === 'top' || textBaseline.value === 'hanging') {
|
199 | offsetY = 0;
|
200 | }
|
201 |
|
202 | return {
|
203 | font: font,
|
204 | width: width,
|
205 | height: height,
|
206 | lines: lines,
|
207 | lineWidths: lineWidths,
|
208 | lineHeight: lineHeight,
|
209 | maxLineWidth: maxLineWidth,
|
210 | fontProperties: fontProperties,
|
211 | lineMetrics: lineWidths.map(function (width, i) {
|
212 | var offsetX = 0;
|
213 |
|
214 | if (textAlign.value === 'center') {
|
215 | offsetX -= width / 2;
|
216 | } else if (textAlign.value === 'right' || textAlign.value === 'end') {
|
217 | offsetX -= width;
|
218 | }
|
219 |
|
220 | return new Rectangle(offsetX - lineWidth.value / 2, offsetY + i * lineHeight, width + lineWidth.value, lineHeight);
|
221 | })
|
222 | };
|
223 | };
|
224 |
|
225 | TextService.prototype.wordWrap = function (text, _a, offscreenCanvas) {
|
226 | var _this = this;
|
227 |
|
228 | var _b = _a.wordWrapWidth,
|
229 | wordWrapWidth = _b === void 0 ? 0 : _b,
|
230 | _c = _a.letterSpacing,
|
231 | letterSpacing = _c === void 0 ? 0 : _c;
|
232 | var context = this.offscreenCanvas.getOrCreateContext(offscreenCanvas);
|
233 | var maxWidth = wordWrapWidth + letterSpacing;
|
234 | var lines = [];
|
235 | var currentIndex = 0;
|
236 | var currentWidth = 0;
|
237 | var cache = {};
|
238 |
|
239 | var calcWidth = function calcWidth(char) {
|
240 | return _this.getFromCache(char, letterSpacing, cache, context);
|
241 | };
|
242 |
|
243 | Array.from(text).forEach(function (char, i) {
|
244 | var prevChar = text[i - 1];
|
245 | var nextChar = text[i + 1];
|
246 | var width = calcWidth(char);
|
247 |
|
248 | if (_this.isNewline(char)) {
|
249 | currentIndex++;
|
250 | currentWidth = 0;
|
251 | lines[currentIndex] = '';
|
252 | return;
|
253 | }
|
254 |
|
255 | if (currentWidth > 0 && currentWidth + width > maxWidth) {
|
256 | currentIndex++;
|
257 | currentWidth = 0;
|
258 | lines[currentIndex] = '';
|
259 |
|
260 | if (_this.isBreakingSpace(char)) {
|
261 | return;
|
262 | }
|
263 |
|
264 | if (!_this.canBreakInLastChar(char)) {
|
265 | lines = _this.trimToBreakable(lines);
|
266 | currentWidth = _this.sumTextWidthByCache(lines[currentIndex] || '', cache);
|
267 | }
|
268 |
|
269 | if (_this.shouldBreakByKinsokuShorui(char, nextChar)) {
|
270 | lines = _this.trimByKinsokuShorui(lines);
|
271 | currentWidth += calcWidth(prevChar || '');
|
272 | }
|
273 | }
|
274 |
|
275 | currentWidth += width;
|
276 | lines[currentIndex] = (lines[currentIndex] || '') + char;
|
277 | });
|
278 | return lines.join('\n');
|
279 | };
|
280 |
|
281 | TextService.prototype.isBreakingSpace = function (char) {
|
282 | if (typeof char !== 'string') {
|
283 | return false;
|
284 | }
|
285 |
|
286 | return TEXT_METRICS.BreakingSpaces.indexOf(char.charCodeAt(0)) >= 0;
|
287 | };
|
288 |
|
289 | TextService.prototype.isNewline = function (char) {
|
290 | if (typeof char !== 'string') {
|
291 | return false;
|
292 | }
|
293 |
|
294 | return TEXT_METRICS.Newlines.indexOf(char.charCodeAt(0)) >= 0;
|
295 | };
|
296 |
|
297 | TextService.prototype.trimToBreakable = function (prev) {
|
298 | var next = __spreadArray([], __read(prev), false);
|
299 |
|
300 | var prevLine = next[next.length - 2];
|
301 | var index = this.findBreakableIndex(prevLine);
|
302 | if (index === -1 || !prevLine) return next;
|
303 | var trimmedChar = prevLine.slice(index, index + 1);
|
304 | var isTrimmedWithSpace = this.isBreakingSpace(trimmedChar);
|
305 | var trimFrom = index + 1;
|
306 | var trimTo = index + (isTrimmedWithSpace ? 0 : 1);
|
307 | next[next.length - 1] += prevLine.slice(trimFrom, prevLine.length);
|
308 | next[next.length - 2] = prevLine.slice(0, trimTo);
|
309 | return next;
|
310 | };
|
311 |
|
312 | TextService.prototype.canBreakInLastChar = function (char) {
|
313 | if (char && LATIN_REGEX.test(char)) return false;
|
314 | return true;
|
315 | };
|
316 |
|
317 | TextService.prototype.sumTextWidthByCache = function (text, cache) {
|
318 | return text.split('').reduce(function (sum, c) {
|
319 | if (!cache[c]) throw Error('cannot count the word without cache');
|
320 | return sum + cache[c];
|
321 | }, 0);
|
322 | };
|
323 |
|
324 | TextService.prototype.findBreakableIndex = function (line) {
|
325 | for (var i = line.length - 1; i >= 0; i--) {
|
326 | if (!LATIN_REGEX.test(line[i])) return i;
|
327 | }
|
328 |
|
329 | return -1;
|
330 | };
|
331 |
|
332 | TextService.prototype.getFromCache = function (key, letterSpacing, cache, context) {
|
333 | var width = cache[key];
|
334 |
|
335 | if (typeof width !== 'number') {
|
336 | var spacing = key.length * letterSpacing;
|
337 | width = context.measureText(key).width + spacing;
|
338 | cache[key] = width;
|
339 | }
|
340 |
|
341 | return width;
|
342 | };
|
343 |
|
344 | __decorate([inject(OffscreenCanvasCreator), __metadata("design:type", OffscreenCanvasCreator)], TextService.prototype, "offscreenCanvas", void 0);
|
345 |
|
346 | TextService = __decorate([singleton()], TextService);
|
347 | return TextService;
|
348 | }();
|
349 |
|
350 | export { TextService }; |
\ | No newline at end of file |