Update rive-cpp to 2.0 version
[platform/core/uifw/rive-tizen.git] / submodule / skia / modules / canvaskit / tests / bazel / font_test.js
1 describe('Font Behavior', () => {
2     let container;
3
4     let notoSerifFontBuffer = null;
5     // This font is known to support kerning
6     const notoSerifFontLoaded = fetch('/assets/NotoSerif-Regular.ttf').then(
7         (response) => response.arrayBuffer()).then(
8         (buffer) => {
9             notoSerifFontBuffer = buffer;
10         });
11
12     let bungeeFontBuffer = null;
13     // This font has tofu for incorrect null terminators
14     // see https://bugs.chromium.org/p/skia/issues/detail?id=9314
15     const bungeeFontLoaded = fetch('/assets/Bungee-Regular.ttf').then(
16         (response) => response.arrayBuffer()).then(
17         (buffer) => {
18             bungeeFontBuffer = buffer;
19         });
20
21     let colrv1FontBuffer = null;
22     // This font has glyphs for COLRv1. Also used in gms/colrv1.cpp
23     const colrv1FontLoaded = fetch('/assets/more_samples-glyf_colr_1.ttf').then(
24         (response) => response.arrayBuffer()).then(
25         (buffer) => {
26             colrv1FontBuffer = buffer;
27         });
28
29     beforeEach(async () => {
30         await EverythingLoaded;
31         await notoSerifFontLoaded;
32         await bungeeFontLoaded;
33         await colrv1FontLoaded;
34         container = document.createElement('div');
35         container.innerHTML = `
36             <canvas width=600 height=600 id=test></canvas>
37             <canvas width=600 height=600 id=report></canvas>`;
38         document.body.appendChild(container);
39     });
40
41     afterEach(() => {
42         document.body.removeChild(container);
43     });
44
45     gm('monospace_text_on_path', (canvas) => {
46         const paint = new CanvasKit.Paint();
47         paint.setAntiAlias(true);
48         paint.setStyle(CanvasKit.PaintStyle.Stroke);
49
50         const font = new CanvasKit.Font(null, 24);
51         const fontPaint = new CanvasKit.Paint();
52         fontPaint.setAntiAlias(true);
53         fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
54
55
56         const arc = new CanvasKit.Path();
57         arc.arcToOval(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
58         arc.lineTo(210, 140);
59         arc.arcToOval(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
60
61         // Only 1 dot should show up in the image, because we run out of path.
62         const str = 'This téxt should follow the curve across contours...';
63         const textBlob = CanvasKit.TextBlob.MakeOnPath(str, arc, font);
64
65         canvas.drawPath(arc, paint);
66         canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
67
68         textBlob.delete();
69         arc.delete();
70         paint.delete();
71         font.delete();
72         fontPaint.delete();
73     });
74
75     gm('serif_text_on_path', (canvas) => {
76         const notoSerif = CanvasKit.Typeface.MakeFreeTypeFaceFromData(notoSerifFontBuffer);
77
78         const paint = new CanvasKit.Paint();
79         paint.setAntiAlias(true);
80         paint.setStyle(CanvasKit.PaintStyle.Stroke);
81
82         const font = new CanvasKit.Font(notoSerif, 24);
83         const fontPaint = new CanvasKit.Paint();
84         fontPaint.setAntiAlias(true);
85         fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
86
87         const arc = new CanvasKit.Path();
88         arc.arcToOval(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
89         arc.lineTo(210, 140);
90         arc.arcToOval(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
91
92         const str = 'This téxt should follow the curve across contours...';
93         const textBlob = CanvasKit.TextBlob.MakeOnPath(str, arc, font, 60.5);
94
95         canvas.drawPath(arc, paint);
96         canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
97
98         textBlob.delete();
99         arc.delete();
100         paint.delete();
101         notoSerif.delete();
102         font.delete();
103         fontPaint.delete();
104     });
105
106     // https://bugs.chromium.org/p/skia/issues/detail?id=9314
107     gm('nullterminators_skbug_9314', (canvas) => {
108         const bungee = CanvasKit.Typeface.MakeFreeTypeFaceFromData(bungeeFontBuffer);
109
110         // yellow, to make sure tofu is plainly visible
111         canvas.clear(CanvasKit.Color(255, 255, 0, 1));
112
113         const font = new CanvasKit.Font(bungee, 24);
114         const fontPaint = new CanvasKit.Paint();
115         fontPaint.setAntiAlias(true);
116         fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
117
118
119         const str = 'This is téxt';
120         const textBlob = CanvasKit.TextBlob.MakeFromText(str + ' text blob', font);
121
122         canvas.drawTextBlob(textBlob, 10, 50, fontPaint);
123
124         canvas.drawText(str + ' normal', 10, 100, fontPaint, font);
125
126         canvas.drawText('null terminator ->\u0000<- on purpose', 10, 150, fontPaint, font);
127
128         textBlob.delete();
129         bungee.delete();
130         font.delete();
131         fontPaint.delete();
132     });
133
134     gm('textblobs_with_glyphs', (canvas) => {
135         canvas.clear(CanvasKit.WHITE);
136         const notoSerif = CanvasKit.Typeface.MakeFreeTypeFaceFromData(notoSerifFontBuffer);
137
138         const font = new CanvasKit.Font(notoSerif, 24);
139         const bluePaint = new CanvasKit.Paint();
140         bluePaint.setColor(CanvasKit.parseColorString('#04083f')); // arbitrary deep blue
141         bluePaint.setAntiAlias(true);
142         bluePaint.setStyle(CanvasKit.PaintStyle.Fill);
143
144         const redPaint = new CanvasKit.Paint();
145         redPaint.setColor(CanvasKit.parseColorString('#770b1e')); // arbitrary deep red
146
147         const ids = notoSerif.getGlyphIDs('AEGIS ægis');
148         expect(ids.length).toEqual(10); // one glyph id per glyph
149         expect(ids[0]).toEqual(36); // spot check this, should be consistent as long as the font is.
150
151         const bounds = font.getGlyphBounds(ids, bluePaint);
152         expect(bounds.length).toEqual(40); // 4 measurements per glyph
153         expect(bounds[0]).toEqual(0); // again, spot check the measurements for the first glyph.
154         expect(bounds[1]).toEqual(-17);
155         expect(bounds[2]).toEqual(17);
156         expect(bounds[3]).toEqual(0);
157
158         const widths = font.getGlyphWidths(ids, bluePaint);
159         expect(widths.length).toEqual(10); // 1 width per glyph
160         expect(widths[0]).toEqual(17);
161
162         const topBlob = CanvasKit.TextBlob.MakeFromGlyphs(ids, font);
163         canvas.drawTextBlob(topBlob, 5, 30, bluePaint);
164         canvas.drawTextBlob(topBlob, 5, 60, redPaint);
165         topBlob.delete();
166
167         const mIDs = CanvasKit.MallocGlyphIDs(ids.length);
168         const mArr = mIDs.toTypedArray();
169         mArr.set(ids);
170
171         const mXforms = CanvasKit.Malloc(Float32Array, ids.length * 4);
172         const mXformsArr = mXforms.toTypedArray();
173         // Draw each glyph rotated slightly and slightly lower than the glyph before it.
174         let currX = 0;
175         for (let i = 0; i < ids.length; i++) {
176             mXformsArr[i * 4] = Math.cos(-Math.PI / 16); // scos
177             mXformsArr[i * 4 + 1] = Math.sin(-Math.PI / 16); // ssin
178             mXformsArr[i * 4 + 2] = currX; // tx
179             mXformsArr[i * 4 + 3] = i*2; // ty
180             currX += widths[i];
181         }
182
183         const bottomBlob = CanvasKit.TextBlob.MakeFromRSXformGlyphs(mIDs, mXforms, font);
184         canvas.drawTextBlob(bottomBlob, 5, 110, bluePaint);
185         canvas.drawTextBlob(bottomBlob, 5, 140, redPaint);
186         bottomBlob.delete();
187
188         CanvasKit.Free(mIDs);
189         CanvasKit.Free(mXforms);
190         bluePaint.delete();
191         redPaint.delete();
192         notoSerif.delete();
193         font.delete();
194     });
195
196     it('can make a font mgr with passed in fonts', () => {
197         // CanvasKit.FontMgr.FromData([bungeeFontBuffer, notoSerifFontBuffer]) also works
198         const fontMgr = CanvasKit.FontMgr.FromData(bungeeFontBuffer, notoSerifFontBuffer);
199         expect(fontMgr).toBeTruthy();
200         expect(fontMgr.countFamilies()).toBe(2);
201         // in debug mode, let's list them.
202         if (fontMgr.dumpFamilies) {
203             fontMgr.dumpFamilies();
204         }
205         fontMgr.delete();
206     });
207
208     it('can make a font provider with passed in fonts and aliases', () => {
209         const fontProvider = CanvasKit.TypefaceFontProvider.Make();
210         fontProvider.registerFont(bungeeFontBuffer, "My Bungee Alias");
211         fontProvider.registerFont(notoSerifFontBuffer, "My Noto Serif Alias");
212         expect(fontProvider).toBeTruthy();
213         expect(fontProvider.countFamilies()).toBe(2);
214         // in debug mode, let's list them.
215         if (fontProvider.dumpFamilies) {
216             fontProvider.dumpFamilies();
217         }
218         fontProvider.delete();
219     });
220
221     gm('various_font_formats', (canvas, fetchedByteBuffers) => {
222         const fontPaint = new CanvasKit.Paint();
223         fontPaint.setAntiAlias(true);
224         fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
225         const inputs = [{
226             type: '.ttf font',
227             buffer: bungeeFontBuffer,
228             y: 60,
229         },{
230             type: '.otf font',
231             buffer: fetchedByteBuffers[0],
232             y: 90,
233         },{
234             type: '.woff font',
235             buffer: fetchedByteBuffers[1],
236             y: 120,
237         },{
238             type: '.woff2 font',
239             buffer: fetchedByteBuffers[2],
240             y: 150,
241         }];
242
243         const defaultFont = new CanvasKit.Font(null, 24);
244         canvas.drawText(`The following should be ${inputs.length + 1} lines of text:`, 5, 30, fontPaint, defaultFont);
245
246         for (const fontType of inputs) {
247             // smoke test that the font bytes loaded.
248             expect(fontType.buffer).toBeTruthy(fontType.type + ' did not load');
249
250             const typeface = CanvasKit.Typeface.MakeFreeTypeFaceFromData(fontType.buffer);
251             const font = new CanvasKit.Font(typeface, 24);
252
253             if (font && typeface) {
254                 canvas.drawText(fontType.type + ' loaded', 5, fontType.y, fontPaint, font);
255             } else {
256                 canvas.drawText(fontType.type + ' *not* loaded', 5, fontType.y, fontPaint, defaultFont);
257             }
258             font && font.delete();
259             typeface && typeface.delete();
260         }
261
262         // The only ttc font I could find was 14 MB big, so I'm using the smaller test font,
263         // which doesn't have very many glyphs in it, so we just check that we got a non-zero
264         // typeface for it. I was able to load NotoSansCJK-Regular.ttc just fine in a
265         // manual test.
266         const typeface = CanvasKit.Typeface.MakeFreeTypeFaceFromData(fetchedByteBuffers[3]);
267         expect(typeface).toBeTruthy('.ttc font');
268         if (typeface) {
269             canvas.drawText('.ttc loaded', 5, 180, fontPaint, defaultFont);
270             typeface.delete();
271         } else {
272             canvas.drawText('.ttc *not* loaded', 5, 180, fontPaint, defaultFont);
273         }
274
275         defaultFont.delete();
276         fontPaint.delete();
277     }, '/assets/Roboto-Regular.otf', '/assets/Roboto-Regular.woff', '/assets/Roboto-Regular.woff2', '/assets/test.ttc');
278
279     it('can measure text very precisely with proper settings', () => {
280         const typeface = CanvasKit.Typeface.MakeFreeTypeFaceFromData(notoSerifFontBuffer);
281         const fontSizes = [257, 100, 11];
282         // The point of these values is to let us know 1) we can measure to sub-pixel levels
283         // and 2) that measurements don't drastically change. If these change a little bit,
284         // just update them with the new values. For super-accurate readings, one could
285         // run a C++ snippet of code and compare the values, but that is likely unnecessary
286         // unless we suspect a bug with the bindings.
287         const expectedSizes = [241.06299, 93.79883, 10.31787];
288         for (const idx in fontSizes) {
289             const font = new CanvasKit.Font(typeface, fontSizes[idx]);
290             font.setHinting(CanvasKit.FontHinting.None);
291             font.setLinearMetrics(true);
292             font.setSubpixel(true);
293
294             const ids = font.getGlyphIDs('M');
295             const widths = font.getGlyphWidths(ids);
296             expect(widths[0]).toBeCloseTo(expectedSizes[idx], 5);
297             font.delete();
298         }
299
300         typeface.delete();
301     });
302
303     gm('font_edging', (canvas) => {
304         // Draw a small font scaled up to see the aliasing artifacts.
305         canvas.scale(8, 8);
306         canvas.clear(CanvasKit.WHITE);
307         const notoSerif = CanvasKit.Typeface.MakeFreeTypeFaceFromData(notoSerifFontBuffer);
308
309         const textPaint = new CanvasKit.Paint();
310         const annotationFont = new CanvasKit.Font(notoSerif, 6);
311
312         canvas.drawText('Default', 5, 5, textPaint, annotationFont);
313         canvas.drawText('Alias', 5, 25, textPaint, annotationFont);
314         canvas.drawText('AntiAlias', 5, 45, textPaint, annotationFont);
315         canvas.drawText('Subpixel', 5, 65, textPaint, annotationFont);
316
317         const testFont = new CanvasKit.Font(notoSerif, 20);
318
319         canvas.drawText('SEA', 35, 15, textPaint, testFont);
320         testFont.setEdging(CanvasKit.FontEdging.Alias);
321         canvas.drawText('SEA', 35, 35, textPaint, testFont);
322         testFont.setEdging(CanvasKit.FontEdging.AntiAlias);
323         canvas.drawText('SEA', 35, 55, textPaint, testFont);
324         testFont.setEdging(CanvasKit.FontEdging.SubpixelAntiAlias);
325         canvas.drawText('SEA', 35, 75, textPaint, testFont);
326
327         textPaint.delete();
328         annotationFont.delete();
329         testFont.delete();
330         notoSerif.delete();
331     });
332
333     it('can get the intercepts of glyphs', () => {
334         const font = new CanvasKit.Font(null, 100);
335         const ids = font.getGlyphIDs('I');
336         expect(ids.length).toEqual(1);
337
338         // aim for the middle of the I at 100 point, expecting a hit
339         let sects = font.getGlyphIntercepts(ids, [0, 0], -60, -40);
340         expect(sects.length).toEqual(2, "expected one pair of intercepts");
341         expect(sects[0]).toBeCloseTo(25.39063, 5);
342         expect(sects[1]).toBeCloseTo(34.52148, 5);
343
344         // aim below the baseline where we expect no intercepts
345         sects = font.getGlyphIntercepts(ids, [0, 0], 20, 30);
346         expect(sects.length).toEqual(0, "expected no intercepts");
347         font.delete();
348     });
349
350     it('can use mallocd and normal arrays', () => {
351         const font = new CanvasKit.Font(null, 100);
352         const ids = font.getGlyphIDs('I');
353         expect(ids.length).toEqual(1);
354         const glyphID = ids[0];
355
356         // aim for the middle of the I at 100 point, expecting a hit
357         const sects = font.getGlyphIntercepts(Array.of(glyphID), Float32Array.of(0, 0), -60, -40);
358         expect(sects.length).toEqual(2);
359         expect(sects[0]).toBeLessThan(sects[1]);
360         // these values were recorded from the first time it was run
361         expect(sects[0]).toBeCloseTo(25.39063, 5);
362         expect(sects[1]).toBeCloseTo(34.52148, 5);
363
364         const free_list = [];   // will free CanvasKit.Malloc objects at the end
365
366         // Want to exercise 4 different ways we can receive an array:
367         //  1. normal array
368         //  2. typed-array
369         //  3. CanvasKit.Malloc typeed-array
370         //  4. CavnasKit.Malloc (raw)
371
372         const id_makers = [
373             (id) => [ id ],
374             (id) => new Uint16Array([ id ]),
375             (id) => {
376                 const a = CanvasKit.Malloc(Uint16Array, 1);
377                 free_list.push(a);
378                 const ta = a.toTypedArray();
379                 ta[0] = id;
380                 return ta;  // return typed-array
381             },
382             (id) => {
383                 const a = CanvasKit.Malloc(Uint16Array, 1);
384                 free_list.push(a);
385                 a.toTypedArray()[0] = id;
386                 return a;   // return raw obj
387             },
388         ];
389         const pos_makers = [
390             (x, y) => [ x, y ],
391             (x, y) => new Float32Array([ x, y ]),
392             (x, y) => {
393                 const a = CanvasKit.Malloc(Float32Array, 2);
394                 free_list.push(a);
395                 const ta = a.toTypedArray();
396                 ta[0] = x;
397                 ta[1] = y;
398                 return ta;  // return typed-array
399             },
400             (x, y) => {
401                 const a = CanvasKit.Malloc(Float32Array, 2);
402                 free_list.push(a);
403                 const ta = a.toTypedArray();
404                 ta[0] = x;
405                 ta[1] = y;
406                 return a;   // return raw obj
407             },
408         ];
409
410         for (const idm of id_makers) {
411             for (const posm of pos_makers) {
412                 const s = font.getGlyphIntercepts(idm(glyphID), posm(0, 0), -60, -40);
413                 expect(s.length).toEqual(sects.length);
414                 for (let i = 0; i < s.length; ++i) {
415                     expect(s[i]).toEqual(sects[i]);
416                 }
417             }
418
419         }
420
421         free_list.forEach(obj => CanvasKit.Free(obj));
422         font.delete();
423     });
424
425     gm('colrv1_gradients', (canvas) => {
426         // Inspired by gm/colrv1.cpp, specifically the kColorFontsRepoGradients one.
427         canvas.clear(CanvasKit.WHITE);
428         const colrFace = CanvasKit.Typeface.MakeFreeTypeFaceFromData(colrv1FontBuffer);
429
430         const textPaint = new CanvasKit.Paint();
431         const annotationFont = new CanvasKit.Font(null, 20);
432
433         canvas.drawText('You should see 4 lines of gradient glyphs below',
434             5, 25, textPaint, annotationFont);
435
436         // These glyphIDs show off gradients in the COLRv1 font.
437         const glyphIDs = [2, 5, 6, 7, 8, 55];
438         const testFont = new CanvasKit.Font(colrFace);
439         const sizes = [12, 18, 30, 100];
440         let y = 30;
441         for (let i = 0; i < sizes.length; i++) {
442             const size = sizes[i];
443             testFont.setSize(size);
444             const metrics = testFont.getMetrics();
445             y -= metrics.ascent;
446             const positions = calculateRun(testFont, glyphIDs)
447             canvas.drawGlyphs(glyphIDs, positions, 5, y, testFont, textPaint);
448             y += metrics.descent + metrics.leading;
449         }
450
451         textPaint.delete();
452         annotationFont.delete();
453         testFont.delete();
454         colrFace.delete();
455     });
456
457     function calculateRun(font, glyphIDs) {
458         const spacing = 5; // put 5 pixels between each glyph
459         const bounds = font.getGlyphBounds(glyphIDs);
460         const positions = [0, 0];
461         let width = 0;
462         for (let i = 0; i < glyphIDs.length - 1; i++) {
463             // subtract the right bounds from the left bounds to get glyph width
464             const glyphWidth = bounds[2 + i*4] - bounds[i*4];
465             width += glyphWidth + spacing
466             positions.push(width, 0);
467         }
468         return positions;
469     }
470 });