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