215 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			215 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			HTML
		
	
	
	
| <!DOCTYPE html>
 | |
| <title>CanvasKit Viewer (Skia via Web Assembly)</title>
 | |
| <meta charset="utf-8" />
 | |
| <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | |
| <style>
 | |
|   html, body {
 | |
|     margin: 0;
 | |
|     padding: 0;
 | |
|   }
 | |
| </style>
 | |
| 
 | |
| <canvas id=viewer_canvas></canvas>
 | |
| 
 | |
| <script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
 | |
| 
 | |
| <script type="text/javascript" charset="utf-8">
 | |
|   const flags = {};
 | |
|   for (const pair of location.hash.substring(1).split(',')) {
 | |
|     // Parse "values" as an array in case the value has a colon (e.g., "slide:http://...").
 | |
|     const [key, ...values] = pair.split(':');
 | |
|     flags[key] = values.join(':');
 | |
|   }
 | |
|   window.onhashchange = function() {
 | |
|     location.reload();
 | |
|   };
 | |
| 
 | |
|   CanvasKitInit({
 | |
|     locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
 | |
|   }).then((CK) => {
 | |
|     if (!CK) {
 | |
|       throw 'CanvasKit not available.';
 | |
|     }
 | |
|     LoadSlide(CK);
 | |
|   });
 | |
| 
 | |
