blob: 69ae7a6c18ef4ed1e8381cdf925c0536055cb907 [file] [log] [blame]
<!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>