xref: /aosp_15_r20/external/skia/modules/canvaskit/npm_build/paragraphs.html (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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