pathfinder/demo/client/src/index.ts

231 lines
7.5 KiB
TypeScript
Raw Normal View History

2017-08-08 14:23:30 -04:00
// pathfinder/demo/src/index.ts
2017-08-10 21:38:54 -04:00
const base64js = require('base64-js');
2017-08-08 14:23:30 -04:00
const opentype = require('opentype.js');
2017-08-10 21:38:54 -04:00
const TEXT: string = "A";
const FONT_SIZE: number = 16.0;
const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font";
2017-08-13 16:39:51 -04:00
const COMMON_SHADER_URL: string = '/glsl/gles2/common.inc.glsl';
2017-08-12 00:26:25 -04:00
const SHADER_URLS: ShaderURLMap = {
directCurve: {
vertex: "/glsl/gles2/direct-curve.vs.glsl",
fragment: "/glsl/gles2/direct-curve.fs.glsl",
},
directInterior: {
vertex: "/glsl/gles2/direct-interior.vs.glsl",
fragment: "/glsl/gles2/direct-interior.fs.glsl",
},
};
interface ShaderURLMap {
2017-08-13 16:39:51 -04:00
[shaderName: string]: { 'vertex': string, 'fragment': string };
2017-08-12 00:26:25 -04:00
}
interface ShaderMap {
[shaderName: string]: { [shaderType: number]: WebGLShader };
}
interface ShaderProgramMap {
[shaderProgramName: string]: PathfinderShaderProgram;
}
type ShaderType = number;
type ShaderTypeName = 'vertex' | 'fragment';
function expect<T>(value: T | null, message: string): T {
if (value == null)
2017-08-13 16:39:51 -04:00
throw new PathfinderError(message);
2017-08-12 00:26:25 -04:00
return value;
}
2017-08-13 16:39:51 -04:00
class PathfinderError extends Error {
constructor(message?: string | undefined) {
super(message);
}
}
2017-08-10 21:38:54 -04:00
class PathfinderMeshes {
constructor(encodedResponse: string) {
const response = JSON.parse(encodedResponse);
if (!('Ok' in response))
2017-08-13 16:39:51 -04:00
throw new PathfinderError("Failed to partition the font!");
2017-08-10 21:38:54 -04:00
const meshes = response.Ok;
this.bQuadPositions = base64js.toByteArray(meshes.bQuadPositions);
this.bQuadInfo = base64js.toByteArray(meshes.bQuadInfo);
2017-08-10 21:38:54 -04:00
this.bVertices = base64js.toByteArray(meshes.bVertices);
2017-08-08 14:23:30 -04:00
}
bQuadPositions: ArrayBuffer;
bQuadInfo: ArrayBuffer;
2017-08-10 21:38:54 -04:00
bVertices: ArrayBuffer;
}
class AppController {
constructor() {}
2017-08-08 14:23:30 -04:00
start() {
this.view = new PathfinderView(document.getElementById('pf-canvas') as HTMLCanvasElement);
this.loadFontButton = document.getElementById('pf-load-font-button') as HTMLInputElement;
this.loadFontButton.addEventListener('change', () => this.loadFont(), false);
}
loadFont() {
2017-08-12 00:26:25 -04:00
const file = expect(this.loadFontButton.files, "No file selected!")[0];
2017-08-10 21:38:54 -04:00
const reader = new FileReader;
reader.addEventListener('loadend', () => {
this.fontData = reader.result;
this.fontLoaded();
}, false);
reader.readAsArrayBuffer(file);
}
fontLoaded() {
this.font = opentype.parse(this.fontData);
2017-08-12 00:26:25 -04:00
if (!this.font.supported)
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-12 00:26:25 -04:00
const glyphIDs = this.font.stringToGlyphs(TEXT).map((glyph: any) => glyph.index);
2017-08-10 21:38:54 -04:00
const request = {
otf: base64js.fromByteArray(new Uint8Array(this.fontData)),
fontIndex: 0,
glyphIDs: glyphIDs,
pointSize: FONT_SIZE,
};
2017-08-12 13:08:35 -04:00
window.fetch(PARTITION_FONT_ENDPOINT_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
}).then((response) => {
response.text().then((encodedMeshes) => {
this.meshes = new PathfinderMeshes(encodedMeshes);
this.meshesReceived();
});
});
2017-08-08 14:23:30 -04:00
}
2017-08-10 21:38:54 -04:00
meshesReceived() {
2017-08-08 14:23:30 -04:00
// TODO(pcwalton)
}
view: PathfinderView;
loadFontButton: HTMLInputElement;
2017-08-10 21:38:54 -04:00
fontData: ArrayBuffer;
font: any;
meshes: PathfinderMeshes;
2017-08-08 14:23:30 -04:00
}
class PathfinderView {
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
2017-08-12 00:26:25 -04:00
this.initContext();
2017-08-13 16:39:51 -04:00
this.shaderProgramsPromise = this.loadShaders().then(shaders => this.linkShaders(shaders));
2017-08-12 00:26:25 -04:00
window.addEventListener('resize', () => this.resizeToFit(), false);
this.resizeToFit();
}
initContext() {
this.gl = expect(this.canvas.getContext('webgl', { antialias: false, depth: true }),
"Failed to initialize WebGL! Check that your browser supports it.");
this.gl.clearColor(0.0, 0.0, 1.0, 1.0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.gl.flush();
}
loadShaders(): Promise<ShaderMap> {
2017-08-12 13:08:35 -04:00
let shaders: ShaderMap = {};
2017-08-13 16:39:51 -04:00
return window.fetch(COMMON_SHADER_URL)
.then((response) => response.text())
.then((commonSource) => {
const shaderKeys = Object.keys(SHADER_URLS);
let promises = [];
for (const shaderKey of shaderKeys) {
for (const typeName of ['vertex', 'fragment'] as Array<ShaderTypeName>) {
const type = {
vertex: this.gl.VERTEX_SHADER,
fragment: this.gl.FRAGMENT_SHADER,
}[typeName];
const url = SHADER_URLS[shaderKey][typeName];
promises.push(window.fetch(url)
.then(response => response.text())
.then(source => {
2017-08-12 13:08:35 -04:00
const shader = this.gl.createShader(type);
if (shader == null)
2017-08-13 16:39:51 -04:00
throw new PathfinderError("Failed to create shader!");
this.gl.shaderSource(shader, commonSource + "\n#line 1\n" + source);
2017-08-12 13:08:35 -04:00
this.gl.compileShader(shader);
2017-08-13 16:39:51 -04:00
if (this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS) == 0) {
const infoLog = this.gl.getShaderInfoLog(shader);
throw new PathfinderError(`Failed to compile ${typeName} shader ` +
`"${shaderKey}":\n${infoLog}`);
}
2017-08-12 13:08:35 -04:00
if (!(shaderKey in shaders))
shaders[shaderKey] = {};
shaders[shaderKey][type] = shader;
2017-08-13 16:39:51 -04:00
}));
}
2017-08-12 00:26:25 -04:00
}
2017-08-12 13:08:35 -04:00
2017-08-13 16:39:51 -04:00
return Promise.all(promises);
}).then(() => shaders);
2017-08-12 00:26:25 -04:00
}
linkShaders(shaders: ShaderMap): Promise<ShaderProgramMap> {
2017-08-13 16:39:51 -04:00
return new Promise((resolve, reject) => {
let shaderProgramMap: ShaderProgramMap = {};
for (const shaderKey of Object.keys(shaders)) {
const program = expect(this.gl.createProgram(), "Failed to create shader program!");
const compiledShaders = shaders[shaderKey];
for (const compiledShader of Object.values(compiledShaders))
this.gl.attachShader(program, compiledShader);
this.gl.linkProgram(program);
if (this.gl.getProgramParameter(program, this.gl.LINK_STATUS) == 0) {
const infoLog = this.gl.getProgramInfoLog(program);
throw new PathfinderError(`Failed to link program "${program}":\n${infoLog}`);
}
shaderProgramMap[shaderKey] = program;
}
resolve(shaderProgramMap);
});
2017-08-12 00:26:25 -04:00
}
resizeToFit() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight - this.canvas.scrollTop;
2017-08-08 14:23:30 -04:00
}
canvas: HTMLCanvasElement;
2017-08-12 00:26:25 -04:00
gl: WebGLRenderingContext;
shaderProgramsPromise: Promise<ShaderProgramMap>;
}
class PathfinderShaderProgram {
2017-08-13 16:39:51 -04:00
constructor(vertexShader: WebGLShader, fragmentShader: WebGLShader) {
2017-08-12 00:26:25 -04:00
// TODO(pcwalton)
}
2017-08-08 14:23:30 -04:00
}
function main() {
const controller = new AppController;
window.addEventListener('load', () => controller.start(), false);
}
main();