Keep the camera and view in a sensible state when changing fonts in the text demo

This commit is contained in:
Patrick Walton 2017-09-23 13:09:45 -07:00
parent db32009d7b
commit 2197896c4f
7 changed files with 70 additions and 65 deletions

View File

@ -127,14 +127,14 @@ class ThreeDController extends DemoAppController<ThreeDView> {
return sides.map(side => ({ lines: side.upper.lines.concat(side.lower.lines) })); return sides.map(side => ({ lines: side.upper.lines.concat(side.lower.lines) }));
} }
protected fileLoaded(): void { protected fileLoaded(fileData: ArrayBuffer): void {
const font = opentype.parse(this.fileData); const font = opentype.parse(fileData);
assert(font.isSupported(), "The font type is unsupported!"); assert(font.isSupported(), "The font type is unsupported!");
this.monumentPromise.then(monument => this.layoutMonument(font, monument)); this.monumentPromise.then(monument => this.layoutMonument(font, fileData, monument));
} }
private layoutMonument(font: opentype.Font, monument: MonumentSide[]) { private layoutMonument(font: opentype.Font, fileData: ArrayBuffer, monument: MonumentSide[]) {
const createGlyph = (glyph: opentype.Glyph) => new ThreeDGlyph(glyph); const createGlyph = (glyph: opentype.Glyph) => new ThreeDGlyph(glyph);
let textFrames = []; let textFrames = [];
for (const monumentSide of monument) { for (const monumentSide of monument) {
@ -163,7 +163,7 @@ class ThreeDController extends DemoAppController<ThreeDView> {
textFrames.push(new TextFrame(textRuns, font)); textFrames.push(new TextFrame(textRuns, font));
} }
this.glyphStorage = new TextFrameGlyphStorage(this.fileData, textFrames, font); this.glyphStorage = new TextFrameGlyphStorage(fileData, textFrames, font);
this.glyphStorage.layoutRuns(); this.glyphStorage.layoutRuns();
this.glyphStorage.partition().then((baseMeshes: PathfinderMeshData) => { this.glyphStorage.partition().then((baseMeshes: PathfinderMeshData) => {

View File

@ -33,19 +33,14 @@ export abstract class AppController {
protected fetchFile(file: string, builtinFileURI: string) { protected fetchFile(file: string, builtinFileURI: string) {
window.fetch(`${builtinFileURI}/${file}`) window.fetch(`${builtinFileURI}/${file}`)
.then(response => response.arrayBuffer()) .then(response => response.arrayBuffer())
.then(data => { .then(data => this.fileLoaded(data));
this.fileData = data;
this.fileLoaded();
});
} }
protected canvas: HTMLCanvasElement; protected canvas: HTMLCanvasElement;
protected screenshotButton: HTMLButtonElement | null; protected screenshotButton: HTMLButtonElement | null;
protected fileData: ArrayBuffer; protected abstract fileLoaded(data: ArrayBuffer): void;
protected abstract fileLoaded(): void;
protected abstract get defaultFile(): string; protected abstract get defaultFile(): string;
} }
@ -108,10 +103,7 @@ export abstract class DemoAppController<View extends PathfinderDemoView> extends
this.filePickerView = FilePickerView.create(); this.filePickerView = FilePickerView.create();
if (this.filePickerView != null) { if (this.filePickerView != null) {
this.filePickerView.onFileLoaded = fileData => { this.filePickerView.onFileLoaded = fileData => this.fileLoaded(fileData);
this.fileData = fileData;
this.fileLoaded();
};
} }
const selectFileElement = document.getElementById('pf-select-file') as const selectFileElement = document.getElementById('pf-select-file') as

View File

@ -59,8 +59,8 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
this.loadInitialFile(this.builtinFileURI); this.loadInitialFile(this.builtinFileURI);
} }
protected fileLoaded(): void { protected fileLoaded(fileData: ArrayBuffer): void {
const font = opentype.parse(this.fileData); const font = opentype.parse(fileData);
this.font = font; this.font = font;
assert(this.font.isSupported(), "The font type is unsupported!"); assert(this.font.isSupported(), "The font type is unsupported!");
@ -68,7 +68,7 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
const textRun = new TextRun<BenchmarkGlyph>(STRING, [0, 0], font, createGlyph); const textRun = new TextRun<BenchmarkGlyph>(STRING, [0, 0], font, createGlyph);
this.textRun = textRun; this.textRun = textRun;
const textFrame = new TextFrame([textRun], font); const textFrame = new TextFrame([textRun], font);
this.glyphStorage = new TextFrameGlyphStorage(this.fileData, [textFrame], font); this.glyphStorage = new TextFrameGlyphStorage(fileData, [textFrame], font);
this.glyphStorage.partition().then(baseMeshes => { this.glyphStorage.partition().then(baseMeshes => {
this.baseMeshes = baseMeshes; this.baseMeshes = baseMeshes;

View File

@ -150,7 +150,10 @@ export class OrthographicCamera extends Camera {
if (this.ignoreBounds) if (this.ignoreBounds)
return; return;
const bounds = this.bounds; const bounds = glmatrix.vec4.clone(this.bounds);
if (!this.scaleBounds)
glmatrix.vec4.scale(bounds, bounds, this.scale);
for (let axis = 0; axis < 2; axis++) { for (let axis = 0; axis < 2; axis++) {
const viewportLength = axis === 0 ? this.canvas.width : this.canvas.height; const viewportLength = axis === 0 ? this.canvas.width : this.canvas.height;
const axisBounds = [bounds[axis + 0], bounds[axis + 2]]; const axisBounds = [bounds[axis + 0], bounds[axis + 2]];
@ -170,8 +173,8 @@ export class OrthographicCamera extends Camera {
} }
zoomToFit(): void { zoomToFit(): void {
const upperLeft = glmatrix.vec2.fromValues(this._bounds[0], this._bounds[1]); const upperLeft = glmatrix.vec2.clone([this._bounds[0], this._bounds[1]]);
const lowerRight = glmatrix.vec2.fromValues(this._bounds[2], this._bounds[3]); const lowerRight = glmatrix.vec2.clone([this._bounds[2], this._bounds[3]]);
const width = this._bounds[2] - this._bounds[0]; const width = this._bounds[2] - this._bounds[0];
const height = Math.abs(this._bounds[1] - this._bounds[3]); const height = Math.abs(this._bounds[1] - this._bounds[3]);
@ -184,11 +187,6 @@ export class OrthographicCamera extends Camera {
glmatrix.vec2.scale(this.translation, this.translation, -this.scale); glmatrix.vec2.scale(this.translation, this.translation, -this.scale);
this.translation[0] += this.canvas.width * 0.5; this.translation[0] += this.canvas.width * 0.5;
this.translation[1] += this.canvas.height * 0.5; this.translation[1] += this.canvas.height * 0.5;
if (this.onZoom != null)
this.onZoom();
if (this.onPan != null)
this.onPan();
} }
zoomIn(): void { zoomIn(): void {

View File

@ -57,10 +57,7 @@ class MeshDebuggerAppController extends AppController {
this.view = new MeshDebuggerView(this); this.view = new MeshDebuggerView(this);
this.filePicker = unwrapNull(FilePickerView.create()); this.filePicker = unwrapNull(FilePickerView.create());
this.filePicker.onFileLoaded = fileData => { this.filePicker.onFileLoaded = fileData => this.fileLoaded(fileData);
this.fileData = fileData;
this.fileLoaded();
};
this.openModal = unwrapNull(document.getElementById('pf-open-modal')); this.openModal = unwrapNull(document.getElementById('pf-open-modal'));
this.fontPathSelectGroup = this.fontPathSelectGroup =
@ -101,20 +98,20 @@ class MeshDebuggerAppController extends AppController {
this.fetchFile(results[2], BUILTIN_URIS[this.fileType]); this.fetchFile(results[2], BUILTIN_URIS[this.fileType]);
} }
protected fileLoaded(): void { protected fileLoaded(fileData: ArrayBuffer): void {
while (this.fontPathSelect.lastChild != null) while (this.fontPathSelect.lastChild != null)
this.fontPathSelect.removeChild(this.fontPathSelect.lastChild); this.fontPathSelect.removeChild(this.fontPathSelect.lastChild);
this.fontPathSelectGroup.classList.remove('pf-display-none'); this.fontPathSelectGroup.classList.remove('pf-display-none');
if (this.fileType === 'font') if (this.fileType === 'font')
this.fontLoaded(); this.fontLoaded(fileData);
else if (this.fileType === 'svg') else if (this.fileType === 'svg')
this.svgLoaded(); this.svgLoaded(fileData);
} }
private fontLoaded(): void { private fontLoaded(fileData: ArrayBuffer): void {
this.file = opentype.parse(this.fileData); this.file = opentype.parse(fileData);
assert(this.file.isSupported(), "The font type is unsupported!"); assert(this.file.isSupported(), "The font type is unsupported!");
const glyphCount = this.file.numGlyphs; const glyphCount = this.file.numGlyphs;
@ -131,10 +128,10 @@ class MeshDebuggerAppController extends AppController {
this.loadPath(this.file.charToGlyph(CHARACTER)); this.loadPath(this.file.charToGlyph(CHARACTER));
} }
private svgLoaded(): void { private svgLoaded(fileData: ArrayBuffer): void {
this.file = new SVGLoader; this.file = new SVGLoader;
this.file.scale = SVG_SCALE; this.file.scale = SVG_SCALE;
this.file.loadFile(this.fileData); this.file.loadFile(fileData);
const pathCount = this.file.pathInstances.length; const pathCount = this.file.pathInstances.length;
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) { for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
@ -157,7 +154,7 @@ class MeshDebuggerAppController extends AppController {
} }
const glyph = new MeshDebuggerGlyph(opentypeGlyph); const glyph = new MeshDebuggerGlyph(opentypeGlyph);
const glyphStorage = new GlyphStorage(this.fileData, [glyph], this.file); const glyphStorage = new GlyphStorage(this.file.toArrayBuffer(), [glyph], this.file);
promise = glyphStorage.partition(); promise = glyphStorage.partition();
} else if (this.file instanceof SVGLoader) { } else if (this.file instanceof SVGLoader) {
promise = this.file.partition(this.fontPathSelect.selectedIndex); promise = this.file.partition(this.fontPathSelect.selectedIndex);

View File

@ -50,8 +50,8 @@ class SVGDemoController extends DemoAppController<SVGDemoView> {
this.loadInitialFile(this.builtinFileURI); this.loadInitialFile(this.builtinFileURI);
} }
protected fileLoaded() { protected fileLoaded(fileData: ArrayBuffer) {
this.loader.loadFile(this.fileData); this.loader.loadFile(fileData);
this.loader.partition().then(meshes => { this.loader.partition().then(meshes => {
this.meshes = meshes; this.meshes = meshes;
this.meshesReceived(); this.meshesReceived();

View File

@ -76,7 +76,7 @@ const B_PATH_INDEX_SIZE: number = 2;
const ATLAS_SIZE: glmatrix.vec2 = glmatrix.vec2.fromValues(2048, 4096); const ATLAS_SIZE: glmatrix.vec2 = glmatrix.vec2.fromValues(2048, 4096);
const MIN_SCALE: number = 0.02; const MIN_SCALE: number = 0.001;
const MAX_SCALE: number = 10.0; const MAX_SCALE: number = 10.0;
declare global { declare global {
@ -157,7 +157,7 @@ class TextDemoController extends DemoAppController<TextDemoView> {
private updateText(): void { private updateText(): void {
this.text = this.editTextArea.value; this.text = this.editTextArea.value;
this.recreateLayout(); //this.recreateLayout();
window.jQuery(this.editTextModal).modal('hide'); window.jQuery(this.editTextModal).modal('hide');
} }
@ -168,17 +168,19 @@ class TextDemoController extends DemoAppController<TextDemoView> {
unwrapNull(this.shaderSources)); unwrapNull(this.shaderSources));
} }
protected fileLoaded() { protected fileLoaded(fileData: ArrayBuffer) {
this.recreateLayout(); this.recreateLayout(fileData);
} }
private recreateLayout() { private recreateLayout(fileData: ArrayBuffer) {
this.layout = new SimpleTextLayout(this.fileData, const newLayout = new SimpleTextLayout(fileData,
this.text, this.text,
glyph => new GlyphInstance(glyph)); glyph => new GlyphInstance(glyph));
this.layout.glyphStorage.partition().then((meshes: PathfinderMeshData) => { newLayout.glyphStorage.partition().then((meshes: PathfinderMeshData) => {
this.meshes = meshes;
this.view.then(view => { this.view.then(view => {
this.layout = newLayout;
this.meshes = meshes;
view.attachText(); view.attachText();
view.uploadPathColors(1); view.uploadPathColors(1);
view.attachMeshes([this.meshes]); view.attachMeshes([this.meshes]);
@ -250,8 +252,6 @@ class TextDemoView extends MonochromePathfinderView {
minScale: MIN_SCALE, minScale: MIN_SCALE,
maxScale: MAX_SCALE, maxScale: MAX_SCALE,
}); });
this.camera.onPan = () => this.onPan();
this.camera.onZoom = () => this.onZoom();
this.canvas.addEventListener('dblclick', () => this.appController.showTextEditor(), false); this.canvas.addEventListener('dblclick', () => this.appController.showTextEditor(), false);
} }
@ -281,7 +281,6 @@ class TextDemoView extends MonochromePathfinderView {
layout.layoutRuns(); layout.layoutRuns();
let textBounds = layout.textFrame.bounds; let textBounds = layout.textFrame.bounds;
textBounds = scaleRect(textBounds, this.appController.pixelsPerUnit);
this.camera.bounds = textBounds; this.camera.bounds = textBounds;
const textGlyphs = layout.glyphStorage.allGlyphs; const textGlyphs = layout.glyphStorage.allGlyphs;
@ -361,6 +360,8 @@ class TextDemoView extends MonochromePathfinderView {
const pathHintsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathHints'); const pathHintsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathHints');
pathHintsBufferTexture.upload(this.gl, pathHints); pathHintsBufferTexture.upload(this.gl, pathHints);
this.pathHintsBufferTexture = pathHintsBufferTexture; this.pathHintsBufferTexture = pathHintsBufferTexture;
this.setGlyphTexCoords();
} }
protected pathTransformsForObject(objectIndex: number): Float32Array { protected pathTransformsForObject(objectIndex: number): Float32Array {
@ -446,37 +447,42 @@ class TextDemoView extends MonochromePathfinderView {
} }
attachText() { attachText() {
this.panZoomEventsEnabled = false;
if (this.atlasFramebuffer == null) if (this.atlasFramebuffer == null)
this.createAtlasFramebuffer(); this.createAtlasFramebuffer();
this.relayoutText();
this.layoutText();
this.camera.zoomToFit(); this.camera.zoomToFit();
this.appController.fontSize = this.camera.scale *
this.appController.layout.glyphStorage.font.unitsPerEm;
this.buildAtlasGlyphs();
this.setDirty();
this.panZoomEventsEnabled = true;
} }
relayoutText() { relayoutText() {
this.layoutText(); this.layoutText();
this.rebuildAtlasIfNecessary();
}
private rebuildAtlasIfNecessary() {
this.buildAtlasGlyphs(); this.buildAtlasGlyphs();
this.setGlyphTexCoords();
this.setDirty(); this.setDirty();
} }
protected onPan() { protected onPan() {
this.buildAtlasGlyphs();
this.setDirty(); this.setDirty();
this.rebuildAtlasIfNecessary();
} }
protected onZoom() { protected onZoom() {
this.appController.fontSize = this.camera.scale * INITIAL_FONT_SIZE; this.appController.fontSize = this.camera.scale *
this.appController.layout.glyphStorage.font.unitsPerEm;
this.buildAtlasGlyphs();
this.setDirty(); this.setDirty();
this.rebuildAtlasIfNecessary();
} }
updateHinting(): void { updateHinting(): void {
this.buildAtlasGlyphs();
this.setDirty(); this.setDirty();
this.rebuildAtlasIfNecessary();
} }
private setIdentityTexScaleUniform(uniforms: UniformMap) { private setIdentityTexScaleUniform(uniforms: UniformMap) {
@ -524,7 +530,9 @@ class TextDemoView extends MonochromePathfinderView {
[2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]); [2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]);
glmatrix.mat4.translate(transform, glmatrix.mat4.translate(transform,
transform, transform,
[this.camera.translation[0], this.camera.translation[1], 0.0]); [this.camera.translation[0],
this.camera.translation[1],
0.0]);
// Blit. // Blit.
this.gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform); this.gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform);
@ -545,6 +553,16 @@ class TextDemoView extends MonochromePathfinderView {
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
} }
private set panZoomEventsEnabled(flag: boolean) {
if (flag) {
this.camera.onPan = () => this.onPan();
this.camera.onZoom = () => this.onZoom();
} else {
this.camera.onPan = null;
this.camera.onZoom = null;
}
}
readonly bgColor: glmatrix.vec4 = glmatrix.vec4.fromValues(1.0, 1.0, 1.0, 0.0); readonly bgColor: glmatrix.vec4 = glmatrix.vec4.fromValues(1.0, 1.0, 1.0, 0.0);
readonly fgColor: glmatrix.vec4 = glmatrix.vec4.fromValues(0.0, 0.0, 0.0, 1.0); readonly fgColor: glmatrix.vec4 = glmatrix.vec4.fromValues(0.0, 0.0, 0.0, 1.0);