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