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