Do some more refactoring in preparation for the 3D view
This commit is contained in:
parent
b75c327017
commit
f182686ba8
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue