Keep the camera and view in a sensible state when changing fonts in the text demo
This commit is contained in:
parent
db32009d7b
commit
2197896c4f
|
@ -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) => {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue