1*c8dee2aaSAndroid Build Coastguard Worker// The size of the golden images (DMs) 2*c8dee2aaSAndroid Build Coastguard Workerconst CANVAS_WIDTH = 600; 3*c8dee2aaSAndroid Build Coastguard Workerconst CANVAS_HEIGHT = 600; 4*c8dee2aaSAndroid Build Coastguard Worker 5*c8dee2aaSAndroid Build Coastguard Workerconst SHOULD_SKIP = 'should_skip'; 6*c8dee2aaSAndroid Build Coastguard Worker 7*c8dee2aaSAndroid Build Coastguard Workerconst _commonGM = (it, pause, name, callback, assetsToFetchOrPromisesToWaitOn) => { 8*c8dee2aaSAndroid Build Coastguard Worker if (name.includes(' ')) { 9*c8dee2aaSAndroid Build Coastguard Worker throw name + " cannot contain spaces"; 10*c8dee2aaSAndroid Build Coastguard Worker } 11*c8dee2aaSAndroid Build Coastguard Worker const fetchPromises = []; 12*c8dee2aaSAndroid Build Coastguard Worker for (const assetOrPromise of assetsToFetchOrPromisesToWaitOn) { 13*c8dee2aaSAndroid Build Coastguard Worker // https://stackoverflow.com/a/9436948 14*c8dee2aaSAndroid Build Coastguard Worker if (typeof assetOrPromise === 'string' || assetOrPromise instanceof String) { 15*c8dee2aaSAndroid Build Coastguard Worker const newPromise = fetchWithRetries(assetOrPromise) 16*c8dee2aaSAndroid Build Coastguard Worker .then((response) => response.arrayBuffer()) 17*c8dee2aaSAndroid Build Coastguard Worker .catch((err) => { 18*c8dee2aaSAndroid Build Coastguard Worker console.error(err); 19*c8dee2aaSAndroid Build Coastguard Worker throw err; 20*c8dee2aaSAndroid Build Coastguard Worker }); 21*c8dee2aaSAndroid Build Coastguard Worker fetchPromises.push(newPromise); 22*c8dee2aaSAndroid Build Coastguard Worker } else if (typeof assetOrPromise.then === 'function') { 23*c8dee2aaSAndroid Build Coastguard Worker fetchPromises.push(assetOrPromise); 24*c8dee2aaSAndroid Build Coastguard Worker } else { 25*c8dee2aaSAndroid Build Coastguard Worker throw 'Neither a string nor a promise ' + assetOrPromise; 26*c8dee2aaSAndroid Build Coastguard Worker } 27*c8dee2aaSAndroid Build Coastguard Worker } 28*c8dee2aaSAndroid Build Coastguard Worker it('draws gm '+name, (done) => { 29*c8dee2aaSAndroid Build Coastguard Worker const surface = CanvasKit.MakeCanvasSurface('test'); 30*c8dee2aaSAndroid Build Coastguard Worker expect(surface).toBeTruthy('Could not make surface'); 31*c8dee2aaSAndroid Build Coastguard Worker if (!surface) { 32*c8dee2aaSAndroid Build Coastguard Worker done(); 33*c8dee2aaSAndroid Build Coastguard Worker return; 34*c8dee2aaSAndroid Build Coastguard Worker } 35*c8dee2aaSAndroid Build Coastguard Worker // if fetchPromises is empty, the returned promise will 36*c8dee2aaSAndroid Build Coastguard Worker // resolve right away and just call the callback. 37*c8dee2aaSAndroid Build Coastguard Worker Promise.all(fetchPromises).then((values) => { 38*c8dee2aaSAndroid Build Coastguard Worker try { 39*c8dee2aaSAndroid Build Coastguard Worker // If callback returns a promise, the chained .then 40*c8dee2aaSAndroid Build Coastguard Worker // will wait for it. Otherwise, we'll pass the return value on, 41*c8dee2aaSAndroid Build Coastguard Worker // which could indicate to skip this test and not report it to Gold. 42*c8dee2aaSAndroid Build Coastguard Worker surface.getCanvas().clear(CanvasKit.WHITE); 43*c8dee2aaSAndroid Build Coastguard Worker return callback(surface.getCanvas(), values, surface); 44*c8dee2aaSAndroid Build Coastguard Worker } catch (e) { 45*c8dee2aaSAndroid Build Coastguard Worker console.log(`gm ${name} failed with error`, e); 46*c8dee2aaSAndroid Build Coastguard Worker expect(e).toBeFalsy(); 47*c8dee2aaSAndroid Build Coastguard Worker debugger; 48*c8dee2aaSAndroid Build Coastguard Worker done(); 49*c8dee2aaSAndroid Build Coastguard Worker } 50*c8dee2aaSAndroid Build Coastguard Worker }).then((shouldSkip) => { 51*c8dee2aaSAndroid Build Coastguard Worker surface.flush(); 52*c8dee2aaSAndroid Build Coastguard Worker if (shouldSkip === SHOULD_SKIP) { 53*c8dee2aaSAndroid Build Coastguard Worker surface.delete(); 54*c8dee2aaSAndroid Build Coastguard Worker done(); 55*c8dee2aaSAndroid Build Coastguard Worker console.log(`skipped gm ${name}`); 56*c8dee2aaSAndroid Build Coastguard Worker return; 57*c8dee2aaSAndroid Build Coastguard Worker } 58*c8dee2aaSAndroid Build Coastguard Worker if (pause) { 59*c8dee2aaSAndroid Build Coastguard Worker reportSurface(surface, name, null); 60*c8dee2aaSAndroid Build Coastguard Worker console.error('pausing due to pause_gm being invoked'); 61*c8dee2aaSAndroid Build Coastguard Worker } else { 62*c8dee2aaSAndroid Build Coastguard Worker reportSurface(surface, name, done); 63*c8dee2aaSAndroid Build Coastguard Worker } 64*c8dee2aaSAndroid Build Coastguard Worker }).catch((e) => { 65*c8dee2aaSAndroid Build Coastguard Worker console.log(`could not load assets for gm ${name}`, e); 66*c8dee2aaSAndroid Build Coastguard Worker debugger; 67*c8dee2aaSAndroid Build Coastguard Worker done(); 68*c8dee2aaSAndroid Build Coastguard Worker }); 69*c8dee2aaSAndroid Build Coastguard Worker }) 70*c8dee2aaSAndroid Build Coastguard Worker}; 71*c8dee2aaSAndroid Build Coastguard Worker 72*c8dee2aaSAndroid Build Coastguard Workerconst fetchWithRetries = (url) => { 73*c8dee2aaSAndroid Build Coastguard Worker const MAX_ATTEMPTS = 3; 74*c8dee2aaSAndroid Build Coastguard Worker const DELAY_AFTER_FAILURE = 1000; 75*c8dee2aaSAndroid Build Coastguard Worker 76*c8dee2aaSAndroid Build Coastguard Worker return new Promise((resolve, reject) => { 77*c8dee2aaSAndroid Build Coastguard Worker let attempts = 0; 78*c8dee2aaSAndroid Build Coastguard Worker const attemptFetch = () => { 79*c8dee2aaSAndroid Build Coastguard Worker attempts++; 80*c8dee2aaSAndroid Build Coastguard Worker fetch(url).then((resp) => resolve(resp)) 81*c8dee2aaSAndroid Build Coastguard Worker .catch((err) => { 82*c8dee2aaSAndroid Build Coastguard Worker if (attempts < MAX_ATTEMPTS) { 83*c8dee2aaSAndroid Build Coastguard Worker console.warn(`got error in fetching ${url}, retrying`, err); 84*c8dee2aaSAndroid Build Coastguard Worker retryAfterDelay(); 85*c8dee2aaSAndroid Build Coastguard Worker } else { 86*c8dee2aaSAndroid Build Coastguard Worker console.error(`got error in fetching ${url} even after ${attempts} attempts`, err); 87*c8dee2aaSAndroid Build Coastguard Worker reject(err); 88*c8dee2aaSAndroid Build Coastguard Worker } 89*c8dee2aaSAndroid Build Coastguard Worker }); 90*c8dee2aaSAndroid Build Coastguard Worker }; 91*c8dee2aaSAndroid Build Coastguard Worker const retryAfterDelay = () => { 92*c8dee2aaSAndroid Build Coastguard Worker setTimeout(() => { 93*c8dee2aaSAndroid Build Coastguard Worker attemptFetch(); 94*c8dee2aaSAndroid Build Coastguard Worker }, DELAY_AFTER_FAILURE); 95*c8dee2aaSAndroid Build Coastguard Worker } 96*c8dee2aaSAndroid Build Coastguard Worker attemptFetch(); 97*c8dee2aaSAndroid Build Coastguard Worker }); 98*c8dee2aaSAndroid Build Coastguard Worker 99*c8dee2aaSAndroid Build Coastguard Worker} 100*c8dee2aaSAndroid Build Coastguard Worker 101*c8dee2aaSAndroid Build Coastguard Worker/** 102*c8dee2aaSAndroid Build Coastguard Worker * Takes a name, a callback, and any number of assets or promises. It executes the 103*c8dee2aaSAndroid Build Coastguard Worker * callback (presumably, the test) and reports the resulting surface to Gold. 104*c8dee2aaSAndroid Build Coastguard Worker * @param name {string} 105*c8dee2aaSAndroid Build Coastguard Worker * @param callback {Function}, has two params, the first is a CanvasKit.Canvas 106*c8dee2aaSAndroid Build Coastguard Worker * and the second is an array of results from the passed in assets or promises. 107*c8dee2aaSAndroid Build Coastguard Worker * If a given assetOrPromise was a string, the result will be an ArrayBuffer. 108*c8dee2aaSAndroid Build Coastguard Worker * @param assetsToFetchOrPromisesToWaitOn {string|Promise}. If a string, it will 109*c8dee2aaSAndroid Build Coastguard Worker * be treated as a url to fetch and return an ArrayBuffer with the contents as 110*c8dee2aaSAndroid Build Coastguard Worker * a result in the callback. Otherwise, the promise will be waited on and its 111*c8dee2aaSAndroid Build Coastguard Worker * result will be whatever the promise resolves to. 112*c8dee2aaSAndroid Build Coastguard Worker */ 113*c8dee2aaSAndroid Build Coastguard Workerconst gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => { 114*c8dee2aaSAndroid Build Coastguard Worker _commonGM(it, false, name, callback, assetsToFetchOrPromisesToWaitOn); 115*c8dee2aaSAndroid Build Coastguard Worker}; 116*c8dee2aaSAndroid Build Coastguard Worker 117*c8dee2aaSAndroid Build Coastguard Worker/** 118*c8dee2aaSAndroid Build Coastguard Worker * fgm is like gm, except only tests declared with fgm, force_gm, or fit will be 119*c8dee2aaSAndroid Build Coastguard Worker * executed. This mimics the behavior of Jasmine.js. 120*c8dee2aaSAndroid Build Coastguard Worker */ 121*c8dee2aaSAndroid Build Coastguard Workerconst fgm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => { 122*c8dee2aaSAndroid Build Coastguard Worker _commonGM(fit, false, name, callback, assetsToFetchOrPromisesToWaitOn); 123*c8dee2aaSAndroid Build Coastguard Worker}; 124*c8dee2aaSAndroid Build Coastguard Worker 125*c8dee2aaSAndroid Build Coastguard Worker/** 126*c8dee2aaSAndroid Build Coastguard Worker * force_gm is like gm, except only tests declared with fgm, force_gm, or fit will be 127*c8dee2aaSAndroid Build Coastguard Worker * executed. This mimics the behavior of Jasmine.js. 128*c8dee2aaSAndroid Build Coastguard Worker */ 129*c8dee2aaSAndroid Build Coastguard Workerconst force_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => { 130*c8dee2aaSAndroid Build Coastguard Worker fgm(name, callback, assetsToFetchOrPromisesToWaitOn); 131*c8dee2aaSAndroid Build Coastguard Worker}; 132*c8dee2aaSAndroid Build Coastguard Worker 133*c8dee2aaSAndroid Build Coastguard Worker/** 134*c8dee2aaSAndroid Build Coastguard Worker * skip_gm does nothing. It is a convenient way to skip a test temporarily. 135*c8dee2aaSAndroid Build Coastguard Worker */ 136*c8dee2aaSAndroid Build Coastguard Workerconst skip_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => { 137*c8dee2aaSAndroid Build Coastguard Worker console.log(`Skipping gm ${name}`); 138*c8dee2aaSAndroid Build Coastguard Worker // do nothing, skip the test for now 139*c8dee2aaSAndroid Build Coastguard Worker}; 140*c8dee2aaSAndroid Build Coastguard Worker 141*c8dee2aaSAndroid Build Coastguard Worker/** 142*c8dee2aaSAndroid Build Coastguard Worker * pause_gm is like fgm, except the test will not finish right away and clear, 143*c8dee2aaSAndroid Build Coastguard Worker * making it ideal for a human to manually inspect the results. 144*c8dee2aaSAndroid Build Coastguard Worker */ 145*c8dee2aaSAndroid Build Coastguard Workerconst pause_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => { 146*c8dee2aaSAndroid Build Coastguard Worker _commonGM(fit, true, name, callback, assetsToFetchOrPromisesToWaitOn); 147*c8dee2aaSAndroid Build Coastguard Worker}; 148*c8dee2aaSAndroid Build Coastguard Worker 149*c8dee2aaSAndroid Build Coastguard Workerconst _commonMultipleCanvasGM = (it, pause, name, callback) => { 150*c8dee2aaSAndroid Build Coastguard Worker if (name.includes(' ')) { 151*c8dee2aaSAndroid Build Coastguard Worker throw name + " cannot contain spaces"; 152*c8dee2aaSAndroid Build Coastguard Worker } 153*c8dee2aaSAndroid Build Coastguard Worker it(`draws gm ${name} on both CanvasKit and using Canvas2D`, (done) => { 154*c8dee2aaSAndroid Build Coastguard Worker const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT); 155*c8dee2aaSAndroid Build Coastguard Worker skcanvas._config = 'software_canvas'; 156*c8dee2aaSAndroid Build Coastguard Worker const realCanvas = document.getElementById('test'); 157*c8dee2aaSAndroid Build Coastguard Worker realCanvas._config = 'html_canvas'; 158*c8dee2aaSAndroid Build Coastguard Worker realCanvas.width = CANVAS_WIDTH; 159*c8dee2aaSAndroid Build Coastguard Worker realCanvas.height = CANVAS_HEIGHT; 160*c8dee2aaSAndroid Build Coastguard Worker 161*c8dee2aaSAndroid Build Coastguard Worker if (pause) { 162*c8dee2aaSAndroid Build Coastguard Worker console.log('debugging canvaskit version'); 163*c8dee2aaSAndroid Build Coastguard Worker callback(realCanvas); 164*c8dee2aaSAndroid Build Coastguard Worker callback(skcanvas); 165*c8dee2aaSAndroid Build Coastguard Worker const png = skcanvas.toDataURL(); 166*c8dee2aaSAndroid Build Coastguard Worker const img = document.createElement('img'); 167*c8dee2aaSAndroid Build Coastguard Worker document.body.appendChild(img); 168*c8dee2aaSAndroid Build Coastguard Worker img.src = png; 169*c8dee2aaSAndroid Build Coastguard Worker debugger; 170*c8dee2aaSAndroid Build Coastguard Worker return; 171*c8dee2aaSAndroid Build Coastguard Worker } 172*c8dee2aaSAndroid Build Coastguard Worker 173*c8dee2aaSAndroid Build Coastguard Worker const promises = []; 174*c8dee2aaSAndroid Build Coastguard Worker 175*c8dee2aaSAndroid Build Coastguard Worker for (const canvas of [skcanvas, realCanvas]) { 176*c8dee2aaSAndroid Build Coastguard Worker callback(canvas); 177*c8dee2aaSAndroid Build Coastguard Worker // canvas has .toDataURL (even though skcanvas is not a real Canvas) 178*c8dee2aaSAndroid Build Coastguard Worker // so this will work. 179*c8dee2aaSAndroid Build Coastguard Worker promises.push(reportCanvas(canvas, name, canvas._config)); 180*c8dee2aaSAndroid Build Coastguard Worker } 181*c8dee2aaSAndroid Build Coastguard Worker Promise.all(promises).then(() => { 182*c8dee2aaSAndroid Build Coastguard Worker skcanvas.dispose(); 183*c8dee2aaSAndroid Build Coastguard Worker done(); 184*c8dee2aaSAndroid Build Coastguard Worker }).catch(reportError(done)); 185*c8dee2aaSAndroid Build Coastguard Worker }); 186*c8dee2aaSAndroid Build Coastguard Worker}; 187*c8dee2aaSAndroid Build Coastguard Worker 188*c8dee2aaSAndroid Build Coastguard Worker/** 189*c8dee2aaSAndroid Build Coastguard Worker * Takes a name and a callback. It executes the callback (presumably, the test) 190*c8dee2aaSAndroid Build Coastguard Worker * for both a CanvasKit.Canvas and a native Canvas2D. The result of both will be 191*c8dee2aaSAndroid Build Coastguard Worker * uploaded to Gold. 192*c8dee2aaSAndroid Build Coastguard Worker * @param name {string} 193*c8dee2aaSAndroid Build Coastguard Worker * @param callback {Function}, has one param, either a CanvasKit.Canvas or a native 194*c8dee2aaSAndroid Build Coastguard Worker * Canvas2D object. 195*c8dee2aaSAndroid Build Coastguard Worker */ 196*c8dee2aaSAndroid Build Coastguard Workerconst multipleCanvasGM = (name, callback) => { 197*c8dee2aaSAndroid Build Coastguard Worker _commonMultipleCanvasGM(it, false, name, callback); 198*c8dee2aaSAndroid Build Coastguard Worker}; 199*c8dee2aaSAndroid Build Coastguard Worker 200*c8dee2aaSAndroid Build Coastguard Worker/** 201*c8dee2aaSAndroid Build Coastguard Worker * fmultipleCanvasGM is like multipleCanvasGM, except only tests declared with 202*c8dee2aaSAndroid Build Coastguard Worker * fmultipleCanvasGM, force_multipleCanvasGM, or fit will be executed. This 203*c8dee2aaSAndroid Build Coastguard Worker * mimics the behavior of Jasmine.js. 204*c8dee2aaSAndroid Build Coastguard Worker */ 205*c8dee2aaSAndroid Build Coastguard Workerconst fmultipleCanvasGM = (name, callback) => { 206*c8dee2aaSAndroid Build Coastguard Worker _commonMultipleCanvasGM(fit, false, name, callback); 207*c8dee2aaSAndroid Build Coastguard Worker}; 208*c8dee2aaSAndroid Build Coastguard Worker 209*c8dee2aaSAndroid Build Coastguard Worker/** 210*c8dee2aaSAndroid Build Coastguard Worker * force_multipleCanvasGM is like multipleCanvasGM, except only tests declared 211*c8dee2aaSAndroid Build Coastguard Worker * with fmultipleCanvasGM, force_multipleCanvasGM, or fit will be executed. This 212*c8dee2aaSAndroid Build Coastguard Worker * mimics the behavior of Jasmine.js. 213*c8dee2aaSAndroid Build Coastguard Worker */ 214*c8dee2aaSAndroid Build Coastguard Workerconst force_multipleCanvasGM = (name, callback) => { 215*c8dee2aaSAndroid Build Coastguard Worker fmultipleCanvasGM(name, callback); 216*c8dee2aaSAndroid Build Coastguard Worker}; 217*c8dee2aaSAndroid Build Coastguard Worker 218*c8dee2aaSAndroid Build Coastguard Worker/** 219*c8dee2aaSAndroid Build Coastguard Worker * pause_multipleCanvasGM is like fmultipleCanvasGM, except the test will not 220*c8dee2aaSAndroid Build Coastguard Worker * finish right away and clear, making it ideal for a human to manually inspect the results. 221*c8dee2aaSAndroid Build Coastguard Worker */ 222*c8dee2aaSAndroid Build Coastguard Workerconst pause_multipleCanvasGM = (name, callback) => { 223*c8dee2aaSAndroid Build Coastguard Worker _commonMultipleCanvasGM(fit, true, name, callback); 224*c8dee2aaSAndroid Build Coastguard Worker}; 225*c8dee2aaSAndroid Build Coastguard Worker 226*c8dee2aaSAndroid Build Coastguard Worker/** 227*c8dee2aaSAndroid Build Coastguard Worker * skip_multipleCanvasGM does nothing. It is a convenient way to skip a test temporarily. 228*c8dee2aaSAndroid Build Coastguard Worker */ 229*c8dee2aaSAndroid Build Coastguard Workerconst skip_multipleCanvasGM = (name, callback) => { 230*c8dee2aaSAndroid Build Coastguard Worker console.log(`Skipping multiple canvas gm ${name}`); 231*c8dee2aaSAndroid Build Coastguard Worker}; 232*c8dee2aaSAndroid Build Coastguard Worker 233*c8dee2aaSAndroid Build Coastguard Worker 234*c8dee2aaSAndroid Build Coastguard Workerfunction reportSurface(surface, testname, done) { 235*c8dee2aaSAndroid Build Coastguard Worker // Sometimes, the webgl canvas is blank, but the surface has the pixel 236*c8dee2aaSAndroid Build Coastguard Worker // data. So, we copy it out and draw it to a normal canvas to take a picture. 237*c8dee2aaSAndroid Build Coastguard Worker // To be consistent across CPU and GPU, we just do it for all configurations 238*c8dee2aaSAndroid Build Coastguard Worker // (even though the CPU canvas shows up after flush just fine). 239*c8dee2aaSAndroid Build Coastguard Worker let pixels = surface.getCanvas().readPixels(0, 0, { 240*c8dee2aaSAndroid Build Coastguard Worker width: CANVAS_WIDTH, 241*c8dee2aaSAndroid Build Coastguard Worker height: CANVAS_HEIGHT, 242*c8dee2aaSAndroid Build Coastguard Worker colorType: CanvasKit.ColorType.RGBA_8888, 243*c8dee2aaSAndroid Build Coastguard Worker alphaType: CanvasKit.AlphaType.Unpremul, 244*c8dee2aaSAndroid Build Coastguard Worker colorSpace: CanvasKit.ColorSpace.SRGB, 245*c8dee2aaSAndroid Build Coastguard Worker }); 246*c8dee2aaSAndroid Build Coastguard Worker if (!pixels) { 247*c8dee2aaSAndroid Build Coastguard Worker throw 'Could not get pixels for test '+testname; 248*c8dee2aaSAndroid Build Coastguard Worker } 249*c8dee2aaSAndroid Build Coastguard Worker pixels = new Uint8ClampedArray(pixels.buffer); 250*c8dee2aaSAndroid Build Coastguard Worker const imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT); 251*c8dee2aaSAndroid Build Coastguard Worker 252*c8dee2aaSAndroid Build Coastguard Worker const reportingCanvas = document.getElementById('report'); 253*c8dee2aaSAndroid Build Coastguard Worker if (!reportingCanvas) { 254*c8dee2aaSAndroid Build Coastguard Worker throw 'Reporting canvas not found'; 255*c8dee2aaSAndroid Build Coastguard Worker } 256*c8dee2aaSAndroid Build Coastguard Worker reportingCanvas.getContext('2d').putImageData(imageData, 0, 0); 257*c8dee2aaSAndroid Build Coastguard Worker if (!done) { 258*c8dee2aaSAndroid Build Coastguard Worker return; 259*c8dee2aaSAndroid Build Coastguard Worker } 260*c8dee2aaSAndroid Build Coastguard Worker reportCanvas(reportingCanvas, testname).then(() => { 261*c8dee2aaSAndroid Build Coastguard Worker surface.delete(); 262*c8dee2aaSAndroid Build Coastguard Worker done(); 263*c8dee2aaSAndroid Build Coastguard Worker }).catch(reportError(done)); 264*c8dee2aaSAndroid Build Coastguard Worker} 265*c8dee2aaSAndroid Build Coastguard Worker 266*c8dee2aaSAndroid Build Coastguard Worker 267*c8dee2aaSAndroid Build Coastguard Workerfunction starPath(CanvasKit, X=128, Y=128, R=116) { 268*c8dee2aaSAndroid Build Coastguard Worker const p = new CanvasKit.Path(); 269*c8dee2aaSAndroid Build Coastguard Worker p.moveTo(X + R, Y); 270*c8dee2aaSAndroid Build Coastguard Worker for (let i = 1; i < 8; i++) { 271*c8dee2aaSAndroid Build Coastguard Worker let a = 2.6927937 * i; 272*c8dee2aaSAndroid Build Coastguard Worker p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a)); 273*c8dee2aaSAndroid Build Coastguard Worker } 274*c8dee2aaSAndroid Build Coastguard Worker p.close(); 275*c8dee2aaSAndroid Build Coastguard Worker return p; 276*c8dee2aaSAndroid Build Coastguard Worker} 277