@scratch/scratch-render 11.0.0-beta.1

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.
@@ -0,0 +1,249 @@
1
+ precision mediump float;
2
+
3
+ #ifdef DRAW_MODE_silhouette
4
+ uniform vec4 u_silhouetteColor;
5
+ #else // DRAW_MODE_silhouette
6
+ # ifdef ENABLE_color
7
+ uniform float u_color;
8
+ # endif // ENABLE_color
9
+ # ifdef ENABLE_brightness
10
+ uniform float u_brightness;
11
+ # endif // ENABLE_brightness
12
+ #endif // DRAW_MODE_silhouette
13
+
14
+ #ifdef DRAW_MODE_colorMask
15
+ uniform vec3 u_colorMask;
16
+ uniform float u_colorMaskTolerance;
17
+ #endif // DRAW_MODE_colorMask
18
+
19
+ #ifdef ENABLE_fisheye
20
+ uniform float u_fisheye;
21
+ #endif // ENABLE_fisheye
22
+ #ifdef ENABLE_whirl
23
+ uniform float u_whirl;
24
+ #endif // ENABLE_whirl
25
+ #ifdef ENABLE_pixelate
26
+ uniform float u_pixelate;
27
+ uniform vec2 u_skinSize;
28
+ #endif // ENABLE_pixelate
29
+ #ifdef ENABLE_mosaic
30
+ uniform float u_mosaic;
31
+ #endif // ENABLE_mosaic
32
+ #ifdef ENABLE_ghost
33
+ uniform float u_ghost;
34
+ #endif // ENABLE_ghost
35
+
36
+ #ifdef DRAW_MODE_line
37
+ uniform vec4 u_lineColor;
38
+ uniform float u_lineThickness;
39
+ uniform float u_lineLength;
40
+ #endif // DRAW_MODE_line
41
+
42
+ #ifdef DRAW_MODE_background
43
+ uniform vec4 u_backgroundColor;
44
+ #endif // DRAW_MODE_background
45
+
46
+ uniform sampler2D u_skin;
47
+
48
+ #ifndef DRAW_MODE_background
49
+ varying vec2 v_texCoord;
50
+ #endif
51
+
52
+ // Add this to divisors to prevent division by 0, which results in NaNs propagating through calculations.
53
+ // Smaller values can cause problems on some mobile devices.
54
+ const float epsilon = 1e-3;
55
+
56
+ #if !defined(DRAW_MODE_silhouette) && (defined(ENABLE_color))
57
+ // Branchless color conversions based on code from:
58
+ // http://www.chilliant.com/rgb2hsv.html by Ian Taylor
59
+ // Based in part on work by Sam Hocevar and Emil Persson
60
+ // See also: https://en.wikipedia.org/wiki/HSL_and_HSV#Formal_derivation
61
+
62
+
63
+ // Convert an RGB color to Hue, Saturation, and Value.
64
+ // All components of input and output are expected to be in the [0,1] range.
65
+ vec3 convertRGB2HSV(vec3 rgb)
66
+ {
67
+ // Hue calculation has 3 cases, depending on which RGB component is largest, and one of those cases involves a "mod"
68
+ // operation. In order to avoid that "mod" we split the M==R case in two: one for G<B and one for B>G. The B>G case
69
+ // will be calculated in the negative and fed through abs() in the hue calculation at the end.
70
+ // See also: https://en.wikipedia.org/wiki/HSL_and_HSV#Hue_and_chroma
71
+ const vec4 hueOffsets = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
72
+
73
+ // temp1.xy = sort B & G (largest first)
74
+ // temp1.z = the hue offset we'll use if it turns out that R is the largest component (M==R)
75
+ // temp1.w = the hue offset we'll use if it turns out that R is not the largest component (M==G or M==B)
76
+ vec4 temp1 = rgb.b > rgb.g ? vec4(rgb.bg, hueOffsets.wz) : vec4(rgb.gb, hueOffsets.xy);
77
+
78
+ // temp2.x = the largest component of RGB ("M" / "Max")
79
+ // temp2.yw = the smaller components of RGB, ordered for the hue calculation (not necessarily sorted by magnitude!)
80
+ // temp2.z = the hue offset we'll use in the hue calculation
81
+ vec4 temp2 = rgb.r > temp1.x ? vec4(rgb.r, temp1.yzx) : vec4(temp1.xyw, rgb.r);
82
+
83
+ // m = the smallest component of RGB ("min")
84
+ float m = min(temp2.y, temp2.w);
85
+
86
+ // Chroma = M - m
87
+ float C = temp2.x - m;
88
+
89
+ // Value = M
90
+ float V = temp2.x;
91
+
92
+ return vec3(
93
+ abs(temp2.z + (temp2.w - temp2.y) / (6.0 * C + epsilon)), // Hue
94
+ C / (temp2.x + epsilon), // Saturation
95
+ V); // Value
96
+ }
97
+
98
+ vec3 convertHue2RGB(float hue)
99
+ {
100
+ float r = abs(hue * 6.0 - 3.0) - 1.0;
101
+ float g = 2.0 - abs(hue * 6.0 - 2.0);
102
+ float b = 2.0 - abs(hue * 6.0 - 4.0);
103
+ return clamp(vec3(r, g, b), 0.0, 1.0);
104
+ }
105
+
106
+ vec3 convertHSV2RGB(vec3 hsv)
107
+ {
108
+ vec3 rgb = convertHue2RGB(hsv.x);
109
+ float c = hsv.z * hsv.y;
110
+ return rgb * c + hsv.z - c;
111
+ }
112
+ #endif // !defined(DRAW_MODE_silhouette) && (defined(ENABLE_color))
113
+
114
+ const vec2 kCenter = vec2(0.5, 0.5);
115
+
116
+ void main()
117
+ {
118
+ #if !(defined(DRAW_MODE_line) || defined(DRAW_MODE_background))
119
+ vec2 texcoord0 = v_texCoord;
120
+
121
+ #ifdef ENABLE_mosaic
122
+ texcoord0 = fract(u_mosaic * texcoord0);
123
+ #endif // ENABLE_mosaic
124
+
125
+ #ifdef ENABLE_pixelate
126
+ {
127
+ // TODO: clean up "pixel" edges
128
+ vec2 pixelTexelSize = u_skinSize / u_pixelate;
129
+ texcoord0 = (floor(texcoord0 * pixelTexelSize) + kCenter) / pixelTexelSize;
130
+ }
131
+ #endif // ENABLE_pixelate
132
+
133
+ #ifdef ENABLE_whirl
134
+ {
135
+ const float kRadius = 0.5;
136
+ vec2 offset = texcoord0 - kCenter;
137
+ float offsetMagnitude = length(offset);
138
+ float whirlFactor = max(1.0 - (offsetMagnitude / kRadius), 0.0);
139
+ float whirlActual = u_whirl * whirlFactor * whirlFactor;
140
+ float sinWhirl = sin(whirlActual);
141
+ float cosWhirl = cos(whirlActual);
142
+ mat2 rotationMatrix = mat2(
143
+ cosWhirl, -sinWhirl,
144
+ sinWhirl, cosWhirl
145
+ );
146
+
147
+ texcoord0 = rotationMatrix * offset + kCenter;
148
+ }
149
+ #endif // ENABLE_whirl
150
+
151
+ #ifdef ENABLE_fisheye
152
+ {
153
+ vec2 vec = (texcoord0 - kCenter) / kCenter;
154
+ float vecLength = length(vec);
155
+ float r = pow(min(vecLength, 1.0), u_fisheye) * max(1.0, vecLength);
156
+ vec2 unit = vec / vecLength;
157
+
158
+ texcoord0 = kCenter + r * unit * kCenter;
159
+ }
160
+ #endif // ENABLE_fisheye
161
+
162
+ gl_FragColor = texture2D(u_skin, texcoord0);
163
+
164
+ #if defined(ENABLE_color) || defined(ENABLE_brightness)
165
+ // Divide premultiplied alpha values for proper color processing
166
+ // Add epsilon to avoid dividing by 0 for fully transparent pixels
167
+ gl_FragColor.rgb = clamp(gl_FragColor.rgb / (gl_FragColor.a + epsilon), 0.0, 1.0);
168
+
169
+ #ifdef ENABLE_color
170
+ {
171
+ vec3 hsv = convertRGB2HSV(gl_FragColor.xyz);
172
+
173
+ // this code forces grayscale values to be slightly saturated
174
+ // so that some slight change of hue will be visible
175
+ const float minLightness = 0.11 / 2.0;
176
+ const float minSaturation = 0.09;
177
+ if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness);
178
+ else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z);
179
+
180
+ hsv.x = mod(hsv.x + u_color, 1.0);
181
+ if (hsv.x < 0.0) hsv.x += 1.0;
182
+
183
+ gl_FragColor.rgb = convertHSV2RGB(hsv);
184
+ }
185
+ #endif // ENABLE_color
186
+
187
+ #ifdef ENABLE_brightness
188
+ gl_FragColor.rgb = clamp(gl_FragColor.rgb + vec3(u_brightness), vec3(0), vec3(1));
189
+ #endif // ENABLE_brightness
190
+
191
+ // Re-multiply color values
192
+ gl_FragColor.rgb *= gl_FragColor.a + epsilon;
193
+
194
+ #endif // defined(ENABLE_color) || defined(ENABLE_brightness)
195
+
196
+ #ifdef ENABLE_ghost
197
+ gl_FragColor *= u_ghost;
198
+ #endif // ENABLE_ghost
199
+
200
+ #ifdef DRAW_MODE_silhouette
201
+ // Discard fully transparent pixels for stencil test
202
+ if (gl_FragColor.a == 0.0) {
203
+ discard;
204
+ }
205
+ // switch to u_silhouetteColor only AFTER the alpha test
206
+ gl_FragColor = u_silhouetteColor;
207
+ #else // DRAW_MODE_silhouette
208
+
209
+ #ifdef DRAW_MODE_colorMask
210
+ vec3 maskDistance = abs(gl_FragColor.rgb - u_colorMask);
211
+ vec3 colorMaskTolerance = vec3(u_colorMaskTolerance, u_colorMaskTolerance, u_colorMaskTolerance);
212
+ if (any(greaterThan(maskDistance, colorMaskTolerance)))
213
+ {
214
+ discard;
215
+ }
216
+ #endif // DRAW_MODE_colorMask
217
+ #endif // DRAW_MODE_silhouette
218
+
219
+ #ifdef DRAW_MODE_straightAlpha
220
+ // Un-premultiply alpha.
221
+ gl_FragColor.rgb /= gl_FragColor.a + epsilon;
222
+ #endif
223
+
224
+ #endif // !(defined(DRAW_MODE_line) || defined(DRAW_MODE_background))
225
+
226
+ #ifdef DRAW_MODE_line
227
+ // Maaaaagic antialiased-line-with-round-caps shader.
228
+
229
+ // "along-the-lineness". This increases parallel to the line.
230
+ // It goes from negative before the start point, to 0.5 through the start to the end, then ramps up again
231
+ // past the end point.
232
+ float d = ((v_texCoord.x - clamp(v_texCoord.x, 0.0, u_lineLength)) * 0.5) + 0.5;
233
+
234
+ // Distance from (0.5, 0.5) to (d, the perpendicular coordinate). When we're in the middle of the line,
235
+ // d will be 0.5, so the distance will be 0 at points close to the line and will grow at points further from it.
236
+ // For the "caps", d will ramp down/up, giving us rounding.
237
+ // See https://www.youtube.com/watch?v=PMltMdi1Wzg for a rough outline of the technique used to round the lines.
238
+ float line = distance(vec2(0.5), vec2(d, v_texCoord.y)) * 2.0;
239
+ // Expand out the line by its thickness.
240
+ line -= ((u_lineThickness - 1.0) * 0.5);
241
+ // Because "distance to the center of the line" decreases the closer we get to the line, but we want more opacity
242
+ // the closer we are to the line, invert it.
243
+ gl_FragColor = u_lineColor * clamp(1.0 - line, 0.0, 1.0);
244
+ #endif // DRAW_MODE_line
245
+
246
+ #ifdef DRAW_MODE_background
247
+ gl_FragColor = u_backgroundColor;
248
+ #endif
249
+ }
@@ -0,0 +1,75 @@
1
+ precision mediump float;
2
+
3
+ #ifdef DRAW_MODE_line
4
+ uniform vec2 u_stageSize;
5
+ uniform float u_lineThickness;
6
+ uniform float u_lineLength;
7
+ // The X and Y components of u_penPoints hold the first pen point. The Z and W components hold the difference between
8
+ // the second pen point and the first. This is done because calculating the difference in the shader leads to floating-
9
+ // point error when both points have large-ish coordinates.
10
+ uniform vec4 u_penPoints;
11
+
12
+ // Add this to divisors to prevent division by 0, which results in NaNs propagating through calculations.
13
+ // Smaller values can cause problems on some mobile devices.
14
+ const float epsilon = 1e-3;
15
+ #endif
16
+
17
+ #if !(defined(DRAW_MODE_line) || defined(DRAW_MODE_background))
18
+ uniform mat4 u_projectionMatrix;
19
+ uniform mat4 u_modelMatrix;
20
+ attribute vec2 a_texCoord;
21
+ #endif
22
+
23
+ attribute vec2 a_position;
24
+
25
+ varying vec2 v_texCoord;
26
+
27
+ void main() {
28
+ #ifdef DRAW_MODE_line
29
+ // Calculate a rotated ("tight") bounding box around the two pen points.
30
+ // Yes, we're doing this 6 times (once per vertex), but on actual GPU hardware,
31
+ // it's still faster than doing it in JS combined with the cost of uniformMatrix4fv.
32
+
33
+ // Expand line bounds by sqrt(2) / 2 each side-- this ensures that all antialiased pixels
34
+ // fall within the quad, even at a 45-degree diagonal
35
+ vec2 position = a_position;
36
+ float expandedRadius = (u_lineThickness * 0.5) + 1.4142135623730951;
37
+
38
+ // The X coordinate increases along the length of the line. It's 0 at the center of the origin point
39
+ // and is in pixel-space (so at n pixels along the line, its value is n).
40
+ v_texCoord.x = mix(0.0, u_lineLength + (expandedRadius * 2.0), a_position.x) - expandedRadius;
41
+ // The Y coordinate is perpendicular to the line. It's also in pixel-space.
42
+ v_texCoord.y = ((a_position.y - 0.5) * expandedRadius) + 0.5;
43
+
44
+ position.x *= u_lineLength + (2.0 * expandedRadius);
45
+ position.y *= 2.0 * expandedRadius;
46
+
47
+ // 1. Center around first pen point
48
+ position -= expandedRadius;
49
+
50
+ // 2. Rotate quad to line angle
51
+ vec2 pointDiff = u_penPoints.zw;
52
+ // Ensure line has a nonzero length so it's rendered properly
53
+ // As long as either component is nonzero, the line length will be nonzero
54
+ // If the line is zero-length, give it a bit of horizontal length
55
+ pointDiff.x = (abs(pointDiff.x) < epsilon && abs(pointDiff.y) < epsilon) ? epsilon : pointDiff.x;
56
+ // The `normalized` vector holds rotational values equivalent to sine/cosine
57
+ // We're applying the standard rotation matrix formula to the position to rotate the quad to the line angle
58
+ // pointDiff can hold large values so we must divide by u_lineLength instead of calling GLSL's normalize function:
59
+ // https://asawicki.info/news_1596_watch_out_for_reduced_precision_normalizelength_in_opengl_es
60
+ vec2 normalized = pointDiff / max(u_lineLength, epsilon);
61
+ position = mat2(normalized.x, normalized.y, -normalized.y, normalized.x) * position;
62
+
63
+ // 3. Translate quad
64
+ position += u_penPoints.xy;
65
+
66
+ // 4. Apply view transform
67
+ position *= 2.0 / u_stageSize;
68
+ gl_Position = vec4(position, 0, 1);
69
+ #elif defined(DRAW_MODE_background)
70
+ gl_Position = vec4(a_position * 2.0, 0, 1);
71
+ #else
72
+ gl_Position = u_projectionMatrix * u_modelMatrix * vec4(a_position, 0, 1);
73
+ v_texCoord = a_texCoord;
74
+ #endif
75
+ }
@@ -0,0 +1,41 @@
1
+ class CanvasMeasurementProvider {
2
+ /**
3
+ * @param {CanvasRenderingContext2D} ctx - provides a canvas rendering context
4
+ * with 'font' set to the text style of the text to be wrapped.
5
+ */
6
+ constructor (ctx) {
7
+ this._ctx = ctx;
8
+ this._cache = {};
9
+ }
10
+
11
+
12
+ // We don't need to set up or tear down anything here. Should these be removed altogether?
13
+
14
+ /**
15
+ * Called by the TextWrapper before a batch of zero or more calls to measureText().
16
+ */
17
+ beginMeasurementSession () {
18
+
19
+ }
20
+
21
+ /**
22
+ * Called by the TextWrapper after a batch of zero or more calls to measureText().
23
+ */
24
+ endMeasurementSession () {
25
+
26
+ }
27
+
28
+ /**
29
+ * Measure a whole string as one unit.
30
+ * @param {string} text - the text to measure.
31
+ * @returns {number} - the length of the string.
32
+ */
33
+ measureText (text) {
34
+ if (!this._cache[text]) {
35
+ this._cache[text] = this._ctx.measureText(text).width;
36
+ }
37
+ return this._cache[text];
38
+ }
39
+ }
40
+
41
+ module.exports = CanvasMeasurementProvider;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Converts an RGB color value to HSV. Conversion formula
3
+ * adapted from http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv.
4
+ * Assumes r, g, and b are in the range [0, 255] and
5
+ * returns h, s, and v in the range [0, 1].
6
+ *
7
+ * @param {Array<number>} rgb The RGB color value
8
+ * @param {number} rgb.r The red color value
9
+ * @param {number} rgb.g The green color value
10
+ * @param {number} rgb.b The blue color value
11
+ * @param {Array<number>} dst The array to store the HSV values in
12
+ * @return {Array<number>} The `dst` array passed in
13
+ */
14
+ const rgbToHsv = ([r, g, b], dst) => {
15
+ let K = 0.0;
16
+
17
+ r /= 255;
18
+ g /= 255;
19
+ b /= 255;
20
+ let tmp = 0;
21
+
22
+ if (g < b) {
23
+ tmp = g;
24
+ g = b;
25
+ b = tmp;
26
+
27
+ K = -1;
28
+ }
29
+
30
+ if (r < g) {
31
+ tmp = r;
32
+ r = g;
33
+ g = tmp;
34
+
35
+ K = (-2 / 6) - K;
36
+ }
37
+
38
+ const chroma = r - Math.min(g, b);
39
+ const h = Math.abs(K + ((g - b) / ((6 * chroma) + Number.EPSILON)));
40
+ const s = chroma / (r + Number.EPSILON);
41
+ const v = r;
42
+
43
+ dst[0] = h;
44
+ dst[1] = s;
45
+ dst[2] = v;
46
+
47
+ return dst;
48
+ };
49
+
50
+ /**
51
+ * Converts an HSV color value to RGB. Conversion formula
52
+ * adapted from https://gist.github.com/mjackson/5311256.
53
+ * Assumes h, s, and v are contained in the set [0, 1] and
54
+ * returns r, g, and b in the set [0, 255].
55
+ *
56
+ * @param {Array<number>} hsv The HSV color value
57
+ * @param {number} hsv.h The hue
58
+ * @param {number} hsv.s The saturation
59
+ * @param {number} hsv.v The value
60
+ * @param {Uint8Array|Uint8ClampedArray} dst The array to store the RGB values in
61
+ * @return {Uint8Array|Uint8ClampedArray} The `dst` array passed in
62
+ */
63
+ const hsvToRgb = ([h, s, v], dst) => {
64
+ if (s === 0) {
65
+ dst[0] = dst[1] = dst[2] = (v * 255) + 0.5;
66
+ return dst;
67
+ }
68
+
69
+ // keep hue in [0,1) so the `switch(i)` below only needs 6 cases (0-5)
70
+ h %= 1;
71
+ const i = (h * 6) | 0;
72
+ const f = (h * 6) - i;
73
+ const p = v * (1 - s);
74
+ const q = v * (1 - (s * f));
75
+ const t = v * (1 - (s * (1 - f)));
76
+
77
+ let r = 0;
78
+ let g = 0;
79
+ let b = 0;
80
+
81
+ switch (i) {
82
+ case 0: r = v; g = t; b = p; break;
83
+ case 1: r = q; g = v; b = p; break;
84
+ case 2: r = p; g = v; b = t; break;
85
+ case 3: r = p; g = q; b = v; break;
86
+ case 4: r = t; g = p; b = v; break;
87
+ case 5: r = v; g = p; b = q; break;
88
+ }
89
+
90
+ // Add 0.5 in order to round. Setting integer TypedArray elements implicitly floors.
91
+ dst[0] = (r * 255) + 0.5;
92
+ dst[1] = (g * 255) + 0.5;
93
+ dst[2] = (b * 255) + 0.5;
94
+ return dst;
95
+ };
96
+
97
+ module.exports = {rgbToHsv, hsvToRgb};
@@ -0,0 +1,4 @@
1
+ const minilog = require('minilog');
2
+ minilog.enable();
3
+
4
+ module.exports = minilog('scratch-render');
@@ -0,0 +1,112 @@
1
+ const LineBreaker = require('!ify-loader!linebreak');
2
+ const GraphemeBreaker = require('!ify-loader!grapheme-breaker');
3
+
4
+ /**
5
+ * Tell this text wrapper to use a specific measurement provider.
6
+ * @typedef {object} MeasurementProvider - the new measurement provider.
7
+ * @property {Function} beginMeasurementSession - this will be called before a batch of measurements are made.
8
+ * Optionally, this function may return an object to be provided to the endMeasurementSession function.
9
+ * @property {Function} measureText - this will be called each time a piece of text must be measured.
10
+ * @property {Function} endMeasurementSession - this will be called after a batch of measurements is finished.
11
+ * It will be passed whatever value beginMeasurementSession returned, if any.
12
+ */
13
+
14
+ /**
15
+ * Utility to wrap text across several lines, respecting Unicode grapheme clusters and, when possible, Unicode line
16
+ * break opportunities.
17
+ * Reference material:
18
+ * - Unicode Standard Annex #14: http://unicode.org/reports/tr14/
19
+ * - Unicode Standard Annex #29: http://unicode.org/reports/tr29/
20
+ * - "JavaScript has a Unicode problem" by Mathias Bynens: https://mathiasbynens.be/notes/javascript-unicode
21
+ */
22
+ class TextWrapper {
23
+ /**
24
+ * Construct a text wrapper which will measure text using the specified measurement provider.
25
+ * @param {MeasurementProvider} measurementProvider - a helper object to provide text measurement services.
26
+ */
27
+ constructor (measurementProvider) {
28
+ this._measurementProvider = measurementProvider;
29
+ this._cache = {};
30
+ }
31
+
32
+ /**
33
+ * Wrap the provided text into lines restricted to a maximum width. See Unicode Standard Annex (UAX) #14.
34
+ * @param {number} maxWidth - the maximum allowed width of a line.
35
+ * @param {string} text - the text to be wrapped. Will be split on whitespace.
36
+ * @returns {Array.<string>} an array containing the wrapped lines of text.
37
+ */
38
+ wrapText (maxWidth, text) {
39
+ // Normalize to canonical composition (see Unicode Standard Annex (UAX) #15)
40
+ text = text.normalize();
41
+
42
+ const cacheKey = `${maxWidth}-${text}`;
43
+ if (this._cache[cacheKey]) {
44
+ return this._cache[cacheKey];
45
+ }
46
+
47
+ const measurementSession = this._measurementProvider.beginMeasurementSession();
48
+
49
+ const breaker = new LineBreaker(text);
50
+ let lastPosition = 0;
51
+ let nextBreak;
52
+ let currentLine = null;
53
+ const lines = [];
54
+
55
+ while ((nextBreak = breaker.nextBreak())) {
56
+ const word = text.slice(lastPosition, nextBreak.position).replace(/\n+$/, '');
57
+
58
+ let proposedLine = (currentLine || '').concat(word);
59
+ let proposedLineWidth = this._measurementProvider.measureText(proposedLine);
60
+
61
+ if (proposedLineWidth > maxWidth) {
62
+ // The next word won't fit on this line. Will it fit on a line by itself?
63
+ const wordWidth = this._measurementProvider.measureText(word);
64
+ if (wordWidth > maxWidth) {
65
+ // The next word can't even fit on a line by itself. Consume it one grapheme cluster at a time.
66
+ let lastCluster = 0;
67
+ let nextCluster;
68
+ while (lastCluster !== (nextCluster = GraphemeBreaker.nextBreak(word, lastCluster))) {
69
+ const cluster = word.substring(lastCluster, nextCluster);
70
+ proposedLine = (currentLine || '').concat(cluster);
71
+ proposedLineWidth = this._measurementProvider.measureText(proposedLine);
72
+ if ((currentLine === null) || (proposedLineWidth <= maxWidth)) {
73
+ // first cluster of a new line or the cluster fits
74
+ currentLine = proposedLine;
75
+ } else {
76
+ // no more can fit
77
+ lines.push(currentLine);
78
+ currentLine = cluster;
79
+ }
80
+ lastCluster = nextCluster;
81
+ }
82
+ } else {
83
+ // The next word can fit on the next line. Finish the current line and move on.
84
+ if (currentLine !== null) lines.push(currentLine);
85
+ currentLine = word;
86
+ }
87
+ } else {
88
+ // The next word fits on this line. Just keep going.
89
+ currentLine = proposedLine;
90
+ }
91
+
92
+ // Did we find a \n or similar?
93
+ if (nextBreak.required) {
94
+ if (currentLine !== null) lines.push(currentLine);
95
+ currentLine = null;
96
+ }
97
+
98
+ lastPosition = nextBreak.position;
99
+ }
100
+
101
+ currentLine = currentLine || '';
102
+ if (currentLine.length > 0 || lines.length === 0) {
103
+ lines.push(currentLine);
104
+ }
105
+
106
+ this._cache[cacheKey] = lines;
107
+ this._measurementProvider.endMeasurementSession(measurementSession);
108
+ return lines;
109
+ }
110
+ }
111
+
112
+ module.exports = TextWrapper;