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-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 {
|
|
|
|
[shaderName: string]: { 'vertex': string, 'fragment': string }
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
throw new Error(message);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2017-08-10 21:38:54 -04:00
|
|
|
class PathfinderMeshes {
|
|
|
|
constructor(encodedResponse: string) {
|
|
|
|
const response = JSON.parse(encodedResponse);
|
|
|
|
if (!('Ok' in response))
|
|
|
|
throw new Error("Failed to partition the font!");
|
|
|
|
const meshes = response.Ok;
|
2017-08-11 18:56:07 -04:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2017-08-11 18:56:07 -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)
|
|
|
|
throw new Error("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 00:26:25 -04:00
|
|
|
const xhr = new XMLHttpRequest;
|
2017-08-10 21:38:54 -04:00
|
|
|
xhr.addEventListener('load', () => {
|
|
|
|
this.meshes = new PathfinderMeshes(xhr.responseText);
|
|
|
|
this.meshesReceived();
|
|
|
|
}, false);
|
|
|
|
xhr.open('POST', PARTITION_FONT_ENDPOINT_URL, true);
|
|
|
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
|
|
xhr.send(JSON.stringify(request));
|
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();
|
|
|
|
|
|
|
|
this.loadShaders().then(shaders => this.shaderProgramsPromise = this.linkShaders(shaders));
|
|
|
|
|
|
|
|
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> {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
let shaders: ShaderMap = {};
|
|
|
|
const shaderKeys = Object.keys(SHADER_URLS);
|
|
|
|
let shaderKeysLeft = shaderKeys.length;
|
|
|
|
|
|
|
|
let loaded = (type: ShaderType, shaderKey: string, source: string) => {
|
|
|
|
const shader = this.gl.createShader(type);
|
|
|
|
if (shader == null)
|
|
|
|
throw new Error("Failed to create shader!");
|
|
|
|
this.gl.shaderSource(shader, source);
|
|
|
|
this.gl.compileShader(shader);
|
|
|
|
if (!(shaderKey in shaders))
|
|
|
|
shaders[shaderKey] = {};
|
|
|
|
shaders[shaderKey][type] = shader;
|
|
|
|
|
|
|
|
shaderKeysLeft--;
|
|
|
|
if (shaderKeysLeft == 0)
|
|
|
|
resolve(shaders);
|
|
|
|
};
|
|
|
|
|
|
|
|
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 xhr = new XMLHttpRequest;
|
|
|
|
xhr.addEventListener('load',
|
|
|
|
() => loaded(type, shaderKey, xhr.responseText),
|
|
|
|
false);
|
|
|
|
xhr.addEventListener('error', () => reject(), false);
|
|
|
|
xhr.open('GET', SHADER_URLS[shaderKey][typeName], true);
|
|
|
|
xhr.send();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
linkShaders(shaders: ShaderMap): Promise<ShaderProgramMap> {
|
|
|
|
// TODO(pcwalton)
|
|
|
|
throw new Error("TODO");
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
constructor(vertexShaderSource: string, fragmentShaderSource: string) {
|
|
|
|
// TODO(pcwalton)
|
|
|
|
}
|
2017-08-08 14:23:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function main() {
|
|
|
|
const controller = new AppController;
|
|
|
|
window.addEventListener('load', () => controller.start(), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
main();
|