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
// 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<ThreeDView> {
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<ShaderProgramSource>):
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 {
protected resized(initialSize: boolean): void {
throw new Error("Method not implemented.");
constructor(appController: ThreeDController,
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):
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() {

View File

@ -30,13 +30,21 @@ export default abstract class AppController<View extends PathfinderView> {
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<View extends PathfinderView> {
}
protected loadInitialFile() {
this.fileSelectionChanged();
this.fetchFile(this.defaultFile);
}
private updateAALevel() {
@ -62,8 +70,9 @@ export default abstract class AppController<View extends PathfinderView> {
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<View extends PathfinderView> {
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<View extends PathfinderView> {
protected abstract get builtinFileURI(): string;
protected abstract get defaultFile(): string;
protected abstract createView(canvas: HTMLCanvasElement,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>): View;
@ -114,8 +130,7 @@ export default abstract class AppController<View extends PathfinderView> {
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;

View File

@ -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<SVGDemoView> {
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);
}

View File

@ -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<TextDemoView> {
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<TextDemoView> {
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<TextDemoView> {
/// 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<TextDemoView> {
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);
}

View File

@ -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() {

View File

@ -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.