|   function LoadSlide(CanvasKit) {
 | |
|     if (!CanvasKit.MakeSlide || !CanvasKit.MakeSkpSlide || !CanvasKit.MakeSvgSlide) {
 | |
|       throw 'Not compiled with Viewer.';
 | |
|     }
 | |
|     const slideName = flags.slide || 'PathText';
 | |
|     if (slideName.endsWith('.skp') || slideName.endsWith('.svg')) {
 | |
|       fetch(slideName).then(function(response) {
 | |
|         if (response.status != 200) {
 | |
|             throw 'Error fetching ' + slideName;
 | |
|         }
 | |
|         if (slideName.endsWith('.skp')) {
 | |
|           response.arrayBuffer().then((data) => ViewerMain(
 | |
|               CanvasKit, CanvasKit.MakeSkpSlide(slideName, data)));
 | |
|         } else {
 | |
|           response.text().then((text) => ViewerMain(
 | |
|               CanvasKit, CanvasKit.MakeSvgSlide(slideName, text)));
 | |
|         }
 | |
|       });
 | |
|     } else {
 | |
|       ViewerMain(CanvasKit, CanvasKit.MakeSlide(slideName));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function ViewerMain(CanvasKit, slide) {
 | |
|     if (!slide) {
 | |
|       throw 'Failed to parse slide.'
 | |
|     }
 | |
|     const width = window.innerWidth;
 | |
|     const height = window.innerHeight;
 | |
|     const htmlCanvas = document.getElementById('viewer_canvas');
 | |
|     htmlCanvas.width = width;
 | |
|     htmlCanvas.height = height;
 | |
|     slide.load(width, height);
 | |
| 
 | |
|     // For the msaa flag, only check if the key exists in flags. That way we don't need to assign it
 | |
|     // a value in the location hash. i.e.,:  http://.../viewer.html#msaa
 | |
|     const doMSAA = ('msaa' in flags);
 | |
|     // Create the WebGL context with our desired attribs before calling MakeWebGLCanvasSurface.
 | |
|     CanvasKit.GetWebGLContext(htmlCanvas, {antialias: doMSAA});
 | |
|     const surface = CanvasKit.MakeWebGLCanvasSurface(htmlCanvas, null);
 | |
|     if (!surface) {
 | |
|       throw 'Could not make canvas surface';
 | |
|     }
 | |
|     if (doMSAA && surface.sampleCnt() <= 1) {
 | |
|       // We requested antialias on the canvas but did not get MSAA. Since we don't know what type of
 | |
|       // AA is in use right now (if any), this surface is unusable.
 | |
|       throw 'MSAA rendering to the on-screen canvas is not supported. ' +
 | |
|             'Please try again without MSAA.';
 | |
|     }
 | |
| 
 | |
|     window.onmousedown = (event) => (event.button === 0) && Mouse(CanvasKit.InputState.Down, event);
 | |
|     window.onmouseup = (event) => (event.button === 0) && Mouse(CanvasKit.InputState.Up, event);
 | |
|     window.onmousemove = (event) => Mouse(CanvasKit.InputState.Move, event);
 | |
|     window.onkeypress = function(event) {
 | |
|       if (slide.onChar(event.keyCode)) {
 | |
|         ScheduleDraw();
 | |
|         return false;
 | |
|       } else {
 | |
|         switch (event.keyCode) {
 | |
|           case 's'.charCodeAt(0):
 | |
|             // 's' is the magic key in the native viewer app that turns on FPS monitoring. Toggle
 | |
|             // forced animation when it is pressed in order to get fps logs.
 | |
|             // HINT: Launch chrome with --disable-frame-rate-limit and --disable-gpu-vsync in order
 | |
|             // to measure frame rates above 60.
 | |
|             ScheduleDraw.forceAnimation = !ScheduleDraw.forceAnimation;
 | |
|             ScheduleDraw();
 | |
|             break;
 | |
|         }
 | |
|       }
 | |
|       return true;
 | |
|     }
 | |
|     window.onkeydown = function(event) {
 | |
|       const upArrowCode = 38;
 | |
|       if (event.keyCode === upArrowCode) {
 | |
|         ScaleCanvas((event.shiftKey) ? Infinity : 1.1);
 | |
|         return false;
 | |
|       }
 | |
|       const downArrowCode = 40;
 | |
|       if (event.keyCode === downArrowCode) {
 | |
|         ScaleCanvas((event.shiftKey) ? 0 : 1/1.1);
 | |
|         return false;
 | |
|       }
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     let [canvasScale, canvasTranslateX, canvasTranslateY] = [1, 0, 0];
 | |
|     function ScaleCanvas(factor) {
 | |
|       factor = Math.min(Math.max(1/(5*canvasScale), factor), 5/canvasScale);
 | |
|       canvasTranslateX *= factor;
 | |
|       canvasTranslateY *= factor;
 | |
|       canvasScale *= factor;
 | |
|       ScheduleDraw();
 | |
|     }
 | |
|     function TranslateCanvas(dx, dy) {
 | |
|       canvasTranslateX += dx;
 | |
|       canvasTranslateY += dy;
 | |
|       ScheduleDraw();
 | |
|     }
 | |
| 
 | |
|     function Mouse(state, event) {
 | |
|       let modifierKeys = CanvasKit.ModifierKey.None;
 | |
|       if (event.shiftKey) {
 | |
|         modifierKeys |= CanvasKit.ModifierKey.Shift;
 | |
|       }
 | |
|       if (event.altKey) {
 | |
|         modifierKeys |= CanvasKit.ModifierKey.Option;
 | |
|       }
 | |
|       if (event.ctrlKey) {
 | |
|         modifierKeys |= CanvasKit.ModifierKey.Ctrl;
 | |
|       }
 | |
|       if (event.metaKey) {
 | |
|         modifierKeys |= CanvasKit.ModifierKey.Command;
 | |
|       }
 | |
|       let [dx, dy] = [event.pageX - this.lastX, event.pageY - this.lastY];
 | |
|       this.lastX = event.pageX;
 | |
|       this.lastY = event.pageY;
 | |
|       if (slide.onMouse(event.pageX, event.pageY, state, modifierKeys)) {
 | |
|         ScheduleDraw();
 | |
|         return false;
 | |
|       } else if (event.buttons & 1) {  // Left-button pressed.
 | |
|         TranslateCanvas(dx, dy);
 | |
|         return false;
 | |
|       }
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     function ScheduleDraw() {
 | |
|       if (ScheduleDraw.hasPendingAnimationRequest) {
 | |
|         // It's possible for this ScheduleDraw() method to be called multiple times before an
 | |
|         // animation callback actually gets invoked. Make sure we only ever have one single
 | |
|         // requestAnimationFrame scheduled at a time, because otherwise we can get stuck in a
 | |
|         // position where multiple callbacks are coming in on a single compositing frame, and then
 | |
|         // rescheduling multiple more for the next frame.
 | |
|         return;
 | |
|       }
 | |
|       ScheduleDraw.hasPendingAnimationRequest = true;
 | |
|       surface.requestAnimationFrame((canvas) => {
 | |
|         ScheduleDraw.hasPendingAnimationRequest = false;
 | |
| 
 | |
|         canvas.save();
 | |
|         canvas.translate(canvasTranslateX, canvasTranslateY);
 | |
|         canvas.scale(canvasScale, canvasScale);
 | |
|         canvas.clear(CanvasKit.WHITE);
 | |
|         slide.draw(canvas);
 | |
|         canvas.restore();
 | |
| 
 | |
|         // HINT: Launch chrome with --disable-frame-rate-limit and --disable-gpu-vsync in order to
 | |
|         // allow this to go faster than 60fps.
 | |
|         const ms = (ScheduleDraw.fps && ScheduleDraw.fps.markFrameComplete()) ||
 | |
|                    window.performance.now();
 | |
|         if (slide.animate(ms * 1e6) || ScheduleDraw.forceAnimation) {
 | |
|           ScheduleDraw.fps = ScheduleDraw.fps || new FPSMeter(ms);
 | |
|           ScheduleDraw();
 | |
|         } else {
 | |
|           delete ScheduleDraw.fps;
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     ScheduleDraw();
 | |
|   }
 | |
| 
 | |
|   function FPSMeter(startMs) {
 | |
|     this.frames = 0;
 | |
|     this.startMs = startMs;
 | |
|     this.markFrameComplete = () => {
 | |
|       ++this.frames;
 | |
|       const ms = window.performance.now();
 | |
|       const sec = (ms - this.startMs) / 1000;
 | |
|       if (sec > 2) {
 | |
|         console.log(Math.round(this.frames / sec) + ' fps');
 | |
|         this.frames = 0;
 | |
|         this.startMs = ms;
 | |
|       }
 | |
|       return ms;
 | |
|     };
 | |
|   }
 | |
| </script>
 |