xref: /aosp_15_r20/external/skia/modules/canvaskit/wasm_tools/viewer.html (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker<!DOCTYPE html>
2*c8dee2aaSAndroid Build Coastguard Worker<title>CanvasKit Viewer (Skia via Web Assembly)</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<style>
7*c8dee2aaSAndroid Build Coastguard Worker  html, body {
8*c8dee2aaSAndroid Build Coastguard Worker    margin: 0;
9*c8dee2aaSAndroid Build Coastguard Worker    padding: 0;
10*c8dee2aaSAndroid Build Coastguard Worker  }
11*c8dee2aaSAndroid Build Coastguard Worker</style>
12*c8dee2aaSAndroid Build Coastguard Worker
13*c8dee2aaSAndroid Build Coastguard Worker<canvas id=viewer_canvas></canvas>
14*c8dee2aaSAndroid Build Coastguard Worker
15*c8dee2aaSAndroid Build Coastguard Worker<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
16*c8dee2aaSAndroid Build Coastguard Worker
17*c8dee2aaSAndroid Build Coastguard Worker<script type="text/javascript" charset="utf-8">
18*c8dee2aaSAndroid Build Coastguard Worker  const flags = {};
19*c8dee2aaSAndroid Build Coastguard Worker  for (const pair of location.hash.substring(1).split(',')) {
20*c8dee2aaSAndroid Build Coastguard Worker    // Parse "values" as an array in case the value has a colon (e.g., "slide:http://...").
21*c8dee2aaSAndroid Build Coastguard Worker    const [key, ...values] = pair.split(':');
22*c8dee2aaSAndroid Build Coastguard Worker    flags[key] = values.join(':');
23*c8dee2aaSAndroid Build Coastguard Worker  }
24*c8dee2aaSAndroid Build Coastguard Worker  window.onhashchange = function() {
25*c8dee2aaSAndroid Build Coastguard Worker    location.reload();
26*c8dee2aaSAndroid Build Coastguard Worker  };
27*c8dee2aaSAndroid Build Coastguard Worker
28*c8dee2aaSAndroid Build Coastguard Worker  CanvasKitInit({
29*c8dee2aaSAndroid Build Coastguard Worker    locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
30*c8dee2aaSAndroid Build Coastguard Worker  }).then((CK) => {
31*c8dee2aaSAndroid Build Coastguard Worker    if (!CK) {
32*c8dee2aaSAndroid Build Coastguard Worker      throw 'CanvasKit not available.';
33*c8dee2aaSAndroid Build Coastguard Worker    }
34*c8dee2aaSAndroid Build Coastguard Worker    LoadSlide(CK);
35*c8dee2aaSAndroid Build Coastguard Worker  });
36*c8dee2aaSAndroid Build Coastguard Worker
37*c8dee2aaSAndroid Build Coastguard Worker  function LoadSlide(CanvasKit) {
38*c8dee2aaSAndroid Build Coastguard Worker    if (!CanvasKit.MakeSlide || !CanvasKit.MakeSkpSlide || !CanvasKit.MakeSvgSlide) {
39*c8dee2aaSAndroid Build Coastguard Worker      throw 'Not compiled with Viewer.';
40*c8dee2aaSAndroid Build Coastguard Worker    }
41*c8dee2aaSAndroid Build Coastguard Worker    const slideName = flags.slide || 'PathText';
42*c8dee2aaSAndroid Build Coastguard Worker    if (slideName.endsWith('.skp') || slideName.endsWith('.svg')) {
43*c8dee2aaSAndroid Build Coastguard Worker      fetch(slideName).then(function(response) {
44*c8dee2aaSAndroid Build Coastguard Worker        if (response.status != 200) {
45*c8dee2aaSAndroid Build Coastguard Worker            throw 'Error fetching ' + slideName;
46*c8dee2aaSAndroid Build Coastguard Worker        }
47*c8dee2aaSAndroid Build Coastguard Worker        if (slideName.endsWith('.skp')) {
48*c8dee2aaSAndroid Build Coastguard Worker          response.arrayBuffer().then((data) => ViewerMain(
49*c8dee2aaSAndroid Build Coastguard Worker              CanvasKit, CanvasKit.MakeSkpSlide(slideName, data)));
50*c8dee2aaSAndroid Build Coastguard Worker        } else {
51*c8dee2aaSAndroid Build Coastguard Worker          response.text().then((text) => ViewerMain(
52*c8dee2aaSAndroid Build Coastguard Worker              CanvasKit, CanvasKit.MakeSvgSlide(slideName, text)));
53*c8dee2aaSAndroid Build Coastguard Worker        }
54*c8dee2aaSAndroid Build Coastguard Worker      });
55*c8dee2aaSAndroid Build Coastguard Worker    } else {
56*c8dee2aaSAndroid Build Coastguard Worker      ViewerMain(CanvasKit, CanvasKit.MakeSlide(slideName));
57*c8dee2aaSAndroid Build Coastguard Worker    }
58*c8dee2aaSAndroid Build Coastguard Worker  }
59*c8dee2aaSAndroid Build Coastguard Worker
60*c8dee2aaSAndroid Build Coastguard Worker  function ViewerMain(CanvasKit, slide) {
61*c8dee2aaSAndroid Build Coastguard Worker    if (!slide) {
62*c8dee2aaSAndroid Build Coastguard Worker      throw 'Failed to parse slide.'
63*c8dee2aaSAndroid Build Coastguard Worker    }
64*c8dee2aaSAndroid Build Coastguard Worker    const width = window.innerWidth;
65*c8dee2aaSAndroid Build Coastguard Worker    const height = window.innerHeight;
66*c8dee2aaSAndroid Build Coastguard Worker    const htmlCanvas = document.getElementById('viewer_canvas');
67*c8dee2aaSAndroid Build Coastguard Worker    htmlCanvas.width = width;
68*c8dee2aaSAndroid Build Coastguard Worker    htmlCanvas.height = height;
69*c8dee2aaSAndroid Build Coastguard Worker    slide.load(width, height);
70*c8dee2aaSAndroid Build Coastguard Worker
71*c8dee2aaSAndroid Build Coastguard Worker    // For the msaa flag, only check if the key exists in flags. That way we don't need to assign it
72*c8dee2aaSAndroid Build Coastguard Worker    // a value in the location hash. i.e.,:  http://.../viewer.html#msaa
73*c8dee2aaSAndroid Build Coastguard Worker    const doMSAA = ('msaa' in flags);
74*c8dee2aaSAndroid Build Coastguard Worker    // Create the WebGL context with our desired attribs before calling MakeWebGLCanvasSurface.
75*c8dee2aaSAndroid Build Coastguard Worker    CanvasKit.GetWebGLContext(htmlCanvas, {antialias: doMSAA});
76*c8dee2aaSAndroid Build Coastguard Worker    const surface = CanvasKit.MakeWebGLCanvasSurface(htmlCanvas, null);
77*c8dee2aaSAndroid Build Coastguard Worker    if (!surface) {
78*c8dee2aaSAndroid Build Coastguard Worker      throw 'Could not make canvas surface';
79*c8dee2aaSAndroid Build Coastguard Worker    }
80*c8dee2aaSAndroid Build Coastguard Worker    if (doMSAA && surface.sampleCnt() <= 1) {
81*c8dee2aaSAndroid Build Coastguard Worker      // We requested antialias on the canvas but did not get MSAA. Since we don't know what type of
82*c8dee2aaSAndroid Build Coastguard Worker      // AA is in use right now (if any), this surface is unusable.
83*c8dee2aaSAndroid Build Coastguard Worker      throw 'MSAA rendering to the on-screen canvas is not supported. ' +
84*c8dee2aaSAndroid Build Coastguard Worker            'Please try again without MSAA.';
85*c8dee2aaSAndroid Build Coastguard Worker    }
86*c8dee2aaSAndroid Build Coastguard Worker
87*c8dee2aaSAndroid Build Coastguard Worker    window.onmousedown = (event) => (event.button === 0) && Mouse(CanvasKit.InputState.Down, event);
88*c8dee2aaSAndroid Build Coastguard Worker    window.onmouseup = (event) => (event.button === 0) && Mouse(CanvasKit.InputState.Up, event);
89*c8dee2aaSAndroid Build Coastguard Worker    window.onmousemove = (event) => Mouse(CanvasKit.InputState.Move, event);
90*c8dee2aaSAndroid Build Coastguard Worker    window.onkeypress = function(event) {
91*c8dee2aaSAndroid Build Coastguard Worker      if (slide.onChar(event.keyCode)) {
92*c8dee2aaSAndroid Build Coastguard Worker        ScheduleDraw();
93*c8dee2aaSAndroid Build Coastguard Worker        return false;
94*c8dee2aaSAndroid Build Coastguard Worker      } else {
95*c8dee2aaSAndroid Build Coastguard Worker        switch (event.keyCode) {
96*c8dee2aaSAndroid Build Coastguard Worker          case 's'.charCodeAt(0):
97*c8dee2aaSAndroid Build Coastguard Worker            // 's' is the magic key in the native viewer app that turns on FPS monitoring. Toggle
98*c8dee2aaSAndroid Build Coastguard Worker            // forced animation when it is pressed in order to get fps logs.
99*c8dee2aaSAndroid Build Coastguard Worker            // HINT: Launch chrome with --disable-frame-rate-limit and --disable-gpu-vsync in order
100*c8dee2aaSAndroid Build Coastguard Worker            // to measure frame rates above 60.
101*c8dee2aaSAndroid Build Coastguard Worker            ScheduleDraw.forceAnimation = !ScheduleDraw.forceAnimation;
102*c8dee2aaSAndroid Build Coastguard Worker            ScheduleDraw();
103*c8dee2aaSAndroid Build Coastguard Worker            break;
104*c8dee2aaSAndroid Build Coastguard Worker        }
105*c8dee2aaSAndroid Build Coastguard Worker      }
106*c8dee2aaSAndroid Build Coastguard Worker      return true;
107*c8dee2aaSAndroid Build Coastguard Worker    }
108*c8dee2aaSAndroid Build Coastguard Worker    window.onkeydown = function(event) {
109*c8dee2aaSAndroid Build Coastguard Worker      const upArrowCode = 38;
110*c8dee2aaSAndroid Build Coastguard Worker      if (event.keyCode === upArrowCode) {
111*c8dee2aaSAndroid Build Coastguard Worker        ScaleCanvas((event.shiftKey) ? Infinity : 1.1);
112*c8dee2aaSAndroid Build Coastguard Worker        return false;
113*c8dee2aaSAndroid Build Coastguard Worker      }
114*c8dee2aaSAndroid Build Coastguard Worker      const downArrowCode = 40;
115*c8dee2aaSAndroid Build Coastguard Worker      if (event.keyCode === downArrowCode) {
116*c8dee2aaSAndroid Build Coastguard Worker        ScaleCanvas((event.shiftKey) ? 0 : 1/1.1);
117*c8dee2aaSAndroid Build Coastguard Worker        return false;
118*c8dee2aaSAndroid Build Coastguard Worker      }
119*c8dee2aaSAndroid Build Coastguard Worker      return true;
120*c8dee2aaSAndroid Build Coastguard Worker    }
121*c8dee2aaSAndroid Build Coastguard Worker
122*c8dee2aaSAndroid Build Coastguard Worker    let [canvasScale, canvasTranslateX, canvasTranslateY] = [1, 0, 0];
123*c8dee2aaSAndroid Build Coastguard Worker    function ScaleCanvas(factor) {
124*c8dee2aaSAndroid Build Coastguard Worker      factor = Math.min(Math.max(1/(5*canvasScale), factor), 5/canvasScale);
125*c8dee2aaSAndroid Build Coastguard Worker      canvasTranslateX *= factor;
126*c8dee2aaSAndroid Build Coastguard Worker      canvasTranslateY *= factor;
127*c8dee2aaSAndroid Build Coastguard Worker      canvasScale *= factor;
128*c8dee2aaSAndroid Build Coastguard Worker      ScheduleDraw();
129*c8dee2aaSAndroid Build Coastguard Worker    }
130*c8dee2aaSAndroid Build Coastguard Worker    function TranslateCanvas(dx, dy) {
131*c8dee2aaSAndroid Build Coastguard Worker      canvasTranslateX += dx;
132*c8dee2aaSAndroid Build Coastguard Worker      canvasTranslateY += dy;
133*c8dee2aaSAndroid Build Coastguard Worker      ScheduleDraw();
134*c8dee2aaSAndroid Build Coastguard Worker    }
135*c8dee2aaSAndroid Build Coastguard Worker
136*c8dee2aaSAndroid Build Coastguard Worker    function Mouse(state, event) {
137*c8dee2aaSAndroid Build Coastguard Worker      let modifierKeys = CanvasKit.ModifierKey.None;
138*c8dee2aaSAndroid Build Coastguard Worker      if (event.shiftKey) {
139*c8dee2aaSAndroid Build Coastguard Worker        modifierKeys |= CanvasKit.ModifierKey.Shift;
140*c8dee2aaSAndroid Build Coastguard Worker      }
141*c8dee2aaSAndroid Build Coastguard Worker      if (event.altKey) {
142*c8dee2aaSAndroid Build Coastguard Worker        modifierKeys |= CanvasKit.ModifierKey.Option;
143*c8dee2aaSAndroid Build Coastguard Worker      }
144*c8dee2aaSAndroid Build Coastguard Worker      if (event.ctrlKey) {
145*c8dee2aaSAndroid Build Coastguard Worker        modifierKeys |= CanvasKit.ModifierKey.Ctrl;
146*c8dee2aaSAndroid Build Coastguard Worker      }
147*c8dee2aaSAndroid Build Coastguard Worker      if (event.metaKey) {
148*c8dee2aaSAndroid Build Coastguard Worker        modifierKeys |= CanvasKit.ModifierKey.Command;
149*c8dee2aaSAndroid Build Coastguard Worker      }
150*c8dee2aaSAndroid Build Coastguard Worker      let [dx, dy] = [event.pageX - this.lastX, event.pageY - this.lastY];
151*c8dee2aaSAndroid Build Coastguard Worker      this.lastX = event.pageX;
152*c8dee2aaSAndroid Build Coastguard Worker      this.lastY = event.pageY;
153*c8dee2aaSAndroid Build Coastguard Worker      if (slide.onMouse(event.pageX, event.pageY, state, modifierKeys)) {
154*c8dee2aaSAndroid Build Coastguard Worker        ScheduleDraw();
155*c8dee2aaSAndroid Build Coastguard Worker        return false;
156*c8dee2aaSAndroid Build Coastguard Worker      } else if (event.buttons & 1) {  // Left-button pressed.
157*c8dee2aaSAndroid Build Coastguard Worker        TranslateCanvas(dx, dy);
158*c8dee2aaSAndroid Build Coastguard Worker        return false;
159*c8dee2aaSAndroid Build Coastguard Worker      }
160*c8dee2aaSAndroid Build Coastguard Worker      return true;
161*c8dee2aaSAndroid Build Coastguard Worker    }
162*c8dee2aaSAndroid Build Coastguard Worker
163*c8dee2aaSAndroid Build Coastguard Worker    function ScheduleDraw() {
164*c8dee2aaSAndroid Build Coastguard Worker      if (ScheduleDraw.hasPendingAnimationRequest) {
165*c8dee2aaSAndroid Build Coastguard Worker        // It's possible for this ScheduleDraw() method to be called multiple times before an
166*c8dee2aaSAndroid Build Coastguard Worker        // animation callback actually gets invoked. Make sure we only ever have one single
167*c8dee2aaSAndroid Build Coastguard Worker        // requestAnimationFrame scheduled at a time, because otherwise we can get stuck in a
168*c8dee2aaSAndroid Build Coastguard Worker        // position where multiple callbacks are coming in on a single compositing frame, and then
169*c8dee2aaSAndroid Build Coastguard Worker        // rescheduling multiple more for the next frame.
170*c8dee2aaSAndroid Build Coastguard Worker        return;
171*c8dee2aaSAndroid Build Coastguard Worker      }
172*c8dee2aaSAndroid Build Coastguard Worker      ScheduleDraw.hasPendingAnimationRequest = true;
173*c8dee2aaSAndroid Build Coastguard Worker      surface.requestAnimationFrame((canvas) => {
174*c8dee2aaSAndroid Build Coastguard Worker        ScheduleDraw.hasPendingAnimationRequest = false;
175*c8dee2aaSAndroid Build Coastguard Worker
176*c8dee2aaSAndroid Build Coastguard Worker        canvas.save();
177*c8dee2aaSAndroid Build Coastguard Worker        canvas.translate(canvasTranslateX, canvasTranslateY);
178*c8dee2aaSAndroid Build Coastguard Worker        canvas.scale(canvasScale, canvasScale);
179*c8dee2aaSAndroid Build Coastguard Worker        canvas.clear(CanvasKit.WHITE);
180*c8dee2aaSAndroid Build Coastguard Worker        slide.draw(canvas);
181*c8dee2aaSAndroid Build Coastguard Worker        canvas.restore();
182*c8dee2aaSAndroid Build Coastguard Worker
183*c8dee2aaSAndroid Build Coastguard Worker        // HINT: Launch chrome with --disable-frame-rate-limit and --disable-gpu-vsync in order to
184*c8dee2aaSAndroid Build Coastguard Worker        // allow this to go faster than 60fps.
185*c8dee2aaSAndroid Build Coastguard Worker        const ms = (ScheduleDraw.fps && ScheduleDraw.fps.markFrameComplete()) ||
186*c8dee2aaSAndroid Build Coastguard Worker                   window.performance.now();
187*c8dee2aaSAndroid Build Coastguard Worker        if (slide.animate(ms * 1e6) || ScheduleDraw.forceAnimation) {
188*c8dee2aaSAndroid Build Coastguard Worker          ScheduleDraw.fps = ScheduleDraw.fps || new FPSMeter(ms);
189*c8dee2aaSAndroid Build Coastguard Worker          ScheduleDraw();
190*c8dee2aaSAndroid Build Coastguard Worker        } else {
191*c8dee2aaSAndroid Build Coastguard Worker          delete ScheduleDraw.fps;
192*c8dee2aaSAndroid Build Coastguard Worker        }
193*c8dee2aaSAndroid Build Coastguard Worker      });
194*c8dee2aaSAndroid Build Coastguard Worker    }
195*c8dee2aaSAndroid Build Coastguard Worker
196*c8dee2aaSAndroid Build Coastguard Worker    ScheduleDraw();
197*c8dee2aaSAndroid Build Coastguard Worker  }
198*c8dee2aaSAndroid Build Coastguard Worker
199*c8dee2aaSAndroid Build Coastguard Worker  function FPSMeter(startMs) {
200*c8dee2aaSAndroid Build Coastguard Worker    this.frames = 0;
201*c8dee2aaSAndroid Build Coastguard Worker    this.startMs = startMs;
202*c8dee2aaSAndroid Build Coastguard Worker    this.markFrameComplete = () => {
203*c8dee2aaSAndroid Build Coastguard Worker      ++this.frames;
204*c8dee2aaSAndroid Build Coastguard Worker      const ms = window.performance.now();
205*c8dee2aaSAndroid Build Coastguard Worker      const sec = (ms - this.startMs) / 1000;
206*c8dee2aaSAndroid Build Coastguard Worker      if (sec > 2) {
207*c8dee2aaSAndroid Build Coastguard Worker        console.log(Math.round(this.frames / sec) + ' fps');
208*c8dee2aaSAndroid Build Coastguard Worker        this.frames = 0;
209*c8dee2aaSAndroid Build Coastguard Worker        this.startMs = ms;
210*c8dee2aaSAndroid Build Coastguard Worker      }
211*c8dee2aaSAndroid Build Coastguard Worker      return ms;
212*c8dee2aaSAndroid Build Coastguard Worker    };
213*c8dee2aaSAndroid Build Coastguard Worker  }
214*c8dee2aaSAndroid Build Coastguard Worker</script>
215