2017-08-25 23:20:45 -04:00
|
|
|
|
// pathfinder/client/src/text.ts
|
2017-08-18 20:12:58 -04:00
|
|
|
|
//
|
2017-08-25 23:20:45 -04:00
|
|
|
|
// Copyright © 2017 The Pathfinder Project Developers.
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
|
// except according to those terms.
|
2017-08-08 14:23:30 -04:00
|
|
|
|
|
2017-08-19 19:34:02 -04:00
|
|
|
|
import * as _ from 'lodash';
|
|
|
|
|
import * as base64js from 'base64-js';
|
|
|
|
|
import * as glmatrix from 'gl-matrix';
|
|
|
|
|
import * as opentype from 'opentype.js';
|
2017-08-08 14:23:30 -04:00
|
|
|
|
|
2017-08-28 20:18:44 -04:00
|
|
|
|
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy';
|
2017-08-26 16:47:18 -04:00
|
|
|
|
import {ECAAMonochromeStrategy, ECAAStrategy} from './ecaa-strategy';
|
|
|
|
|
import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
|
|
|
|
|
import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from './gl-utils';
|
|
|
|
|
import {UniformMap} from './gl-utils';
|
|
|
|
|
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
|
|
|
|
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
|
2017-08-28 20:18:44 -04:00
|
|
|
|
import {PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic} from './utils';
|
|
|
|
|
import {MonochromePathfinderView, Timings} from './view';
|
2017-08-26 15:54:25 -04:00
|
|
|
|
import AppController from './app-controller';
|
2017-08-26 16:47:18 -04:00
|
|
|
|
import PathfinderBufferTexture from './buffer-texture';
|
|
|
|
|
import SSAAStrategy from './ssaa-strategy';
|
2017-08-26 15:54:25 -04:00
|
|
|
|
|
2017-08-25 20:02:11 -04:00
|
|
|
|
const TEXT: string =
|
|
|
|
|
`’Twas brillig, and the slithy toves
|
|
|
|
|
Did gyre and gimble in the wabe;
|
|
|
|
|
All mimsy were the borogoves,
|
|
|
|
|
And the mome raths outgrabe.
|
|
|
|
|
|
|
|
|
|
“Beware the Jabberwock, my son!
|
|
|
|
|
The jaws that bite, the claws that catch!
|
|
|
|
|
Beware the Jubjub bird, and shun
|
|
|
|
|
The frumious Bandersnatch!”
|
|
|
|
|
|
|
|
|
|
He took his vorpal sword in hand:
|
|
|
|
|
Long time the manxome foe he sought—
|
|
|
|
|
So rested he by the Tumtum tree,
|
|
|
|
|
And stood awhile in thought.
|
|
|
|
|
|
|
|
|
|
And as in uffish thought he stood,
|
|
|
|
|
The Jabberwock, with eyes of flame,
|
|
|
|
|
Came whiffling through the tulgey wood,
|
|
|
|
|
And burbled as it came!
|
|
|
|
|
|
|
|
|
|
One, two! One, two! And through and through
|
|
|
|
|
The vorpal blade went snicker-snack!
|
|
|
|
|
He left it dead, and with its head
|
|
|
|
|
He went galumphing back.
|
|
|
|
|
|
|
|
|
|
“And hast thou slain the Jabberwock?
|
|
|
|
|
Come to my arms, my beamish boy!
|
|
|
|
|
O frabjous day! Callooh! Callay!”
|
|
|
|
|
He chortled in his joy.
|
|
|
|
|
|
|
|
|
|
’Twas brillig, and the slithy toves
|
|
|
|
|
Did gyre and gimble in the wabe;
|
|
|
|
|
All mimsy were the borogoves,
|
|
|
|
|
And the mome raths outgrabe.`;
|
2017-08-22 21:25:32 -04:00
|
|
|
|
|
|
|
|
|
const INITIAL_FONT_SIZE: number = 72.0;
|
2017-08-10 21:38:54 -04:00
|
|
|
|
|
|
|
|
|
const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font";
|
|
|
|
|
|
2017-08-14 19:08:45 -04:00
|
|
|
|
const B_POSITION_SIZE: number = 8;
|
|
|
|
|
|
2017-08-17 15:47:50 -04:00
|
|
|
|
const B_PATH_INDEX_SIZE: number = 2;
|
|
|
|
|
|
2017-08-25 18:39:13 -04:00
|
|
|
|
const ATLAS_SIZE: glmatrix.vec2 = glmatrix.vec2.fromValues(3072, 3072);
|
2017-08-23 16:23:29 -04:00
|
|
|
|
|
2017-08-19 01:11:52 -04:00
|
|
|
|
type Matrix4D = Float32Array;
|
|
|
|
|
|
2017-08-19 19:52:14 -04:00
|
|
|
|
type Rect = glmatrix.vec4;
|
2017-08-19 19:34:02 -04:00
|
|
|
|
|
2017-08-19 01:11:52 -04:00
|
|
|
|
interface Point2D {
|
|
|
|
|
x: number;
|
|
|
|
|
y: number;
|
|
|
|
|
}
|
2017-08-14 19:08:45 -04:00
|
|
|
|
|
2017-08-19 19:52:14 -04:00
|
|
|
|
type Size2D = glmatrix.vec2;
|
2017-08-15 00:24:58 -04:00
|
|
|
|
|
2017-08-12 00:26:25 -04:00
|
|
|
|
type ShaderType = number;
|
|
|
|
|
|
2017-08-22 21:25:32 -04:00
|
|
|
|
// `opentype.js` monkey patches
|
|
|
|
|
|
|
|
|
|
declare module 'opentype.js' {
|
|
|
|
|
interface Font {
|
|
|
|
|
isSupported(): boolean;
|
|
|
|
|
}
|
|
|
|
|
interface Glyph {
|
|
|
|
|
getIndex(): number;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
opentype.Font.prototype.isSupported = function() {
|
|
|
|
|
return (this as any).supported;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 18:39:13 -04:00
|
|
|
|
/// The separating axis theorem.
|
|
|
|
|
function rectsIntersect(a: glmatrix.vec4, b: glmatrix.vec4): boolean {
|
|
|
|
|
return a[2] > b[0] && a[3] > b[1] && a[0] < b[2] && a[1] < b[3];
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-26 16:47:18 -04:00
|
|
|
|
class TextDemoController extends AppController<TextDemoView> {
|
2017-08-23 16:23:29 -04:00
|
|
|
|
constructor() {
|
2017-08-26 15:54:25 -04:00
|
|
|
|
super();
|
2017-08-23 16:23:29 -04:00
|
|
|
|
this._atlas = new Atlas;
|
|
|
|
|
}
|
2017-08-10 21:38:54 -04:00
|
|
|
|
|
2017-08-08 14:23:30 -04:00
|
|
|
|
start() {
|
2017-08-26 15:54:25 -04:00
|
|
|
|
super.start();
|
|
|
|
|
|
2017-08-22 21:25:32 -04:00
|
|
|
|
this.fontSize = INITIAL_FONT_SIZE;
|
|
|
|
|
|
2017-08-19 12:57:54 -04:00
|
|
|
|
this.fpsLabel = unwrapNull(document.getElementById('pf-fps-label'));
|
|
|
|
|
|
2017-08-28 19:47:27 -04:00
|
|
|
|
this.loadFileButton = document.getElementById('pf-load-font-button') as HTMLInputElement;
|
|
|
|
|
this.loadFileButton.addEventListener('change', () => this.loadFile(), false);
|
2017-08-08 14:23:30 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-26 15:54:25 -04:00
|
|
|
|
protected createView(canvas: HTMLCanvasElement,
|
|
|
|
|
commonShaderSource: string,
|
|
|
|
|
shaderSources: ShaderMap<ShaderProgramSource>) {
|
2017-08-26 16:47:18 -04:00
|
|
|
|
return new TextDemoView(this, canvas, commonShaderSource, shaderSources);
|
2017-08-26 15:54:25 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-28 19:47:27 -04:00
|
|
|
|
protected fileLoaded() {
|
|
|
|
|
this.font = opentype.parse(this.fileData);
|
2017-08-22 21:25:32 -04:00
|
|
|
|
if (!this.font.isSupported())
|
2017-08-13 16:39:51 -04:00
|
|
|
|
throw new PathfinderError("The font type is unsupported.");
|
2017-08-10 21:38:54 -04:00
|
|
|
|
|
2017-08-22 21:25:32 -04:00
|
|
|
|
// Lay out the text.
|
2017-08-25 20:02:11 -04:00
|
|
|
|
this.lineGlyphs = TEXT.split("\n").map(line => {
|
|
|
|
|
return this.font.stringToGlyphs(line).map(glyph => new TextGlyph(glyph));
|
|
|
|
|
});
|
|
|
|
|
this.textGlyphs = _.flatten(this.lineGlyphs);
|
2017-08-19 19:34:02 -04:00
|
|
|
|
|
2017-08-25 18:39:13 -04:00
|
|
|
|
// Determine all glyphs potentially needed.
|
2017-08-25 20:02:11 -04:00
|
|
|
|
this.uniqueGlyphs = this.textGlyphs.map(textGlyph => textGlyph);
|
|
|
|
|
this.uniqueGlyphs.sort((a, b) => a.index - b.index);
|
|
|
|
|
this.uniqueGlyphs = _.sortedUniqBy(this.uniqueGlyphs, glyph => glyph.index);
|
2017-08-10 21:38:54 -04:00
|
|
|
|
|
2017-08-19 19:34:02 -04:00
|
|
|
|
// Build the partitioning request to the server.
|
2017-08-10 21:38:54 -04:00
|
|
|
|
const request = {
|
2017-08-28 19:47:27 -04:00
|
|
|
|
otf: base64js.fromByteArray(new Uint8Array(this.fileData)),
|
2017-08-10 21:38:54 -04:00
|
|
|
|
fontIndex: 0,
|
2017-08-25 18:39:13 -04:00
|
|
|
|
glyphs: this.uniqueGlyphs.map(glyph => {
|
2017-08-25 20:02:11 -04:00
|
|
|
|
const metrics = glyph.metrics;
|
2017-08-19 19:34:02 -04:00
|
|
|
|
return {
|
2017-08-25 20:02:11 -04:00
|
|
|
|
id: glyph.index,
|
2017-08-22 21:25:32 -04:00
|
|
|
|
transform: [1, 0, 0, 1, 0, 0],
|
2017-08-19 19:34:02 -04:00
|
|
|
|
};
|
|
|
|
|
}),
|
|
|
|
|
pointSize: this.font.unitsPerEm,
|
2017-08-10 21:38:54 -04:00
|
|
|
|
};
|
|
|
|
|
|
2017-08-28 19:47:27 -04:00
|
|
|
|
// Make the request.
|
2017-08-12 13:08:35 -04:00
|
|
|
|
window.fetch(PARTITION_FONT_ENDPOINT_URL, {
|
|
|
|
|
method: 'POST',
|
2017-08-28 19:47:27 -04:00
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
2017-08-12 13:08:35 -04:00
|
|
|
|
body: JSON.stringify(request),
|
2017-08-28 19:47:27 -04:00
|
|
|
|
}).then(response => response.text()).then(responseText => {
|
|
|
|
|
const response = JSON.parse(responseText);
|
|
|
|
|
if (!('Ok' in response))
|
|
|
|
|
panic("Failed to partition the font!");
|
|
|
|
|
const meshes = response.Ok.pathData;
|
|
|
|
|
this.meshes = new PathfinderMeshData(meshes);
|
2017-08-15 00:24:58 -04:00
|
|
|
|
this.meshesReceived();
|
2017-08-12 13:08:35 -04:00
|
|
|
|
});
|
2017-08-08 14:23:30 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-24 22:30:53 -04:00
|
|
|
|
private meshesReceived() {
|
2017-08-17 23:09:18 -04:00
|
|
|
|
this.view.then(view => {
|
2017-08-25 18:39:13 -04:00
|
|
|
|
view.attachText();
|
|
|
|
|
view.uploadPathData(this.uniqueGlyphs.length);
|
2017-08-17 23:09:18 -04:00
|
|
|
|
view.attachMeshes(this.meshes);
|
|
|
|
|
})
|
2017-08-08 14:23:30 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-28 20:18:44 -04:00
|
|
|
|
updateTimings(newTimes: Timings) {
|
2017-08-24 19:18:26 -04:00
|
|
|
|
this.fpsLabel.innerHTML =
|
|
|
|
|
`${newTimes.atlasRendering} ms atlas, ${newTimes.compositing} ms compositing`;
|
2017-08-19 12:57:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-23 16:23:29 -04:00
|
|
|
|
get atlas(): Atlas {
|
|
|
|
|
return this._atlas;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-29 01:11:15 -04:00
|
|
|
|
/// The font size in pixels per em.
|
|
|
|
|
get fontSize(): number {
|
|
|
|
|
return this._fontSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The font size in pixels per em.
|
|
|
|
|
set fontSize(newFontSize: number) {
|
|
|
|
|
this._fontSize = newFontSize;
|
|
|
|
|
this.view.then(view => view.attachText());
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-24 22:30:53 -04:00
|
|
|
|
private fpsLabel: HTMLElement;
|
2017-08-22 21:25:32 -04:00
|
|
|
|
|
2017-08-25 18:39:13 -04:00
|
|
|
|
font: opentype.Font;
|
2017-08-25 20:02:11 -04:00
|
|
|
|
lineGlyphs: TextGlyph[][];
|
2017-08-25 18:39:13 -04:00
|
|
|
|
textGlyphs: TextGlyph[];
|
2017-08-25 20:02:11 -04:00
|
|
|
|
uniqueGlyphs: PathfinderGlyph[];
|
2017-08-23 16:23:29 -04:00
|
|
|
|
|
|
|
|
|
private _atlas: Atlas;
|
2017-08-25 18:39:13 -04:00
|
|
|
|
atlasGlyphs: AtlasGlyph[];
|
2017-08-22 21:25:32 -04:00
|
|
|
|
|
2017-08-24 22:30:53 -04:00
|
|
|
|
private meshes: PathfinderMeshData;
|
2017-08-22 21:25:32 -04:00
|
|
|
|
|
2017-08-29 01:11:15 -04:00
|
|
|
|
private _fontSize: number;
|
2017-08-08 14:23:30 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-27 15:43:17 -04:00
|
|
|
|
class TextDemoView extends MonochromePathfinderView {
|
2017-08-26 15:54:25 -04:00
|
|
|
|
constructor(appController: TextDemoController,
|
2017-08-19 12:57:54 -04:00
|
|
|
|
canvas: HTMLCanvasElement,
|
2017-08-17 23:09:18 -04:00
|
|
|
|
commonShaderSource: string,
|
|
|
|
|
shaderSources: ShaderMap<ShaderProgramSource>) {
|
2017-08-26 16:47:18 -04:00
|
|
|
|
super(canvas, commonShaderSource, shaderSources);
|
|
|
|
|
|
2017-08-19 12:57:54 -04:00
|
|
|
|
this.appController = appController;
|
2017-08-15 16:38:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-26 16:47:18 -04:00
|
|
|
|
protected initContext() {
|
|
|
|
|
super.initContext();
|
2017-08-12 00:26:25 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-15 00:24:58 -04:00
|
|
|
|
uploadPathData(pathCount: number) {
|
2017-08-24 19:23:02 -04:00
|
|
|
|
const pathColors = new Uint8Array(4 * (pathCount + 1));
|
2017-08-15 00:24:58 -04:00
|
|
|
|
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
|
|
|
|
|
for (let channel = 0; channel < 3; channel++)
|
2017-08-24 19:23:02 -04:00
|
|
|
|
pathColors[(pathIndex + 1) * 4 + channel] = 0x00; // RGB
|
|
|
|
|
pathColors[(pathIndex + 1) * 4 + 3] = 0xff; // alpha
|
2017-08-15 00:24:58 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-23 16:23:29 -04:00
|
|
|
|
this.pathColorsBufferTexture.upload(this.gl, pathColors);
|
2017-08-15 00:24:58 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 18:39:13 -04:00
|
|
|
|
/// Lays out glyphs on the canvas.
|
|
|
|
|
private layoutGlyphs() {
|
2017-08-25 20:02:11 -04:00
|
|
|
|
const lineGlyphs = this.appController.lineGlyphs;
|
2017-08-25 18:39:13 -04:00
|
|
|
|
const textGlyphs = this.appController.textGlyphs;
|
2017-08-22 21:25:32 -04:00
|
|
|
|
|
2017-08-25 20:02:11 -04:00
|
|
|
|
const font = this.appController.font;
|
|
|
|
|
this.pixelsPerUnit = this.appController.fontSize / font.unitsPerEm;
|
2017-08-22 21:25:32 -04:00
|
|
|
|
|
|
|
|
|
const glyphPositions = new Float32Array(textGlyphs.length * 8);
|
|
|
|
|
const glyphIndices = new Uint32Array(textGlyphs.length * 6);
|
|
|
|
|
|
2017-08-25 20:02:11 -04:00
|
|
|
|
const os2Table = font.tables.os2;
|
|
|
|
|
const lineHeight = (os2Table.sTypoAscender - os2Table.sTypoDescender +
|
|
|
|
|
os2Table.sTypoLineGap) * this.pixelsPerUnit;
|
|
|
|
|
|
2017-08-22 21:25:32 -04:00
|
|
|
|
const currentPosition = glmatrix.vec2.create();
|
|
|
|
|
|
2017-08-25 20:02:11 -04:00
|
|
|
|
let glyphIndex = 0;
|
|
|
|
|
for (const line of lineGlyphs) {
|
|
|
|
|
for (let lineCharIndex = 0; lineCharIndex < line.length; lineCharIndex++) {
|
|
|
|
|
const textGlyph = textGlyphs[glyphIndex];
|
|
|
|
|
const glyphMetrics = textGlyph.metrics;
|
|
|
|
|
|
|
|
|
|
// Determine the atlas size.
|
|
|
|
|
const atlasSize = glmatrix.vec2.fromValues(glyphMetrics.xMax - glyphMetrics.xMin,
|
|
|
|
|
glyphMetrics.yMax - glyphMetrics.yMin);
|
|
|
|
|
glmatrix.vec2.scale(atlasSize, atlasSize, this.pixelsPerUnit);
|
|
|
|
|
glmatrix.vec2.ceil(atlasSize, atlasSize);
|
|
|
|
|
|
|
|
|
|
// Set positions.
|
|
|
|
|
const textGlyphBL = glmatrix.vec2.create(), textGlyphTR = glmatrix.vec2.create();
|
|
|
|
|
const offset = glmatrix.vec2.fromValues(glyphMetrics.leftSideBearing,
|
|
|
|
|
glyphMetrics.yMin);
|
|
|
|
|
glmatrix.vec2.scale(offset, offset, this.pixelsPerUnit);
|
|
|
|
|
glmatrix.vec2.add(textGlyphBL, currentPosition, offset);
|
|
|
|
|
glmatrix.vec2.round(textGlyphBL, textGlyphBL);
|
|
|
|
|
glmatrix.vec2.add(textGlyphTR, textGlyphBL, atlasSize);
|
|
|
|
|
|
|
|
|
|
glyphPositions.set([
|
|
|
|
|
textGlyphBL[0], textGlyphTR[1],
|
|
|
|
|
textGlyphTR[0], textGlyphTR[1],
|
|
|
|
|
textGlyphBL[0], textGlyphBL[1],
|
|
|
|
|
textGlyphTR[0], textGlyphBL[1],
|
|
|
|
|
], glyphIndex * 8);
|
|
|
|
|
|
|
|
|
|
textGlyph.canvasRect = glmatrix.vec4.fromValues(textGlyphBL[0], textGlyphBL[1],
|
|
|
|
|
textGlyphTR[0], textGlyphTR[1]);
|
|
|
|
|
|
|
|
|
|
// Set indices.
|
|
|
|
|
glyphIndices.set(Array.from(QUAD_ELEMENTS).map(index => index + 4 * glyphIndex),
|
|
|
|
|
glyphIndex * 6);
|
|
|
|
|
|
|
|
|
|
// Advance.
|
|
|
|
|
currentPosition[0] += textGlyph.advanceWidth * this.pixelsPerUnit;
|
|
|
|
|
|
|
|
|
|
glyphIndex++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentPosition[0] = 0;
|
|
|
|
|
currentPosition[1] -= lineHeight;
|
2017-08-22 21:25:32 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.glyphPositionsBuffer = unwrapNull(this.gl.createBuffer());
|
|
|
|
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
|
|
|
|
|
this.gl.bufferData(this.gl.ARRAY_BUFFER, glyphPositions, this.gl.STATIC_DRAW);
|
|
|
|
|
this.glyphElementsBuffer = unwrapNull(this.gl.createBuffer());
|
|
|
|
|
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer);
|
|
|
|
|
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, glyphIndices, this.gl.STATIC_DRAW);
|
2017-08-14 19:08:45 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 18:39:13 -04:00
|
|
|
|
private buildAtlasGlyphs() {
|
|
|
|
|
const textGlyphs = this.appController.textGlyphs;
|
|
|
|
|
|
|
|
|
|
// Only build glyphs in view.
|
|
|
|
|
const canvasRect = glmatrix.vec4.fromValues(-this.translation[0],
|
|
|
|
|
-this.translation[1],
|
|
|
|
|
-this.translation[0] + this.canvas.width,
|
|
|
|
|
-this.translation[1] + this.canvas.height);
|
|
|
|
|
|
|
|
|
|
let atlasGlyphs =
|
|
|
|
|
textGlyphs.filter(textGlyph => rectsIntersect(textGlyph.canvasRect, canvasRect))
|
2017-08-25 20:02:11 -04:00
|
|
|
|
.map(textGlyph => new AtlasGlyph(textGlyph));
|
2017-08-25 18:39:13 -04:00
|
|
|
|
atlasGlyphs.sort((a, b) => a.index - b.index);
|
|
|
|
|
atlasGlyphs = _.sortedUniqBy(atlasGlyphs, glyph => glyph.index);
|
|
|
|
|
this.appController.atlasGlyphs = atlasGlyphs;
|
|
|
|
|
|
|
|
|
|
const fontSize = this.appController.fontSize;
|
|
|
|
|
const unitsPerEm = this.appController.font.unitsPerEm;
|
|
|
|
|
|
|
|
|
|
this.appController.atlas.layoutGlyphs(atlasGlyphs, fontSize, unitsPerEm);
|
|
|
|
|
|
2017-08-25 20:02:11 -04:00
|
|
|
|
const uniqueGlyphIndices = this.appController.uniqueGlyphs.map(glyph => glyph.index);
|
2017-08-25 18:39:13 -04:00
|
|
|
|
uniqueGlyphIndices.sort((a, b) => a - b);
|
|
|
|
|
|
|
|
|
|
// TODO(pcwalton): Regenerate the IBOs to include only the glyphs we care about.
|
|
|
|
|
const transforms = new Float32Array((this.appController.uniqueGlyphs.length + 1) * 4);
|
|
|
|
|
for (let glyphIndex = 0; glyphIndex < atlasGlyphs.length; glyphIndex++) {
|
|
|
|
|
const glyph = atlasGlyphs[glyphIndex];
|
|
|
|
|
|
|
|
|
|
let pathID = _.sortedIndexOf(uniqueGlyphIndices, glyph.index);
|
|
|
|
|
assert(pathID >= 0, "No path ID!");
|
|
|
|
|
pathID++;
|
2017-08-22 21:25:32 -04:00
|
|
|
|
|
2017-08-24 22:30:53 -04:00
|
|
|
|
const atlasLocation = glyph.atlasRect;
|
|
|
|
|
const metrics = glyph.metrics;
|
2017-08-22 21:25:32 -04:00
|
|
|
|
const left = metrics.xMin * this.pixelsPerUnit;
|
|
|
|
|
const bottom = metrics.yMin * this.pixelsPerUnit;
|
2017-08-25 18:39:13 -04:00
|
|
|
|
|
|
|
|
|
transforms[pathID * 4 + 0] = this.pixelsPerUnit;
|
|
|
|
|
transforms[pathID * 4 + 1] = this.pixelsPerUnit;
|
|
|
|
|
transforms[pathID * 4 + 2] = atlasLocation[0] - left;
|
|
|
|
|
transforms[pathID * 4 + 3] = atlasLocation[1] - bottom;
|
|
|
|
|
}
|
2017-08-22 21:25:32 -04:00
|
|
|
|
|
2017-08-29 15:29:16 -04:00
|
|
|
|
this.pathTransformBufferTexture.upload(this.gl, transforms);
|
2017-08-25 18:39:13 -04:00
|
|
|
|
}
|
2017-08-22 21:25:32 -04:00
|
|
|
|
|
2017-08-25 18:39:13 -04:00
|
|
|
|
private createAtlasFramebuffer() {
|
|
|
|
|
const atlasColorTexture = this.appController.atlas.ensureTexture(this.gl);
|
|
|
|
|
this.atlasDepthTexture = createFramebufferDepthTexture(this.gl, ATLAS_SIZE);
|
|
|
|
|
this.atlasFramebuffer = createFramebuffer(this.gl,
|
|
|
|
|
this.drawBuffersExt,
|
|
|
|
|
[atlasColorTexture],
|
|
|
|
|
this.atlasDepthTexture);
|
|
|
|
|
|
|
|
|
|
// Allow the antialiasing strategy to set up framebuffers as necessary.
|
2017-08-29 15:29:16 -04:00
|
|
|
|
this.antialiasingStrategy.setFramebufferSize(this);
|
2017-08-25 18:39:13 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private setGlyphTexCoords() {
|
|
|
|
|
const textGlyphs = this.appController.textGlyphs;
|
|
|
|
|
const atlasGlyphs = this.appController.atlasGlyphs;
|
|
|
|
|
|
|
|
|
|
const atlasGlyphIndices = atlasGlyphs.map(atlasGlyph => atlasGlyph.index);
|
|
|
|
|
|
|
|
|
|
const glyphTexCoords = new Float32Array(textGlyphs.length * 8);
|
|
|
|
|
|
|
|
|
|
const currentPosition = glmatrix.vec2.create();
|
|
|
|
|
|
|
|
|
|
for (let textGlyphIndex = 0; textGlyphIndex < textGlyphs.length; textGlyphIndex++) {
|
|
|
|
|
const textGlyph = textGlyphs[textGlyphIndex];
|
2017-08-25 20:02:11 -04:00
|
|
|
|
const textGlyphMetrics = textGlyph.metrics;
|
2017-08-25 18:39:13 -04:00
|
|
|
|
|
2017-08-25 20:02:11 -04:00
|
|
|
|
let atlasGlyphIndex = _.sortedIndexOf(atlasGlyphIndices, textGlyph.index);
|
2017-08-25 18:39:13 -04:00
|
|
|
|
if (atlasGlyphIndex < 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Set texture coordinates.
|
|
|
|
|
const atlasGlyph = atlasGlyphs[atlasGlyphIndex];
|
|
|
|
|
const atlasGlyphRect = atlasGlyph.atlasRect;
|
|
|
|
|
const atlasGlyphBL = atlasGlyphRect.slice(0, 2) as glmatrix.vec2;
|
|
|
|
|
const atlasGlyphTR = atlasGlyphRect.slice(2, 4) as glmatrix.vec2;
|
|
|
|
|
glmatrix.vec2.div(atlasGlyphBL, atlasGlyphBL, ATLAS_SIZE);
|
|
|
|
|
glmatrix.vec2.div(atlasGlyphTR, atlasGlyphTR, ATLAS_SIZE);
|
|
|
|
|
|
|
|
|
|
glyphTexCoords.set([
|
|
|
|
|
atlasGlyphBL[0], atlasGlyphTR[1],
|
|
|
|
|
atlasGlyphTR[0], atlasGlyphTR[1],
|
|
|
|
|
atlasGlyphBL[0], atlasGlyphBL[1],
|
|
|
|
|
atlasGlyphTR[0], atlasGlyphBL[1],
|
|
|
|
|
], textGlyphIndex * 8);
|
2017-08-23 16:23:29 -04:00
|
|
|
|
}
|
2017-08-22 21:25:32 -04:00
|
|
|
|
|
2017-08-25 18:39:13 -04:00
|
|
|
|
this.glyphTexCoordsBuffer = unwrapNull(this.gl.createBuffer());
|
|
|
|
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
|
|
|
|
|
this.gl.bufferData(this.gl.ARRAY_BUFFER, glyphTexCoords, this.gl.STATIC_DRAW);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
attachText() {
|
|
|
|
|
if (this.atlasFramebuffer == null)
|
|
|
|
|
this.createAtlasFramebuffer();
|
|
|
|
|
this.layoutGlyphs();
|
|
|
|
|
|
|
|
|
|
this.rebuildAtlasIfNecessary();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private rebuildAtlasIfNecessary() {
|
|
|
|
|
this.buildAtlasGlyphs();
|
|
|
|
|
this.setGlyphTexCoords();
|
|
|
|
|
this.setDirty();
|
2017-08-22 21:25:32 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-29 01:11:15 -04:00
|
|
|
|
protected panned() {
|
2017-08-25 18:39:13 -04:00
|
|
|
|
this.rebuildAtlasIfNecessary();
|
2017-08-19 01:11:52 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-28 19:47:27 -04:00
|
|
|
|
protected resized(initialSize: boolean) {
|
|
|
|
|
if (!initialSize)
|
|
|
|
|
this.antialiasingStrategy.init(this);
|
2017-08-14 19:08:45 -04:00
|
|
|
|
this.setDirty();
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-29 01:11:15 -04:00
|
|
|
|
private setIdentityTexScaleUniform(uniforms: UniformMap) {
|
2017-08-23 22:18:24 -04:00
|
|
|
|
this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-29 01:11:15 -04:00
|
|
|
|
protected get usedSizeFactor(): glmatrix.vec2 {
|
2017-08-23 22:18:24 -04:00
|
|
|
|
const usedSize = glmatrix.vec2.create();
|
|
|
|
|
glmatrix.vec2.div(usedSize, this.appController.atlas.usedSize, ATLAS_SIZE);
|
|
|
|
|
return usedSize;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-28 20:18:44 -04:00
|
|
|
|
protected compositeIfNecessary() {
|
2017-08-22 21:25:32 -04:00
|
|
|
|
// Set up composite state.
|
|
|
|
|
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
|
|
|
|
|
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
|
|
|
this.gl.disable(this.gl.DEPTH_TEST);
|
|
|
|
|
this.gl.disable(this.gl.BLEND);
|
2017-08-24 19:18:26 -04:00
|
|
|
|
this.gl.disable(this.gl.SCISSOR_TEST);
|
2017-08-22 21:25:32 -04:00
|
|
|
|
|
|
|
|
|
// Clear.
|
|
|
|
|
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
|
|
|
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
|
|
|
|
|
|
|
|
// Set up the composite VAO.
|
|
|
|
|
const blitProgram = this.shaderPrograms.blit;
|
|
|
|
|
const attributes = blitProgram.attributes;
|
|
|
|
|
this.gl.useProgram(blitProgram.program);
|
|
|
|
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
|
|
|
|
|
this.gl.vertexAttribPointer(attributes.aPosition, 2, this.gl.FLOAT, false, 0, 0);
|
|
|
|
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
|
|
|
|
|
this.gl.vertexAttribPointer(attributes.aTexCoord, 2, this.gl.FLOAT, false, 0, 0);
|
|
|
|
|
this.gl.enableVertexAttribArray(attributes.aPosition);
|
|
|
|
|
this.gl.enableVertexAttribArray(attributes.aTexCoord);
|
|
|
|
|
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer);
|
|
|
|
|
|
|
|
|
|
// Create the transform.
|
|
|
|
|
const transform = glmatrix.mat4.create();
|
|
|
|
|
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
|
|
|
|
|
glmatrix.mat4.scale(transform,
|
|
|
|
|
transform,
|
|
|
|
|
[2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]);
|
|
|
|
|
glmatrix.mat4.translate(transform,
|
|
|
|
|
transform,
|
|
|
|
|
[this.translation[0], this.translation[1], 0.0]);
|
|
|
|
|
|
|
|
|
|
// Blit.
|
|
|
|
|
this.gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform);
|
|
|
|
|
this.gl.activeTexture(this.gl.TEXTURE0);
|
2017-08-23 16:23:29 -04:00
|
|
|
|
this.gl.bindTexture(this.gl.TEXTURE_2D, this.appController.atlas.ensureTexture(this.gl));
|
2017-08-22 21:25:32 -04:00
|
|
|
|
this.gl.uniform1i(blitProgram.uniforms.uSource, 0);
|
2017-08-23 22:18:24 -04:00
|
|
|
|
this.setIdentityTexScaleUniform(blitProgram.uniforms);
|
2017-08-25 20:02:11 -04:00
|
|
|
|
this.gl.drawElements(this.gl.TRIANGLES,
|
|
|
|
|
this.appController.textGlyphs.length * 6,
|
|
|
|
|
this.gl.UNSIGNED_INT,
|
|
|
|
|
0);
|
2017-08-22 21:25:32 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-24 15:08:58 -04:00
|
|
|
|
get bgColor(): glmatrix.vec4 {
|
|
|
|
|
return glmatrix.vec4.fromValues(1.0, 1.0, 1.0, 1.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get fgColor(): glmatrix.vec4 {
|
|
|
|
|
return glmatrix.vec4.fromValues(0.0, 0.0, 0.0, 1.0);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-26 16:47:18 -04:00
|
|
|
|
get destFramebuffer(): WebGLFramebuffer {
|
|
|
|
|
return this.atlasFramebuffer;
|
|
|
|
|
}
|
2017-08-19 12:57:54 -04:00
|
|
|
|
|
2017-08-26 16:47:18 -04:00
|
|
|
|
get destAllocatedSize(): glmatrix.vec2 {
|
|
|
|
|
return ATLAS_SIZE;
|
|
|
|
|
}
|
2017-08-19 12:57:54 -04:00
|
|
|
|
|
2017-08-26 16:47:18 -04:00
|
|
|
|
get destUsedSize(): glmatrix.vec2 {
|
|
|
|
|
return this.appController.atlas.usedSize;
|
|
|
|
|
}
|
2017-08-19 12:57:54 -04:00
|
|
|
|
|
2017-08-29 01:11:15 -04:00
|
|
|
|
protected get scale(): number {
|
|
|
|
|
return this.appController.fontSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected set scale(newScale: number) {
|
|
|
|
|
this.appController.fontSize = newScale;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-28 20:18:44 -04:00
|
|
|
|
protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number):
|
|
|
|
|
AntialiasingStrategy {
|
|
|
|
|
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel);
|
|
|
|
|
}
|
2017-08-19 12:57:54 -04:00
|
|
|
|
|
2017-08-28 20:18:44 -04:00
|
|
|
|
protected updateTimings(timings: Timings) {
|
|
|
|
|
this.appController.updateTimings(timings);
|
|
|
|
|
}
|
2017-08-19 12:57:54 -04:00
|
|
|
|
|
2017-08-29 15:29:16 -04:00
|
|
|
|
protected get worldTransform() {
|
|
|
|
|
return glmatrix.mat4.create();
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-22 21:25:32 -04:00
|
|
|
|
atlasFramebuffer: WebGLFramebuffer;
|
|
|
|
|
atlasDepthTexture: WebGLTexture;
|
|
|
|
|
|
2017-08-24 22:33:58 -04:00
|
|
|
|
private pixelsPerUnit: number;
|
2017-08-22 21:25:32 -04:00
|
|
|
|
|
|
|
|
|
glyphPositionsBuffer: WebGLBuffer;
|
|
|
|
|
glyphTexCoordsBuffer: WebGLBuffer;
|
|
|
|
|
glyphElementsBuffer: WebGLBuffer;
|
|
|
|
|
|
2017-08-26 15:54:25 -04:00
|
|
|
|
appController: TextDemoController;
|
2017-08-12 00:26:25 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-15 16:38:54 -04:00
|
|
|
|
interface AntialiasingStrategyTable {
|
2017-08-15 18:57:52 -04:00
|
|
|
|
none: typeof NoAAStrategy;
|
|
|
|
|
ssaa: typeof SSAAStrategy;
|
2017-08-16 01:09:09 -04:00
|
|
|
|
ecaa: typeof ECAAStrategy;
|
2017-08-15 16:38:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 20:02:11 -04:00
|
|
|
|
class PathfinderGlyph {
|
|
|
|
|
constructor(glyph: opentype.Glyph | PathfinderGlyph) {
|
|
|
|
|
this.glyph = glyph instanceof PathfinderGlyph ? glyph.glyph : glyph;
|
|
|
|
|
this._metrics = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get index(): number {
|
|
|
|
|
return (this.glyph as any).index;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get metrics(): opentype.Metrics {
|
|
|
|
|
if (this._metrics == null)
|
|
|
|
|
this._metrics = this.glyph.getMetrics();
|
|
|
|
|
return this._metrics;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get advanceWidth(): number {
|
|
|
|
|
return this.glyph.advanceWidth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private glyph: opentype.Glyph;
|
|
|
|
|
private _metrics: opentype.Metrics | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class TextGlyph extends PathfinderGlyph {
|
|
|
|
|
constructor(glyph: opentype.Glyph | PathfinderGlyph) {
|
|
|
|
|
super(glyph);
|
|
|
|
|
this._canvasRect = glmatrix.vec4.create();
|
2017-08-25 18:39:13 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get canvasRect() {
|
|
|
|
|
return this._canvasRect;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set canvasRect(rect: Rect) {
|
|
|
|
|
this._canvasRect = rect;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _canvasRect: Rect;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 20:02:11 -04:00
|
|
|
|
class AtlasGlyph extends PathfinderGlyph {
|
|
|
|
|
constructor(glyph: opentype.Glyph | PathfinderGlyph) {
|
|
|
|
|
super(glyph);
|
|
|
|
|
this._atlasRect = glmatrix.vec4.create();
|
2017-08-19 19:34:02 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-24 22:30:53 -04:00
|
|
|
|
get atlasRect() {
|
|
|
|
|
return this._atlasRect;
|
2017-08-22 21:25:32 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 18:39:13 -04:00
|
|
|
|
set atlasRect(rect: Rect) {
|
|
|
|
|
this._atlasRect = rect;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-24 22:30:53 -04:00
|
|
|
|
get atlasSize(): Size2D {
|
2017-08-22 21:25:32 -04:00
|
|
|
|
let atlasSize = glmatrix.vec2.create();
|
|
|
|
|
glmatrix.vec2.sub(atlasSize,
|
2017-08-24 22:30:53 -04:00
|
|
|
|
this._atlasRect.slice(2, 4) as glmatrix.vec2,
|
|
|
|
|
this._atlasRect.slice(0, 2) as glmatrix.vec2);
|
2017-08-22 21:25:32 -04:00
|
|
|
|
return atlasSize;
|
2017-08-19 19:34:02 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-24 22:30:53 -04:00
|
|
|
|
private _atlasRect: Rect;
|
2017-08-19 19:34:02 -04:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-23 16:23:29 -04:00
|
|
|
|
class Atlas {
|
|
|
|
|
constructor() {
|
|
|
|
|
this._texture = null;
|
|
|
|
|
this._usedSize = glmatrix.vec2.create();
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 18:39:13 -04:00
|
|
|
|
layoutGlyphs(glyphs: AtlasGlyph[], fontSize: number, unitsPerEm: number) {
|
2017-08-23 16:23:29 -04:00
|
|
|
|
const pixelsPerUnit = fontSize / unitsPerEm;
|
|
|
|
|
|
|
|
|
|
let nextOrigin = glmatrix.vec2.create();
|
|
|
|
|
let shelfBottom = 0.0;
|
|
|
|
|
|
|
|
|
|
for (const glyph of glyphs) {
|
2017-08-24 22:30:53 -04:00
|
|
|
|
const metrics = glyph.metrics;
|
2017-08-25 15:20:36 -04:00
|
|
|
|
|
2017-08-23 16:23:29 -04:00
|
|
|
|
const glyphSize = glmatrix.vec2.fromValues(metrics.xMax - metrics.xMin,
|
|
|
|
|
metrics.yMax - metrics.yMin);
|
|
|
|
|
glmatrix.vec2.scale(glyphSize, glyphSize, pixelsPerUnit);
|
|
|
|
|
glmatrix.vec2.ceil(glyphSize, glyphSize);
|
|
|
|
|
|
|
|
|
|
// Make a new shelf if necessary.
|
|
|
|
|
const initialGlyphRight = nextOrigin[0] + glyphSize[0] + 2;
|
|
|
|
|
if (initialGlyphRight > ATLAS_SIZE[0])
|
|
|
|
|
nextOrigin = glmatrix.vec2.fromValues(0.0, shelfBottom);
|
|
|
|
|
|
|
|
|
|
const glyphRect = glmatrix.vec4.fromValues(nextOrigin[0] + 1,
|
|
|
|
|
nextOrigin[1] + 1,
|
2017-08-24 22:39:07 -04:00
|
|
|
|
nextOrigin[0] + glyphSize[0] + 1,
|
|
|
|
|
nextOrigin[1] + glyphSize[1] + 1);
|
2017-08-23 16:23:29 -04:00
|
|
|
|
|
2017-08-25 18:39:13 -04:00
|
|
|
|
glyph.atlasRect = glyphRect;
|
2017-08-23 16:23:29 -04:00
|
|
|
|
|
2017-08-24 22:39:07 -04:00
|
|
|
|
nextOrigin[0] = glyphRect[2] + 1;
|
2017-08-23 16:23:29 -04:00
|
|
|
|
shelfBottom = Math.max(shelfBottom, glyphRect[3]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIXME(pcwalton): Could be more precise if we don't have a full row.
|
|
|
|
|
this._usedSize = glmatrix.vec2.fromValues(ATLAS_SIZE[0], shelfBottom);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensureTexture(gl: WebGLRenderingContext): WebGLTexture {
|
|
|
|
|
if (this._texture != null)
|
|
|
|
|
return this._texture;
|
|
|
|
|
|
|
|
|
|
const texture = unwrapNull(gl.createTexture());
|
|
|
|
|
this._texture = texture;
|
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
|
|
|
gl.texImage2D(gl.TEXTURE_2D,
|
|
|
|
|
0,
|
|
|
|
|
gl.RGBA,
|
|
|
|
|
ATLAS_SIZE[0],
|
|
|
|
|
ATLAS_SIZE[1],
|
|
|
|
|
0,
|
|
|
|
|
gl.RGBA,
|
|
|
|
|
gl.UNSIGNED_BYTE,
|
|
|
|
|
null);
|
|
|
|
|
setTextureParameters(gl, gl.NEAREST);
|
|
|
|
|
|
|
|
|
|
return texture;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get usedSize(): glmatrix.vec2 {
|
|
|
|
|
return this._usedSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _texture: WebGLTexture | null;
|
|
|
|
|
private _usedSize: Size2D;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-15 16:38:54 -04:00
|
|
|
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
|
|
|
|
none: NoAAStrategy,
|
|
|
|
|
ssaa: SSAAStrategy,
|
2017-08-24 15:08:58 -04:00
|
|
|
|
ecaa: ECAAMonochromeStrategy,
|
2017-08-15 16:38:54 -04:00
|
|
|
|
};
|
|
|
|
|
|
2017-08-08 14:23:30 -04:00
|
|
|
|
function main() {
|
2017-08-26 15:54:25 -04:00
|
|
|
|
const controller = new TextDemoController;
|
2017-08-08 14:23:30 -04:00
|
|
|
|
window.addEventListener('load', () => controller.start(), false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main();
|