Decouple text renderers from HTML canvas elements.

This allows for offscreen rendering of text.
This commit is contained in:
Patrick Walton 2017-10-17 12:10:20 -07:00
parent 562851fd6e
commit 32de1d3441
3 changed files with 46 additions and 22 deletions

View File

@ -63,10 +63,23 @@ interface PerspectiveMovementKeys {
[keyCode: number]: boolean; [keyCode: number]: boolean;
} }
export abstract class Camera { export interface CameraView {
protected canvas: HTMLCanvasElement; readonly width: number;
readonly height: number;
readonly classList: DOMTokenList | null;
constructor(canvas: HTMLCanvasElement) { addEventListener<K extends keyof HTMLElementEventMap>(type: K,
listener: (this: HTMLCanvasElement,
ev: HTMLElementEventMap[K]) =>
any,
useCapture?: boolean): void;
getBoundingClientRect(): ClientRect;
}
export abstract class Camera {
protected canvas: CameraView;
constructor(canvas: CameraView) {
this.canvas = canvas; this.canvas = canvas;
} }
@ -88,7 +101,7 @@ export class OrthographicCamera extends Camera {
private readonly scaleBounds: boolean; private readonly scaleBounds: boolean;
private readonly ignoreBounds: boolean; private readonly ignoreBounds: boolean;
constructor(canvas: HTMLCanvasElement, options?: OrthographicCameraOptions) { constructor(canvas: CameraView, options?: OrthographicCameraOptions) {
super(canvas); super(canvas);
if (options == null) if (options == null)
@ -104,16 +117,21 @@ export class OrthographicCamera extends Camera {
this._bounds = glmatrix.vec4.create(); this._bounds = glmatrix.vec4.create();
this.canvas.addEventListener('wheel', event => this.onWheel(event), false); if (this.canvas != null) {
this.canvas.addEventListener('mousedown', event => this.onMouseDown(event), false); this.canvas.addEventListener('wheel', event => this.onWheel(event), false);
this.canvas.addEventListener('mouseup', event => this.onMouseUp(event), false); this.canvas.addEventListener('mousedown', event => this.onMouseDown(event), false);
this.canvas.addEventListener('mousemove', event => this.onMouseMove(event), false); this.canvas.addEventListener('mouseup', event => this.onMouseUp(event), false);
this.canvas.addEventListener('mousemove', event => this.onMouseMove(event), false);
}
this.onPan = null; this.onPan = null;
this.onZoom = null; this.onZoom = null;
} }
onWheel(event: MouseWheelEvent): void { onWheel(event: MouseWheelEvent): void {
if (this.canvas == null)
throw new Error("onWheel() with no canvas?!");
event.preventDefault(); event.preventDefault();
if (!event.ctrlKey) { if (!event.ctrlKey) {
@ -161,11 +179,13 @@ export class OrthographicCamera extends Camera {
} }
private onMouseDown(event: MouseEvent): void { private onMouseDown(event: MouseEvent): void {
this.canvas.classList.add('pf-grabbing'); if (this.canvas.classList != null)
this.canvas.classList.add('pf-grabbing');
} }
private onMouseUp(event: MouseEvent): void { private onMouseUp(event: MouseEvent): void {
this.canvas.classList.remove('pf-grabbing'); if (this.canvas.classList != null)
this.canvas.classList.remove('pf-grabbing');
} }
private onMouseMove(event: MouseEvent): void { private onMouseMove(event: MouseEvent): void {
@ -243,6 +263,8 @@ export class OrthographicCamera extends Camera {
} }
export class PerspectiveCamera extends Camera { export class PerspectiveCamera extends Camera {
canvas: HTMLCanvasElement;
onChange: (() => void) | null; onChange: (() => void) | null;
translation: glmatrix.vec3; translation: glmatrix.vec3;

View File

@ -19,7 +19,7 @@ import {StemDarkeningMode, SubpixelAAType} from './aa-strategy';
import {DemoAppController} from './app-controller'; import {DemoAppController} from './app-controller';
import {Atlas, AtlasGlyph, SUBPIXEL_GRANULARITY} from './atlas'; import {Atlas, AtlasGlyph, SUBPIXEL_GRANULARITY} from './atlas';
import PathfinderBufferTexture from './buffer-texture'; import PathfinderBufferTexture from './buffer-texture';
import {OrthographicCamera} from "./camera"; import {CameraView, OrthographicCamera} from "./camera";
import {createFramebuffer, createFramebufferColorTexture} from './gl-utils'; import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from './gl-utils'; import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from './gl-utils';
import {UniformMap} from './gl-utils'; import {UniformMap} from './gl-utils';
@ -260,6 +260,10 @@ class TextDemoView extends DemoView implements TextRenderContext {
appController: TextDemoController; appController: TextDemoController;
get cameraView(): CameraView {
return this.canvas;
}
get atlasGlyphs(): AtlasGlyph[] { get atlasGlyphs(): AtlasGlyph[] {
return this.appController.atlasGlyphs; return this.appController.atlasGlyphs;
} }

View File

@ -14,7 +14,7 @@ import * as _ from 'lodash';
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy'; import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy';
import {StemDarkeningMode, SubpixelAAType} from './aa-strategy'; import {StemDarkeningMode, SubpixelAAType} from './aa-strategy';
import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey, SUBPIXEL_GRANULARITY} from './atlas'; import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey, SUBPIXEL_GRANULARITY} from './atlas';
import {OrthographicCamera} from './camera'; import {CameraView, OrthographicCamera} from './camera';
import {createFramebuffer, createFramebufferDepthTexture, QUAD_ELEMENTS} from './gl-utils'; import {createFramebuffer, createFramebufferDepthTexture, QUAD_ELEMENTS} from './gl-utils';
import {UniformMap} from './gl-utils'; import {UniformMap} from './gl-utils';
import {Renderer} from './renderer'; import {Renderer} from './renderer';
@ -44,6 +44,7 @@ const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
export interface TextRenderContext extends RenderContext { export interface TextRenderContext extends RenderContext {
atlasGlyphs: AtlasGlyph[]; atlasGlyphs: AtlasGlyph[];
readonly cameraView: CameraView;
readonly atlas: Atlas; readonly atlas: Atlas;
readonly layout: SimpleTextLayout; readonly layout: SimpleTextLayout;
readonly glyphStore: GlyphStore; readonly glyphStore: GlyphStore;
@ -53,9 +54,6 @@ export interface TextRenderContext extends RenderContext {
readonly layoutPixelsPerUnit: number; readonly layoutPixelsPerUnit: number;
readonly useHinting: boolean; readonly useHinting: boolean;
// TODO(pcwalton): Remove this.
readonly canvas: HTMLCanvasElement;
newTimingsReceived(timings: Timings): void; newTimingsReceived(timings: Timings): void;
} }
@ -139,7 +137,7 @@ export class TextRenderer extends Renderer {
constructor(renderContext: TextRenderContext) { constructor(renderContext: TextRenderContext) {
super(renderContext); super(renderContext);
this.camera = new OrthographicCamera(this.renderContext.canvas, { this.camera = new OrthographicCamera(this.renderContext.cameraView, {
maxScale: MAX_SCALE, maxScale: MAX_SCALE,
minScale: MIN_SCALE, minScale: MIN_SCALE,
}); });
@ -244,8 +242,8 @@ export class TextRenderer extends Renderer {
this.renderContext.gl.bindFramebuffer(this.renderContext.gl.FRAMEBUFFER, null); this.renderContext.gl.bindFramebuffer(this.renderContext.gl.FRAMEBUFFER, null);
this.renderContext.gl.viewport(0, this.renderContext.gl.viewport(0,
0, 0,
this.renderContext.canvas.width, this.renderContext.cameraView.width,
this.renderContext.canvas.height); this.renderContext.cameraView.height);
this.renderContext.gl.disable(this.renderContext.gl.DEPTH_TEST); this.renderContext.gl.disable(this.renderContext.gl.DEPTH_TEST);
this.renderContext.gl.disable(this.renderContext.gl.SCISSOR_TEST); this.renderContext.gl.disable(this.renderContext.gl.SCISSOR_TEST);
this.renderContext.gl.blendEquation(this.renderContext.gl.FUNC_ADD); this.renderContext.gl.blendEquation(this.renderContext.gl.FUNC_ADD);
@ -288,8 +286,8 @@ export class TextRenderer extends Renderer {
const transform = glmatrix.mat4.create(); const transform = glmatrix.mat4.create();
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]); glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [ glmatrix.mat4.scale(transform, transform, [
2.0 / this.renderContext.canvas.width, 2.0 / this.renderContext.cameraView.width,
2.0 / this.renderContext.canvas.height, 2.0 / this.renderContext.cameraView.height,
1.0, 1.0,
]); ]);
glmatrix.mat4.translate(transform, glmatrix.mat4.translate(transform,
@ -439,8 +437,8 @@ export class TextRenderer extends Renderer {
const canvasRect = glmatrix.vec4.clone([ const canvasRect = glmatrix.vec4.clone([
-translation[0], -translation[0],
-translation[1], -translation[1],
-translation[0] + this.renderContext.canvas.width, -translation[0] + this.renderContext.cameraView.width,
-translation[1] + this.renderContext.canvas.height, -translation[1] + this.renderContext.cameraView.height,
]); ]);
let atlasGlyphs = []; let atlasGlyphs = [];