Do some more refactoring in preparation for the 3D view

This commit is contained in:
Patrick Walton 2017-08-31 17:08:22 -07:00
parent b75c327017
commit f182686ba8
6 changed files with 174 additions and 66 deletions

View File

@ -8,55 +8,151 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // 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 {mat4, vec2} from "gl-matrix";
import {PathfinderMeshData} from "./meshes";
import {ShaderMap, ShaderProgramSource} from "./shader-loader"; import {ShaderMap, ShaderProgramSource} from "./shader-loader";
import {BUILTIN_FONT_URI, TextLayout, PathfinderGlyph} from "./text";
import {PathfinderView, Timings} from "./view"; import {PathfinderView, Timings} from "./view";
import AppController from "./app-controller"; 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<ThreeDView> { class ThreeDController extends AppController<ThreeDView> {
protected fileLoaded(): void { 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, protected createView(canvas: HTMLCanvasElement,
commonShaderSource: string, commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>): shaderSources: ShaderMap<ShaderProgramSource>):
ThreeDView { 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<Glyph>;
private meshes: PathfinderMeshData;
} }
class ThreeDView extends PathfinderView { class ThreeDView extends PathfinderView {
protected resized(initialSize: boolean): void { constructor(appController: ThreeDController,
throw new Error("Method not implemented."); canvas: HTMLCanvasElement,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>) {
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): protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number):
AntialiasingStrategy { 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 { protected compositeIfNecessary(): void {}
throw new Error("Method not implemented.");
}
protected updateTimings(timings: Timings): void { protected updateTimings(timings: Timings) {
throw new Error("Method not implemented."); // TODO(pcwalton)
} }
protected panned(): void { protected panned(): void {
throw new Error("Method not implemented."); this.setDirty();
} }
destFramebuffer: WebGLFramebuffer | null; get destAllocatedSize(): glmatrix.vec2 {
destAllocatedSize: vec2; return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
destUsedSize: vec2; }
protected usedSizeFactor: vec2;
protected scale: number; get destFramebuffer(): WebGLFramebuffer | null {
protected worldTransform: mat4; 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() { function main() {

View File

@ -30,13 +30,21 @@ export default abstract class AppController<View extends PathfinderView> {
this.settingsCard.classList.add('pf-invisible'); this.settingsCard.classList.add('pf-invisible');
}, false); }, false);
this.filePickerElement = document.getElementById('pf-file-select') as HTMLInputElement; this.filePickerElement = document.getElementById('pf-file-select') as
this.filePickerElement.addEventListener('change', () => this.loadFile(), false); (HTMLInputElement | null);
if (this.filePickerElement != null) {
this.filePickerElement.addEventListener('change',
event => this.loadFile(event),
false);
}
this.selectFileElement = document.getElementById('pf-select-file') as HTMLSelectElement; const selectFileElement = document.getElementById('pf-select-file') as
this.selectFileElement.addEventListener('click', () => { (HTMLSelectElement | null);
this.fileSelectionChanged(); if (selectFileElement != null) {
}, false); selectFileElement.addEventListener('click',
event => this.fileSelectionChanged(event),
false);
}
const shaderLoader = new ShaderLoader; const shaderLoader = new ShaderLoader;
shaderLoader.load(); shaderLoader.load();
@ -51,7 +59,7 @@ export default abstract class AppController<View extends PathfinderView> {
} }
protected loadInitialFile() { protected loadInitialFile() {
this.fileSelectionChanged(); this.fetchFile(this.defaultFile);
} }
private updateAALevel() { private updateAALevel() {
@ -62,8 +70,9 @@ export default abstract class AppController<View extends PathfinderView> {
this.view.then(view => view.setAntialiasingOptions(aaType, aaLevel)); this.view.then(view => view.setAntialiasingOptions(aaType, aaLevel));
} }
protected loadFile() { protected loadFile(event: Event) {
const file = expectNotNull(this.filePickerElement.files, "No file selected!")[0]; const filePickerElement = event.target as HTMLInputElement;
const file = expectNotNull(filePickerElement.files, "No file selected!")[0];
const reader = new FileReader; const reader = new FileReader;
reader.addEventListener('loadend', () => { reader.addEventListener('loadend', () => {
this.fileData = reader.result; this.fileData = reader.result;
@ -72,28 +81,33 @@ export default abstract class AppController<View extends PathfinderView> {
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
} }
protected fileSelectionChanged() { private fileSelectionChanged(event: Event) {
const selectedOption = this.selectFileElement.selectedOptions[0] as HTMLOptionElement; 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(); this.filePickerElement.click();
const oldSelectedIndex = this.selectFileElement.selectedIndex; const oldSelectedIndex = selectFileElement.selectedIndex;
const newOption = document.createElement('option'); const newOption = document.createElement('option');
newOption.id = 'pf-custom-option-placeholder'; newOption.id = 'pf-custom-option-placeholder';
newOption.appendChild(document.createTextNode("Custom")); newOption.appendChild(document.createTextNode("Custom"));
this.selectFileElement.insertBefore(newOption, selectedOption); selectFileElement.insertBefore(newOption, selectedOption);
this.selectFileElement.selectedIndex = oldSelectedIndex; selectFileElement.selectedIndex = oldSelectedIndex;
return; return;
} }
// Remove the "Custom…" placeholder if it exists. // Remove the "Custom…" placeholder if it exists.
const placeholder = document.getElementById('pf-custom-option-placeholder'); const placeholder = document.getElementById('pf-custom-option-placeholder');
if (placeholder != null) if (placeholder != null)
this.selectFileElement.removeChild(placeholder); selectFileElement.removeChild(placeholder);
// Fetch the file. // 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(response => response.arrayBuffer())
.then(data => { .then(data => {
this.fileData = data; this.fileData = data;
@ -105,6 +119,8 @@ export default abstract class AppController<View extends PathfinderView> {
protected abstract get builtinFileURI(): string; protected abstract get builtinFileURI(): string;
protected abstract get defaultFile(): string;
protected abstract createView(canvas: HTMLCanvasElement, protected abstract createView(canvas: HTMLCanvasElement,
commonShaderSource: string, commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>): View; shaderSources: ShaderMap<ShaderProgramSource>): View;
@ -114,8 +130,7 @@ export default abstract class AppController<View extends PathfinderView> {
protected fileData: ArrayBuffer; protected fileData: ArrayBuffer;
protected canvas: HTMLCanvasElement; protected canvas: HTMLCanvasElement;
protected selectFileElement: HTMLSelectElement; protected filePickerElement: HTMLInputElement | null;
protected filePickerElement: HTMLInputElement;
private aaLevelSelect: HTMLSelectElement; private aaLevelSelect: HTMLSelectElement;
private settingsCard: HTMLElement; private settingsCard: HTMLElement;
private settingsButton: HTMLButtonElement; private settingsButton: HTMLButtonElement;

View File

@ -29,6 +29,8 @@ const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths";
const BUILTIN_SVG_URI: string = "/svg/demo"; const BUILTIN_SVG_URI: string = "/svg/demo";
const DEFAULT_FILE: string = 'tiger';
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
none: NoAAStrategy, none: NoAAStrategy,
ssaa: SSAAStrategy, ssaa: SSAAStrategy,
@ -147,6 +149,10 @@ class SVGDemoController extends AppController<SVGDemoView> {
return BUILTIN_SVG_URI; return BUILTIN_SVG_URI;
} }
protected get defaultFile(): string {
return DEFAULT_FILE;
}
private meshesReceived() { private meshesReceived() {
this.view.then(view => { this.view.then(view => {
view.uploadPathData(this.pathElements); view.uploadPathData(this.pathElements);
@ -171,11 +177,6 @@ class SVGDemoView extends PathfinderView {
this._scale = 1.0; this._scale = 1.0;
} }
protected resized(initialSize: boolean) {
this.antialiasingStrategy.init(this);
this.setDirty();
}
get destAllocatedSize(): glmatrix.vec2 { get destAllocatedSize(): glmatrix.vec2 {
return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height); return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
} }

View File

@ -21,7 +21,7 @@ import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from
import {UniformMap} from './gl-utils'; import {UniformMap} from './gl-utils';
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes'; import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader'; 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 {PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic} from './utils';
import {MonochromePathfinderView, Timings} from './view'; import {MonochromePathfinderView, Timings} from './view';
import AppController from './app-controller'; 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 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; const B_POSITION_SIZE: number = 8;
@ -115,9 +115,7 @@ class TextDemoController extends AppController<TextDemoView> {
super.start(); super.start();
this._fontSize = INITIAL_FONT_SIZE; this._fontSize = INITIAL_FONT_SIZE;
this.fpsLabel = unwrapNull(document.getElementById('pf-fps-label')); this.fpsLabel = unwrapNull(document.getElementById('pf-fps-label'));
this.loadInitialFile(); this.loadInitialFile();
} }
@ -131,18 +129,14 @@ class TextDemoController extends AppController<TextDemoView> {
this.layout = new TextLayout(this.fileData, TEXT, glyph => new GlyphInstance(glyph)); this.layout = new TextLayout(this.fileData, TEXT, glyph => new GlyphInstance(glyph));
this.layout.partition().then((meshes: PathfinderMeshData) => { this.layout.partition().then((meshes: PathfinderMeshData) => {
this.meshes = meshes; 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) { updateTimings(newTimes: Timings) {
this.fpsLabel.innerHTML = this.fpsLabel.innerHTML =
`${newTimes.atlasRendering} ms atlas, ${newTimes.compositing} ms compositing`; `${newTimes.atlasRendering} ms atlas, ${newTimes.compositing} ms compositing`;
@ -160,7 +154,6 @@ class TextDemoController extends AppController<TextDemoView> {
/// The font size in pixels per em. /// The font size in pixels per em.
set fontSize(newFontSize: number) { set fontSize(newFontSize: number) {
this._fontSize = newFontSize; this._fontSize = newFontSize;
this.layout
this.view.then(view => view.attachText()); this.view.then(view => view.attachText());
} }
@ -172,6 +165,10 @@ class TextDemoController extends AppController<TextDemoView> {
return BUILTIN_FONT_URI; return BUILTIN_FONT_URI;
} }
protected get defaultFile(): string {
return DEFAULT_FONT;
}
private fpsLabel: HTMLElement; private fpsLabel: HTMLElement;
private _atlas: Atlas; private _atlas: Atlas;
@ -198,7 +195,7 @@ class TextDemoView extends MonochromePathfinderView {
super.initContext(); super.initContext();
} }
uploadPathData(pathCount: number) { uploadPathMetadata(pathCount: number) {
const pathColors = new Uint8Array(4 * (pathCount + 1)); const pathColors = new Uint8Array(4 * (pathCount + 1));
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) { for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
for (let channel = 0; channel < 3; channel++) for (let channel = 0; channel < 3; channel++)
@ -354,12 +351,6 @@ class TextDemoView extends MonochromePathfinderView {
this.rebuildAtlasIfNecessary(); this.rebuildAtlasIfNecessary();
} }
protected resized(initialSize: boolean) {
if (!initialSize)
this.antialiasingStrategy.init(this);
this.setDirty();
}
private setIdentityTexScaleUniform(uniforms: UniformMap) { private setIdentityTexScaleUniform(uniforms: UniformMap) {
this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0); this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
} }

View File

@ -17,6 +17,8 @@ import * as opentype from "opentype.js";
import {PathfinderMeshData} from "./meshes"; import {PathfinderMeshData} from "./meshes";
import {assert, panic} from "./utils"; import {assert, panic} from "./utils";
export const BUILTIN_FONT_URI: string = "/otf/demo";
const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font"; const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font";
opentype.Font.prototype.isSupported = function() { opentype.Font.prototype.isSupported = function() {

View File

@ -105,10 +105,13 @@ export abstract class PathfinderView {
this.canvas.width = canvasSize[0]; this.canvas.width = canvasSize[0];
this.canvas.height = canvasSize[1]; 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() { protected initContext() {
// Initialize the OpenGL context. // Initialize the OpenGL context.