From 0642e65c9d01b73d553825ba5d1c04448f519469 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 13 Nov 2017 15:26:46 -0800 Subject: [PATCH] Stub integration test functionality some more --- demo/client/css/pathfinder.css | 2 +- demo/client/html/3d-demo.html.hbs | 2 +- demo/client/html/integration-test.html.hbs | 7 +- demo/client/html/mesh-debugger.html.hbs | 3 +- demo/client/html/svg-demo.html.hbs | 3 +- demo/client/html/text-demo.html.hbs | 3 +- demo/client/src/app-controller.ts | 4 +- demo/client/src/benchmark.ts | 2 +- demo/client/src/integration-test.ts | 218 +++++++++++++++++++-- demo/client/src/view.ts | 52 ++--- 10 files changed, 239 insertions(+), 57 deletions(-) diff --git a/demo/client/css/pathfinder.css b/demo/client/css/pathfinder.css index 5e834b5e..9201981d 100644 --- a/demo/client/css/pathfinder.css +++ b/demo/client/css/pathfinder.css @@ -99,7 +99,7 @@ button > svg path { #pf-rendering-options-group { right: 1em; } -#pf-canvas { +.pf-maximized-canvas { display: block; position: absolute; touch-action: none; diff --git a/demo/client/html/3d-demo.html.hbs b/demo/client/html/3d-demo.html.hbs index c9859ebe..963c3693 100644 --- a/demo/client/html/3d-demo.html.hbs +++ b/demo/client/html/3d-demo.html.hbs @@ -8,7 +8,7 @@ {{>partials/navbar.html isDemo=true}} - +
diff --git a/demo/client/html/integration-test.html.hbs b/demo/client/html/integration-test.html.hbs index 1b1f3cd1..f1382257 100644 --- a/demo/client/html/integration-test.html.hbs +++ b/demo/client/html/integration-test.html.hbs @@ -28,10 +28,11 @@
- - - + + diff --git a/demo/client/html/mesh-debugger.html.hbs b/demo/client/html/mesh-debugger.html.hbs index 7e21cc4a..03a19e25 100644 --- a/demo/client/html/mesh-debugger.html.hbs +++ b/demo/client/html/mesh-debugger.html.hbs @@ -8,7 +8,8 @@ {{>partials/navbar.html isTool=true}} - +
diff --git a/demo/client/html/svg-demo.html.hbs b/demo/client/html/svg-demo.html.hbs index 1b432372..6fa70a13 100644 --- a/demo/client/html/svg-demo.html.hbs +++ b/demo/client/html/svg-demo.html.hbs @@ -8,7 +8,8 @@ {{>partials/navbar.html isDemo=true}} - +
diff --git a/demo/client/html/text-demo.html.hbs b/demo/client/html/text-demo.html.hbs index 14e73fea..157eec3d 100644 --- a/demo/client/html/text-demo.html.hbs +++ b/demo/client/html/text-demo.html.hbs @@ -8,7 +8,8 @@ {{>partials/navbar.html isDemo=true}} - +
diff --git a/demo/client/src/app-controller.ts b/demo/client/src/app-controller.ts index 7f4a675b..e131abb8 100644 --- a/demo/client/src/app-controller.ts +++ b/demo/client/src/app-controller.ts @@ -68,9 +68,7 @@ export abstract class AppController { protected screenshotButton: HTMLButtonElement | null; - start() { - const canvas = document.getElementById('pf-canvas') as HTMLCanvasElement; - } + start(): void {} protected loadInitialFile(builtinFileURI: string) { const selectFileElement = document.getElementById('pf-select-file') as diff --git a/demo/client/src/benchmark.ts b/demo/client/src/benchmark.ts index b8b63cb3..eb4fa3be 100644 --- a/demo/client/src/benchmark.ts +++ b/demo/client/src/benchmark.ts @@ -74,7 +74,7 @@ class BenchmarkAppController extends DemoAppController { private elapsedTimes: ElapsedTime[]; private partitionTime: number; - start() { + start(): void { super.start(); this.resultsModal = unwrapNull(document.getElementById('pf-benchmark-results-modal')) as diff --git a/demo/client/src/integration-test.ts b/demo/client/src/integration-test.ts index c278dd95..0889c323 100644 --- a/demo/client/src/integration-test.ts +++ b/demo/client/src/integration-test.ts @@ -15,18 +15,27 @@ import {SubpixelAAType} from './aa-strategy'; import {DemoAppController} from "./app-controller"; import {OrthographicCamera} from './camera'; import {UniformMap} from './gl-utils'; +import {PathfinderMeshData} from './meshes'; import {Renderer} from "./renderer"; -import {ShaderMap} from "./shader-loader"; +import {ShaderMap, ShaderProgramSource} from "./shader-loader"; import SSAAStrategy from './ssaa-strategy'; +import {BUILTIN_FONT_URI, computeStemDarkeningAmount, ExpandedMeshData, GlyphStore} from "./text"; +import {PathfinderFont, TextFrame, TextRun} from "./text"; +import {unwrapNull} from "./utils"; import {DemoView} from "./view"; import {AdaptiveMonochromeXCAAStrategy} from './xcaa-strategy'; +const FONT: string = 'open-sans'; +const TEXT_COLOR: number[] = [0, 0, 0, 255]; + const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { none: NoAAStrategy, ssaa: SSAAStrategy, xcaa: AdaptiveMonochromeXCAAStrategy, }; +const STRING: string = "A"; + interface AntialiasingStrategyTable { none: typeof NoAAStrategy; ssaa: typeof SSAAStrategy; @@ -34,38 +43,183 @@ interface AntialiasingStrategyTable { } class IntegrationTestAppController extends DemoAppController { - protected builtinFileURI: string; - protected defaultFile: string; - protected createView(): IntegrationTestView { - throw new Error("Method not implemented."); + font: PathfinderFont | null; + textRun: TextRun | null; + + protected readonly defaultFile: string = FONT; + protected readonly builtinFileURI: string = BUILTIN_FONT_URI; + + private glyphStore: GlyphStore; + private baseMeshes: PathfinderMeshData; + private expandedMeshes: ExpandedMeshData; + + start(): void { + super.start(); + + this.loadInitialFile(this.builtinFileURI); } - protected fileLoaded(data: ArrayBuffer, builtinName: string | null): void { - throw new Error("Method not implemented."); + + protected createView(): IntegrationTestView { + return new IntegrationTestView(this, + unwrapNull(this.gammaLUT), + unwrapNull(this.commonShaderSource), + unwrapNull(this.shaderSources)); + } + + protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void { + const font = new PathfinderFont(fileData, builtinName); + this.font = font; + + const textRun = new TextRun(STRING, [0, 0], font); + textRun.layout(); + this.textRun = textRun; + const textFrame = new TextFrame([textRun], font); + + const glyphIDs = textFrame.allGlyphIDs; + glyphIDs.sort((a, b) => a - b); + this.glyphStore = new GlyphStore(font, glyphIDs); + + this.glyphStore.partition().then(result => { + this.baseMeshes = result.meshes; + + const expandedMeshes = textFrame.expandMeshes(this.baseMeshes, glyphIDs); + this.expandedMeshes = expandedMeshes; + + this.view.then(view => { + view.attachMeshes([expandedMeshes.meshes]); + }); + }); } } class IntegrationTestView extends DemoView { + readonly renderer: IntegrationTestRenderer; + readonly appController: IntegrationTestAppController; + get camera(): OrthographicCamera { return this.renderer.camera; } - readonly renderer: IntegrationTestRenderer; + + constructor(appController: IntegrationTestAppController, + gammaLUT: HTMLImageElement, + commonShaderSource: string, + shaderSources: ShaderMap) { + super(gammaLUT, commonShaderSource, shaderSources); + + this.appController = appController; + this.renderer = new IntegrationTestRenderer(this); + + this.resizeToFit(true); + } } class IntegrationTestRenderer extends Renderer { + renderContext: IntegrationTestView; camera: OrthographicCamera; - destFramebuffer: WebGLFramebuffer | null; - destAllocatedSize: glmatrix.vec2; - destUsedSize: glmatrix.vec2; - protected objectCount: number; - protected usedSizeFactor: glmatrix.vec2; - protected worldTransform: glmatrix.mat4; + + private _pixelsPerEm: number = 32.0; + + get destFramebuffer(): WebGLFramebuffer | null { + return null; + } + + get bgColor(): glmatrix.vec4 { + return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]); + } + + get fgColor(): glmatrix.vec4 { + return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]); + } + + get destAllocatedSize(): glmatrix.vec2 { + const canvas = this.renderContext.canvas; + return glmatrix.vec2.clone([canvas.width, canvas.height]); + } + + get destUsedSize(): glmatrix.vec2 { + return this.destAllocatedSize; + } + + get emboldenAmount(): glmatrix.vec2 { + return this.stemDarkeningAmount; + } + + protected get objectCount(): number { + return this.meshes.length; + } + + protected get usedSizeFactor(): glmatrix.vec2 { + return glmatrix.vec2.clone([1.0, 1.0]); + } + + protected get worldTransform() { + const canvas = this.renderContext.canvas; + + const transform = glmatrix.mat4.create(); + const translation = this.camera.translation; + glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]); + glmatrix.mat4.scale(transform, transform, [2.0 / canvas.width, 2.0 / canvas.height, 1.0]); + glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]); + glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]); + + const pixelsPerUnit = this.pixelsPerUnit; + glmatrix.mat4.scale(transform, transform, [pixelsPerUnit, pixelsPerUnit, 1.0]); + + return transform; + } + + private get pixelsPerUnit(): number { + const font = unwrapNull(this.renderContext.appController.font); + return this._pixelsPerEm / font.opentypeFont.unitsPerEm; + } + + private get stemDarkeningAmount(): glmatrix.vec2 { + return computeStemDarkeningAmount(this._pixelsPerEm, this.pixelsPerUnit); + } + + constructor(renderContext: IntegrationTestView) { + super(renderContext); + + this.camera = new OrthographicCamera(renderContext.canvas); + this.camera.onPan = () => renderContext.setDirty(); + this.camera.onZoom = () => renderContext.setDirty(); + } + + attachMeshes(meshes: PathfinderMeshData[]): void { + super.attachMeshes(meshes); + + this.uploadPathColors(1); + this.uploadPathTransforms(1); + } + + pathCountForObject(objectIndex: number): number { + return STRING.length; + } pathBoundingRects(objectIndex: number): Float32Array { - throw new Error("Method not implemented."); + const appController = this.renderContext.appController; + const font = unwrapNull(appController.font); + + const boundingRects = new Float32Array((STRING.length + 1) * 4); + + for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) { + const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex]; + + const metrics = font.metricsForGlyph(glyphID); + if (metrics == null) + continue; + + boundingRects[(glyphIndex + 1) * 4 + 0] = metrics.xMin; + boundingRects[(glyphIndex + 1) * 4 + 1] = metrics.yMin; + boundingRects[(glyphIndex + 1) * 4 + 2] = metrics.xMax; + boundingRects[(glyphIndex + 1) * 4 + 3] = metrics.yMax; + } + + return boundingRects; } setHintsUniform(uniforms: UniformMap): void { - throw new Error("Method not implemented."); + this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0); } protected createAAStrategy(aaType: AntialiasingStrategyName, @@ -75,16 +229,38 @@ class IntegrationTestRenderer extends Renderer { return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA); } - protected compositeIfNecessary(): void { - throw new Error("Method not implemented."); - } + protected compositeIfNecessary(): void {} protected pathColorsForObject(objectIndex: number): Uint8Array { - throw new Error("Method not implemented."); + const pathColors = new Uint8Array(4 * (STRING.length + 1)); + for (let pathIndex = 0; pathIndex < STRING.length; pathIndex++) + pathColors.set(TEXT_COLOR, (pathIndex + 1) * 4); + return pathColors; } protected pathTransformsForObject(objectIndex: number): Float32Array { - throw new Error("Method not implemented."); + const appController = this.renderContext.appController; + const canvas = this.renderContext.canvas; + const font = unwrapNull(appController.font); + + const pathTransforms = new Float32Array(4 * (STRING.length + 1)); + + let currentX = 0, currentY = 0; + const availableWidth = canvas.width / this.pixelsPerUnit; + const lineHeight = font.opentypeFont.lineHeight(); + + for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) { + const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex]; + pathTransforms.set([1, 1, currentX, currentY], (glyphIndex + 1) * 4); + + currentX += font.opentypeFont.glyphs.get(glyphID).advanceWidth; + if (currentX > availableWidth) { + currentX = 0; + currentY += lineHeight; + } + } + + return pathTransforms; } protected directCurveProgramName(): keyof ShaderMap { diff --git a/demo/client/src/view.ts b/demo/client/src/view.ts index c12b5804..0205ceb2 100644 --- a/demo/client/src/view.ts +++ b/demo/client/src/view.ts @@ -63,7 +63,7 @@ export abstract class PathfinderView { window.addEventListener('resize', () => this.resizeToFit(false), false); } - setDirty() { + setDirty(): void { if (this.dirty) return; this.dirty = true; @@ -101,31 +101,35 @@ export abstract class PathfinderView { this.setDirty(); } - protected redraw() { + protected redraw(): void { this.dirty = false; } - protected resizeToFit(initialSize: boolean) { - const width = window.innerWidth; + protected resizeToFit(initialSize: boolean): void { + if (!this.canvas.classList.contains('pf-pane')) { + const windowWidth = window.innerWidth; + const canvasTop = this.canvas.getBoundingClientRect().top; + let height = window.scrollY + window.innerHeight - canvasTop; - let height = window.scrollY + window.innerHeight - this.canvas.getBoundingClientRect().top; - const nonoverlappingBottomBar = document.getElementById('pf-nonoverlapping-bottom-bar'); - if (nonoverlappingBottomBar != null) { - const rect = nonoverlappingBottomBar.getBoundingClientRect(); - height -= window.innerHeight - rect.top; + const nonoverlappingBottomBar = + document.getElementById('pf-nonoverlapping-bottom-bar'); + if (nonoverlappingBottomBar != null) { + const rect = nonoverlappingBottomBar.getBoundingClientRect(); + height -= window.innerHeight - rect.top; + } + + const devicePixelRatio = window.devicePixelRatio; + + const canvasSize = new Float32Array([windowWidth, height]) as glmatrix.vec2; + glmatrix.vec2.scale(canvasSize, canvasSize, devicePixelRatio); + glmatrix.vec2.round(canvasSize, canvasSize); + + this.canvas.style.width = windowWidth + 'px'; + this.canvas.style.height = height + 'px'; + this.canvas.width = canvasSize[0]; + this.canvas.height = canvasSize[1]; } - const devicePixelRatio = window.devicePixelRatio; - - const canvasSize = new Float32Array([width, height]) as glmatrix.vec2; - glmatrix.vec2.scale(canvasSize, canvasSize, devicePixelRatio); - glmatrix.vec2.round(canvasSize, canvasSize); - - this.canvas.style.width = width + 'px'; - this.canvas.style.height = height + 'px'; - this.canvas.width = canvasSize[0]; - this.canvas.height = canvasSize[1]; - this.resized(); } } @@ -196,7 +200,7 @@ export abstract class DemoView extends PathfinderView implements RenderContext { this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.quadElementsBuffer); } - queueScreenshot() { + queueScreenshot(): void { this.wantsScreenshot = true; this.setDirty(); } @@ -213,7 +217,7 @@ export abstract class DemoView extends PathfinderView implements RenderContext { this.renderer.canvasResized(); } - protected initContext() { + protected initContext(): void { // Initialize the OpenGL context. this.gl = expectNotNull(this.canvas.getContext('webgl', { antialias: false, depth: true }), "Failed to initialize WebGL! Check that your browser supports it."); @@ -243,7 +247,7 @@ export abstract class DemoView extends PathfinderView implements RenderContext { this.compositingTimerQuery = this.timerQueryExt.createQueryEXT(); } - protected redraw() { + protected redraw(): void { super.redraw(); this.renderer.redraw(); @@ -304,7 +308,7 @@ export abstract class DemoView extends PathfinderView implements RenderContext { return shaderProgramMap as ShaderMap; } - private takeScreenshot() { + private takeScreenshot(): void { const width = this.canvas.width, height = this.canvas.height; const scratchCanvas = document.createElement('canvas'); scratchCanvas.width = width;