From f182686ba8c3b151a0e6334502fe5377c56e3b72 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 31 Aug 2017 17:08:22 -0700 Subject: [PATCH] Do some more refactoring in preparation for the 3D view --- demo/client/src/3d-demo.ts | 134 +++++++++++++++++++++++++----- demo/client/src/app-controller.ts | 53 +++++++----- demo/client/src/svg-demo.ts | 11 +-- demo/client/src/text-demo.ts | 33 +++----- demo/client/src/text.ts | 2 + demo/client/src/view.ts | 7 +- 6 files changed, 174 insertions(+), 66 deletions(-) diff --git a/demo/client/src/3d-demo.ts b/demo/client/src/3d-demo.ts index 29db7b9a..f20d8acf 100644 --- a/demo/client/src/3d-demo.ts +++ b/demo/client/src/3d-demo.ts @@ -8,55 +8,151 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -import {AntialiasingStrategy, AntialiasingStrategyName} from "./aa-strategy"; +import * as glmatrix from 'gl-matrix'; + +import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; import {mat4, vec2} from "gl-matrix"; +import {PathfinderMeshData} from "./meshes"; import {ShaderMap, ShaderProgramSource} from "./shader-loader"; +import {BUILTIN_FONT_URI, TextLayout, PathfinderGlyph} from "./text"; import {PathfinderView, Timings} from "./view"; import AppController from "./app-controller"; +import SSAAStrategy from "./ssaa-strategy"; +import { panic, PathfinderError } from "./utils"; + +const TEXT: string = "Lorem ipsum dolor sit amet"; + +const FONT: string = 'open-sans'; + +const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { + none: NoAAStrategy, + ssaa: SSAAStrategy, +}; + +interface AntialiasingStrategyTable { + none: typeof NoAAStrategy; + ssaa: typeof SSAAStrategy; +} class ThreeDController extends AppController { protected fileLoaded(): void { - throw new Error("Method not implemented."); + this.layout = new TextLayout(this.fileData, TEXT, glyph => new Glyph(glyph)); + this.layout.partition().then((meshes: PathfinderMeshData) => { + this.meshes = meshes; + this.view.then(view => { + view.uploadPathMetadata(this.layout.textGlyphs.length); + view.attachMeshes(this.meshes); + }); + }); } protected createView(canvas: HTMLCanvasElement, commonShaderSource: string, shaderSources: ShaderMap): ThreeDView { - throw new Error("Method not implemented."); + return new ThreeDView(this, canvas, commonShaderSource, shaderSources); } - protected builtinFileURI: string; + protected get builtinFileURI(): string { + return BUILTIN_FONT_URI; + } + + protected get defaultFile(): string { + return FONT; + } + + layout: TextLayout; + private meshes: PathfinderMeshData; } class ThreeDView extends PathfinderView { - protected resized(initialSize: boolean): void { - throw new Error("Method not implemented."); + constructor(appController: ThreeDController, + canvas: HTMLCanvasElement, + commonShaderSource: string, + shaderSources: ShaderMap) { + super(canvas, commonShaderSource, shaderSources); + + this.appController = appController; + } + + uploadPathMetadata(pathCount: number) { + const pathColors = new Uint8Array(4 * (pathCount + 1)); + for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) { + for (let channel = 0; channel < 3; channel++) + pathColors[(pathIndex + 1) * 4 + channel] = 0x00; // RGB + pathColors[(pathIndex + 1) * 4 + 3] = 0xff; // alpha + } + + this.pathColorsBufferTexture.upload(this.gl, pathColors); } protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number): AntialiasingStrategy { - throw new Error("Method not implemented."); + if (aaType != 'ecaa') + return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel); + throw new PathfinderError("Unsupported antialiasing type!"); } - protected compositeIfNecessary(): void { - throw new Error("Method not implemented."); - } + protected compositeIfNecessary(): void {} - protected updateTimings(timings: Timings): void { - throw new Error("Method not implemented."); + protected updateTimings(timings: Timings) { + // TODO(pcwalton) } protected panned(): void { - throw new Error("Method not implemented."); + this.setDirty(); } - destFramebuffer: WebGLFramebuffer | null; - destAllocatedSize: vec2; - destUsedSize: vec2; - protected usedSizeFactor: vec2; - protected scale: number; - protected worldTransform: mat4; + get destAllocatedSize(): glmatrix.vec2 { + return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height); + } + + get destFramebuffer(): WebGLFramebuffer | null { + return null; + } + + get destUsedSize(): glmatrix.vec2 { + return this.destAllocatedSize; + } + + protected get usedSizeFactor(): glmatrix.vec2 { + return glmatrix.vec2.fromValues(1.0, 1.0); + } + + protected get scale(): number { + return this._scale; + } + + protected set scale(newScale: number) { + this._scale = newScale; + this.setDirty(); + } + + protected get worldTransform() { + const transform = glmatrix.mat4.create(); + glmatrix.mat4.fromTranslation(transform, [this.translation[0], this.translation[1], 0]); + glmatrix.mat4.scale(transform, transform, [this.scale, this.scale, 1.0]); + return transform; + } + + private _scale: number; + + private appController: ThreeDController; +} + +class Glyph extends PathfinderGlyph { + constructor(glyph: opentype.Glyph) { + super(glyph); + } + + getRect(pixelsPerUnit: number): glmatrix.vec4 { + const rect = glmatrix.vec4.fromValues(this.position[0], + this.position[1], + this.metrics.xMax - this.metrics.xMin, + this.metrics.yMax - this.metrics.yMin); + glmatrix.vec4.scale(rect, rect, pixelsPerUnit); + return rect; + } } function main() { diff --git a/demo/client/src/app-controller.ts b/demo/client/src/app-controller.ts index cb630eaa..0a0c2f1d 100644 --- a/demo/client/src/app-controller.ts +++ b/demo/client/src/app-controller.ts @@ -30,13 +30,21 @@ export default abstract class AppController { this.settingsCard.classList.add('pf-invisible'); }, false); - this.filePickerElement = document.getElementById('pf-file-select') as HTMLInputElement; - this.filePickerElement.addEventListener('change', () => this.loadFile(), false); + this.filePickerElement = document.getElementById('pf-file-select') as + (HTMLInputElement | null); + if (this.filePickerElement != null) { + this.filePickerElement.addEventListener('change', + event => this.loadFile(event), + false); + } - this.selectFileElement = document.getElementById('pf-select-file') as HTMLSelectElement; - this.selectFileElement.addEventListener('click', () => { - this.fileSelectionChanged(); - }, false); + const selectFileElement = document.getElementById('pf-select-file') as + (HTMLSelectElement | null); + if (selectFileElement != null) { + selectFileElement.addEventListener('click', + event => this.fileSelectionChanged(event), + false); + } const shaderLoader = new ShaderLoader; shaderLoader.load(); @@ -51,7 +59,7 @@ export default abstract class AppController { } protected loadInitialFile() { - this.fileSelectionChanged(); + this.fetchFile(this.defaultFile); } private updateAALevel() { @@ -62,8 +70,9 @@ export default abstract class AppController { this.view.then(view => view.setAntialiasingOptions(aaType, aaLevel)); } - protected loadFile() { - const file = expectNotNull(this.filePickerElement.files, "No file selected!")[0]; + protected loadFile(event: Event) { + const filePickerElement = event.target as HTMLInputElement; + const file = expectNotNull(filePickerElement.files, "No file selected!")[0]; const reader = new FileReader; reader.addEventListener('loadend', () => { this.fileData = reader.result; @@ -72,28 +81,33 @@ export default abstract class AppController { reader.readAsArrayBuffer(file); } - protected fileSelectionChanged() { - const selectedOption = this.selectFileElement.selectedOptions[0] as HTMLOptionElement; + private fileSelectionChanged(event: Event) { + const selectFileElement = event.target as HTMLSelectElement; + const selectedOption = selectFileElement.selectedOptions[0] as HTMLOptionElement; - if (selectedOption.value === 'load-custom') { + if (selectedOption.value === 'load-custom' && this.filePickerElement != null) { this.filePickerElement.click(); - const oldSelectedIndex = this.selectFileElement.selectedIndex; + const oldSelectedIndex = selectFileElement.selectedIndex; const newOption = document.createElement('option'); newOption.id = 'pf-custom-option-placeholder'; newOption.appendChild(document.createTextNode("Custom")); - this.selectFileElement.insertBefore(newOption, selectedOption); - this.selectFileElement.selectedIndex = oldSelectedIndex; + selectFileElement.insertBefore(newOption, selectedOption); + selectFileElement.selectedIndex = oldSelectedIndex; return; } // Remove the "Custom…" placeholder if it exists. const placeholder = document.getElementById('pf-custom-option-placeholder'); if (placeholder != null) - this.selectFileElement.removeChild(placeholder); + selectFileElement.removeChild(placeholder); // Fetch the file. - window.fetch(`${this.builtinFileURI}/${selectedOption.value}`) + this.fetchFile(selectedOption.value); + } + + private fetchFile(file: string) { + window.fetch(`${this.builtinFileURI}/${file}`) .then(response => response.arrayBuffer()) .then(data => { this.fileData = data; @@ -105,6 +119,8 @@ export default abstract class AppController { protected abstract get builtinFileURI(): string; + protected abstract get defaultFile(): string; + protected abstract createView(canvas: HTMLCanvasElement, commonShaderSource: string, shaderSources: ShaderMap): View; @@ -114,8 +130,7 @@ export default abstract class AppController { protected fileData: ArrayBuffer; protected canvas: HTMLCanvasElement; - protected selectFileElement: HTMLSelectElement; - protected filePickerElement: HTMLInputElement; + protected filePickerElement: HTMLInputElement | null; private aaLevelSelect: HTMLSelectElement; private settingsCard: HTMLElement; private settingsButton: HTMLButtonElement; diff --git a/demo/client/src/svg-demo.ts b/demo/client/src/svg-demo.ts index fd81ce1f..9808b5bf 100644 --- a/demo/client/src/svg-demo.ts +++ b/demo/client/src/svg-demo.ts @@ -29,6 +29,8 @@ const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths"; const BUILTIN_SVG_URI: string = "/svg/demo"; +const DEFAULT_FILE: string = 'tiger'; + const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { none: NoAAStrategy, ssaa: SSAAStrategy, @@ -147,6 +149,10 @@ class SVGDemoController extends AppController { return BUILTIN_SVG_URI; } + protected get defaultFile(): string { + return DEFAULT_FILE; + } + private meshesReceived() { this.view.then(view => { view.uploadPathData(this.pathElements); @@ -171,11 +177,6 @@ class SVGDemoView extends PathfinderView { this._scale = 1.0; } - protected resized(initialSize: boolean) { - this.antialiasingStrategy.init(this); - this.setDirty(); - } - get destAllocatedSize(): glmatrix.vec2 { return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height); } diff --git a/demo/client/src/text-demo.ts b/demo/client/src/text-demo.ts index ec752dd7..43298917 100644 --- a/demo/client/src/text-demo.ts +++ b/demo/client/src/text-demo.ts @@ -21,7 +21,7 @@ import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from import {UniformMap} from './gl-utils'; import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes'; import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader'; -import {PathfinderGlyph, TextLayout} from "./text"; +import {BUILTIN_FONT_URI, PathfinderGlyph, TextLayout} from "./text"; import {PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic} from './utils'; import {MonochromePathfinderView, Timings} from './view'; import AppController from './app-controller'; @@ -68,7 +68,7 @@ const INITIAL_FONT_SIZE: number = 72.0; const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font"; -const BUILTIN_FONT_URI: string = "/otf/demo"; +const DEFAULT_FONT: string = 'open-sans'; const B_POSITION_SIZE: number = 8; @@ -115,9 +115,7 @@ class TextDemoController extends AppController { super.start(); this._fontSize = INITIAL_FONT_SIZE; - this.fpsLabel = unwrapNull(document.getElementById('pf-fps-label')); - this.loadInitialFile(); } @@ -131,18 +129,14 @@ class TextDemoController extends AppController { this.layout = new TextLayout(this.fileData, TEXT, glyph => new GlyphInstance(glyph)); this.layout.partition().then((meshes: PathfinderMeshData) => { this.meshes = meshes; - this.meshesReceived(); + this.view.then(view => { + view.attachText(); + view.uploadPathMetadata(this.layout.uniqueGlyphs.length); + view.attachMeshes(this.meshes); + }); }); } - private meshesReceived() { - this.view.then(view => { - view.attachText(); - view.uploadPathData(this.layout.uniqueGlyphs.length); - view.attachMeshes(this.meshes); - }) - } - updateTimings(newTimes: Timings) { this.fpsLabel.innerHTML = `${newTimes.atlasRendering} ms atlas, ${newTimes.compositing} ms compositing`; @@ -160,7 +154,6 @@ class TextDemoController extends AppController { /// The font size in pixels per em. set fontSize(newFontSize: number) { this._fontSize = newFontSize; - this.layout this.view.then(view => view.attachText()); } @@ -172,6 +165,10 @@ class TextDemoController extends AppController { return BUILTIN_FONT_URI; } + protected get defaultFile(): string { + return DEFAULT_FONT; + } + private fpsLabel: HTMLElement; private _atlas: Atlas; @@ -198,7 +195,7 @@ class TextDemoView extends MonochromePathfinderView { super.initContext(); } - uploadPathData(pathCount: number) { + uploadPathMetadata(pathCount: number) { const pathColors = new Uint8Array(4 * (pathCount + 1)); for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) { for (let channel = 0; channel < 3; channel++) @@ -354,12 +351,6 @@ class TextDemoView extends MonochromePathfinderView { this.rebuildAtlasIfNecessary(); } - protected resized(initialSize: boolean) { - if (!initialSize) - this.antialiasingStrategy.init(this); - this.setDirty(); - } - private setIdentityTexScaleUniform(uniforms: UniformMap) { this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0); } diff --git a/demo/client/src/text.ts b/demo/client/src/text.ts index 6ea3837b..12770df0 100644 --- a/demo/client/src/text.ts +++ b/demo/client/src/text.ts @@ -17,6 +17,8 @@ import * as opentype from "opentype.js"; import {PathfinderMeshData} from "./meshes"; import {assert, panic} from "./utils"; +export const BUILTIN_FONT_URI: string = "/otf/demo"; + const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font"; opentype.Font.prototype.isSupported = function() { diff --git a/demo/client/src/view.ts b/demo/client/src/view.ts index 3cd3c61b..6e227886 100644 --- a/demo/client/src/view.ts +++ b/demo/client/src/view.ts @@ -105,10 +105,13 @@ export abstract class PathfinderView { this.canvas.width = canvasSize[0]; this.canvas.height = canvasSize[1]; - this.resized(initialSize); + this.resized(); } - protected abstract resized(initialSize: boolean): void; + private resized(): void { + this.antialiasingStrategy.init(this); + this.setDirty(); + } protected initContext() { // Initialize the OpenGL context.