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