1cb93a386Sopenharmony_ci<!DOCTYPE html>
2cb93a386Sopenharmony_ci<title>CanvasKit Extra features (Skia via Web Assembly)</title>
3cb93a386Sopenharmony_ci<meta charset="utf-8" />
4cb93a386Sopenharmony_ci<meta http-equiv="X-UA-Compatible" content="IE=edge">
5cb93a386Sopenharmony_ci<meta name="viewport" content="width=device-width, initial-scale=1.0">
6cb93a386Sopenharmony_ci
7cb93a386Sopenharmony_ci<style>
8cb93a386Sopenharmony_ci  canvas {
9cb93a386Sopenharmony_ci    border: 1px dashed #AAA;
10cb93a386Sopenharmony_ci  }
11cb93a386Sopenharmony_ci  #sk_legos,#sk_drinks,#sk_party,#sk_onboarding, #sk_animated_gif {
12cb93a386Sopenharmony_ci      width: 300px;
13cb93a386Sopenharmony_ci      height: 300px;
14cb93a386Sopenharmony_ci  }
15cb93a386Sopenharmony_ci
16cb93a386Sopenharmony_ci</style>
17cb93a386Sopenharmony_ci
18cb93a386Sopenharmony_ci<h2> Skottie </h2>
19cb93a386Sopenharmony_ci<canvas id=sk_legos width=300 height=300></canvas>
20cb93a386Sopenharmony_ci<canvas id=sk_drinks width=500 height=500></canvas>
21cb93a386Sopenharmony_ci<canvas id=sk_party width=500 height=500></canvas>
22cb93a386Sopenharmony_ci<canvas id=sk_onboarding width=500 height=500></canvas>
23cb93a386Sopenharmony_ci<canvas id=sk_animated_gif width=500 height=500
24cb93a386Sopenharmony_ci        title='This is an animated gif being animated in Skottie'></canvas>
25cb93a386Sopenharmony_ci
26cb93a386Sopenharmony_ci<h2> RT Shader </h2>
27cb93a386Sopenharmony_ci<canvas id=rtshader width=300 height=300></canvas>
28cb93a386Sopenharmony_ci<canvas id=rtshader2 width=300 height=300></canvas>
29cb93a386Sopenharmony_ci
30cb93a386Sopenharmony_ci<h2> Particles </h2>
31cb93a386Sopenharmony_ci<canvas id=particles width=500 height=500></canvas>
32cb93a386Sopenharmony_ci
33cb93a386Sopenharmony_ci<h2> Paragraph </h2>
34cb93a386Sopenharmony_ci<canvas id=para1 width=600 height=600></canvas>
35cb93a386Sopenharmony_ci<canvas id=para2 width=600 height=600 tabindex='-1'></canvas>
36cb93a386Sopenharmony_ci
37cb93a386Sopenharmony_ci<h2> CanvasKit can serialize/deserialize .skp files</h2>
38cb93a386Sopenharmony_ci<canvas id=skp width=500 height=500></canvas>
39cb93a386Sopenharmony_ci
40cb93a386Sopenharmony_ci<h2> 3D perspective transformations </h2>
41cb93a386Sopenharmony_ci<canvas id=glyphgame width=500 height=500></canvas>
42cb93a386Sopenharmony_ci
43cb93a386Sopenharmony_ci<h2> Support for extended color spaces </h2>
44cb93a386Sopenharmony_ci<a href="chrome://flags/#force-color-profile">Force P3 profile</a>
45cb93a386Sopenharmony_ci<canvas id=colorsupport width=300 height=300></canvas>
46cb93a386Sopenharmony_ci
47cb93a386Sopenharmony_ci<script type="text/javascript" src="/build/canvaskit.js"></script>
48cb93a386Sopenharmony_ci
49cb93a386Sopenharmony_ci<script type="text/javascript" src="textapi_utils.js"></script>
50cb93a386Sopenharmony_ci
51cb93a386Sopenharmony_ci<script type="text/javascript" charset="utf-8">
52cb93a386Sopenharmony_ci
53cb93a386Sopenharmony_ci  var CanvasKit = null;
54cb93a386Sopenharmony_ci  var cdn = 'https://storage.googleapis.com/skia-cdn/misc/';
55cb93a386Sopenharmony_ci
56cb93a386Sopenharmony_ci  const ckLoaded = CanvasKitInit({locateFile: (file) => '/build/'+file});
57cb93a386Sopenharmony_ci
58cb93a386Sopenharmony_ci  const loadLegoJSON = fetch(cdn + 'lego_loader.json').then((response) => response.text());
59cb93a386Sopenharmony_ci  const loadDrinksJSON = fetch(cdn + 'drinks.json').then((response) => response.text());
60cb93a386Sopenharmony_ci  const loadConfettiJSON = fetch(cdn + 'confetti.json').then((response) => response.text());
61cb93a386Sopenharmony_ci  const loadOnboardingJSON = fetch(cdn + 'onboarding.json').then((response) => response.text());
62cb93a386Sopenharmony_ci  const loadMultiframeJSON = fetch(cdn + 'skottie_sample_multiframe.json').then((response) => response.text());
63cb93a386Sopenharmony_ci
64cb93a386Sopenharmony_ci  const loadFlightGif = fetch(cdn + 'flightAnim.gif').then((response) => response.arrayBuffer());
65cb93a386Sopenharmony_ci  const loadFont = fetch(cdn + 'Roboto-Regular.ttf').then((response) => response.arrayBuffer());
66cb93a386Sopenharmony_ci  const loadDog = fetch(cdn + 'dog.jpg').then((response) => response.arrayBuffer());
67cb93a386Sopenharmony_ci  const loadMandrill = fetch(cdn + 'mandrill_256.png').then((response) => response.arrayBuffer());
68cb93a386Sopenharmony_ci  const loadBrickTex = fetch(cdn + 'brickwork-texture.jpg').then((response) => response.arrayBuffer());
69cb93a386Sopenharmony_ci  const loadBrickBump = fetch(cdn + 'brickwork_normal-map.jpg').then((response) => response.arrayBuffer());
70cb93a386Sopenharmony_ci
71cb93a386Sopenharmony_ci  const curves = {
72cb93a386Sopenharmony_ci    "MaxCount": 1000,
73cb93a386Sopenharmony_ci    "Drawable": {
74cb93a386Sopenharmony_ci      "Type": "SkCircleDrawable",
75cb93a386Sopenharmony_ci      "Radius": 2
76cb93a386Sopenharmony_ci    },
77cb93a386Sopenharmony_ci    "Code": [`
78cb93a386Sopenharmony_ci      void effectSpawn(inout Effect effect) {
79cb93a386Sopenharmony_ci        effect.rate = 200;
80cb93a386Sopenharmony_ci        effect.color = float4(1, 0, 0, 1);
81cb93a386Sopenharmony_ci      }
82cb93a386Sopenharmony_ci      void spawn(inout Particle p) {
83cb93a386Sopenharmony_ci        p.lifetime = 3 + rand(p.seed);
84cb93a386Sopenharmony_ci        p.vel.y = -50;
85cb93a386Sopenharmony_ci      }
86cb93a386Sopenharmony_ci
87cb93a386Sopenharmony_ci      void update(inout Particle p) {
88cb93a386Sopenharmony_ci        float w = mix(15, 3, p.age);
89cb93a386Sopenharmony_ci        p.pos.x = sin(radians(p.age * 320)) * mix(25, 10, p.age) + mix(-w, w, rand(p.seed));
90cb93a386Sopenharmony_ci        if (rand(p.seed) < 0.5) { p.pos.x = -p.pos.x; }
91cb93a386Sopenharmony_ci
92cb93a386Sopenharmony_ci        p.color.g = (mix(75, 220, p.age) + mix(-30, 30, rand(p.seed))) / 255;
93cb93a386Sopenharmony_ci      }
94cb93a386Sopenharmony_ci      `
95cb93a386Sopenharmony_ci    ],
96cb93a386Sopenharmony_ci    "Bindings": []
97cb93a386Sopenharmony_ci  };
98cb93a386Sopenharmony_ci
99cb93a386Sopenharmony_ci  const spiralSkSL = `
100cb93a386Sopenharmony_ci  uniform float rad_scale;
101cb93a386Sopenharmony_ci  uniform float2 in_center;
102cb93a386Sopenharmony_ci  uniform float4 in_colors0;
103cb93a386Sopenharmony_ci  uniform float4 in_colors1;
104cb93a386Sopenharmony_ci
105cb93a386Sopenharmony_ci  half4 main(float2 p) {
106cb93a386Sopenharmony_ci      float2 pp = p - in_center;
107cb93a386Sopenharmony_ci      float radius = sqrt(dot(pp, pp));
108cb93a386Sopenharmony_ci      radius = sqrt(radius);
109cb93a386Sopenharmony_ci      float angle = atan(pp.y / pp.x);
110cb93a386Sopenharmony_ci      float t = (angle + 3.1415926/2) / (3.1415926);
111cb93a386Sopenharmony_ci      t += radius * rad_scale;
112cb93a386Sopenharmony_ci      t = fract(t);
113cb93a386Sopenharmony_ci      return half4(mix(in_colors0, in_colors1, t));
114cb93a386Sopenharmony_ci  }`;
115cb93a386Sopenharmony_ci
116cb93a386Sopenharmony_ci  // Examples which only require canvaskit
117cb93a386Sopenharmony_ci  ckLoaded.then((CK) => {
118cb93a386Sopenharmony_ci    CanvasKit = CK;
119cb93a386Sopenharmony_ci    ParticlesAPI1(CanvasKit);
120cb93a386Sopenharmony_ci    RTShaderAPI1(CanvasKit);
121cb93a386Sopenharmony_ci    ColorSupport(CanvasKit);
122cb93a386Sopenharmony_ci    SkpExample(CanvasKit);
123cb93a386Sopenharmony_ci  });
124cb93a386Sopenharmony_ci
125cb93a386Sopenharmony_ci  // Examples requiring external resources.
126cb93a386Sopenharmony_ci  // Set bounds to fix the 4:3 resolution of the legos
127cb93a386Sopenharmony_ci  Promise.all([ckLoaded, loadLegoJSON]).then(([ck, jsonstr]) => {
128cb93a386Sopenharmony_ci    SkottieExample(ck, 'sk_legos', jsonstr, [-50, 0, 350, 300]);
129cb93a386Sopenharmony_ci  });
130cb93a386Sopenharmony_ci  // Re-size to fit
131cb93a386Sopenharmony_ci  let fullBounds = [0, 0, 500, 500];
132cb93a386Sopenharmony_ci  Promise.all([ckLoaded, loadDrinksJSON]).then(([ck, jsonstr]) => {
133cb93a386Sopenharmony_ci    SkottieExample(ck, 'sk_drinks', jsonstr, fullBounds);
134cb93a386Sopenharmony_ci  });
135cb93a386Sopenharmony_ci  Promise.all([ckLoaded, loadConfettiJSON]).then(([ck, jsonstr]) => {
136cb93a386Sopenharmony_ci    SkottieExample(ck, 'sk_party', jsonstr, fullBounds);
137cb93a386Sopenharmony_ci  });
138cb93a386Sopenharmony_ci  Promise.all([ckLoaded, loadOnboardingJSON]).then(([ck, jsonstr]) => {
139cb93a386Sopenharmony_ci    SkottieExample(ck, 'sk_onboarding', jsonstr, fullBounds);
140cb93a386Sopenharmony_ci  });
141cb93a386Sopenharmony_ci  Promise.all([ckLoaded, loadMultiframeJSON, loadFlightGif]).then(([ck, jsonstr, gif]) => {
142cb93a386Sopenharmony_ci    SkottieExample(ck, 'sk_animated_gif', jsonstr, fullBounds, {'image_0.png': gif});
143cb93a386Sopenharmony_ci  });
144cb93a386Sopenharmony_ci
145cb93a386Sopenharmony_ci  Promise.all([ckLoaded, loadFont]).then((results) => {
146cb93a386Sopenharmony_ci    ParagraphAPI1(...results);
147cb93a386Sopenharmony_ci    ParagraphAPI2(...results);
148cb93a386Sopenharmony_ci    GlyphGame(...results)
149cb93a386Sopenharmony_ci  });
150cb93a386Sopenharmony_ci
151cb93a386Sopenharmony_ci  const rectLeft = 0;
152cb93a386Sopenharmony_ci  const rectTop = 1;
153cb93a386Sopenharmony_ci  const rectRight = 2;
154cb93a386Sopenharmony_ci  const rectBottom = 3;
155cb93a386Sopenharmony_ci
156cb93a386Sopenharmony_ci  function SkottieExample(CanvasKit, id, jsonStr, bounds, assets) {
157cb93a386Sopenharmony_ci    if (!CanvasKit || !jsonStr) {
158cb93a386Sopenharmony_ci      return;
159cb93a386Sopenharmony_ci    }
160cb93a386Sopenharmony_ci    const animation = CanvasKit.MakeManagedAnimation(jsonStr, assets);
161cb93a386Sopenharmony_ci    const duration = animation.duration() * 1000;
162cb93a386Sopenharmony_ci    const size = animation.size();
163cb93a386Sopenharmony_ci    let c = document.getElementById(id);
164cb93a386Sopenharmony_ci    bounds = bounds || CanvasKit.LTRBRect(0, 0, size.w, size.h);
165cb93a386Sopenharmony_ci
166cb93a386Sopenharmony_ci    // Basic managed animation test.
167cb93a386Sopenharmony_ci    if (id === 'sk_drinks') {
168cb93a386Sopenharmony_ci      animation.setColor('BACKGROUND_FILL', CanvasKit.Color(0, 163, 199, 1.0));
169cb93a386Sopenharmony_ci    }
170cb93a386Sopenharmony_ci
171cb93a386Sopenharmony_ci    const surface = CanvasKit.MakeCanvasSurface(id);
172cb93a386Sopenharmony_ci    if (!surface) {
173cb93a386Sopenharmony_ci      console.error('Could not make surface');
174cb93a386Sopenharmony_ci      return;
175cb93a386Sopenharmony_ci    }
176cb93a386Sopenharmony_ci
177cb93a386Sopenharmony_ci    let firstFrame = Date.now();
178cb93a386Sopenharmony_ci
179cb93a386Sopenharmony_ci    function drawFrame(canvas) {
180cb93a386Sopenharmony_ci      let seek = ((Date.now() - firstFrame) / duration) % 1.0;
181cb93a386Sopenharmony_ci      let damage = animation.seek(seek);
182cb93a386Sopenharmony_ci
183cb93a386Sopenharmony_ci      if (damage[rectRight] > damage[rectLeft] && damage[rectBottom] > damage[rectTop]) {
184cb93a386Sopenharmony_ci        canvas.clear(CanvasKit.WHITE);
185cb93a386Sopenharmony_ci        animation.render(canvas, bounds);
186cb93a386Sopenharmony_ci      }
187cb93a386Sopenharmony_ci      surface.requestAnimationFrame(drawFrame);
188cb93a386Sopenharmony_ci    }
189cb93a386Sopenharmony_ci    surface.requestAnimationFrame(drawFrame);
190cb93a386Sopenharmony_ci
191cb93a386Sopenharmony_ci    return surface;
192cb93a386Sopenharmony_ci  }
193cb93a386Sopenharmony_ci
194cb93a386Sopenharmony_ci  function ParticlesAPI1(CanvasKit) {
195cb93a386Sopenharmony_ci    const surface = CanvasKit.MakeCanvasSurface('particles');
196cb93a386Sopenharmony_ci    if (!surface) {
197cb93a386Sopenharmony_ci      console.error('Could not make surface');
198cb93a386Sopenharmony_ci      return;
199cb93a386Sopenharmony_ci    }
200cb93a386Sopenharmony_ci    const canvas = surface.getCanvas();
201cb93a386Sopenharmony_ci    canvas.translate(250, 450);
202cb93a386Sopenharmony_ci
203cb93a386Sopenharmony_ci    const particles = CanvasKit.MakeParticles(JSON.stringify(curves));
204cb93a386Sopenharmony_ci    particles.start(Date.now() / 1000.0, true);
205cb93a386Sopenharmony_ci
206cb93a386Sopenharmony_ci    function drawFrame(canvas) {
207cb93a386Sopenharmony_ci      canvas.clear(CanvasKit.BLACK);
208cb93a386Sopenharmony_ci
209cb93a386Sopenharmony_ci      particles.update(Date.now() / 1000.0);
210cb93a386Sopenharmony_ci      particles.draw(canvas);
211cb93a386Sopenharmony_ci      surface.requestAnimationFrame(drawFrame);
212cb93a386Sopenharmony_ci    }
213cb93a386Sopenharmony_ci    surface.requestAnimationFrame(drawFrame);
214cb93a386Sopenharmony_ci  }
215cb93a386Sopenharmony_ci
216cb93a386Sopenharmony_ci  function ParagraphAPI1(CanvasKit, fontData) {
217cb93a386Sopenharmony_ci    if (!CanvasKit || !fontData) {
218cb93a386Sopenharmony_ci      return;
219cb93a386Sopenharmony_ci    }
220cb93a386Sopenharmony_ci
221cb93a386Sopenharmony_ci    const surface = CanvasKit.MakeCanvasSurface('para1');
222cb93a386Sopenharmony_ci    if (!surface) {
223cb93a386Sopenharmony_ci      console.error('Could not make surface');
224cb93a386Sopenharmony_ci      return;
225cb93a386Sopenharmony_ci    }
226cb93a386Sopenharmony_ci
227cb93a386Sopenharmony_ci    const canvas = surface.getCanvas();
228cb93a386Sopenharmony_ci    const fontMgr = CanvasKit.FontMgr.FromData([fontData]);
229cb93a386Sopenharmony_ci
230cb93a386Sopenharmony_ci    const paraStyle = new CanvasKit.ParagraphStyle({
231cb93a386Sopenharmony_ci        textStyle: {
232cb93a386Sopenharmony_ci            color: CanvasKit.BLACK,
233cb93a386Sopenharmony_ci            fontFamilies: ['Roboto'],
234cb93a386Sopenharmony_ci            fontSize: 50,
235cb93a386Sopenharmony_ci        },
236cb93a386Sopenharmony_ci        textAlign: CanvasKit.TextAlign.Left,
237cb93a386Sopenharmony_ci        maxLines: 5,
238cb93a386Sopenharmony_ci    });
239cb93a386Sopenharmony_ci
240cb93a386Sopenharmony_ci    const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
241cb93a386Sopenharmony_ci    builder.addText('The quick brown fox ate a hamburgerfons and got sick.');
242cb93a386Sopenharmony_ci    const paragraph = builder.build();
243cb93a386Sopenharmony_ci
244cb93a386Sopenharmony_ci    let wrapTo = 0;
245cb93a386Sopenharmony_ci
246cb93a386Sopenharmony_ci    let X = 100;
247cb93a386Sopenharmony_ci    let Y = 100;
248cb93a386Sopenharmony_ci
249cb93a386Sopenharmony_ci    const fontPaint = new CanvasKit.Paint();
250cb93a386Sopenharmony_ci    fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
251cb93a386Sopenharmony_ci    fontPaint.setAntiAlias(true);
252cb93a386Sopenharmony_ci
253cb93a386Sopenharmony_ci    function drawFrame(canvas) {
254cb93a386Sopenharmony_ci      canvas.clear(CanvasKit.WHITE);
255cb93a386Sopenharmony_ci      wrapTo = 350 + 150 * Math.sin(Date.now() / 2000);
256cb93a386Sopenharmony_ci      paragraph.layout(wrapTo);
257cb93a386Sopenharmony_ci      canvas.drawParagraph(paragraph, 0, 0);
258cb93a386Sopenharmony_ci
259cb93a386Sopenharmony_ci      canvas.drawLine(wrapTo, 0, wrapTo, 400, fontPaint);
260cb93a386Sopenharmony_ci
261cb93a386Sopenharmony_ci      surface.requestAnimationFrame(drawFrame);
262cb93a386Sopenharmony_ci    }
263cb93a386Sopenharmony_ci    surface.requestAnimationFrame(drawFrame);
264cb93a386Sopenharmony_ci
265cb93a386Sopenharmony_ci    let interact = (e) => {
266cb93a386Sopenharmony_ci      X = e.offsetX*2; // multiply by 2 because the canvas is 300 css pixels wide,
267cb93a386Sopenharmony_ci      Y = e.offsetY*2; // but the canvas itself is 600px wide
268cb93a386Sopenharmony_ci    };
269cb93a386Sopenharmony_ci
270cb93a386Sopenharmony_ci    document.getElementById('para1').addEventListener('pointermove', interact);
271cb93a386Sopenharmony_ci    return surface;
272cb93a386Sopenharmony_ci  }
273cb93a386Sopenharmony_ci
274cb93a386Sopenharmony_ci    function ParagraphAPI2(CanvasKit, fontData) {
275cb93a386Sopenharmony_ci      if (!CanvasKit || !fontData) {
276cb93a386Sopenharmony_ci        return;
277cb93a386Sopenharmony_ci      }
278cb93a386Sopenharmony_ci
279cb93a386Sopenharmony_ci      const surface = CanvasKit.MakeCanvasSurface('para2');
280cb93a386Sopenharmony_ci      if (!surface) {
281cb93a386Sopenharmony_ci        console.error('Could not make surface');
282cb93a386Sopenharmony_ci        return;
283cb93a386Sopenharmony_ci      }
284cb93a386Sopenharmony_ci
285cb93a386Sopenharmony_ci      const mouse = MakeMouse();
286cb93a386Sopenharmony_ci      const cursor = MakeCursor(CanvasKit);
287cb93a386Sopenharmony_ci      const canvas = surface.getCanvas();
288cb93a386Sopenharmony_ci
289cb93a386Sopenharmony_ci      const text0 = "In a hole in the ground there lived a hobbit. Not a nasty, dirty, " +
290cb93a386Sopenharmony_ci                    "wet hole full of worms and oozy smells. This was a hobbit-hole and " +
291cb93a386Sopenharmony_ci                    "that means good food, a warm hearth, and all the comforts of home.";
292cb93a386Sopenharmony_ci      const LOC_X = 20,
293cb93a386Sopenharmony_ci            LOC_Y = 20;
294cb93a386Sopenharmony_ci
295cb93a386Sopenharmony_ci      const bgPaint = new CanvasKit.Paint();
296cb93a386Sopenharmony_ci      bgPaint.setColor([0.965, 0.965, 0.965, 1]);
297cb93a386Sopenharmony_ci
298cb93a386Sopenharmony_ci      const editor = MakeEditor(text0, {typeface:null, size:24}, cursor, 400);
299cb93a386Sopenharmony_ci
300cb93a386Sopenharmony_ci      editor.applyStyleToRange({size:100}, 0, 1);
301cb93a386Sopenharmony_ci      editor.applyStyleToRange({italic:true}, 38, 38+6);
302cb93a386Sopenharmony_ci      editor.applyStyleToRange({color:[1,0,0,1]}, 5, 5+4);
303cb93a386Sopenharmony_ci
304cb93a386Sopenharmony_ci      editor.setXY(LOC_X, LOC_Y);
305cb93a386Sopenharmony_ci
306cb93a386Sopenharmony_ci      function drawFrame(canvas) {
307cb93a386Sopenharmony_ci        const lines = editor.getLines();
308cb93a386Sopenharmony_ci
309cb93a386Sopenharmony_ci        canvas.clear(CanvasKit.WHITE);
310cb93a386Sopenharmony_ci
311cb93a386Sopenharmony_ci        if (mouse.isActive()) {
312cb93a386Sopenharmony_ci            const pos = mouse.getPos(-LOC_X, -LOC_Y);
313cb93a386Sopenharmony_ci            const a = lines_pos_to_index(lines, pos[0], pos[1]);
314cb93a386Sopenharmony_ci            const b = lines_pos_to_index(lines, pos[2], pos[3]);
315cb93a386Sopenharmony_ci            if (a == b) {
316cb93a386Sopenharmony_ci                editor.setIndex(a);
317cb93a386Sopenharmony_ci            } else {
318cb93a386Sopenharmony_ci                editor.setIndices(a, b);
319cb93a386Sopenharmony_ci            }
320cb93a386Sopenharmony_ci        }
321cb93a386Sopenharmony_ci
322cb93a386Sopenharmony_ci        canvas.drawRect(editor.bounds(), bgPaint);
323cb93a386Sopenharmony_ci        editor.draw(canvas);
324cb93a386Sopenharmony_ci
325cb93a386Sopenharmony_ci        surface.requestAnimationFrame(drawFrame);
326cb93a386Sopenharmony_ci      }
327cb93a386Sopenharmony_ci      surface.requestAnimationFrame(drawFrame);
328cb93a386Sopenharmony_ci
329cb93a386Sopenharmony_ci      function interact(e) {
330cb93a386Sopenharmony_ci        const type = e.type;
331cb93a386Sopenharmony_ci        if (type === 'pointerup') {
332cb93a386Sopenharmony_ci            mouse.setUp(e.offsetX, e.offsetY);
333cb93a386Sopenharmony_ci        } else if (type === 'pointermove') {
334cb93a386Sopenharmony_ci            mouse.setMove(e.offsetX, e.offsetY);
335cb93a386Sopenharmony_ci        } else if (type === 'pointerdown') {
336cb93a386Sopenharmony_ci            mouse.setDown(e.offsetX, e.offsetY);
337cb93a386Sopenharmony_ci        }
338cb93a386Sopenharmony_ci      };
339cb93a386Sopenharmony_ci
340cb93a386Sopenharmony_ci      function keyhandler(e) {
341cb93a386Sopenharmony_ci          switch (e.key) {
342cb93a386Sopenharmony_ci              case 'ArrowLeft':  editor.moveDX(-1); return;
343cb93a386Sopenharmony_ci              case 'ArrowRight': editor.moveDX(1); return;
344cb93a386Sopenharmony_ci              case 'ArrowUp':
345cb93a386Sopenharmony_ci                e.preventDefault();
346cb93a386Sopenharmony_ci                editor.moveDY(-1);
347cb93a386Sopenharmony_ci                return;
348cb93a386Sopenharmony_ci              case 'ArrowDown':
349cb93a386Sopenharmony_ci                e.preventDefault();
350cb93a386Sopenharmony_ci                editor.moveDY(1);
351cb93a386Sopenharmony_ci                return;
352cb93a386Sopenharmony_ci              case 'Backspace':
353cb93a386Sopenharmony_ci                editor.deleteSelection();
354cb93a386Sopenharmony_ci                return;
355cb93a386Sopenharmony_ci              case 'Shift':
356cb93a386Sopenharmony_ci                return;
357cb93a386Sopenharmony_ci            }
358cb93a386Sopenharmony_ci            if (e.ctrlKey) {
359cb93a386Sopenharmony_ci                switch (e.key) {
360cb93a386Sopenharmony_ci                    case 'r': editor.applyStyleToSelection({color:[1,0,0,1]}); return;
361cb93a386Sopenharmony_ci                    case 'g': editor.applyStyleToSelection({color:[0,0.6,0,1]}); return;
362cb93a386Sopenharmony_ci                    case 'u': editor.applyStyleToSelection({color:[0,0,1,1]}); return;
363cb93a386Sopenharmony_ci                    case 'k': editor.applyStyleToSelection({color:[0,0,0,1]}); return;
364cb93a386Sopenharmony_ci
365cb93a386Sopenharmony_ci                    case 'i': editor.applyStyleToSelection({italic:'toggle'}); return;
366cb93a386Sopenharmony_ci                    case 'b': editor.applyStyleToSelection({bold:'toggle'}); return;
367cb93a386Sopenharmony_ci                    case 'l': editor.applyStyleToSelection({underline:'toggle'}); return;
368cb93a386Sopenharmony_ci
369cb93a386Sopenharmony_ci                    case ']': editor.applyStyleToSelection({size_add:1}); return;
370cb93a386Sopenharmony_ci                    case '[': editor.applyStyleToSelection({size_add:-1}); return;
371cb93a386Sopenharmony_ci                }
372cb93a386Sopenharmony_ci            }
373cb93a386Sopenharmony_ci            if (!e.ctrlKey && !e.metaKey) {
374cb93a386Sopenharmony_ci                e.preventDefault(); // at least needed for 'space'
375cb93a386Sopenharmony_ci                editor.insert(e.key);
376cb93a386Sopenharmony_ci            }
377cb93a386Sopenharmony_ci      }
378cb93a386Sopenharmony_ci
379cb93a386Sopenharmony_ci      document.getElementById('para2').addEventListener('pointermove', interact);
380cb93a386Sopenharmony_ci      document.getElementById('para2').addEventListener('pointerdown', interact);
381cb93a386Sopenharmony_ci      document.getElementById('para2').addEventListener('pointerup', interact);
382cb93a386Sopenharmony_ci      document.getElementById('para2').addEventListener('keydown', keyhandler);
383cb93a386Sopenharmony_ci      return surface;
384cb93a386Sopenharmony_ci    }
385cb93a386Sopenharmony_ci
386cb93a386Sopenharmony_ci  function RTShaderAPI1(CanvasKit) {
387cb93a386Sopenharmony_ci    if (!CanvasKit) {
388cb93a386Sopenharmony_ci      return;
389cb93a386Sopenharmony_ci    }
390cb93a386Sopenharmony_ci
391cb93a386Sopenharmony_ci    const surface = CanvasKit.MakeCanvasSurface('rtshader');
392cb93a386Sopenharmony_ci    if (!surface) {
393cb93a386Sopenharmony_ci      console.error('Could not make surface');
394cb93a386Sopenharmony_ci      return;
395cb93a386Sopenharmony_ci    }
396cb93a386Sopenharmony_ci
397cb93a386Sopenharmony_ci    const canvas = surface.getCanvas();
398cb93a386Sopenharmony_ci
399cb93a386Sopenharmony_ci    const effect = CanvasKit.RuntimeEffect.Make(spiralSkSL);
400cb93a386Sopenharmony_ci    const shader = effect.makeShader([
401cb93a386Sopenharmony_ci      0.5,
402cb93a386Sopenharmony_ci      150, 150,
403cb93a386Sopenharmony_ci      0, 1, 0, 1,
404cb93a386Sopenharmony_ci      1, 0, 0, 1], true);
405cb93a386Sopenharmony_ci    const paint = new CanvasKit.Paint();
406cb93a386Sopenharmony_ci    paint.setShader(shader);
407cb93a386Sopenharmony_ci    canvas.drawRect(CanvasKit.LTRBRect(0, 0, 300, 300), paint);
408cb93a386Sopenharmony_ci
409cb93a386Sopenharmony_ci    surface.flush();
410cb93a386Sopenharmony_ci    shader.delete();
411cb93a386Sopenharmony_ci    paint.delete();
412cb93a386Sopenharmony_ci    effect.delete();
413cb93a386Sopenharmony_ci  }
414cb93a386Sopenharmony_ci
415cb93a386Sopenharmony_ci  // RTShader2 demo
416cb93a386Sopenharmony_ci  Promise.all([ckLoaded, loadDog, loadMandrill]).then((values) => {
417cb93a386Sopenharmony_ci    const [CanvasKit, dogData, mandrillData] = values;
418cb93a386Sopenharmony_ci    const dogImg = CanvasKit.MakeImageFromEncoded(dogData);
419cb93a386Sopenharmony_ci    if (!dogImg) {
420cb93a386Sopenharmony_ci      console.error('could not decode dog');
421cb93a386Sopenharmony_ci      return;
422cb93a386Sopenharmony_ci    }
423cb93a386Sopenharmony_ci    const mandrillImg = CanvasKit.MakeImageFromEncoded(mandrillData);
424cb93a386Sopenharmony_ci    if (!mandrillImg) {
425cb93a386Sopenharmony_ci      console.error('could not decode mandrill');
426cb93a386Sopenharmony_ci      return;
427cb93a386Sopenharmony_ci    }
428cb93a386Sopenharmony_ci    const quadrantSize = 150;
429cb93a386Sopenharmony_ci
430cb93a386Sopenharmony_ci    const dogShader = dogImg.makeShaderCubic(
431cb93a386Sopenharmony_ci        CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp,
432cb93a386Sopenharmony_ci        1/3, 1/3,
433cb93a386Sopenharmony_ci        CanvasKit.Matrix.scaled(quadrantSize/dogImg.width(),
434cb93a386Sopenharmony_ci        quadrantSize/dogImg.height()));
435cb93a386Sopenharmony_ci    const mandrillShader = mandrillImg.makeShaderCubic(
436cb93a386Sopenharmony_ci        CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp,
437cb93a386Sopenharmony_ci        1/3, 1/3,
438cb93a386Sopenharmony_ci        CanvasKit.Matrix.scaled(
439cb93a386Sopenharmony_ci            quadrantSize/mandrillImg.width(),
440cb93a386Sopenharmony_ci            quadrantSize/mandrillImg.height()));
441cb93a386Sopenharmony_ci
442cb93a386Sopenharmony_ci    const surface = CanvasKit.MakeCanvasSurface('rtshader2');
443cb93a386Sopenharmony_ci    if (!surface) {
444cb93a386Sopenharmony_ci      console.error('Could not make surface');
445cb93a386Sopenharmony_ci      return;
446cb93a386Sopenharmony_ci    }
447cb93a386Sopenharmony_ci
448cb93a386Sopenharmony_ci    const prog = `
449cb93a386Sopenharmony_ci      uniform shader before_map;
450cb93a386Sopenharmony_ci      uniform shader after_map;
451cb93a386Sopenharmony_ci      uniform shader threshold_map;
452cb93a386Sopenharmony_ci
453cb93a386Sopenharmony_ci      uniform float cutoff;
454cb93a386Sopenharmony_ci      uniform float slope;
455cb93a386Sopenharmony_ci
456cb93a386Sopenharmony_ci      float smooth_cutoff(float x) {
457cb93a386Sopenharmony_ci          x = x * slope + (0.5 - slope * cutoff);
458cb93a386Sopenharmony_ci          return clamp(x, 0, 1);
459cb93a386Sopenharmony_ci      }
460cb93a386Sopenharmony_ci
461cb93a386Sopenharmony_ci      half4 main(float2 xy) {
462cb93a386Sopenharmony_ci          half4 before = before_map.eval(xy);
463cb93a386Sopenharmony_ci          half4 after = after_map.eval(xy);
464cb93a386Sopenharmony_ci
465cb93a386Sopenharmony_ci          float m = smooth_cutoff(threshold_map.eval(xy).r);
466cb93a386Sopenharmony_ci          return mix(before, after, half(m));
467cb93a386Sopenharmony_ci      }`;
468cb93a386Sopenharmony_ci
469cb93a386Sopenharmony_ci    const canvas = surface.getCanvas();
470cb93a386Sopenharmony_ci
471cb93a386Sopenharmony_ci    const thresholdEffect = CanvasKit.RuntimeEffect.Make(prog);
472cb93a386Sopenharmony_ci    const spiralEffect = CanvasKit.RuntimeEffect.Make(spiralSkSL);
473cb93a386Sopenharmony_ci
474cb93a386Sopenharmony_ci    const draw = (x, y, shader) => {
475cb93a386Sopenharmony_ci      const paint = new CanvasKit.Paint();
476cb93a386Sopenharmony_ci      paint.setShader(shader);
477cb93a386Sopenharmony_ci      canvas.save();
478cb93a386Sopenharmony_ci      canvas.translate(x, y);
479cb93a386Sopenharmony_ci      canvas.drawRect(CanvasKit.LTRBRect(0, 0, quadrantSize, quadrantSize), paint);
480cb93a386Sopenharmony_ci      canvas.restore();
481cb93a386Sopenharmony_ci      paint.delete();
482cb93a386Sopenharmony_ci    };
483cb93a386Sopenharmony_ci
484cb93a386Sopenharmony_ci    const offscreenSurface = CanvasKit.MakeSurface(quadrantSize, quadrantSize);
485cb93a386Sopenharmony_ci    const getBlurrySpiralShader = (rad_scale) => {
486cb93a386Sopenharmony_ci      const oCanvas = offscreenSurface.getCanvas();
487cb93a386Sopenharmony_ci
488cb93a386Sopenharmony_ci      const spiralShader = spiralEffect.makeShader([
489cb93a386Sopenharmony_ci      rad_scale,
490cb93a386Sopenharmony_ci      quadrantSize/2, quadrantSize/2,
491cb93a386Sopenharmony_ci      1, 1, 1, 1,
492cb93a386Sopenharmony_ci      0, 0, 0, 1], true);
493cb93a386Sopenharmony_ci
494cb93a386Sopenharmony_ci      const blur = CanvasKit.ImageFilter.MakeBlur(0.1, 0.1, CanvasKit.TileMode.Clamp, null);
495cb93a386Sopenharmony_ci
496cb93a386Sopenharmony_ci      const paint = new CanvasKit.Paint();
497cb93a386Sopenharmony_ci      paint.setShader(spiralShader);
498cb93a386Sopenharmony_ci      paint.setImageFilter(blur);
499cb93a386Sopenharmony_ci      oCanvas.drawRect(CanvasKit.LTRBRect(0, 0, quadrantSize, quadrantSize), paint);
500cb93a386Sopenharmony_ci
501cb93a386Sopenharmony_ci      paint.delete();
502cb93a386Sopenharmony_ci      blur.delete();
503cb93a386Sopenharmony_ci      spiralShader.delete();
504cb93a386Sopenharmony_ci      return offscreenSurface.makeImageSnapshot()
505cb93a386Sopenharmony_ci                             .makeShaderCubic(CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp,
506cb93a386Sopenharmony_ci                             1/3, 1/3);
507cb93a386Sopenharmony_ci
508cb93a386Sopenharmony_ci    };
509cb93a386Sopenharmony_ci
510cb93a386Sopenharmony_ci    const drawFrame = () => {
511cb93a386Sopenharmony_ci      surface.requestAnimationFrame(drawFrame);
512cb93a386Sopenharmony_ci      const thresholdShader = getBlurrySpiralShader(Math.sin(Date.now() / 5000) / 2);
513cb93a386Sopenharmony_ci
514cb93a386Sopenharmony_ci      const blendShader = thresholdEffect.makeShaderWithChildren(
515cb93a386Sopenharmony_ci        [0.5, 10],
516cb93a386Sopenharmony_ci        true, [dogShader, mandrillShader, thresholdShader]);
517cb93a386Sopenharmony_ci      draw(0, 0, blendShader);
518cb93a386Sopenharmony_ci      draw(quadrantSize, 0, thresholdShader);
519cb93a386Sopenharmony_ci      draw(0, quadrantSize, dogShader);
520cb93a386Sopenharmony_ci      draw(quadrantSize, quadrantSize, mandrillShader);
521cb93a386Sopenharmony_ci
522cb93a386Sopenharmony_ci      blendShader.delete();
523cb93a386Sopenharmony_ci    };
524cb93a386Sopenharmony_ci
525cb93a386Sopenharmony_ci    surface.requestAnimationFrame(drawFrame);
526cb93a386Sopenharmony_ci  });
527cb93a386Sopenharmony_ci
528cb93a386Sopenharmony_ci  function SkpExample(CanvasKit) {
529cb93a386Sopenharmony_ci    if (!CanvasKit) {
530cb93a386Sopenharmony_ci      return;
531cb93a386Sopenharmony_ci    }
532cb93a386Sopenharmony_ci
533cb93a386Sopenharmony_ci    const surface = CanvasKit.MakeSWCanvasSurface('skp');
534cb93a386Sopenharmony_ci    if (!surface) {
535cb93a386Sopenharmony_ci      console.error('Could not make surface');
536cb93a386Sopenharmony_ci      return;
537cb93a386Sopenharmony_ci    }
538cb93a386Sopenharmony_ci
539cb93a386Sopenharmony_ci    const paint = new CanvasKit.Paint();
540cb93a386Sopenharmony_ci    paint.setColor(CanvasKit.RED);
541cb93a386Sopenharmony_ci
542cb93a386Sopenharmony_ci    const textPaint = new CanvasKit.Paint();
543cb93a386Sopenharmony_ci    const textFont = new CanvasKit.Font(null, 20);
544cb93a386Sopenharmony_ci    const pr = new CanvasKit.PictureRecorder();
545cb93a386Sopenharmony_ci    const skpCanvas = pr.beginRecording(CanvasKit.LTRBRect(0, 0, 200, 200));
546cb93a386Sopenharmony_ci    skpCanvas.drawRect(CanvasKit.LTRBRect(10, 10, 50, 50), paint);
547cb93a386Sopenharmony_ci    skpCanvas.drawText('If you see this, CanvasKit loaded!!', 5, 100, textPaint, textFont);
548cb93a386Sopenharmony_ci
549cb93a386Sopenharmony_ci    const pic = pr.finishRecordingAsPicture();
550cb93a386Sopenharmony_ci    const skpData = pic.serialize();
551cb93a386Sopenharmony_ci
552cb93a386Sopenharmony_ci    paint.delete();
553cb93a386Sopenharmony_ci    pr.delete();
554cb93a386Sopenharmony_ci
555cb93a386Sopenharmony_ci    const deserialized = CanvasKit.MakePicture(skpData);
556cb93a386Sopenharmony_ci
557cb93a386Sopenharmony_ci    function drawFrame(canvas) {
558cb93a386Sopenharmony_ci      if (deserialized) {
559cb93a386Sopenharmony_ci        canvas.drawPicture(deserialized);
560cb93a386Sopenharmony_ci      } else {
561cb93a386Sopenharmony_ci        canvas.drawText('SKP did not deserialize', 5, 100, textPaint, textFont);
562cb93a386Sopenharmony_ci      }
563cb93a386Sopenharmony_ci    }
564cb93a386Sopenharmony_ci    surface.drawOnce(drawFrame);
565cb93a386Sopenharmony_ci    textPaint.delete();
566cb93a386Sopenharmony_ci    textFont.delete();
567cb93a386Sopenharmony_ci  }
568cb93a386Sopenharmony_ci
569cb93a386Sopenharmony_ci  // Shows a hidden message by rotating all the characters in a kind of way that makes you
570cb93a386Sopenharmony_ci  // search with your mouse.
571cb93a386Sopenharmony_ci  function GlyphGame(canvas, robotoData) {
572cb93a386Sopenharmony_ci    const surface = CanvasKit.MakeCanvasSurface('glyphgame');
573cb93a386Sopenharmony_ci    if (!surface) {
574cb93a386Sopenharmony_ci      console.error('Could not make surface');
575cb93a386Sopenharmony_ci      return;
576cb93a386Sopenharmony_ci    }
577cb93a386Sopenharmony_ci    const sizeX = document.getElementById('glyphgame').width;
578cb93a386Sopenharmony_ci    const sizeY = document.getElementById('glyphgame').height;
579cb93a386Sopenharmony_ci    const halfDim = Math.min(sizeX, sizeY) / 2;
580cb93a386Sopenharmony_ci    const margin = 50;
581cb93a386Sopenharmony_ci    const marginTop = 25;
582cb93a386Sopenharmony_ci    let rotX = 0; //  expected to be updated in interact()
583cb93a386Sopenharmony_ci    let rotY = 0;
584cb93a386Sopenharmony_ci    let pointer = [500, 450];
585cb93a386Sopenharmony_ci    const radPerPixel = 0.005; // radians of subject rotation per pixel distance moved by mouse.
586cb93a386Sopenharmony_ci
587cb93a386Sopenharmony_ci    const camAngle = Math.PI / 12;
588cb93a386Sopenharmony_ci    const cam = {
589cb93a386Sopenharmony_ci      'eye'  : [0, 0, 1 / Math.tan(camAngle/2) - 1],
590cb93a386Sopenharmony_ci      'coa'  : [0, 0, 0],
591cb93a386Sopenharmony_ci      'up'   : [0, 1, 0],
592cb93a386Sopenharmony_ci      'near' : 0.02,
593cb93a386Sopenharmony_ci      'far'  : 4,
594cb93a386Sopenharmony_ci      'angle': camAngle,
595cb93a386Sopenharmony_ci    };
596cb93a386Sopenharmony_ci
597cb93a386Sopenharmony_ci    let lastImage = null;
598cb93a386Sopenharmony_ci
599cb93a386Sopenharmony_ci    const fontMgr = CanvasKit.FontMgr.FromData([robotoData]);
600cb93a386Sopenharmony_ci
601cb93a386Sopenharmony_ci    const paraStyle = new CanvasKit.ParagraphStyle({
602cb93a386Sopenharmony_ci        textStyle: {
603cb93a386Sopenharmony_ci            color: CanvasKit.Color(105, 56, 16), // brown
604cb93a386Sopenharmony_ci            fontFamilies: ['Roboto'],
605cb93a386Sopenharmony_ci            fontSize: 28,
606cb93a386Sopenharmony_ci        },
607cb93a386Sopenharmony_ci        textAlign: CanvasKit.TextAlign.Left,
608cb93a386Sopenharmony_ci    });
609cb93a386Sopenharmony_ci    const hStyle = CanvasKit.RectHeightStyle.Max;
610cb93a386Sopenharmony_ci    const wStyle = CanvasKit.RectWidthStyle.Tight;
611cb93a386Sopenharmony_ci
612cb93a386Sopenharmony_ci    const quotes = [
613cb93a386Sopenharmony_ci      'Some activities superficially familiar to you are merely stupid and should be avoided for your safety, although they are not illegal as such. These include: giving your bank account details to the son of the Nigerian Minister of Finance; buying title to bridges, skyscrapers, spacecraft, planets, or other real assets; murder; selling your identity; and entering into financial contracts with entities running Economics 2.0 or higher.',
614cb93a386Sopenharmony_ci      // Charles Stross - Accelerando
615cb93a386Sopenharmony_ci      'If only there were evil people somewhere insidiously committing evil deeds, and it were necessary only to separate them from the rest of us and destroy them. But the line dividing good and evil cuts through the heart of every human being. And who is willing to destroy a piece of his own heart?',
616cb93a386Sopenharmony_ci      // Aleksandr Solzhenitsyn - The Gulag Archipelago
617cb93a386Sopenharmony_ci      'There is one metaphor of which the moderns are very fond; they are always saying, “You can’t put the clock back.” The simple and obvious answer is “You can.” A clock, being a piece of human construction, can be restored by the human finger to any figure or hour. In the same way society, being a piece of human construction, can be reconstructed upon any plan that has ever existed.',
618cb93a386Sopenharmony_ci      // G. K. Chesterton - What's Wrong With The World?
619cb93a386Sopenharmony_ci    ];
620cb93a386Sopenharmony_ci
621cb93a386Sopenharmony_ci    // pick one at random
622cb93a386Sopenharmony_ci    const text = quotes[Math.floor(Math.random()*3)];
623cb93a386Sopenharmony_ci    const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
624cb93a386Sopenharmony_ci    builder.addText(text);
625cb93a386Sopenharmony_ci    const paragraph = builder.build();
626cb93a386Sopenharmony_ci    const font = new CanvasKit.Font(null, 18);
627cb93a386Sopenharmony_ci    // wrap the text to a given width.
628cb93a386Sopenharmony_ci    paragraph.layout(sizeX - margin*2);
629cb93a386Sopenharmony_ci
630cb93a386Sopenharmony_ci    // to rotate every glyph individually, calculate the bounding rect of each one,
631cb93a386Sopenharmony_ci    // construct an array of rects and paragraphs that would draw each glyph individually.
632cb93a386Sopenharmony_ci    const letters = Array(text.length);
633cb93a386Sopenharmony_ci    for (let i = 0; i < text.length; i++) {
634cb93a386Sopenharmony_ci      const r = paragraph.getRectsForRange(i, i+1, hStyle, wStyle)[0];
635cb93a386Sopenharmony_ci      // The character is drawn with drawParagraph so we can pass the paraStyle,
636cb93a386Sopenharmony_ci      // and have our character be the exact size and shape the paragraph expected
637cb93a386Sopenharmony_ci      // when it wrapped the text. canvas.drawText wouldn't cut it.
638cb93a386Sopenharmony_ci      const tmpbuilder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
639cb93a386Sopenharmony_ci      tmpbuilder.addText(text[i]);
640cb93a386Sopenharmony_ci      const para = tmpbuilder.build();
641cb93a386Sopenharmony_ci      para.layout(100);
642cb93a386Sopenharmony_ci      letters[i] = {
643cb93a386Sopenharmony_ci        'r': r,
644cb93a386Sopenharmony_ci        'para': para,
645cb93a386Sopenharmony_ci      };
646cb93a386Sopenharmony_ci    }
647cb93a386Sopenharmony_ci
648cb93a386Sopenharmony_ci    function drawFrame(canvas) {
649cb93a386Sopenharmony_ci      // persistence of vision effect is done by drawing the past frame as an image,
650cb93a386Sopenharmony_ci      // then covering with semitransparent background color.
651cb93a386Sopenharmony_ci      if (lastImage) {
652cb93a386Sopenharmony_ci        canvas.drawImage(lastImage, 0, 0, null);
653cb93a386Sopenharmony_ci        canvas.drawColor(CanvasKit.Color(171, 244, 255, 0.1)); // sky blue, almost transparent
654cb93a386Sopenharmony_ci      } else {
655cb93a386Sopenharmony_ci        canvas.clear(CanvasKit.Color(171, 244, 255)); // sky blue, opaque
656cb93a386Sopenharmony_ci      }
657cb93a386Sopenharmony_ci      canvas.save();
658cb93a386Sopenharmony_ci      // Set up 3D view enviroment
659cb93a386Sopenharmony_ci      canvas.concat(CanvasKit.M44.setupCamera(
660cb93a386Sopenharmony_ci        CanvasKit.LTRBRect(0, 0, sizeX, sizeY), halfDim, cam));
661cb93a386Sopenharmony_ci
662cb93a386Sopenharmony_ci      // Rotate the whole paragraph as a unit.
663cb93a386Sopenharmony_ci      const paraRotPoint = [halfDim, halfDim, 1];
664cb93a386Sopenharmony_ci      canvas.concat(CanvasKit.M44.multiply(
665cb93a386Sopenharmony_ci        CanvasKit.M44.translated(paraRotPoint),
666cb93a386Sopenharmony_ci        CanvasKit.M44.rotated([0,1,0], rotX),
667cb93a386Sopenharmony_ci        CanvasKit.M44.rotated([1,0,0], rotY * 0.2),
668cb93a386Sopenharmony_ci        CanvasKit.M44.translated(CanvasKit.Vector.mulScalar(paraRotPoint, -1)),
669cb93a386Sopenharmony_ci      ));
670cb93a386Sopenharmony_ci
671cb93a386Sopenharmony_ci      // Rotate every glyph in the paragraph individually.
672cb93a386Sopenharmony_ci      let i = 0;
673cb93a386Sopenharmony_ci      for (const letter of letters) {
674cb93a386Sopenharmony_ci        canvas.save();
675cb93a386Sopenharmony_ci        let r = letter['r'];
676cb93a386Sopenharmony_ci        // rotate about the center of the glyph's rect.
677cb93a386Sopenharmony_ci        rotationPoint = [
678cb93a386Sopenharmony_ci          margin + r[rectLeft] + (r[rectRight] - r[rectLeft]) / 2,
679cb93a386Sopenharmony_ci          marginTop + r[rectTop] + (r[rectBottom] - r[rectTop]) / 2,
680cb93a386Sopenharmony_ci          0
681cb93a386Sopenharmony_ci        ];
682cb93a386Sopenharmony_ci        distanceFromPointer = CanvasKit.Vector.dist(pointer, rotationPoint.slice(0, 2));
683cb93a386Sopenharmony_ci        // Rotate more around the Y-axis depending on the glyph's distance from the pointer.
684cb93a386Sopenharmony_ci        canvas.concat(CanvasKit.M44.multiply(
685cb93a386Sopenharmony_ci          CanvasKit.M44.translated(rotationPoint),
686cb93a386Sopenharmony_ci          // note that I'm rotating around the x axis first, undoing some of the rotation done to the whole
687cb93a386Sopenharmony_ci          // paragraph above, where x came second. If I rotated y first, a lot of letters would end up
688cb93a386Sopenharmony_ci          // upside down, which is a bit too hard to unscramble.
689cb93a386Sopenharmony_ci          CanvasKit.M44.rotated([1,0,0], rotY * -0.6),
690cb93a386Sopenharmony_ci          CanvasKit.M44.rotated([0,1,0], distanceFromPointer * -0.035),
691cb93a386Sopenharmony_ci          CanvasKit.M44.translated(CanvasKit.Vector.mulScalar(rotationPoint, -1)),
692cb93a386Sopenharmony_ci        ));
693cb93a386Sopenharmony_ci        canvas.drawParagraph(letter['para'], margin + r[rectLeft], marginTop + r[rectTop]);
694cb93a386Sopenharmony_ci        i++;
695cb93a386Sopenharmony_ci        canvas.restore();
696cb93a386Sopenharmony_ci      }
697cb93a386Sopenharmony_ci      canvas.restore();
698cb93a386Sopenharmony_ci      lastImage = surface.makeImageSnapshot();
699cb93a386Sopenharmony_ci    }
700cb93a386Sopenharmony_ci
701cb93a386Sopenharmony_ci    function interact(e) {
702cb93a386Sopenharmony_ci      pointer = [e.offsetX, e.offsetY]
703cb93a386Sopenharmony_ci      rotX = (pointer[0] - halfDim) * radPerPixel;
704cb93a386Sopenharmony_ci      rotY = (pointer[1] - halfDim) * radPerPixel * -1;
705cb93a386Sopenharmony_ci      surface.requestAnimationFrame(drawFrame);
706cb93a386Sopenharmony_ci    };
707cb93a386Sopenharmony_ci
708cb93a386Sopenharmony_ci    document.getElementById('glyphgame').addEventListener('pointermove', interact);
709cb93a386Sopenharmony_ci    surface.requestAnimationFrame(drawFrame);
710cb93a386Sopenharmony_ci  }
711cb93a386Sopenharmony_ci
712cb93a386Sopenharmony_ci  function ColorSupport(CanvasKit) {
713cb93a386Sopenharmony_ci    const surface = CanvasKit.MakeCanvasSurface('colorsupport', CanvasKit.ColorSpace.ADOBE_RGB);
714cb93a386Sopenharmony_ci    if (!surface) {
715cb93a386Sopenharmony_ci      console.error('Could not make surface');
716cb93a386Sopenharmony_ci      return;
717cb93a386Sopenharmony_ci    }
718cb93a386Sopenharmony_ci    const canvas = surface.getCanvas();
719cb93a386Sopenharmony_ci
720cb93a386Sopenharmony_ci    // If the surface is correctly initialized with a higher bit depth color type,
721cb93a386Sopenharmony_ci    // And chrome is compositing it into a buffer with the P3 color space,
722cb93a386Sopenharmony_ci    // then the inner round rect should be distinct and less saturated than the full red background.
723cb93a386Sopenharmony_ci    // Even if the monitor it is viewed on cannot accurately represent that color space.
724cb93a386Sopenharmony_ci
725cb93a386Sopenharmony_ci    let red = CanvasKit.Color4f(1, 0, 0, 1);
726cb93a386Sopenharmony_ci    let paint = new CanvasKit.Paint();
727cb93a386Sopenharmony_ci    paint.setColor(red, CanvasKit.ColorSpace.ADOBE_RGB);
728cb93a386Sopenharmony_ci    canvas.drawPaint(paint);
729cb93a386Sopenharmony_ci    paint.setColor(red, CanvasKit.ColorSpace.DISPLAY_P3);
730cb93a386Sopenharmony_ci    canvas.drawRRect(CanvasKit.RRectXY([50, 50, 250, 250], 30, 30), paint);
731cb93a386Sopenharmony_ci    paint.setColor(red, CanvasKit.ColorSpace.SRGB);
732cb93a386Sopenharmony_ci    canvas.drawRRect(CanvasKit.RRectXY([100, 100, 200, 200], 30, 30), paint);
733cb93a386Sopenharmony_ci
734cb93a386Sopenharmony_ci    surface.flush();
735cb93a386Sopenharmony_ci    surface.delete();
736cb93a386Sopenharmony_ci  }
737cb93a386Sopenharmony_ci</script>
738