1*c8dee2aaSAndroid Build Coastguard Worker<!DOCTYPE html> 2*c8dee2aaSAndroid Build Coastguard Worker<title>CanvasKit Paragraph (with & without ICU)</title> 3*c8dee2aaSAndroid Build Coastguard Worker<meta charset="utf-8" /> 4*c8dee2aaSAndroid Build Coastguard Worker<meta http-equiv="X-UA-Compatible" content="IE=edge"> 5*c8dee2aaSAndroid Build Coastguard Worker<meta name="viewport" content="width=device-width, initial-scale=1.0"> 6*c8dee2aaSAndroid Build Coastguard Worker 7*c8dee2aaSAndroid Build Coastguard Worker<script type="text/javascript" src="/build/canvaskit.js"></script> 8*c8dee2aaSAndroid Build Coastguard Worker 9*c8dee2aaSAndroid Build Coastguard Worker<style> 10*c8dee2aaSAndroid Build Coastguard Worker canvas { 11*c8dee2aaSAndroid Build Coastguard Worker border: 1px dashed #AAA; 12*c8dee2aaSAndroid Build Coastguard Worker } 13*c8dee2aaSAndroid Build Coastguard Worker #withICU { 14*c8dee2aaSAndroid Build Coastguard Worker border-color: red; 15*c8dee2aaSAndroid Build Coastguard Worker } 16*c8dee2aaSAndroid Build Coastguard Worker #withoutICU { 17*c8dee2aaSAndroid Build Coastguard Worker border-color: green; 18*c8dee2aaSAndroid Build Coastguard Worker } 19*c8dee2aaSAndroid Build Coastguard Worker #sampleText { 20*c8dee2aaSAndroid Build Coastguard Worker width: 400px; 21*c8dee2aaSAndroid Build Coastguard Worker height: 200px; 22*c8dee2aaSAndroid Build Coastguard Worker } 23*c8dee2aaSAndroid Build Coastguard Worker</style> 24*c8dee2aaSAndroid Build Coastguard Worker 25*c8dee2aaSAndroid Build Coastguard Worker<table> 26*c8dee2aaSAndroid Build Coastguard Worker <thead> 27*c8dee2aaSAndroid Build Coastguard Worker <th><h2 style="color: red;">With ICU</h2></th> 28*c8dee2aaSAndroid Build Coastguard Worker <th></th> 29*c8dee2aaSAndroid Build Coastguard Worker <th><h2 style="color: green;">Without ICU</h2></th> 30*c8dee2aaSAndroid Build Coastguard Worker </thead> 31*c8dee2aaSAndroid Build Coastguard Worker <tr> 32*c8dee2aaSAndroid Build Coastguard Worker <td><canvas id="withICU" width=600 height=600></canvas></td> 33*c8dee2aaSAndroid Build Coastguard Worker <td style="width: 20px;"></td> 34*c8dee2aaSAndroid Build Coastguard Worker <td><canvas id="withoutICU" width=600 height=600 tabindex='-1'></canvas></td> 35*c8dee2aaSAndroid Build Coastguard Worker </tr> 36*c8dee2aaSAndroid Build Coastguard Worker</table> 37*c8dee2aaSAndroid Build Coastguard Worker 38*c8dee2aaSAndroid Build Coastguard Worker<textarea id="sampleText">The لاquick (brown) fox 39*c8dee2aaSAndroid Build Coastguard Workerواحد (اثنان) ثلاثة 40*c8dee2aaSAndroid Build Coastguard Workerate a hamburger. 41*c8dee2aaSAndroid Build Coastguard Worker</textarea> 42*c8dee2aaSAndroid Build Coastguard Worker 43*c8dee2aaSAndroid Build Coastguard Worker<script type="text/javascript" charset="utf-8"> 44*c8dee2aaSAndroid Build Coastguard Worker 45*c8dee2aaSAndroid Build Coastguard Worker var CanvasKit = null; 46*c8dee2aaSAndroid Build Coastguard Worker var fonts = null; 47*c8dee2aaSAndroid Build Coastguard Worker var sampleText = null; 48*c8dee2aaSAndroid Build Coastguard Worker 49*c8dee2aaSAndroid Build Coastguard Worker var cdn = 'https://storage.googleapis.com/skia-cdn/misc/'; 50*c8dee2aaSAndroid Build Coastguard Worker 51*c8dee2aaSAndroid Build Coastguard Worker const ckLoaded = CanvasKitInit({locateFile: (file) => '/build/'+file}); 52*c8dee2aaSAndroid Build Coastguard Worker 53*c8dee2aaSAndroid Build Coastguard Worker const loadFonts = [ 54*c8dee2aaSAndroid Build Coastguard Worker fetch(cdn + 'Roboto-Regular.ttf').then((response) => response.arrayBuffer()), 55*c8dee2aaSAndroid Build Coastguard Worker fetch('https://fonts.gstatic.com/s/notoemoji/v26/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-r0jwvS-FGJCMY.ttf').then((response) => response.arrayBuffer()), 56*c8dee2aaSAndroid Build Coastguard Worker fetch('https://fonts.gstatic.com/s/notosansarabic/v18/nwpxtLGrOAZMl5nJ_wfgRg3DrWFZWsnVBJ_sS6tlqHHFlhQ5l3sQWIHPqzCfyGyvu3CBFQLaig.ttf').then((response) => response.arrayBuffer()), 57*c8dee2aaSAndroid Build Coastguard Worker ]; 58*c8dee2aaSAndroid Build Coastguard Worker 59*c8dee2aaSAndroid Build Coastguard Worker let paragraphWithICU; 60*c8dee2aaSAndroid Build Coastguard Worker let paragraphWithoutICU; 61*c8dee2aaSAndroid Build Coastguard Worker 62*c8dee2aaSAndroid Build Coastguard Worker Promise.all([ckLoaded, ...loadFonts]).then(([_CanvasKit, ..._fonts]) => { 63*c8dee2aaSAndroid Build Coastguard Worker CanvasKit = _CanvasKit; 64*c8dee2aaSAndroid Build Coastguard Worker fonts = _fonts; 65*c8dee2aaSAndroid Build Coastguard Worker 66*c8dee2aaSAndroid Build Coastguard Worker const textarea = document.getElementById('sampleText'); 67*c8dee2aaSAndroid Build Coastguard Worker sampleText = textarea.value; 68*c8dee2aaSAndroid Build Coastguard Worker textarea.addEventListener('input', (e) => { 69*c8dee2aaSAndroid Build Coastguard Worker sampleText = e.target.value; 70*c8dee2aaSAndroid Build Coastguard Worker paragraphWithICU = ParagraphWithICU(); 71*c8dee2aaSAndroid Build Coastguard Worker paragraphWithoutICU = ParagraphWithoutICU(); 72*c8dee2aaSAndroid Build Coastguard Worker }); 73*c8dee2aaSAndroid Build Coastguard Worker 74*c8dee2aaSAndroid Build Coastguard Worker paragraphWithICU = ParagraphWithICU(); 75*c8dee2aaSAndroid Build Coastguard Worker paragraphWithoutICU = ParagraphWithoutICU(); 76*c8dee2aaSAndroid Build Coastguard Worker 77*c8dee2aaSAndroid Build Coastguard Worker continuousRendering('withICU', () => paragraphWithICU); 78*c8dee2aaSAndroid Build Coastguard Worker continuousRendering('withoutICU', () => paragraphWithoutICU); 79*c8dee2aaSAndroid Build Coastguard Worker }); 80*c8dee2aaSAndroid Build Coastguard Worker 81*c8dee2aaSAndroid Build Coastguard Worker const fontFamilies = [ 82*c8dee2aaSAndroid Build Coastguard Worker 'Roboto', 83*c8dee2aaSAndroid Build Coastguard Worker 'Noto Emoji', 84*c8dee2aaSAndroid Build Coastguard Worker 'Noto Sans Arabic', 85*c8dee2aaSAndroid Build Coastguard Worker ]; 86*c8dee2aaSAndroid Build Coastguard Worker 87*c8dee2aaSAndroid Build Coastguard Worker function continuousRendering(elementId, getParagraph) { 88*c8dee2aaSAndroid Build Coastguard Worker const surface = CanvasKit.MakeCanvasSurface(elementId); 89*c8dee2aaSAndroid Build Coastguard Worker if (!surface) { 90*c8dee2aaSAndroid Build Coastguard Worker throw new Error('Could not make surface'); 91*c8dee2aaSAndroid Build Coastguard Worker } 92*c8dee2aaSAndroid Build Coastguard Worker 93*c8dee2aaSAndroid Build Coastguard Worker function drawFrame(canvas) { 94*c8dee2aaSAndroid Build Coastguard Worker drawParagraph(canvas, getParagraph()); 95*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 96*c8dee2aaSAndroid Build Coastguard Worker } 97*c8dee2aaSAndroid Build Coastguard Worker surface.requestAnimationFrame(drawFrame); 98*c8dee2aaSAndroid Build Coastguard Worker } 99*c8dee2aaSAndroid Build Coastguard Worker 100*c8dee2aaSAndroid Build Coastguard Worker function ParagraphWithICU() { 101*c8dee2aaSAndroid Build Coastguard Worker if (!CanvasKit || !fonts) { 102*c8dee2aaSAndroid Build Coastguard Worker throw new Error('CanvasKit or fonts not loaded'); 103*c8dee2aaSAndroid Build Coastguard Worker } 104*c8dee2aaSAndroid Build Coastguard Worker 105*c8dee2aaSAndroid Build Coastguard Worker const fontMgr = CanvasKit.FontMgr.FromData(fonts); 106*c8dee2aaSAndroid Build Coastguard Worker 107*c8dee2aaSAndroid Build Coastguard Worker const paraStyle = new CanvasKit.ParagraphStyle({ 108*c8dee2aaSAndroid Build Coastguard Worker textStyle: { 109*c8dee2aaSAndroid Build Coastguard Worker color: CanvasKit.BLACK, 110*c8dee2aaSAndroid Build Coastguard Worker fontFamilies: fontFamilies, 111*c8dee2aaSAndroid Build Coastguard Worker fontSize: 50, 112*c8dee2aaSAndroid Build Coastguard Worker }, 113*c8dee2aaSAndroid Build Coastguard Worker textAlign: CanvasKit.TextAlign.Left, 114*c8dee2aaSAndroid Build Coastguard Worker maxLines: 4, 115*c8dee2aaSAndroid Build Coastguard Worker }); 116*c8dee2aaSAndroid Build Coastguard Worker 117*c8dee2aaSAndroid Build Coastguard Worker const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr); 118*c8dee2aaSAndroid Build Coastguard Worker builder.addText(sampleText); 119*c8dee2aaSAndroid Build Coastguard Worker const paragraph = builder.build(); 120*c8dee2aaSAndroid Build Coastguard Worker 121*c8dee2aaSAndroid Build Coastguard Worker fontMgr.delete(); 122*c8dee2aaSAndroid Build Coastguard Worker 123*c8dee2aaSAndroid Build Coastguard Worker return paragraph; 124*c8dee2aaSAndroid Build Coastguard Worker } 125*c8dee2aaSAndroid Build Coastguard Worker 126*c8dee2aaSAndroid Build Coastguard Worker function ParagraphWithoutICU() { 127*c8dee2aaSAndroid Build Coastguard Worker if (!CanvasKit || !fonts) { 128*c8dee2aaSAndroid Build Coastguard Worker throw new Error('CanvasKit or fonts not loaded'); 129*c8dee2aaSAndroid Build Coastguard Worker } 130*c8dee2aaSAndroid Build Coastguard Worker 131*c8dee2aaSAndroid Build Coastguard Worker const fontMgr = CanvasKit.FontMgr.FromData(fonts); 132*c8dee2aaSAndroid Build Coastguard Worker 133*c8dee2aaSAndroid Build Coastguard Worker const paraStyle = new CanvasKit.ParagraphStyle({ 134*c8dee2aaSAndroid Build Coastguard Worker textStyle: { 135*c8dee2aaSAndroid Build Coastguard Worker color: CanvasKit.BLACK, 136*c8dee2aaSAndroid Build Coastguard Worker fontFamilies: fontFamilies, 137*c8dee2aaSAndroid Build Coastguard Worker fontSize: 50, 138*c8dee2aaSAndroid Build Coastguard Worker }, 139*c8dee2aaSAndroid Build Coastguard Worker maxLines: 4, 140*c8dee2aaSAndroid Build Coastguard Worker textAlign: CanvasKit.TextAlign.Left, 141*c8dee2aaSAndroid Build Coastguard Worker }); 142*c8dee2aaSAndroid Build Coastguard Worker 143*c8dee2aaSAndroid Build Coastguard Worker const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr); 144*c8dee2aaSAndroid Build Coastguard Worker builder.addText(sampleText); 145*c8dee2aaSAndroid Build Coastguard Worker 146*c8dee2aaSAndroid Build Coastguard Worker const text = sampleText; 147*c8dee2aaSAndroid Build Coastguard Worker 148*c8dee2aaSAndroid Build Coastguard Worker // Pass the entire text as one word. It's only used for the method 149*c8dee2aaSAndroid Build Coastguard Worker // getWords. 150*c8dee2aaSAndroid Build Coastguard Worker const mallocedWords = CanvasKit.Malloc(Uint32Array, 2); 151*c8dee2aaSAndroid Build Coastguard Worker mallocedWords.toTypedArray().set([0, text.length]); 152*c8dee2aaSAndroid Build Coastguard Worker 153*c8dee2aaSAndroid Build Coastguard Worker const graphemeBoundaries = getGraphemeBoundaries(text); 154*c8dee2aaSAndroid Build Coastguard Worker const mallocedGraphemes = CanvasKit.Malloc(Uint32Array, graphemeBoundaries.length); 155*c8dee2aaSAndroid Build Coastguard Worker mallocedGraphemes.toTypedArray().set(graphemeBoundaries); 156*c8dee2aaSAndroid Build Coastguard Worker 157*c8dee2aaSAndroid Build Coastguard Worker const lineBreaks = getLineBreaks(text); 158*c8dee2aaSAndroid Build Coastguard Worker const mallocedLineBreaks = CanvasKit.Malloc(Uint32Array, lineBreaks.length); 159*c8dee2aaSAndroid Build Coastguard Worker mallocedLineBreaks.toTypedArray().set(lineBreaks); 160*c8dee2aaSAndroid Build Coastguard Worker 161*c8dee2aaSAndroid Build Coastguard Worker console.log('RequiresClientICU:', CanvasKit.ParagraphBuilder.RequiresClientICU()); 162*c8dee2aaSAndroid Build Coastguard Worker 163*c8dee2aaSAndroid Build Coastguard Worker builder.setWordsUtf16(mallocedWords); 164*c8dee2aaSAndroid Build Coastguard Worker builder.setGraphemeBreaksUtf16(mallocedGraphemes); 165*c8dee2aaSAndroid Build Coastguard Worker builder.setLineBreaksUtf16(mallocedLineBreaks); 166*c8dee2aaSAndroid Build Coastguard Worker const paragraph = builder.build(); 167*c8dee2aaSAndroid Build Coastguard Worker 168*c8dee2aaSAndroid Build Coastguard Worker fontMgr.delete(); 169*c8dee2aaSAndroid Build Coastguard Worker 170*c8dee2aaSAndroid Build Coastguard Worker return paragraph; 171*c8dee2aaSAndroid Build Coastguard Worker } 172*c8dee2aaSAndroid Build Coastguard Worker 173*c8dee2aaSAndroid Build Coastguard Worker function drawParagraph(canvas, paragraph) { 174*c8dee2aaSAndroid Build Coastguard Worker const fontPaint = new CanvasKit.Paint(); 175*c8dee2aaSAndroid Build Coastguard Worker fontPaint.setStyle(CanvasKit.PaintStyle.Fill); 176*c8dee2aaSAndroid Build Coastguard Worker fontPaint.setAntiAlias(true); 177*c8dee2aaSAndroid Build Coastguard Worker 178*c8dee2aaSAndroid Build Coastguard Worker canvas.clear(CanvasKit.WHITE); 179*c8dee2aaSAndroid Build Coastguard Worker const wrapTo = 350 + 150 * Math.sin(Date.now() / 4000); 180*c8dee2aaSAndroid Build Coastguard Worker paragraph.layout(wrapTo); 181*c8dee2aaSAndroid Build Coastguard Worker 182*c8dee2aaSAndroid Build Coastguard Worker const rects = [ 183*c8dee2aaSAndroid Build Coastguard Worker ...paragraph.getRectsForRange(2, 8, CanvasKit.RectHeightStyle.Tight, CanvasKit.RectWidthStyle.Tight), 184*c8dee2aaSAndroid Build Coastguard Worker ...paragraph.getRectsForRange(12, 16, CanvasKit.RectHeightStyle.Tight, CanvasKit.RectWidthStyle.Tight), 185*c8dee2aaSAndroid Build Coastguard Worker ]; 186*c8dee2aaSAndroid Build Coastguard Worker const rectPaint = new CanvasKit.Paint(); 187*c8dee2aaSAndroid Build Coastguard Worker const colors = [CanvasKit.CYAN, CanvasKit.MAGENTA, CanvasKit.BLUE, CanvasKit.YELLOW]; 188*c8dee2aaSAndroid Build Coastguard Worker for (const rect of rects) { 189*c8dee2aaSAndroid Build Coastguard Worker rectPaint.setColor(colors.shift() || CanvasKit.RED); 190*c8dee2aaSAndroid Build Coastguard Worker canvas.drawRect(rect, rectPaint); 191*c8dee2aaSAndroid Build Coastguard Worker } 192*c8dee2aaSAndroid Build Coastguard Worker 193*c8dee2aaSAndroid Build Coastguard Worker canvas.drawParagraph(paragraph, 0, 0); 194*c8dee2aaSAndroid Build Coastguard Worker 195*c8dee2aaSAndroid Build Coastguard Worker canvas.drawLine(wrapTo, 0, wrapTo, 400, fontPaint); 196*c8dee2aaSAndroid Build Coastguard Worker } 197*c8dee2aaSAndroid Build Coastguard Worker 198*c8dee2aaSAndroid Build Coastguard Worker const SOFT = 0; 199*c8dee2aaSAndroid Build Coastguard Worker const HARD = 1; 200*c8dee2aaSAndroid Build Coastguard Worker 201*c8dee2aaSAndroid Build Coastguard Worker function getLineBreaks(text) { 202*c8dee2aaSAndroid Build Coastguard Worker const breaks = [0, SOFT]; 203*c8dee2aaSAndroid Build Coastguard Worker 204*c8dee2aaSAndroid Build Coastguard Worker const iterator = new Intl.v8BreakIterator(['en'], {type: 'line'}); 205*c8dee2aaSAndroid Build Coastguard Worker iterator.adoptText(text); 206*c8dee2aaSAndroid Build Coastguard Worker iterator.first(); 207*c8dee2aaSAndroid Build Coastguard Worker 208*c8dee2aaSAndroid Build Coastguard Worker while (iterator.next() != -1) { 209*c8dee2aaSAndroid Build Coastguard Worker breaks.push(iterator.current(), getBreakType(iterator.breakType())); 210*c8dee2aaSAndroid Build Coastguard Worker } 211*c8dee2aaSAndroid Build Coastguard Worker 212*c8dee2aaSAndroid Build Coastguard Worker return breaks; 213*c8dee2aaSAndroid Build Coastguard Worker } 214*c8dee2aaSAndroid Build Coastguard Worker 215*c8dee2aaSAndroid Build Coastguard Worker function getBreakType(v8BreakType) { 216*c8dee2aaSAndroid Build Coastguard Worker return v8BreakType == 'none' ? SOFT : HARD; 217*c8dee2aaSAndroid Build Coastguard Worker } 218*c8dee2aaSAndroid Build Coastguard Worker 219*c8dee2aaSAndroid Build Coastguard Worker function getGraphemeBoundaries(text) { 220*c8dee2aaSAndroid Build Coastguard Worker const segmenter = new Intl.Segmenter(['en'], {type: 'grapheme'}); 221*c8dee2aaSAndroid Build Coastguard Worker const segments = segmenter.segment(text); 222*c8dee2aaSAndroid Build Coastguard Worker 223*c8dee2aaSAndroid Build Coastguard Worker const graphemeBoundaries = []; 224*c8dee2aaSAndroid Build Coastguard Worker for (const segment of segments) { 225*c8dee2aaSAndroid Build Coastguard Worker graphemeBoundaries.push(segment.index); 226*c8dee2aaSAndroid Build Coastguard Worker } 227*c8dee2aaSAndroid Build Coastguard Worker graphemeBoundaries.push(text.length); 228*c8dee2aaSAndroid Build Coastguard Worker return graphemeBoundaries; 229*c8dee2aaSAndroid Build Coastguard Worker } 230*c8dee2aaSAndroid Build Coastguard Worker</script> 231