229 lines
6.5 KiB
HTML
229 lines
6.5 KiB
HTML
|
|
<!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);
|
|||
|
|
|
|||
|
|
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>
|