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