UNPKG

canvaskit-wasm

Version:

A WASM version of Skia's Canvas API

231 lines (182 loc) 6.72 kB
<!DOCTYPE html> <title>CanvasKit Paragraph (with & without ICU)</title> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script type="text/javascript" src="/build/canvaskit.js"></script> <style> canvas { border: 1px dashed #AAA; } #withICU { border-color: red; } #withoutICU { border-color: green; } #sampleText { width: 400px; height: 200px; } </style> <table> <thead> <th><h2 style="color: red;">With ICU</h2></th> <th></th> <th><h2 style="color: green;">Without ICU</h2></th> </thead> <tr> <td><canvas id="withICU" width=600 height=600></canvas></td> <td style="width: 20px;"></td> <td><canvas id="withoutICU" width=600 height=600 tabindex='-1'></canvas></td> </tr> </table> <textarea id="sampleText">The لاquick 😠(brown) fox واحد (اثنان) ثلاثة ate a hamburger. </textarea> <script type="text/javascript" charset="utf-8"> var CanvasKit = null; var fonts = null; var sampleText = null; var cdn = 'https://storage.googleapis.com/skia-cdn/misc/'; const ckLoaded = CanvasKitInit({locateFile: (file) => '/build/'+file}); const loadFonts = [ fetch(cdn + 'Roboto-Regular.ttf').then((response) => response.arrayBuffer()), fetch('https://fonts.gstatic.com/s/notoemoji/v26/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-r0jwvS-FGJCMY.ttf').then((response) => response.arrayBuffer()), fetch('https://fonts.gstatic.com/s/notosansarabic/v18/nwpxtLGrOAZMl5nJ_wfgRg3DrWFZWsnVBJ_sS6tlqHHFlhQ5l3sQWIHPqzCfyGyvu3CBFQLaig.ttf').then((response) => response.arrayBuffer()), ]; let paragraphWithICU; let paragraphWithoutICU; Promise.all([ckLoaded, ...loadFonts]).then(([_CanvasKit, ..._fonts]) => { CanvasKit = _CanvasKit; fonts = _fonts; const textarea = document.getElementById('sampleText'); sampleText = textarea.value; textarea.addEventListener('input', (e) => { sampleText = e.target.value; paragraphWithICU = ParagraphWithICU(); paragraphWithoutICU = ParagraphWithoutICU(); }); paragraphWithICU = ParagraphWithICU(); paragraphWithoutICU = ParagraphWithoutICU(); continuousRendering('withICU', () => paragraphWithICU); continuousRendering('withoutICU', () => paragraphWithoutICU); }); const fontFamilies = [ 'Roboto', 'Noto Emoji', 'Noto Sans Arabic', ]; function continuousRendering(elementId, getParagraph) { const surface = CanvasKit.MakeCanvasSurface(elementId); if (!surface) { throw new Error('Could not make surface'); } function drawFrame(canvas) { drawParagraph(canvas, getParagraph()); surface.requestAnimationFrame(drawFrame); } surface.requestAnimationFrame(drawFrame); } function ParagraphWithICU() { if (!CanvasKit || !fonts) { throw new Error('CanvasKit or fonts not loaded'); } const fontMgr = CanvasKit.FontMgr.FromData(fonts); const paraStyle = new CanvasKit.ParagraphStyle({ textStyle: { color: CanvasKit.BLACK, fontFamilies: fontFamilies, fontSize: 50, }, textAlign: CanvasKit.TextAlign.Left, maxLines: 4, }); const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr); builder.addText(sampleText); const paragraph = builder.build(); fontMgr.delete(); return paragraph; } function ParagraphWithoutICU() { if (!CanvasKit || !fonts) { throw new Error('CanvasKit or fonts not loaded'); } const fontMgr = CanvasKit.FontMgr.FromData(fonts); const paraStyle = new CanvasKit.ParagraphStyle({ textStyle: { color: CanvasKit.BLACK, fontFamilies: fontFamilies, fontSize: 50, }, maxLines: 4, textAlign: CanvasKit.TextAlign.Left, }); const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr); builder.addText(sampleText); const text = sampleText; // Pass the entire text as one word. It's only used for the method // getWords. const mallocedWords = CanvasKit.Malloc(Uint32Array, 2); mallocedWords.toTypedArray().set([0, text.length]); const graphemeBoundaries = getGraphemeBoundaries(text); const mallocedGraphemes = CanvasKit.Malloc(Uint32Array, graphemeBoundaries.length); mallocedGraphemes.toTypedArray().set(graphemeBoundaries); const lineBreaks = getLineBreaks(text); const mallocedLineBreaks = CanvasKit.Malloc(Uint32Array, lineBreaks.length); mallocedLineBreaks.toTypedArray().set(lineBreaks); console.log('RequiresClientICU:', CanvasKit.ParagraphBuilder.RequiresClientICU()); builder.setWordsUtf16(mallocedWords); builder.setGraphemeBreaksUtf16(mallocedGraphemes); builder.setLineBreaksUtf16(mallocedLineBreaks); const paragraph = builder.build(); fontMgr.delete(); return paragraph; } function drawParagraph(canvas, paragraph) { const fontPaint = new CanvasKit.Paint(); fontPaint.setStyle(CanvasKit.PaintStyle.Fill); fontPaint.setAntiAlias(true); canvas.clear(CanvasKit.WHITE); const wrapTo = 350 + 150 * Math.sin(Date.now() / 4000); paragraph.layout(wrapTo); const rects = [ ...paragraph.getRectsForRange(2, 8, CanvasKit.RectHeightStyle.Tight, CanvasKit.RectWidthStyle.Tight), ...paragraph.getRectsForRange(12, 16, CanvasKit.RectHeightStyle.Tight, CanvasKit.RectWidthStyle.Tight), ]; const rectPaint = new CanvasKit.Paint(); const colors = [CanvasKit.CYAN, CanvasKit.MAGENTA, CanvasKit.BLUE, CanvasKit.YELLOW]; for (const rect of rects) { rectPaint.setColor(colors.shift() || CanvasKit.RED); canvas.drawRect(rect, rectPaint); } canvas.drawParagraph(paragraph, 0, 0); canvas.drawLine(wrapTo, 0, wrapTo, 400, fontPaint); } const SOFT = 0; const HARD = 1; function getLineBreaks(text) { const breaks = [0, SOFT]; const iterator = new Intl.v8BreakIterator(['en'], {type: 'line'}); iterator.adoptText(text); iterator.first(); while (iterator.next() != -1) { breaks.push(iterator.current(), getBreakType(iterator.breakType())); } return breaks; } function getBreakType(v8BreakType) { return v8BreakType == 'none' ? SOFT : HARD; } function getGraphemeBoundaries(text) { const segmenter = new Intl.Segmenter(['en'], {type: 'grapheme'}); const segments = segmenter.segment(text); const graphemeBoundaries = []; for (const segment of segments) { graphemeBoundaries.push(segment.index); } graphemeBoundaries.push(text.length); return graphemeBoundaries; } </script>