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