From af57378852fb83e8a11d23db7dbd21d9cf2097be Mon Sep 17 00:00:00 2001 From: Will Scullin Date: Tue, 25 May 2021 12:08:10 -0700 Subject: [PATCH] Videomode refactor 2 (#80) Remove globals from video implementations to allow further refactoring. Experiment with testing video modes. --- .gitignore | 2 + jest.config.js | 10 + js/apple2.ts | 10 +- js/canvas.ts | 383 +++-- js/gl.ts | 292 ++-- js/mmu.ts | 4 +- js/ui/options_modal.ts | 4 - js/videomodes.ts | 28 +- package-lock.json | 1340 ++++++++++++++++- package.json | 5 +- test/jest-setup.js | 3 + ...hires-page-double-lores-renders-1-snap.png | Bin 0 -> 2448 bytes ...-page-double-lores-renders-mono-1-snap.png | Bin 0 -> 1265 bytes ...canvas-hires-page-hires-renders-1-snap.png | Bin 0 -> 4494 bytes ...s-hires-page-hires-renders-mono-1-snap.png | Bin 0 -> 1186 bytes ...phics-mode-double-lores-renders-1-snap.png | Bin 0 -> 2742 bytes ...mode-double-lores-renders-mixed-1-snap.png | Bin 0 -> 6224 bytes ...-mode-double-lores-renders-mono-1-snap.png | Bin 0 -> 7466 bytes ...age-graphics-mode-lores-renders-1-snap.png | Bin 0 -> 1921 bytes ...aphics-mode-lores-renders-mixed-1-snap.png | Bin 0 -> 3702 bytes ...raphics-mode-lores-renders-mono-1-snap.png | Bin 0 -> 5171 bytes ...age-text-mode-40-column-renders-1-snap.png | Bin 0 -> 11103 bytes ...ode-40-column-renders-alt-chars-1-snap.png | Bin 0 -> 11550 bytes ...age-text-mode-80-column-renders-1-snap.png | Bin 0 -> 20706 bytes ...ode-80-column-renders-alt-chars-1-snap.png | Bin 0 -> 21479 bytes ...hires-page-double-lores-renders-1-snap.png | Bin 0 -> 1265 bytes ...-ts-gl-hires-page-hires-renders-1-snap.png | Bin 0 -> 3697 bytes ...phics-mode-double-lores-renders-1-snap.png | Bin 0 -> 7466 bytes ...mode-double-lores-renders-mixed-1-snap.png | Bin 0 -> 9980 bytes ...age-graphics-mode-lores-renders-1-snap.png | Bin 0 -> 5171 bytes ...aphics-mode-lores-renders-mixed-1-snap.png | Bin 0 -> 6269 bytes ...age-text-mode-40-column-renders-1-snap.png | Bin 0 -> 11103 bytes ...ode-40-column-renders-alt-chars-1-snap.png | Bin 0 -> 11550 bytes ...age-text-mode-80-column-renders-1-snap.png | Bin 0 -> 20706 bytes ...ode-80-column-renders-alt-chars-1-snap.png | Bin 0 -> 21479 bytes test/js/__mocks__/apple2shader.js | 18 + test/js/canvas.test.ts | 258 ++++ test/js/gl.test.ts | 197 +++ test/util/image.ts | 9 + tsconfig.json | 5 +- 40 files changed, 2186 insertions(+), 382 deletions(-) create mode 100644 test/jest-setup.js create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-double-lores-renders-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-double-lores-renders-mono-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-hires-renders-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-hires-renders-mono-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-mixed-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-mono-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-lores-renders-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-lores-renders-mixed-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-lores-renders-mono-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-40-column-renders-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-40-column-renders-alt-chars-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-80-column-renders-1-snap.png create mode 100644 test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-80-column-renders-alt-chars-1-snap.png create mode 100644 test/js/__image_snapshots__/gl-test-ts-gl-hires-page-double-lores-renders-1-snap.png create mode 100644 test/js/__image_snapshots__/gl-test-ts-gl-hires-page-hires-renders-1-snap.png create mode 100644 test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-double-lores-renders-1-snap.png create mode 100644 test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-double-lores-renders-mixed-1-snap.png create mode 100644 test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-lores-renders-1-snap.png create mode 100644 test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-lores-renders-mixed-1-snap.png create mode 100644 test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-40-column-renders-1-snap.png create mode 100644 test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-40-column-renders-alt-chars-1-snap.png create mode 100644 test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-80-column-renders-1-snap.png create mode 100644 test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-80-column-renders-alt-chars-1-snap.png create mode 100644 test/js/__mocks__/apple2shader.js create mode 100644 test/js/canvas.test.ts create mode 100644 test/js/gl.test.ts create mode 100644 test/util/image.ts diff --git a/.gitignore b/.gitignore index 92ac095..26f5cce 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ .checked-*.js .DS_Store .vscode +/coverage /dist /node_modules /tmp +__diff_output__ diff --git a/jest.config.js b/jest.config.js index 088a4a8..7df423e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,7 @@ module.exports = { 'moduleNameMapper': { '^js/(.*)': '/js/$1', + '^test/(.*)': '/test/$1', }, 'roots': [ 'js/', @@ -9,8 +10,17 @@ module.exports = { 'testMatch': [ '**/?(*.)+(spec|test).+(ts|js)' ], + 'transform': { '^.+\\.js$': 'babel-jest', '^.+\\.ts$': 'ts-jest' }, + 'setupFilesAfterEnv': [ + '/test/jest-setup.js' + ], + 'coveragePathIgnorePatterns': [ + '/node_modules/', + '/js/roms/', + '/test/', + ] }; diff --git a/js/apple2.ts b/js/apple2.ts index 619c211..aca0e47 100644 --- a/js/apple2.ts +++ b/js/apple2.ts @@ -82,11 +82,11 @@ export class Apple2 implements Restorable, DebuggerContainer { const VideoModes = options.gl ? VideoModesGL : VideoModes2D; this.cpu = new CPU6502({ '65C02': options.enhanced }); - this.gr = new LoresPage(1, options.characterRom, options.e); - this.gr2 = new LoresPage(2, options.characterRom, options.e); - this.hgr = new HiresPage(1); - this.hgr2 = new HiresPage(2); - this.vm = new VideoModes(this.gr, this.hgr, this.gr2, this.hgr2, options.canvas, options.e); + this.vm = new VideoModes(options.canvas, options.e); + this.gr = new LoresPage(this.vm, 1, options.characterRom, options.e); + this.gr2 = new LoresPage(this.vm, 2, options.characterRom, options.e); + this.hgr = new HiresPage(this.vm, 1); + this.hgr2 = new HiresPage(this.vm, 2); this.io = new Apple2IO(this.cpu, this.vm); this.tick = options.tick; diff --git a/js/canvas.ts b/js/canvas.ts index c3c27b7..0c12ba9 100644 --- a/js/canvas.ts +++ b/js/canvas.ts @@ -22,46 +22,6 @@ import { pageNo } from './videomodes'; -let textMode = true; -let mixedMode = false; -let hiresMode = false; -let pageMode: pageNo = 1; -let _80colMode = false; -let altCharMode = false; -let an3 = false; -let doubleHiresMode = false; -let monoDHRMode = false; -const colorDHRMode = false; -let mixedDHRMode = false; -let highColorHGRMode = false; -let highColorTextMode = false; -let oneSixtyMode = false; - -const tmpCanvas = document.createElement('canvas'); -const tmpContext = tmpCanvas.getContext('2d'); - -const buildScreen = (mainData: ImageData, mixData?: ImageData | null) => { - if (!tmpContext) { - throw new Error('No 2d context'); - } - - const { width, height } = { width: 560, height: 192 }; - const { x, y } = _80colMode ? { x: 0, y: 0 } : { x: 0, y: 0 }; - - tmpCanvas.width = width; - tmpCanvas.height = height; - tmpContext.fillStyle = 'rgba(0,0,0,1)'; - tmpContext.fillRect(0, 0, width, height); - - if (mixData) { - tmpContext.putImageData(mainData, x, y, 0, 0, 560, 160); - tmpContext.putImageData(mixData, x, y, 0, 160, 560, 32); - } else { - tmpContext.putImageData(mainData, x, y); - } - return tmpCanvas; -}; - const dim = (c: Color): Color => { return [ c[0] * 0.75 & 0xff, @@ -119,7 +79,7 @@ const r4 = [ 11, // Light Blue 13, // Yellow 15 // White -]; +] as const; const dcolors: Color[] = [ [0, 0, 0], // 0x0 black @@ -145,7 +105,7 @@ const notDirty: Region = { bottom: -1, left: 561, right: -1 -}; +} as const; /**************************************************************************** * @@ -160,21 +120,25 @@ export class LoresPage2D implements LoresPage { private _buffer: memory[] = []; private _refreshing = false; - private _monoMode = false; private _blink = false; + private highColorTextMode = false + dirty: Region = {...notDirty} imageData: ImageData; - constructor(private page: number, + constructor( + private vm: VideoModes, + private page: pageNo, private readonly charset: rom, - private readonly e: boolean) { - this.imageData = new ImageData(560, 192); - for (let idx = 0; idx < 560 * 192 * 4; idx++) { - this.imageData.data[idx] = 0xff; - } + private readonly e: boolean + ) { + this.imageData = this.vm.context.createImageData(560, 192); + this.imageData.data.fill(0xff); this._buffer[0] = allocMemPages(0x4); this._buffer[1] = allocMemPages(0x4); + + this.vm.setLoresPage(page, this); } private _drawPixel(data: Uint8ClampedArray, off: number, color: Color) { @@ -194,7 +158,7 @@ export class LoresPage2D implements LoresPage { private _checkInverse(val: byte) { let inverse = false; if (this.e) { - if (!_80colMode && !altCharMode) { + if (!this.vm._80colMode && !this.vm.altCharMode) { inverse = ((val & 0xc0) == 0x40) && this._blink; } } else { @@ -264,14 +228,14 @@ export class LoresPage2D implements LoresPage { x += 14; if (x > this.dirty.right) { this.dirty.right = x; } - if (textMode || hiresMode || (mixedMode && row > 19)) { - if (_80colMode) { + if (this.vm.textMode || this.vm.hiresMode || (this.vm.mixedMode && row > 19)) { + if (this.vm._80colMode) { const inverse = this._checkInverse(val); fore = inverse ? blackCol : whiteCol; back = inverse ? whiteCol : blackCol; - if (!altCharMode) { + if (!this.vm.altCharMode) { val = (val >= 0x40 && val < 0x80) ? val - 0x40 : val; } @@ -295,13 +259,13 @@ export class LoresPage2D implements LoresPage { fore = inverse ? blackCol : whiteCol; back = inverse ? whiteCol : blackCol; - if (!altCharMode) { + if (!this.vm.altCharMode) { val = (val >= 0x40 && val < 0x80) ? val - 0x40 : val; } let offset = (col * 14 + row * 560 * 8) * 4; - if (highColorTextMode) { + if (this.highColorTextMode) { fore = _colors[this._buffer[1][base] >> 4]; back = _colors[this._buffer[1][base] & 0x0f]; } @@ -318,7 +282,7 @@ export class LoresPage2D implements LoresPage { offset += 546 * 4; } } else { - const colorMode = mixedMode && !textMode && !this._monoMode; + const colorMode = this.vm.mixedMode && !this.vm.textMode && !this.vm.monoMode; // var val0 = col > 0 ? _buffer[0][base - 1] : 0; // var val2 = col < 39 ? _buffer[0][base + 1] : 0; @@ -356,24 +320,20 @@ export class LoresPage2D implements LoresPage { } } } else { - if (!_80colMode && bank == 1) { - return; - } - if (_80colMode && !an3) { + if (this.vm._80colMode && !this.vm.an3State) { let offset = (col * 14 + (bank ? 0 : 1) * 7 + row * 560 * 8) * 4; - if (this._monoMode) { - fore = whiteCol; - back = blackCol; + if (this.vm.monoMode) { for (let jdx = 0; jdx < 8; jdx++) { - let b = (jdx < 8) ? (val & 0x0f) : (val >> 4); + let b = (jdx < 4) ? (val & 0x0f) : (val >> 4); b |= (b << 4); - if (bank & 0x1) { - b <<= 1; + b |= (b << 8); + if (col & 0x1) { + b >>= 2; } for (let idx = 0; idx < 7; idx++) { - const color = (b & 0x80) ? fore : back; + const color = (b & 0x01) ? whiteCol : blackCol; this._drawHalfPixel(data, offset, color); - b <<= 1; + b >>= 1; offset += 4; } offset += 553 * 4; @@ -387,28 +347,26 @@ export class LoresPage2D implements LoresPage { (val & 0x0f) : (val >> 4)]; for (let idx = 0; idx < 7; idx++) { this._drawHalfPixel(data, offset, color); - off += 4; + offset += 4; } offset += 553 * 4; } } - } else { + } else if (bank === 0) { let offset = (col * 14 + row * 560 * 8) * 4; - if (this._monoMode) { - fore = whiteCol; - back = blackCol; + if (this.vm.monoMode) { for (let jdx = 0; jdx < 8; jdx++) { let b = (jdx < 4) ? (val & 0x0f) : (val >> 4); b |= (b << 4); b |= (b << 8); if (col & 0x1) { - b <<= 2; + b >>= 2; } for (let idx = 0; idx < 14; idx++) { - const color = (b & 0x8000) ? fore : back; + const color = (b & 0x0001) ? whiteCol : blackCol; this._drawHalfPixel(data, offset, color); - b <<= 1; + b >>= 1; offset += 4; } offset += 546 * 4; @@ -429,21 +387,19 @@ export class LoresPage2D implements LoresPage { } refresh() { + this.highColorTextMode = !this.vm.an3State && this.vm.textMode && !this.vm._80colMode; + let addr = 0x400 * this.page; this._refreshing = true; for (let idx = 0; idx < 0x400; idx++, addr++) { this._write(addr >> 8, addr & 0xff, this._buffer[0][idx], 0); - if (_80colMode) { + if (this.vm._80colMode) { this._write(addr >> 8, addr & 0xff, this._buffer[1][idx], 1); } } this._refreshing = false; } - mono(on: boolean) { - this._monoMode = on; - } - blink() { let addr = 0x400 * this.page; this._refreshing = true; @@ -476,8 +432,6 @@ export class LoresPage2D implements LoresPage { getState(): GraphicsState { return { - page: this.page, - mono: this._monoMode, buffer: [ new Uint8Array(this._buffer[0]), new Uint8Array(this._buffer[1]), @@ -486,8 +440,6 @@ export class LoresPage2D implements LoresPage { } setState(state: GraphicsState) { - this.page = state.page; - this._monoMode = state.mono; this._buffer[0] = new Uint8Array(state.buffer[0]); this._buffer[1] = new Uint8Array(state.buffer[1]); @@ -518,7 +470,7 @@ export class LoresPage2D implements LoresPage { for (row = 0; row < 24; row++) { base = this.rowToBase(row); line = ''; - if (this.e && _80colMode) { + if (this.e && this.vm._80colMode) { for (col = 0; col < 80; col++) { charCode = this.mapCharCode(this._buffer[1 - col % 2][base + Math.floor(col / 2)]); line += String.fromCharCode(charCode); @@ -548,16 +500,23 @@ export class HiresPage2D implements HiresPage { private _buffer: memory[] = []; private _refreshing = false; - private _monoMode = false; + + highColorHGRMode: boolean; + oneSixtyMode: boolean; + mixedDHRMode: boolean; + monoDHRMode: boolean; + colorDHRMode: boolean = true; constructor( - private page: number) { - this.imageData = new ImageData(560, 192); - for (let idx = 0; idx < 560 * 192 * 4; idx++) { - this.imageData.data[idx] = 0xff; - } + private vm: VideoModes, + private page: pageNo, + ) { + this.imageData = this.vm.context.createImageData(560, 192); + this.imageData.data.fill(0xff); this._buffer[0] = allocMemPages(0x20); this._buffer[1] = allocMemPages(0x20); + + this.vm.setHiresPage(page, this); } private _drawPixel(data: Uint8ClampedArray, off: number, color: Color) { @@ -645,7 +604,7 @@ export class HiresPage2D implements HiresPage { const data = this.imageData.data; let dx, dy; - if ((rowa < 24) && (col < 40) && hiresMode) { + if ((rowa < 24) && (col < 40) && this.vm.hiresMode) { let y = rowa << 4 | rowb << 1; if (y < this.dirty.top) { this.dirty.top = y; } y += 1; @@ -657,7 +616,7 @@ export class HiresPage2D implements HiresPage { dy = rowa << 4 | rowb << 1; let bz, b0, b1, b2, b3, b4, c, hb; - if (oneSixtyMode && !this._monoMode) { + if (this.oneSixtyMode && !this.vm.monoMode) { // 1 byte = two pixels, but 3:4 ratio const c3 = val & 0xf; const c4 = val >> 4; @@ -667,7 +626,7 @@ export class HiresPage2D implements HiresPage { this._draw3Pixel(data, offset, dcolors[c3]); this._draw4Pixel(data, offset + 12, dcolors[c4]); - } else if (doubleHiresMode) { + } else if (this.vm.doubleHiresMode) { val &= 0x7f; // Every 4 bytes is 7 pixels @@ -719,7 +678,7 @@ export class HiresPage2D implements HiresPage { let offset = dx * 4 + dy * 280 * 4; let monoColor = null; - if (this._monoMode || monoDHRMode) { + if (this.vm.monoMode || this.monoDHRMode) { monoColor = whiteCol; } @@ -734,7 +693,7 @@ export class HiresPage2D implements HiresPage { } else { this._drawHalfPixel(data, offset, blackCol); } - } else if (mixedDHRMode) { + } else if (this.mixedDHRMode) { if (hbs) { this._drawHalfPixel(data, offset, dcolor); } else { @@ -744,7 +703,7 @@ export class HiresPage2D implements HiresPage { this._drawHalfPixel(data, offset, blackCol); } } - } else if (colorDHRMode) { + } else if (this.colorDHRMode) { this._drawHalfPixel(data, offset, dcolor); } else if ( ((c[idx] != c[idx - 1]) && (c[idx] != c[idx + 1])) && @@ -793,7 +752,7 @@ export class HiresPage2D implements HiresPage { let offset = dx * 4 + dy * 280 * 4; - const monoColor = this._monoMode ? whiteCol : null; + const monoColor = this.vm.monoMode ? whiteCol : null; for (let idx = 0; idx < 9; idx++, offset += 8) { val >>= 1; @@ -801,7 +760,7 @@ export class HiresPage2D implements HiresPage { if (v1) { if (monoColor) { color = monoColor; - } else if (highColorHGRMode) { + } else if (this.highColorHGRMode) { color = dcolors[this._buffer[1][base] >> 4]; } else if (v0 || v2) { color = whiteCol; @@ -811,7 +770,7 @@ export class HiresPage2D implements HiresPage { } else { if (monoColor) { color = blackCol; - } else if (highColorHGRMode) { + } else if (this.highColorHGRMode) { color = dcolors[this._buffer[1][base] & 0x0f]; } else if (odd && v2 && v0) { color = v0 ? dim(evenCol) : evenCol; @@ -837,23 +796,24 @@ export class HiresPage2D implements HiresPage { } refresh() { + this.highColorHGRMode = !this.vm.an3State && this.vm.hiresMode && !this.vm._80colMode; + this.oneSixtyMode = this.vm.flag == 1 && this.vm.doubleHiresMode; + this.mixedDHRMode = this.vm.flag == 2 && this.vm.doubleHiresMode; + this.monoDHRMode = this.vm.flag == 3 && this.vm.doubleHiresMode; + let addr = 0x2000 * this.page; this._refreshing = true; for (let idx = 0; idx < 0x2000; idx++, addr++) { const page = addr >> 8; const off = addr & 0xff; this._write(page, off, this._buffer[0][idx], 0); - if (_80colMode) { + if (this.vm._80colMode) { this._write(page, off, this._buffer[1][idx], 1); } } this._refreshing = false; } - mono(on: boolean) { - this._monoMode = on; - } - start() { return this._start(); } @@ -872,8 +832,6 @@ export class HiresPage2D implements HiresPage { getState(): GraphicsState { return { - page: this.page, - mono: this._monoMode, buffer: [ new Uint8Array(this._buffer[0]), new Uint8Array(this._buffer[1]), @@ -882,8 +840,6 @@ export class HiresPage2D implements HiresPage { } setState(state: GraphicsState) { - this.page = state.page; - this._monoMode = state.mono; this._buffer[0] = new Uint8Array(state.buffer[0]); this._buffer[1] = new Uint8Array(state.buffer[1]); @@ -892,37 +848,53 @@ export class HiresPage2D implements HiresPage { } export class VideoModes2D implements VideoModes { - private _grs: LoresPage[]; - private _hgrs: HiresPage[]; - private _flag = 0; - private _context: CanvasRenderingContext2D | null; + private _grs: LoresPage[] = []; + private _hgrs: HiresPage[] = []; + private _screenContext: CanvasRenderingContext2D; + private _canvas: HTMLCanvasElement; private _left: number; private _top: number; private _refreshFlag: boolean = true; public ready = Promise.resolve(); + textMode: boolean; + mixedMode: boolean; + hiresMode: boolean; + pageMode: pageNo; + _80colMode: boolean; + altCharMode: boolean; + an3State: boolean; + doubleHiresMode: boolean; + + flag = 0; + monoMode = false; + + context: CanvasRenderingContext2D; + constructor( - gr: LoresPage, - hgr: HiresPage, - gr2: LoresPage, - hgr2: HiresPage, - private canvas: HTMLCanvasElement, - private e: boolean) { - this._grs = [gr, gr2]; - this._hgrs = [hgr, hgr2]; - this._context = this.canvas.getContext('2d'); - this._left = (this.canvas.width - 560) / 2; - this._top = (this.canvas.height - 384) / 2; + private screen: HTMLCanvasElement, + private e: boolean + ) { + this._canvas = document.createElement('canvas'); + const context = this._canvas.getContext('2d'); + const screenContext = this.screen.getContext('2d'); + if (!context || !screenContext) { + throw new Error('No 2d context'); + } + this.context = context; + + const { width, height } = { width: 560, height: 192 }; + this._canvas.width = width; + this._canvas.height = height; + + this._screenContext = screenContext; + this._left = (this.screen.width - 560) / 2; + this._top = (this.screen.height - 384) / 2; } - private _refresh() { - highColorTextMode = !an3 && textMode && !_80colMode; - highColorHGRMode = !an3 && hiresMode && !_80colMode; - doubleHiresMode = !an3 && hiresMode && _80colMode; - oneSixtyMode = this._flag == 1 && doubleHiresMode; - mixedDHRMode = this._flag == 2 && doubleHiresMode; - monoDHRMode = this._flag == 3 && doubleHiresMode; + _refresh() { + this.doubleHiresMode = !this.an3State && this.hiresMode && this._80colMode; this._refreshFlag = true; } @@ -932,25 +904,33 @@ export class VideoModes2D implements VideoModes { } reset() { - textMode = true; - mixedMode = false; - hiresMode = true; - pageMode = 1; + this.textMode = true; + this.mixedMode = false; + this.hiresMode = true; + this.pageMode = 1; - _80colMode = false; - altCharMode = false; + this._80colMode = false; + this.altCharMode = false; - this._flag = 0; - an3 = true; + this.flag = 0; + this.an3State = true; this._refresh(); } + setLoresPage(page: pageNo, lores: LoresPage) { + this._grs[page - 1] = lores; + } + + setHiresPage(page: pageNo, hires: HiresPage) { + this._hgrs[page - 1] = hires; + } + text(on: boolean) { - const old = textMode; - textMode = on; + const old = this.textMode; + this.textMode = on; if (on) { - this._flag = 0; + this.flag = 0; } if (old != on) { this._refresh(); @@ -960,29 +940,29 @@ export class VideoModes2D implements VideoModes { _80col(on: boolean) { if (!this.e) { return; } - const old = _80colMode; - _80colMode = on; + const old = this._80colMode; + this._80colMode = on; if (old != on) { this._refresh(); } } - altchar(on: boolean) { + altChar(on: boolean) { if (!this.e) { return; } - const old = altCharMode; - altCharMode = on; + const old = this.altCharMode; + this.altCharMode = on; if (old != on) { this._refresh(); } } hires(on: boolean) { - const old = hiresMode; - hiresMode = on; + const old = this.hiresMode; + this.hiresMode = on; if (!on) { - this._flag = 0; + this.flag = 0; } if (old != on) { @@ -993,11 +973,11 @@ export class VideoModes2D implements VideoModes { an3(on: boolean) { if (!this.e) { return; } - const old = an3; - an3 = on; + const old = this.an3State; + this.an3State = on; if (on) { - this._flag = ((this._flag << 1) | (_80colMode ? 0x0 : 0x1)) & 0x3; + this.flag = ((this.flag << 1) | (this._80colMode ? 0x0 : 0x1)) & 0x3; } if (old != on) { @@ -1010,47 +990,60 @@ export class VideoModes2D implements VideoModes { } mixed(on: boolean) { - const old = mixedMode; - mixedMode = on; + const old = this.mixedMode; + this.mixedMode = on; if (old != on) { this._refresh(); } } page(pageNo: pageNo) { - const old = pageMode; - pageMode = pageNo; + const old = this.pageMode; + this.pageMode = pageNo; if (old != pageNo) { this._refresh(); } } isText() { - return textMode; + return this.textMode; } isMixed() { - return mixedMode; + return this.mixedMode; } isPage2() { - return pageMode == 2; + return this.pageMode == 2; } isHires() { - return hiresMode; + return this.hiresMode; } isDoubleHires() { - return doubleHiresMode; + return this.doubleHiresMode; } is80Col() { - return _80colMode; + return this._80colMode; } isAltChar() { - return altCharMode; + return this.altCharMode; + } + + buildScreen(mainData: ImageData, mixData?: ImageData | null) { + // TODO(whscullin): - figure out 80 column offset + const { x, y } = this._80colMode ? { x: 0, y: 0 } : { x: 0, y: 0 }; + + if (mixData) { + this.context.putImageData(mainData, x, y, 0, 0, 560, 160); + this.context.putImageData(mixData, x, y, 0, 160, 560, 32); + } else { + this.context.putImageData(mainData, x, y); + } + return this._canvas; } updateImage( @@ -1059,14 +1052,11 @@ export class VideoModes2D implements VideoModes { mixData?: ImageData | null, mixDirty?: Region | null ) { - if (!this._context) { - throw new Error('No 2D context'); - } let blitted = false; if (mainDirty.bottom !== -1 || (mixDirty && mixDirty.bottom !== -1)) { - const imageData = buildScreen(mainData, mixData); - this._context.drawImage( + const imageData = this.buildScreen(mainData, mixData); + this._screenContext.drawImage( imageData, 0, 0, 560, 192, this._left, this._top, 560, 384 @@ -1078,8 +1068,8 @@ export class VideoModes2D implements VideoModes { blit(altData?: ImageData) { let blitted = false; - const hgr = this._hgrs[pageMode - 1]; - const gr = this._grs[pageMode - 1]; + const hgr = this._hgrs[this.pageMode - 1]; + const gr = this._grs[this.pageMode - 1]; if (this._refreshFlag) { hgr.refresh(); @@ -1092,12 +1082,12 @@ export class VideoModes2D implements VideoModes { altData, { top: 0, left: 0, right: 560, bottom: 192 } ); - } else if (hiresMode && !textMode) { + } else if (this.hiresMode && !this.textMode) { blitted = this.updateImage( hgr.imageData, hgr.dirty, - mixedMode ? gr.imageData : null, - mixedMode ? gr.dirty : null + this.mixedMode ? gr.imageData : null, + this.mixedMode ? gr.dirty : null ); } else { blitted = this.updateImage( @@ -1114,24 +1104,26 @@ export class VideoModes2D implements VideoModes { return { grs: [this._grs[0].getState(), this._grs[1].getState()], hgrs: [this._hgrs[0].getState(), this._hgrs[1].getState()], - textMode: textMode, - mixedMode: mixedMode, - hiresMode: hiresMode, - pageMode: pageMode, - _80colMode: _80colMode, - altCharMode: altCharMode, - an3: an3 + textMode: this.textMode, + mixedMode: this.mixedMode, + hiresMode: this.hiresMode, + pageMode: this.pageMode, + _80colMode: this._80colMode, + altCharMode: this.altCharMode, + an3State: this.an3State, + flag: this.flag }; } setState(state: VideoModesState) { - textMode = state.textMode; - mixedMode = state.mixedMode; - hiresMode = state.hiresMode; - pageMode = state.pageMode; - _80colMode = state._80colMode; - altCharMode = state.altCharMode; - an3 = state.an3; + this.textMode = state.textMode; + this.mixedMode = state.mixedMode; + this.hiresMode = state.hiresMode; + this.pageMode = state.pageMode; + this._80colMode = state._80colMode; + this.altCharMode = state.altCharMode; + this.an3State = state.an3State; + this.flag = state.flag; this._grs[0].setState(state.grs[0]); this._grs[1].setState(state.grs[1]); @@ -1142,20 +1134,17 @@ export class VideoModes2D implements VideoModes { mono(on: boolean) { if (on) { - this.canvas.classList.add('mono'); + this.screen.classList.add('mono'); } else { - this.canvas.classList.remove('mono'); + this.screen.classList.remove('mono'); } - this._grs[0].mono(on); - this._grs[1].mono(on); - this._hgrs[0].mono(on); - this._hgrs[1].mono(on); + this.monoMode = on; this._refresh(); } scanlines(on: boolean) { // Can't apply scanline filter to canvas - const parent = this.canvas.parentElement; + const parent = this.screen.parentElement; if (parent) { if (on) { parent.classList.add('scanlines'); @@ -1166,6 +1155,6 @@ export class VideoModes2D implements VideoModes { } getText() { - return this._grs[pageMode - 1].getText(); + return this._grs[this.pageMode - 1].getText(); } } diff --git a/js/gl.ts b/js/gl.ts index 270dc98..9ace807 100644 --- a/js/gl.ts +++ b/js/gl.ts @@ -25,41 +25,6 @@ import { pageNo } from './videomodes'; -let textMode = true; -let mixedMode = false; -let hiresMode = false; -let pageMode: pageNo = 1; -let _80colMode = false; -let altCharMode = false; -let an3 = false; -let doubleHiresMode = false; - -const tmpCanvas = document.createElement('canvas'); -const tmpContext = tmpCanvas.getContext('2d'); - -const buildScreen = (mainData: ImageData, mixData?: ImageData | null) => { - if (!tmpContext) { - throw new Error('No 2d context'); - } - - const details = screenEmu.C.NTSC_DETAILS; - const { width, height } = details.imageSize; - const { x, y } = _80colMode ? details.topLeft80Col : details.topLeft; - - tmpCanvas.width = width; - tmpCanvas.height = height; - tmpContext.fillStyle = 'rgba(0,0,0,1)'; - tmpContext.fillRect(0, 0, width, height); - - if (mixData) { - tmpContext.putImageData(mainData, x, y, 0, 0, 560, 160); - tmpContext.putImageData(mixData, x, y, 0, 160, 560, 32); - } else { - tmpContext.putImageData(mainData, x, y); - } - return tmpContext.getImageData(0, 0, width, height); -}; - // Color constants const whiteCol: Color = [255, 255, 255]; const blackCol: Color = [0, 0, 0]; @@ -84,21 +49,23 @@ export class LoresPageGL implements LoresPage { private _buffer: memory[] = []; private _refreshing = false; - private _monoMode = false; private _blink = false; dirty: Region = {...notDirty} imageData: ImageData; - constructor(private page: number, + constructor( + private vm: VideoModes, + private page: pageNo, private readonly charset: rom, - private readonly e: boolean) { - this.imageData = new ImageData(560, 192); - for (let idx = 0; idx < 560 * 192 * 4; idx++) { - this.imageData.data[idx] = 0xff; - } + private readonly e: boolean + ) { + this.imageData = this.vm.context.createImageData(560, 192); + this.imageData.data.fill(0xff); this._buffer[0] = allocMemPages(0x4); this._buffer[1] = allocMemPages(0x4); + + this.vm.setLoresPage(page, this); } private _drawPixel(data: Uint8ClampedArray, off: number, color: Color) { @@ -118,7 +85,7 @@ export class LoresPageGL implements LoresPage { private _checkInverse(val: byte) { let inverse = false; if (this.e) { - if (!_80colMode && !altCharMode) { + if (!this.vm._80colMode && !this.vm.altCharMode) { inverse = ((val & 0xc0) == 0x40) && this._blink; } } else { @@ -188,14 +155,14 @@ export class LoresPageGL implements LoresPage { x += 14; if (x > this.dirty.right) { this.dirty.right = x; } - if (textMode || hiresMode || (mixedMode && row > 19)) { - if (_80colMode) { + if (this.vm.textMode || this.vm.hiresMode || (this.vm.mixedMode && row > 19)) { + if (this.vm._80colMode) { const inverse = this._checkInverse(val); fore = inverse ? blackCol : whiteCol; back = inverse ? whiteCol : blackCol; - if (!altCharMode) { + if (!this.vm.altCharMode) { val = (val >= 0x40 && val < 0x80) ? val - 0x40 : val; } @@ -219,7 +186,7 @@ export class LoresPageGL implements LoresPage { fore = inverse ? blackCol : whiteCol; back = inverse ? whiteCol : blackCol; - if (!altCharMode) { + if (!this.vm.altCharMode) { val = (val >= 0x40 && val < 0x80) ? val - 0x40 : val; } @@ -251,7 +218,7 @@ export class LoresPageGL implements LoresPage { } } } else { - if (_80colMode && !an3) { + if (this.vm._80colMode && !this.vm.an3State) { let offset = (col * 14 + (bank ? 0 : 1) * 7 + row * 560 * 8) * 4; for (let jdx = 0; jdx < 8; jdx++) { let b = (jdx < 4) ? (val & 0x0f) : (val >> 4); @@ -295,17 +262,13 @@ export class LoresPageGL implements LoresPage { this._refreshing = true; for (let idx = 0; idx < 0x400; idx++, addr++) { this._write(addr >> 8, addr & 0xff, this._buffer[0][idx], 0); - if (_80colMode) { + if (this.vm._80colMode) { this._write(addr >> 8, addr & 0xff, this._buffer[1][idx], 1); } } this._refreshing = false; } - mono(on: boolean) { - this._monoMode = on; - } - blink() { let addr = 0x400 * this.page; this._refreshing = true; @@ -338,8 +301,6 @@ export class LoresPageGL implements LoresPage { getState(): GraphicsState { return { - page: this.page, - mono: this._monoMode, buffer: [ new Uint8Array(this._buffer[0]), new Uint8Array(this._buffer[1]), @@ -348,8 +309,6 @@ export class LoresPageGL implements LoresPage { } setState(state: GraphicsState) { - this.page = state.page; - this._monoMode = state.mono; this._buffer[0] = new Uint8Array(state.buffer[0]); this._buffer[1] = new Uint8Array(state.buffer[1]); @@ -380,7 +339,7 @@ export class LoresPageGL implements LoresPage { for (row = 0; row < 24; row++) { base = this.rowToBase(row); line = ''; - if (this.e && _80colMode) { + if (this.e && this.vm._80colMode) { for (col = 0; col < 80; col++) { charCode = this.mapCharCode(this._buffer[1 - col % 2][base + Math.floor(col / 2)]); line += String.fromCharCode(charCode); @@ -410,16 +369,17 @@ export class HiresPageGL implements HiresPage { private _buffer: memory[] = []; private _refreshing = false; - private _monoMode = false; constructor( - private page: number) { - this.imageData = new ImageData(560, 192); - for (let idx = 0; idx < 560 * 192 * 4; idx++) { - this.imageData.data[idx] = 0xff; - } + private vm: VideoModes, + private page: pageNo, + ) { + this.imageData = this.vm.context.createImageData(560, 192); + this.imageData.data.fill(0xff); this._buffer[0] = allocMemPages(0x20); this._buffer[1] = allocMemPages(0x20); + + this.vm.setHiresPage(page, this); } private _drawPixel(data: Uint8ClampedArray, off: number, color: Color) { @@ -486,7 +446,7 @@ export class HiresPageGL implements HiresPage { rowb = base >> 10; const data = this.imageData.data; - if ((rowa < 24) && (col < 40) && hiresMode) { + if ((rowa < 24) && (col < 40) && this.vm.hiresMode) { let y = rowa << 3 | rowb; if (y < this.dirty.top) { this.dirty.top = y; } y += 1; @@ -497,7 +457,7 @@ export class HiresPageGL implements HiresPage { if (x > this.dirty.right) { this.dirty.right = x; } const dy = rowa << 3 | rowb; - if (doubleHiresMode) { + if (this.vm.doubleHiresMode) { const dx = col * 14 + (bank ? 0 : 7); let offset = dx * 4 + dy * 280 * 4 * 2; @@ -554,17 +514,13 @@ export class HiresPageGL implements HiresPage { const page = addr >> 8; const off = addr & 0xff; this._write(page, off, this._buffer[0][idx], 0); - if (_80colMode) { + if (this.vm._80colMode) { this._write(page, off, this._buffer[1][idx], 1); } } this._refreshing = false; } - mono(on: boolean) { - this._monoMode = on; - } - start() { return this._start(); } @@ -583,8 +539,6 @@ export class HiresPageGL implements HiresPage { getState(): GraphicsState { return { - page: this.page, - mono: this._monoMode, buffer: [ new Uint8Array(this._buffer[0]), new Uint8Array(this._buffer[1]), @@ -593,8 +547,6 @@ export class HiresPageGL implements HiresPage { } setState(state: GraphicsState) { - this.page = state.page; - this._monoMode = state.mono; this._buffer[0] = new Uint8Array(state.buffer[0]); this._buffer[1] = new Uint8Array(state.buffer[1]); @@ -603,26 +555,44 @@ export class HiresPageGL implements HiresPage { } export class VideoModesGL implements VideoModes { - private _grs: LoresPage[]; - private _hgrs: HiresPage[]; - private _sv: any; + private _grs: LoresPage[] = []; + private _hgrs: HiresPage[] = []; + private _sv: screenEmu.ScreenView; private _displayConfig: screenEmu.DisplayConfiguration; - private _monoMode: boolean = false; private _scanlines: boolean = false; private _refreshFlag: boolean = true; + private _canvas: HTMLCanvasElement; public ready: Promise + public textMode: boolean; + public mixedMode: boolean; + public hiresMode: boolean; + public pageMode: pageNo; + public _80colMode: boolean; + public altCharMode: boolean; + public an3State: boolean; + public doubleHiresMode: boolean; + + public flag = 0; + public monoMode: boolean = false; + + public context: CanvasRenderingContext2D; + constructor( - gr: LoresPage, - hgr: HiresPage, - gr2: LoresPage, - hgr2: HiresPage, - private canvas: HTMLCanvasElement, - private e: boolean) { - this._grs = [gr, gr2]; - this._hgrs = [hgr, hgr2]; - this._sv = new screenEmu.ScreenView(this.canvas); + private screen: HTMLCanvasElement, + private e: boolean + ) { + this._canvas = document.createElement('canvas'); + const context = this._canvas.getContext('2d'); + if (!context) { + throw new Error('no 2d context'); + } + const { width, height } = screenEmu.C.NTSC_DETAILS.imageSize; + this._canvas.width = width; + this._canvas.height = height; + this.context = context; + this._sv = new screenEmu.ScreenView(this.screen); this.ready = this.init(); } @@ -630,15 +600,13 @@ export class VideoModesGL implements VideoModes { async init() { await this._sv.initOpenGL(); - (window as any)._sv = this._sv; - this._displayConfig = this.defaultMonitor(); this._sv.displayConfiguration = this._displayConfig; } private defaultMonitor(): screenEmu.DisplayConfiguration { const config = new screenEmu.DisplayConfiguration(); - config.displayResolution = new screenEmu.Size(this.canvas.width, this.canvas.height); + config.displayResolution = new screenEmu.Size(this.screen.width, this.screen.height); config.displayScanlineLevel = 0.5; config.videoWhiteOnly = true; config.videoSaturation = 0.8; @@ -651,15 +619,15 @@ export class VideoModesGL implements VideoModes { private monitorII(): screenEmu.DisplayConfiguration { // Values taken from openemulator/libemulation/res/library/Monitors/Apple Monitor II.xml const config = new screenEmu.DisplayConfiguration(); - config.displayResolution = new screenEmu.Size(this.canvas.width, this.canvas.height); + config.displayResolution = new screenEmu.Size(this.screen.width, this.screen.height); config.videoDecoder = 'CANVAS_MONOCHROME'; config.videoBrightness = 0.15; config.videoContrast = 0.8; config.videoSaturation = 1.45; config.videoHue = 0.27; - config.videoCenter = new screenEmu.Point(0, 0); - config.videoSize = new screenEmu.Size(1.05, 1.05); - config.videoBandwidth = 6000000; + config.videoCenter = new screenEmu.Point(0.01, 0.02); + config.videoSize = new screenEmu.Size(1.25, 1.15); + config.videoBandwidth = 9000000; config.displayBarrel = 0.1; config.displayScanlineLevel = 0.5; config.displayCenterLighting = 0.5; @@ -668,12 +636,12 @@ export class VideoModesGL implements VideoModes { } private _refresh() { - doubleHiresMode = !an3 && hiresMode && _80colMode; + this.doubleHiresMode = !this.an3State && this.hiresMode && this._80colMode; this._refreshFlag = true; if (this._displayConfig) { - this._displayConfig.videoWhiteOnly = textMode || this._monoMode; + this._displayConfig.videoWhiteOnly = this.textMode || this.monoMode; this._displayConfig.displayScanlineLevel = this._scanlines ? 0.5 : 0; this._sv.displayConfiguration = this._displayConfig; } @@ -684,22 +652,30 @@ export class VideoModesGL implements VideoModes { } reset() { - textMode = true; - mixedMode = false; - hiresMode = true; - pageMode = 1; + this.textMode = true; + this.mixedMode = false; + this.hiresMode = true; + this.pageMode = 1; - _80colMode = false; - altCharMode = false; + this._80colMode = false; + this.altCharMode = false; - an3 = true; + this.an3State = true; this._refresh(); } + setLoresPage(page: pageNo, lores: LoresPage) { + this._grs[page - 1] = lores; + } + + setHiresPage(page: pageNo, hires: HiresPage) { + this._hgrs[page - 1] = hires; + } + text(on: boolean) { - const old = textMode; - textMode = on; + const old = this.textMode; + this.textMode = on; if (old != on) { this._refresh(); @@ -709,27 +685,27 @@ export class VideoModesGL implements VideoModes { _80col(on: boolean) { if (!this.e) { return; } - const old = _80colMode; - _80colMode = on; + const old = this._80colMode; + this._80colMode = on; if (old != on) { this._refresh(); } } - altchar(on: boolean) { + altChar(on: boolean) { if (!this.e) { return; } - const old = altCharMode; - altCharMode = on; + const old = this.altCharMode; + this.altCharMode = on; if (old != on) { this._refresh(); } } hires(on: boolean) { - const old = hiresMode; - hiresMode = on; + const old = this.hiresMode; + this.hiresMode = on; if (old != on) { this._refresh(); @@ -739,8 +715,8 @@ export class VideoModesGL implements VideoModes { an3(on: boolean) { if (!this.e) { return; } - const old = an3; - an3 = on; + const old = this.an3State; + this.an3State = on; if (old != on) { this._refresh(); @@ -752,47 +728,47 @@ export class VideoModesGL implements VideoModes { } mixed(on: boolean) { - const old = mixedMode; - mixedMode = on; + const old = this.mixedMode; + this.mixedMode = on; if (old != on) { this._refresh(); } } page(pageNo: pageNo) { - const old = pageMode; - pageMode = pageNo; + const old = this.pageMode; + this.pageMode = pageNo; if (old != pageNo) { this._refresh(); } } isText() { - return textMode; + return this.textMode; } isMixed() { - return mixedMode; + return this.mixedMode; } isPage2() { - return pageMode == 2; + return this.pageMode == 2; } isHires() { - return hiresMode; + return this.hiresMode; } isDoubleHires() { - return doubleHiresMode; + return this.doubleHiresMode; } is80Col() { - return _80colMode; + return this._80colMode; } isAltChar() { - return altCharMode; + return this.altCharMode; } updateImage( @@ -803,7 +779,7 @@ export class VideoModesGL implements VideoModes { ) { let blitted = false; if (mainDirty.bottom !== -1 || (mixDirty && mixDirty.bottom !== -1)) { - const imageData = buildScreen(mainData, mixData); + const imageData = this.buildScreen(mainData, mixData); const imageInfo = new screenEmu.ImageInfo(imageData); this._sv.image = imageInfo; blitted = true; @@ -812,10 +788,24 @@ export class VideoModesGL implements VideoModes { return blitted; } + buildScreen(mainData: ImageData, mixData?: ImageData | null) { + const details = screenEmu.C.NTSC_DETAILS; + const { width, height } = details.imageSize; + const { x, y } = this._80colMode ? details.topLeft80Col : details.topLeft; + + if (mixData) { + this.context.putImageData(mainData, x, y, 0, 0, 560, 160); + this.context.putImageData(mixData, x, y, 0, 160, 560, 32); + } else { + this.context.putImageData(mainData, x, y); + } + return this.context.getImageData(0, 0, width, height); + } + blit(altData?: ImageData) { let blitted = false; - const hgr = this._hgrs[pageMode - 1]; - const gr = this._grs[pageMode - 1]; + const hgr = this._hgrs[this.pageMode - 1]; + const gr = this._grs[this.pageMode - 1]; if (this._refreshFlag) { hgr.refresh(); @@ -828,12 +818,12 @@ export class VideoModesGL implements VideoModes { altData, { top: 0, left: 0, right: 560, bottom: 192 } ); - } else if (hiresMode && !textMode) { + } else if (this.hiresMode && !this.textMode) { blitted = this.updateImage( hgr.imageData, hgr.dirty, - mixedMode ? gr.imageData : null, - mixedMode ? gr.dirty : null + this.mixedMode ? gr.imageData : null, + this.mixedMode ? gr.dirty : null ); } else { blitted = this.updateImage( @@ -850,24 +840,25 @@ export class VideoModesGL implements VideoModes { return { grs: [this._grs[0].getState(), this._grs[1].getState()], hgrs: [this._hgrs[0].getState(), this._hgrs[1].getState()], - textMode: textMode, - mixedMode: mixedMode, - hiresMode: hiresMode, - pageMode: pageMode, - _80colMode: _80colMode, - altCharMode: altCharMode, - an3: an3 + textMode: this.textMode, + mixedMode: this.mixedMode, + hiresMode: this.hiresMode, + pageMode: this.pageMode, + _80colMode: this._80colMode, + altCharMode: this.altCharMode, + an3State: this.an3State, + flag: 0 }; } setState(state: VideoModesState) { - textMode = state.textMode; - mixedMode = state.mixedMode; - hiresMode = state.hiresMode; - pageMode = state.pageMode; - _80colMode = state._80colMode; - altCharMode = state.altCharMode; - an3 = state.an3; + this.textMode = state.textMode; + this.mixedMode = state.mixedMode; + this.hiresMode = state.hiresMode; + this.pageMode = state.pageMode; + this._80colMode = state._80colMode; + this.altCharMode = state.altCharMode; + this.an3State = state.an3State; this._grs[0].setState(state.grs[0]); this._grs[1].setState(state.grs[1]); @@ -877,12 +868,7 @@ export class VideoModesGL implements VideoModes { } mono(on: boolean) { - this._grs[0].mono(on); - this._grs[1].mono(on); - this._hgrs[0].mono(on); - this._hgrs[1].mono(on); - - this._monoMode = on; + this.monoMode = on; this._displayConfig = on ? this.monitorII() : this.defaultMonitor(); this._refresh(); } @@ -893,6 +879,6 @@ export class VideoModesGL implements VideoModes { } getText() { - return this._grs[pageMode - 1].getText(); + return this._grs[this.pageMode - 1].getText(); } } diff --git a/js/mmu.ts b/js/mmu.ts index 5a70005..f0d1ed3 100644 --- a/js/mmu.ts +++ b/js/mmu.ts @@ -519,11 +519,11 @@ export default class MMU implements Memory, Restorable { break; case LOC.CLRALTCH: this._debug('Alt Char off'); - this.vm.altchar(false); + this.vm.altChar(false); break; case LOC.SETALTCH: this._debug('Alt Char on'); - this.vm.altchar(true); + this.vm.altChar(true); break; } this._updateBanks(); diff --git a/js/ui/options_modal.ts b/js/ui/options_modal.ts index 4e38ca5..9ec4768 100644 --- a/js/ui/options_modal.ts +++ b/js/ui/options_modal.ts @@ -38,10 +38,6 @@ export class OptionsModal { private handlers: Record = {} private sections: OptionSection[] = [] - construct() { - this.prefs = new Prefs(); - } - addOptions(handler: OptionHandler) { const sections = handler.getOptions(); for (const section of sections) { diff --git a/js/videomodes.ts b/js/videomodes.ts index b141aa8..883223d 100644 --- a/js/videomodes.ts +++ b/js/videomodes.ts @@ -1,4 +1,4 @@ -import { MemoryPages, Restorable, byte, memory } from './types'; +import { MemoryPages, Restorable, memory } from './types'; export type bank = 0 | 1; export type pageNo = 1 | 2; @@ -11,8 +11,6 @@ export interface Region { } export interface GraphicsState { - page: byte; - mono: boolean; buffer: memory[]; } @@ -25,7 +23,8 @@ export interface VideoModesState { pageMode: pageNo, _80colMode: boolean, altCharMode: boolean, - an3: boolean, + an3State: boolean, + flag: number, } export interface VideoPage extends MemoryPages, Restorable { @@ -35,7 +34,6 @@ export interface VideoPage extends MemoryPages, Restorable { bank0(): MemoryPages bank1(): MemoryPages - mono: (on: boolean) => void refresh: () => void } @@ -48,14 +46,32 @@ export interface HiresPage extends VideoPage { } export interface VideoModes extends Restorable { + textMode: boolean + mixedMode: boolean + hiresMode: boolean + pageMode: pageNo + _80colMode: boolean + altCharMode: boolean + an3State: boolean + doubleHiresMode: boolean + + flag: number + monoMode: boolean + + context: CanvasRenderingContext2D; + page(pageNo: number): void blit(altData?: ImageData): boolean reset(): void + setLoresPage(page: pageNo, lores: LoresPage): void + setHiresPage(page: pageNo, lores: HiresPage): void + _80col(on: boolean): void - altchar(on: boolean): void + altChar(on: boolean): void + an3(on: boolean): void doubleHires(on: boolean): void hires(on: boolean): void mixed(on: boolean): void diff --git a/package-lock.json b/package-lock.json index b1de460..3da9af4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "version": "0.0.1", "license": "MIT", "dependencies": { - "apple2shader": "0.0.1", + "apple2shader": "0.0.3", "micromodal": "^0.4.2" }, "devDependencies": { @@ -17,14 +17,17 @@ "@testing-library/dom": "^7.30.3", "@testing-library/user-event": "^13.1.3", "@types/jest": "^26.0.14", + "@types/jest-image-snapshot": "^4.3.0", "@types/micromodal": "^0.3.2", "@typescript-eslint/eslint-plugin": "^4.6.1", "@typescript-eslint/parser": "^4.6.1", "ajv": "^6.12.0", "babel-jest": "^26.6.3", + "canvas": "^2.7.0", "eslint": "^7.22.0", "file-loader": "^6.0.0", "jest": "^26.6.3", + "jest-image-snapshot": "^4.5.0", "node-forge": "^0.10.0", "raw-loader": "^4.0.0", "ts-jest": "^26.5.0", @@ -2282,6 +2285,17 @@ "pretty-format": "^26.0.0" } }, + "node_modules/@types/jest-image-snapshot": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/jest-image-snapshot/-/jest-image-snapshot-4.3.0.tgz", + "integrity": "sha512-gb6zF1ICfvzBsQYMTq2qFhhiI46Cab/t5PtLK4Z3mpbyQoyKI2HgCFDi71iE7rjs6TrIPnsf2GXw+mnGvZSgrA==", + "dev": true, + "dependencies": { + "@types/jest": "*", + "@types/pixelmatch": "*", + "ssim.js": "^3.1.1" + } + }, "node_modules/@types/json-schema": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", @@ -2306,6 +2320,15 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "node_modules/@types/pixelmatch": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.3.tgz", + "integrity": "sha512-p+nAQVYK/DUx7+s1Xyu9dqAg0gobf7VmJ+iDA4lljg1o4XRgQHr7R2h1NwFt3gdNOZiftxWB11+0TuZqXYf19w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/prettier": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz", @@ -2726,6 +2749,12 @@ "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", "dev": true }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -2888,9 +2917,25 @@ } }, "node_modules/apple2shader": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/apple2shader/-/apple2shader-0.0.1.tgz", - "integrity": "sha512-RPexfw95Jk34UUHteg66rCG6GpNXySl2dD7P/WqyhJZ+7J5U9AJrIB/AxJMaBlHTyI8SZTlHvGX4r7tNCC6+qw==" + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/apple2shader/-/apple2shader-0.0.3.tgz", + "integrity": "sha512-7nAffvWdGp4FTqpDxgjslN4XqaO9FOUCKnhJ4IPQXGdyiCWIZvZ+E5gXsb+BKt0L+Fs3E5Kcn+5vOamSUSU0xA==" + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } }, "node_modules/argparse": { "version": "1.0.10", @@ -3599,6 +3644,21 @@ "integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==", "dev": true }, + "node_modules/canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.7.0.tgz", + "integrity": "sha512-pzCxtkHb+5su5MQjTtepMDlIOtaXo277x0C0u3nMOxtkhTyQ+h2yNKhlROAaDllWgRyePAUitC08sXw26Eb6aw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.15.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -3661,6 +3721,12 @@ "fsevents": "~2.3.1" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "node_modules/chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -3744,6 +3810,15 @@ "node": ">= 0.12.0" } }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -3874,6 +3949,12 @@ "node": ">=0.8" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, "node_modules/content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -4084,6 +4165,18 @@ "node": ">=0.10" } }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -4101,6 +4194,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -4286,6 +4388,12 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -4301,6 +4409,18 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -5740,6 +5860,15 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, + "dependencies": { + "minipass": "^2.6.0" + } + }, "node_modules/fs-monkey": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.1.tgz", @@ -5778,6 +5907,69 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5819,6 +6011,15 @@ "node": ">=8.0.0" } }, + "node_modules/get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -5928,6 +6129,12 @@ "node": ">= 4" } }, + "node_modules/glur": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/glur/-/glur-1.1.2.tgz", + "integrity": "sha1-8g6jbbEDv8KSNDkh8fkeg8NGdok=", + "dev": true + }, "node_modules/graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -5982,6 +6189,27 @@ "node": ">= 0.4.0" } }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", @@ -6012,6 +6240,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, "node_modules/has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -6076,9 +6310,9 @@ } }, "node_modules/hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "node_modules/hpack.js": { @@ -6226,6 +6460,15 @@ "node": ">= 4" } }, + "node_modules/ignore-walk": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", + "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", + "dev": true, + "dependencies": { + "minimatch": "^3.0.4" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -6301,6 +6544,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "node_modules/internal-ip": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz", @@ -7408,6 +7657,96 @@ "fsevents": "^2.1.2" } }, + "node_modules/jest-image-snapshot": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/jest-image-snapshot/-/jest-image-snapshot-4.5.0.tgz", + "integrity": "sha512-9Q1xyjyUsepNgn6/DaMnT4maaCSi3yaDp/xq1bnsOTk/tR3utygOTLOFOwztNrrkWX7HIXcm5PcHC2Mc5iBwUw==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "get-stdin": "^5.0.1", + "glur": "^1.1.2", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "pixelmatch": "^5.1.0", + "pngjs": "^3.4.0", + "rimraf": "^2.6.2", + "ssim.js": "^3.1.1" + }, + "engines": { + "node": ">= 10.14.2" + }, + "peerDependencies": { + "jest": ">=20 <=26" + } + }, + "node_modules/jest-image-snapshot/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-image-snapshot/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-image-snapshot/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-image-snapshot/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/jest-image-snapshot/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-image-snapshot/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/jest-jasmine2": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", @@ -8984,6 +9323,18 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -9008,6 +9359,31 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, + "node_modules/minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, + "dependencies": { + "minipass": "^2.9.0" + } + }, "node_modules/mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -9070,6 +9446,12 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "node_modules/nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "dev": true + }, "node_modules/nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -9174,6 +9556,32 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/needle": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", + "dev": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -9266,12 +9674,68 @@ "node": ">= 8" } }, + "node_modules/node-pre-gyp": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", + "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", + "deprecated": "Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future", + "dev": true, + "dependencies": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/node-pre-gyp/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/node-pre-gyp/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/node-releases": { "version": "1.1.71", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", "dev": true }, + "node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -9302,6 +9766,32 @@ "node": ">=0.10.0" } }, + "node_modules/npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "node_modules/npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "dev": true, + "dependencies": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -9323,6 +9813,27 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -9338,6 +9849,15 @@ "node": "*" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -9533,6 +10053,34 @@ "url-parse": "^1.4.3" } }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "node_modules/p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -9786,6 +10334,27 @@ "node": ">= 6" } }, + "node_modules/pixelmatch": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz", + "integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==", + "dev": true, + "dependencies": { + "pngjs": "^4.0.1" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz", + "integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -9798,6 +10367,15 @@ "node": ">=8" } }, + "node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -10065,6 +10643,30 @@ "webpack": "^4.0.0 || ^5.0.0" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -10863,6 +11465,12 @@ "node": ">=0.10.0" } }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "node_modules/saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -11119,6 +11727,37 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "dev": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -11546,6 +12185,12 @@ "node": ">=0.10.0" } }, + "node_modules/ssim.js": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/ssim.js/-/ssim.js-3.5.0.tgz", + "integrity": "sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g==", + "dev": true + }, "node_modules/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", @@ -11809,6 +12454,30 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "engines": { + "node": ">=4.5" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -13181,6 +13850,58 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -15193,6 +15914,17 @@ "pretty-format": "^26.0.0" } }, + "@types/jest-image-snapshot": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/jest-image-snapshot/-/jest-image-snapshot-4.3.0.tgz", + "integrity": "sha512-gb6zF1ICfvzBsQYMTq2qFhhiI46Cab/t5PtLK4Z3mpbyQoyKI2HgCFDi71iE7rjs6TrIPnsf2GXw+mnGvZSgrA==", + "dev": true, + "requires": { + "@types/jest": "*", + "@types/pixelmatch": "*", + "ssim.js": "^3.1.1" + } + }, "@types/json-schema": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", @@ -15217,6 +15949,15 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "@types/pixelmatch": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.3.tgz", + "integrity": "sha512-p+nAQVYK/DUx7+s1Xyu9dqAg0gobf7VmJ+iDA4lljg1o4XRgQHr7R2h1NwFt3gdNOZiftxWB11+0TuZqXYf19w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/prettier": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz", @@ -15542,6 +16283,12 @@ "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", "dev": true }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -15657,9 +16404,25 @@ } }, "apple2shader": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/apple2shader/-/apple2shader-0.0.1.tgz", - "integrity": "sha512-RPexfw95Jk34UUHteg66rCG6GpNXySl2dD7P/WqyhJZ+7J5U9AJrIB/AxJMaBlHTyI8SZTlHvGX4r7tNCC6+qw==" + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/apple2shader/-/apple2shader-0.0.3.tgz", + "integrity": "sha512-7nAffvWdGp4FTqpDxgjslN4XqaO9FOUCKnhJ4IPQXGdyiCWIZvZ+E5gXsb+BKt0L+Fs3E5Kcn+5vOamSUSU0xA==" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } }, "argparse": { "version": "1.0.10", @@ -16220,6 +16983,17 @@ "integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==", "dev": true }, + "canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.7.0.tgz", + "integrity": "sha512-pzCxtkHb+5su5MQjTtepMDlIOtaXo277x0C0u3nMOxtkhTyQ+h2yNKhlROAaDllWgRyePAUitC08sXw26Eb6aw==", + "dev": true, + "requires": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.15.0", + "simple-get": "^3.0.3" + } + }, "capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -16268,6 +17042,12 @@ "readdirp": "~3.5.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -16335,6 +17115,12 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -16452,6 +17238,12 @@ "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", "dev": true }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -16615,6 +17407,15 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -16629,6 +17430,12 @@ "regexp.prototype.flags": "^1.2.0" } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -16762,6 +17569,12 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -16774,6 +17587,12 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -17890,6 +18709,15 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", "dev": true }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, + "requires": { + "minipass": "^2.6.0" + } + }, "fs-monkey": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.1.tgz", @@ -17921,6 +18749,59 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -17950,6 +18831,12 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "dev": true + }, "get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -18031,6 +18918,12 @@ } } }, + "glur": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/glur/-/glur-1.1.2.tgz", + "integrity": "sha1-8g6jbbEDv8KSNDkh8fkeg8NGdok=", + "dev": true + }, "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -18075,6 +18968,23 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, "has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", @@ -18093,6 +19003,12 @@ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -18146,9 +19062,9 @@ } }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "hpack.js": { @@ -18273,6 +19189,15 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "ignore-walk": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", + "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -18329,6 +19254,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "internal-ip": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz", @@ -19132,6 +20063,74 @@ "walker": "^1.0.7" } }, + "jest-image-snapshot": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/jest-image-snapshot/-/jest-image-snapshot-4.5.0.tgz", + "integrity": "sha512-9Q1xyjyUsepNgn6/DaMnT4maaCSi3yaDp/xq1bnsOTk/tR3utygOTLOFOwztNrrkWX7HIXcm5PcHC2Mc5iBwUw==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "get-stdin": "^5.0.1", + "glur": "^1.1.2", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "pixelmatch": "^5.1.0", + "pngjs": "^3.4.0", + "rimraf": "^2.6.2", + "ssim.js": "^3.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, "jest-jasmine2": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", @@ -20323,6 +21322,12 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -20344,6 +21349,33 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, + "requires": { + "minipass": "^2.9.0" + } + }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -20396,6 +21428,12 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "dev": true + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -20481,6 +21519,28 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "needle": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", + "dev": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -20554,12 +21614,57 @@ } } }, + "node-pre-gyp": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", + "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", + "dev": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "node-releases": { "version": "1.1.71", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", "dev": true }, + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -20586,6 +21691,32 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -20603,6 +21734,24 @@ } } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -20615,6 +21764,12 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -20761,6 +21916,28 @@ "url-parse": "^1.4.3" } }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -20936,6 +22113,23 @@ "node-modules-regexp": "^1.0.0" } }, + "pixelmatch": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz", + "integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==", + "dev": true, + "requires": { + "pngjs": "^4.0.1" + }, + "dependencies": { + "pngjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz", + "integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==", + "dev": true + } + } + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -20945,6 +22139,12 @@ "find-up": "^4.0.0" } }, + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "dev": true + }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -21146,6 +22346,26 @@ "schema-utils": "^3.0.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + } + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -21770,6 +22990,12 @@ } } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -21995,6 +23221,23 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "dev": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -22366,6 +23609,12 @@ "tweetnacl": "~0.14.0" } }, + "ssim.js": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/ssim.js/-/ssim.js-3.5.0.tgz", + "integrity": "sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g==", + "dev": true + }, "stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", @@ -22568,6 +23817,29 @@ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "dev": true }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, "terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -23590,6 +24862,48 @@ "is-typed-array": "^1.1.3" } }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", diff --git a/package.json b/package.json index 9420295..8e46bda 100644 --- a/package.json +++ b/package.json @@ -29,14 +29,17 @@ "@testing-library/dom": "^7.30.3", "@testing-library/user-event": "^13.1.3", "@types/jest": "^26.0.14", + "@types/jest-image-snapshot": "^4.3.0", "@types/micromodal": "^0.3.2", "@typescript-eslint/eslint-plugin": "^4.6.1", "@typescript-eslint/parser": "^4.6.1", "ajv": "^6.12.0", "babel-jest": "^26.6.3", + "canvas": "^2.7.0", "eslint": "^7.22.0", "file-loader": "^6.0.0", "jest": "^26.6.3", + "jest-image-snapshot": "^4.5.0", "node-forge": "^0.10.0", "raw-loader": "^4.0.0", "ts-jest": "^26.5.0", @@ -48,7 +51,7 @@ "y18n": "^4.0.1" }, "dependencies": { - "apple2shader": "0.0.1", + "apple2shader": "0.0.3", "micromodal": "^0.4.2" } } diff --git a/test/jest-setup.js b/test/jest-setup.js new file mode 100644 index 0000000..579dbe1 --- /dev/null +++ b/test/jest-setup.js @@ -0,0 +1,3 @@ +import { toMatchImageSnapshot } from 'jest-image-snapshot'; + +expect.extend({ toMatchImageSnapshot }); diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-double-lores-renders-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-double-lores-renders-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..f58ae4cd6a6d2bd56e13697605f60a1247c32d06 GIT binary patch literal 2448 zcmeH}{Z~_U9LKL&QktQa(m@_-GZj5Kji<(7^I@=jVwIhfG+-QwNP!JmJdBMkbq%lU3+j!cANUliRj&Rw)3%k0@Hs;+!vtR3n{s7PSobUH@-amfc z@7Mc$Yu-s`ty~_y8~}iod){Wg2LMlP3(h^yE(_k_-VAQ=S$23IiwOiCpH@v<0{}di zx`(;*{fg%KJM1sdPDh125L|ih)t#pY)4$nzc>SIIo5Ogg&LsX&7k6{ji+l6m+ErZe z+6%{5tshT&QGNaU)NQ@{kG}G9&E$qF-`(3GACaxuHFDv%eNT@*TZF8=+gYogCHh-> zf4v<_SDMO=N}EvW7Ao<)yP9kj{HVHb>?}h!CZ7ilusyoI%X)I$u-jxI2fQ<188n?d zq%UPP2iAX0f?9-cGL)n9G>iR~IB<1DN3i#&cDwc(^Jl$Zczf#!vaIlk&3nVt@A2US z5n8zg&Q#j9va~3_KnC+{6@;i$aRA0N4Gm=h?^33kWL53tA7T~*uDP#+qY1hzR63#44fl!u_7!ga_+SKAV7sqn=Tj;Zldy9q*h_ z4dK?gzG^V+gK1xZ;$*d-{)WsO;~3m;uAeZfmcV@Cp_&BIOJeluCfl^PDTFgHTOJh; z5mhu^TK3{E$EwP0c)|=GTY5AP8K+2Ulkvh_EwA5}KTM=-ZVsC ztmNfafYyj-mI0esPo#fs;w~XFnWFr_eXhbEP;P?bQF|Be1d7W6sVYm zm#Jsa*towAS*pWGp}#~kq!mTT1$Q_!kWz;dn{5mq`Ux_`k=Aq-`^^S+^tt-v8PA^) zTH?+Z0o)kK(8McGO1-DialY1EX8v&y@6sIJT!NXp6g#C9x48u!Ro}iDpy6GG3U0Lm zJIR)@?3s$%YNPSxM6Z7oZS~H_TCR#6c6;Mt4~0@;V8avzL~4_(Mpj9gMe1gvZpknBbYEF zNr@NuZZal|bBP0|;&lxCBgB5IB9o&}Q!(x30ghC-MNfS4LwoaeRmy6i+t5h$*ENzT z-OWnvwJlh_))~3r31((siPjT0`4}=eERdJh`4)ps+{0^OR&jMaLQ+g;DI3Ogo1m5C80oYm<35f8*iWtOPun!hH> zwm62tF~_4h*eZ;@_PUB+S()T$)iw@U+U|vg+X9_yg2{$kQ5a!73HJZx8Ry>6?VAzh TeV-BhVgY+n)0tOx<$V4(qlj3x literal 0 HcmV?d00001 diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-double-lores-renders-mono-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-double-lores-renders-mono-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..b58b8f401dc901be220073e22ea106db79fa33d2 GIT binary patch literal 1265 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 KuYB*LFa8&e`=|i` literal 0 HcmV?d00001 diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-hires-renders-mono-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-hires-renders-mono-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..66d21dc4dc4f35045b31641fff3027bab3465520 GIT binary patch literal 1186 zcmeAS@N?(olHy`uVBq!ia0y~yU@`!*4{)#nNvS`P@j!|#$=lt9;Xep2*t>i(0|QIA zr;B4q#hkY{4(2U35OD}Je)a#qQEbnqjah;MTXtTu+|6R&!?7a#?lFnqI}+~y@6Wwm zmip(*pX|Sdb&;R|kU}gAlkcEd~f^q^wgA5}x!xIh(1_k#63=Eb` zYz!v^4Hz8y8WrzXZHPO@#kMZtBdE`nykr-G6Qb_!2=G)tP4UgUU{`dI#@aJ*!-+q5n z_qp=^-S-*i>h^EG|L1S^-`(d=XMs64zu&o&es*qb{=58Vf7nNA^1~gX&!xk8qb6m@ SZB+yoIt-q!elF{r5}E+yUUek^ literal 0 HcmV?d00001 diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..91ce95d04248383267ee835f18a41d5ae7327d7d GIT binary patch literal 2742 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 literal 0 HcmV?d00001 diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-mono-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-mono-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef12a0881cbf6af57aa393f45bc478ec7719797 GIT binary patch literal 7466 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 literal 0 HcmV?d00001 diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-40-column-renders-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-40-column-renders-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..675ca3594a107bf9ae26cb4b54c5d12ca7e6e119 GIT binary patch literal 11103 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 literal 0 HcmV?d00001 diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-80-column-renders-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-80-column-renders-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..456cabb5975131c3fe8bcd5177852eba95bc9619 GIT binary patch literal 20706 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 GfA}|T`idn0 literal 0 HcmV?d00001 diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-double-lores-renders-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-double-lores-renders-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef12a0881cbf6af57aa393f45bc478ec7719797 GIT binary patch literal 7466 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%0DMI05`%v1RBa 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 literal 0 HcmV?d00001 diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-lores-renders-mixed-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-lores-renders-mixed-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..3d04f172a299f872d063bf2220894ea1ae2d7360 GIT binary patch literal 6269 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 literal 0 HcmV?d00001 diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-80-column-renders-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-80-column-renders-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..456cabb5975131c3fe8bcd5177852eba95bc9619 GIT binary patch literal 20706 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 { + + describe('LoresPage', () => { + let canvas: HTMLCanvasElement; + let lores1: LoresPage2D; + let vm: VideoModes2D; + + beforeEach(() => { + canvas = document.createElement('canvas'); + vm = new VideoModes2D(canvas, true); + lores1 = new LoresPage2D(vm, 1, apple2enh_char, true); + vm.reset(); + vm.hires(false); + }); + + describe('text mode', () => { + describe('40 column', () => { + it('renders', () => { + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + + it('renders alt chars', () => { + vm.altChar(true); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + }); + + describe('80 column', () => { + it('renders', () => { + vm._80col(true); + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + + it('renders alt chars', () => { + vm.altChar(true); + vm._80col(true); + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + }); + }); + + describe('graphics mode', () => { + describe('lores', () => { + it('renders', () => { + vm.text(false); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + + it('renders mixed', () => { + vm.text(false); + vm.mixed(true); + + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + + it('renders mono', () => { + vm.text(false); + vm.mono(true); + + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + }); + + describe('double lores', () => { + it('renders', () => { + vm.text(false); + vm._80col(true); + vm.an3(false); + + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + + it('renders mixed', () => { + vm.text(false); + vm.mixed(true); + vm._80col(true); + vm.an3(false); + + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + + it('renders mono', () => { + vm.text(false); + vm._80col(true); + vm.an3(false); + vm.mono(true); + + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + }); + }); + }); + + describe('HiresPage', () => { + let canvas: HTMLCanvasElement; + let hires1: HiresPage2D; + let vm: VideoModes2D; + + beforeEach(() => { + canvas = document.createElement('canvas'); + vm = new VideoModes2D(canvas, true); + hires1 = new HiresPage2D(vm, 1); + vm.reset(); + vm.hires(true); + }); + + describe('hires', () => { + it('renders', () => { + vm.text(false); + for (let page = 0x20; page < 0x40; page++) { + for (let off = 0; off < 0x100; off++) { + hires1.write(page, off, off); + } + } + + checkImageData(hires1); + }); + + it('renders mono', () => { + vm.text(false); + vm.mono(true); + + for (let page = 0x20; page < 0x40; page++) { + for (let off = 0; off < 0x100; off++) { + hires1.write(page, off, off); + } + } + + checkImageData(hires1); + }); + }); + + describe('double lores', () => { + it('renders', () => { + vm.text(false); + vm._80col(true); + vm.an3(false); + + const bank0 = hires1.bank0(); + const bank1 = hires1.bank1(); + for (let page = 0x20; page < 0x40; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(hires1); + }); + + it('renders mono', () => { + vm.text(false); + vm._80col(true); + vm.an3(false); + vm.mono(true); + + const bank0 = hires1.bank0(); + const bank1 = hires1.bank1(); + for (let page = 0x20; page < 0x40; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(hires1); + }); + }); + }); +}); diff --git a/test/js/gl.test.ts b/test/js/gl.test.ts new file mode 100644 index 0000000..8724149 --- /dev/null +++ b/test/js/gl.test.ts @@ -0,0 +1,197 @@ +/** @fileoverview Test for canvas.ts. */ + +import { VideoPage } from 'js/videomodes'; +import { LoresPageGL, HiresPageGL, VideoModesGL } from 'js/gl'; +import apple2enh_char from 'js/roms/apple2enh_char'; +import { createImageFromImageData } from 'test/util/image'; + +function checkImageData(page: VideoPage) { + page.refresh(); + const img = createImageFromImageData(page.imageData); + expect(img).toMatchImageSnapshot(); +} + +describe('gl', () => { + + describe('LoresPage', () => { + let canvas: HTMLCanvasElement; + let lores1: LoresPageGL; + let vm: VideoModesGL; + + beforeEach(async () => { + canvas = document.createElement('canvas'); + vm = new VideoModesGL(canvas, true); + await vm.ready; + lores1 = new LoresPageGL(vm, 1, apple2enh_char, true); + vm.reset(); + vm.hires(false); + }); + + describe('text mode', () => { + describe('40 column', () => { + it('renders', () => { + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + + it('renders alt chars', () => { + vm.altChar(true); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + }); + + describe('80 column', () => { + it('renders', () => { + vm._80col(true); + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + + it('renders alt chars', () => { + vm.altChar(true); + vm._80col(true); + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + }); + }); + + describe('graphics mode', () => { + describe('lores', () => { + it('renders', () => { + vm.text(false); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + + it('renders mixed', () => { + vm.text(false); + vm.mixed(true); + + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + }); + + describe('double lores', () => { + it('renders', () => { + vm.text(false); + vm._80col(true); + vm.an3(false); + + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + + it('renders mixed', () => { + vm.text(false); + vm.mixed(true); + vm._80col(true); + vm.an3(false); + + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + }); + }); + }); + + describe('HiresPage', () => { + let canvas: HTMLCanvasElement; + let hires1: HiresPageGL; + let vm: VideoModesGL; + + beforeEach(() => { + canvas = document.createElement('canvas'); + vm = new VideoModesGL(canvas, true); + hires1 = new HiresPageGL(vm, 1); + vm.reset(); + vm.hires(true); + }); + + describe('hires', () => { + it('renders', () => { + vm.text(false); + for (let page = 0x20; page < 0x40; page++) { + for (let off = 0; off < 0x100; off++) { + hires1.write(page, off, off); + } + } + + checkImageData(hires1); + }); + }); + + describe('double lores', () => { + it('renders', () => { + vm.text(false); + vm._80col(true); + vm.an3(false); + + const bank0 = hires1.bank0(); + const bank1 = hires1.bank1(); + for (let page = 0x20; page < 0x40; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(hires1); + }); + }); + }); +}); diff --git a/test/util/image.ts b/test/util/image.ts new file mode 100644 index 0000000..761e6c2 --- /dev/null +++ b/test/util/image.ts @@ -0,0 +1,9 @@ +export const createImageFromImageData = (data: ImageData) => { + const canvas = document.createElement('canvas'); + canvas.width = data.width; + canvas.height = data.height; + const ctx = canvas.getContext('2d')!; + ctx.putImageData(data, 0, 0); + const url = canvas.toDataURL('image/png'); + return Buffer.from(url.split(',')[1], 'base64'); +}; diff --git a/tsconfig.json b/tsconfig.json index 96d79eb..d008dcf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,8 +21,11 @@ ], "js/*": [ "js/*" + ], + "test/*": [ + "test/*" ] - }, + } }, "include": [ "js/**/*",