Implement bare-bones support for debugging SVG meshes
This commit is contained in:
parent
e7a6861846
commit
0e9a59088c
|
@ -9,6 +9,7 @@
|
|||
<body class="pf-unscrollable">
|
||||
{{>partials/navbar.html isTool=true}}
|
||||
<canvas id="pf-canvas" width="400" height="300"></canvas>
|
||||
<svg id="pf-svg" xmlns="http://www.w3.org/2000/svg" width="400" height="300"></svg>
|
||||
<div class="fixed-bottom mb-3 d-flex justify-content-end align-items-end pf-pointer-events-none">
|
||||
<div id="pf-toolbar">
|
||||
<button id="pf-open-button" type="button"
|
||||
|
|
|
@ -101,7 +101,7 @@ class ThreeDController extends DemoAppController<ThreeDView> {
|
|||
.then(response => response.json())
|
||||
.then(textData => this.parseTextData(textData));
|
||||
|
||||
this.loadInitialFile();
|
||||
this.loadInitialFile(this.builtinFileURI);
|
||||
}
|
||||
|
||||
private parseTextData(textData: any): MonumentSide[] {
|
||||
|
|
|
@ -18,19 +18,19 @@ export abstract class AppController {
|
|||
const canvas = document.getElementById('pf-canvas') as HTMLCanvasElement;
|
||||
}
|
||||
|
||||
protected loadInitialFile() {
|
||||
protected loadInitialFile(builtinFileURI: string) {
|
||||
const selectFileElement = document.getElementById('pf-select-file') as
|
||||
(HTMLSelectElement | null);
|
||||
if (selectFileElement != null) {
|
||||
const selectedOption = selectFileElement.selectedOptions[0] as HTMLOptionElement;
|
||||
this.fetchFile(selectedOption.value);
|
||||
this.fetchFile(selectedOption.value, builtinFileURI);
|
||||
} else {
|
||||
this.fetchFile(this.defaultFile);
|
||||
this.fetchFile(this.defaultFile, builtinFileURI);
|
||||
}
|
||||
}
|
||||
|
||||
protected fetchFile(file: string) {
|
||||
window.fetch(`${this.builtinFileURI}/${file}`)
|
||||
protected fetchFile(file: string, builtinFileURI: string) {
|
||||
window.fetch(`${builtinFileURI}/${file}`)
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(data => {
|
||||
this.fileData = data;
|
||||
|
@ -47,7 +47,6 @@ export abstract class AppController {
|
|||
protected abstract fileLoaded(): void;
|
||||
|
||||
protected abstract get defaultFile(): string;
|
||||
protected abstract get builtinFileURI(): string;
|
||||
}
|
||||
|
||||
export abstract class DemoAppController<View extends PathfinderDemoView> extends AppController {
|
||||
|
@ -221,11 +220,13 @@ export abstract class DemoAppController<View extends PathfinderDemoView> extends
|
|||
selectFileElement.removeChild(placeholder);
|
||||
|
||||
// Fetch the file.
|
||||
this.fetchFile(selectedOption.value);
|
||||
this.fetchFile(selectedOption.value, this.builtinFileURI);
|
||||
}
|
||||
|
||||
protected abstract createView(): View;
|
||||
|
||||
protected abstract readonly builtinFileURI: string;
|
||||
|
||||
view: Promise<View>;
|
||||
|
||||
protected filePickerElement: HTMLInputElement | null;
|
||||
|
|
|
@ -56,7 +56,7 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
|||
const runBenchmarkButton = unwrapNull(document.getElementById('pf-run-benchmark-button'));
|
||||
runBenchmarkButton.addEventListener('click', () => this.runBenchmark(), false);
|
||||
|
||||
this.loadInitialFile();
|
||||
this.loadInitialFile(this.builtinFileURI);
|
||||
}
|
||||
|
||||
protected fileLoaded(): void {
|
||||
|
|
|
@ -17,6 +17,8 @@ import {B_QUAD_UPPER_RIGHT_VERTEX_OFFSET} from "./meshes";
|
|||
import {B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET, B_QUAD_LOWER_LEFT_VERTEX_OFFSET} from "./meshes";
|
||||
import {B_QUAD_LOWER_RIGHT_VERTEX_OFFSET} from "./meshes";
|
||||
import {B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET, PathfinderMeshData} from "./meshes";
|
||||
import {Partitionable} from "./meshes";
|
||||
import { SVGLoader, BUILTIN_SVG_URI } from './svg-loader';
|
||||
import {BUILTIN_FONT_URI, TextFrameGlyphStorage, PathfinderGlyph, TextRun} from "./text";
|
||||
import {GlyphStorage, TextFrame} from "./text";
|
||||
import {unwrapNull, UINT32_SIZE, UINT32_MAX, assert} from "./utils";
|
||||
|
@ -32,10 +34,19 @@ const POINT_LABEL_FONT: string = "12px sans-serif";
|
|||
const POINT_LABEL_OFFSET: glmatrix.vec2 = glmatrix.vec2.fromValues(12.0, 12.0);
|
||||
const POINT_RADIUS: number = 2.0;
|
||||
|
||||
const BUILTIN_URIS = {
|
||||
font: BUILTIN_FONT_URI,
|
||||
svg: BUILTIN_SVG_URI,
|
||||
};
|
||||
|
||||
type FileType = 'font' | 'svg';
|
||||
|
||||
class MeshDebuggerAppController extends AppController {
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
this.fileType = 'font';
|
||||
|
||||
this.view = new MeshDebuggerView(this);
|
||||
|
||||
this.openModal = unwrapNull(document.getElementById('pf-open-modal'));
|
||||
|
@ -54,7 +65,7 @@ class MeshDebuggerAppController extends AppController {
|
|||
const openOKButton = unwrapNull(document.getElementById('pf-open-ok-button'));
|
||||
openOKButton.addEventListener('click', () => this.loadPath(), false);
|
||||
|
||||
this.loadInitialFile();
|
||||
this.loadInitialFile(BUILTIN_FONT_URI);
|
||||
}
|
||||
|
||||
private showOpenDialog(): void {
|
||||
|
@ -67,57 +78,83 @@ class MeshDebuggerAppController extends AppController {
|
|||
|
||||
this.fontPathSelectGroup.classList.add('pf-display-none');
|
||||
|
||||
if (optionValue.startsWith('font-')) {
|
||||
this.fetchFile(optionValue.substr('font-'.length));
|
||||
} else if (optionValue.startsWith('svg-')) {
|
||||
// TODO(pcwalton)
|
||||
}
|
||||
const results = unwrapNull(/^([a-z]+)-(.*)$/.exec(optionValue));
|
||||
this.fileType = results[1] as FileType;
|
||||
this.fetchFile(results[2], BUILTIN_URIS[this.fileType]);
|
||||
}
|
||||
|
||||
protected fileLoaded(): void {
|
||||
this.font = opentype.parse(this.fileData);
|
||||
assert(this.font.isSupported(), "The font type is unsupported!");
|
||||
|
||||
while (this.fontPathSelect.lastChild != null)
|
||||
this.fontPathSelect.removeChild(this.fontPathSelect.lastChild);
|
||||
|
||||
this.fontPathSelectGroup.classList.remove('pf-display-none');
|
||||
|
||||
const glyphCount = this.font.numGlyphs;
|
||||
if (this.fileType === 'font')
|
||||
this.fontLoaded();
|
||||
else if (this.fileType === 'svg')
|
||||
this.svgLoaded();
|
||||
}
|
||||
|
||||
private fontLoaded(): void {
|
||||
this.file = opentype.parse(this.fileData);
|
||||
assert(this.file.isSupported(), "The font type is unsupported!");
|
||||
|
||||
const glyphCount = this.file.numGlyphs;
|
||||
for (let glyphIndex = 1; glyphIndex < glyphCount; glyphIndex++) {
|
||||
const newOption = document.createElement('option');
|
||||
newOption.value = "" + glyphIndex;
|
||||
const glyphName = this.font.glyphIndexToName(glyphIndex);
|
||||
const glyphName = this.file.glyphIndexToName(glyphIndex);
|
||||
newOption.appendChild(document.createTextNode(glyphName));
|
||||
this.fontPathSelect.appendChild(newOption);
|
||||
}
|
||||
|
||||
// Automatically load a path if this is the initial pageload.
|
||||
if (this.meshes == null)
|
||||
this.loadPath(this.font.charToGlyph(CHARACTER));
|
||||
this.loadPath(this.file.charToGlyph(CHARACTER));
|
||||
}
|
||||
|
||||
private svgLoaded(): void {
|
||||
this.file = new SVGLoader;
|
||||
this.file.loadFile(this.fileData);
|
||||
|
||||
const pathCount = this.file.pathInstances.length;
|
||||
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
|
||||
const newOption = document.createElement('option');
|
||||
newOption.value = "" + pathIndex;
|
||||
newOption.appendChild(document.createTextNode(`Path ${pathIndex}`));
|
||||
this.fontPathSelect.appendChild(newOption);
|
||||
}
|
||||
}
|
||||
|
||||
protected loadPath(opentypeGlyph?: opentype.Glyph | null) {
|
||||
window.jQuery(this.openModal).modal('hide');
|
||||
|
||||
let partitionable: Partitionable | null = null;
|
||||
|
||||
if (this.file instanceof opentype.Font) {
|
||||
if (opentypeGlyph == null) {
|
||||
const glyphIndex = parseInt(this.fontPathSelect.selectedOptions[0].value);
|
||||
opentypeGlyph = this.font.glyphs.get(glyphIndex);
|
||||
opentypeGlyph = this.file.glyphs.get(glyphIndex);
|
||||
}
|
||||
|
||||
const glyph = new MeshDebuggerGlyph(opentypeGlyph);
|
||||
const glyphStorage = new GlyphStorage(this.fileData, [glyph], this.font);
|
||||
partitionable = new GlyphStorage(this.fileData, [glyph], this.file);
|
||||
} else if (this.file instanceof SVGLoader) {
|
||||
partitionable = this.file;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
glyphStorage.partition().then(meshes => {
|
||||
partitionable.partition().then(meshes => {
|
||||
this.meshes = meshes;
|
||||
this.view.attachMeshes();
|
||||
})
|
||||
}
|
||||
|
||||
protected readonly defaultFile: string = FONT;
|
||||
protected readonly builtinFileURI: string = BUILTIN_FONT_URI;
|
||||
|
||||
private font: Font;
|
||||
private file: Font | SVGLoader | null;
|
||||
private fileType: FileType;
|
||||
|
||||
meshes: PathfinderMeshData | null;
|
||||
|
||||
|
|
|
@ -37,6 +37,10 @@ export const B_QUAD_LOWER_INDICES_OFFSET: number = B_QUAD_LOWER_LEFT_VERTEX_OFFS
|
|||
|
||||
type BufferType = 'ARRAY_BUFFER' | 'ELEMENT_ARRAY_BUFFER';
|
||||
|
||||
export interface Partitionable {
|
||||
partition(): Promise<PathfinderMeshData>;
|
||||
}
|
||||
|
||||
export interface Meshes<T> {
|
||||
readonly bQuads: T;
|
||||
readonly bVertexPositions: T;
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
import * as glmatrix from 'gl-matrix';
|
||||
import * as _ from 'lodash';
|
||||
import 'path-data-polyfill.js';
|
||||
|
||||
import {DemoAppController} from './app-controller';
|
||||
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
||||
|
@ -18,7 +17,7 @@ import {OrthographicCamera} from "./camera";
|
|||
import {ECAAStrategy, ECAAMulticolorStrategy} from "./ecaa-strategy";
|
||||
import {PathfinderMeshData} from "./meshes";
|
||||
import {ShaderMap, ShaderProgramSource} from './shader-loader';
|
||||
import {SVGLoader} from './svg-loader';
|
||||
import { SVGLoader, BUILTIN_SVG_URI } from './svg-loader';
|
||||
import {panic, unwrapNull} from './utils';
|
||||
import {PathfinderDemoView, Timings} from './view';
|
||||
import SSAAStrategy from "./ssaa-strategy";
|
||||
|
@ -28,8 +27,6 @@ const parseColor = require('parse-color');
|
|||
|
||||
const SVG_NS: string = "http://www.w3.org/2000/svg";
|
||||
|
||||
const BUILTIN_SVG_URI: string = "/svg/demo";
|
||||
|
||||
const DEFAULT_FILE: string = 'tiger';
|
||||
|
||||
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
||||
|
@ -38,17 +35,6 @@ const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
|||
ecaa: ECAAMulticolorStrategy,
|
||||
};
|
||||
|
||||
declare class SVGPathSegment {
|
||||
type: string;
|
||||
values: number[];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface SVGPathElement {
|
||||
getPathData(settings: any): SVGPathSegment[];
|
||||
}
|
||||
}
|
||||
|
||||
interface AntialiasingStrategyTable {
|
||||
none: typeof NoAAStrategy;
|
||||
ssaa: typeof SSAAStrategy;
|
||||
|
@ -61,11 +47,12 @@ class SVGDemoController extends DemoAppController<SVGDemoView> {
|
|||
|
||||
this.loader = new SVGLoader;
|
||||
|
||||
this.loadInitialFile();
|
||||
this.loadInitialFile(this.builtinFileURI);
|
||||
}
|
||||
|
||||
protected fileLoaded() {
|
||||
this.loader.loadFile(this.fileData).then(meshes => {
|
||||
this.loader.loadFile(this.fileData);
|
||||
this.loader.partition().then(meshes => {
|
||||
this.meshes = meshes;
|
||||
this.meshesReceived();
|
||||
})
|
||||
|
@ -77,9 +64,7 @@ class SVGDemoController extends DemoAppController<SVGDemoView> {
|
|||
unwrapNull(this.shaderSources));
|
||||
}
|
||||
|
||||
protected get builtinFileURI(): string {
|
||||
return BUILTIN_SVG_URI;
|
||||
}
|
||||
protected readonly builtinFileURI: string = BUILTIN_SVG_URI;
|
||||
|
||||
protected get defaultFile(): string {
|
||||
return DEFAULT_FILE;
|
||||
|
|
|
@ -11,37 +11,52 @@
|
|||
import * as glmatrix from 'gl-matrix';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import 'path-data-polyfill.js';
|
||||
import {panic, unwrapNull} from "./utils";
|
||||
import {PathfinderMeshData} from "./meshes";
|
||||
import {PathfinderMeshData, Partitionable} from "./meshes";
|
||||
|
||||
export const BUILTIN_SVG_URI: string = "/svg/demo";
|
||||
|
||||
const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths";
|
||||
|
||||
/// The minimum size of a stroke.
|
||||
const HAIRLINE_STROKE_WIDTH: number = 0.25;
|
||||
|
||||
declare class SVGPathSegment {
|
||||
type: string;
|
||||
values: number[];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface SVGPathElement {
|
||||
getPathData(settings: any): SVGPathSegment[];
|
||||
}
|
||||
}
|
||||
|
||||
export interface PathInstance {
|
||||
element: SVGPathElement;
|
||||
stroke: number | 'fill';
|
||||
}
|
||||
|
||||
export class SVGLoader {
|
||||
export class SVGLoader implements Partitionable {
|
||||
constructor() {
|
||||
this.svg = unwrapNull(document.getElementById('pf-svg')) as Element as SVGSVGElement;
|
||||
this.pathInstances = [];
|
||||
this.paths = [];
|
||||
this.bounds = glmatrix.vec4.create();
|
||||
}
|
||||
|
||||
loadFile(fileData: ArrayBuffer): Promise<PathfinderMeshData> {
|
||||
loadFile(fileData: ArrayBuffer) {
|
||||
this.fileData = fileData;
|
||||
|
||||
const decoder = new (window as any).TextDecoder('utf-8');
|
||||
const fileStringData = decoder.decode(new DataView(this.fileData));
|
||||
const svgDocument = (new DOMParser).parseFromString(fileStringData, 'image/svg+xml');
|
||||
const svgElement = svgDocument.documentElement as Element as SVGSVGElement;
|
||||
return this.attachSVG(svgElement);
|
||||
this.attachSVG(svgElement);
|
||||
}
|
||||
|
||||
private attachSVG(svgElement: SVGSVGElement): Promise<PathfinderMeshData> {
|
||||
private attachSVG(svgElement: SVGSVGElement) {
|
||||
// Clear out the current document.
|
||||
let kid;
|
||||
while ((kid = this.svg.firstChild) != null)
|
||||
|
@ -76,8 +91,8 @@ export class SVGLoader {
|
|||
}
|
||||
}
|
||||
|
||||
const request: any = { paths: [] };
|
||||
let minX = 0, minY = 0, maxX = 0, maxY = 0;
|
||||
this.paths = [];
|
||||
|
||||
// Extract, normalize, and transform the path data.
|
||||
for (const instance of this.pathInstances) {
|
||||
|
@ -112,16 +127,18 @@ export class SVGLoader {
|
|||
else
|
||||
kind = { Stroke: Math.max(HAIRLINE_STROKE_WIDTH, instance.stroke) };
|
||||
|
||||
request.paths.push({ segments: segments, kind: kind });
|
||||
this.paths.push({ segments: segments, kind: kind });
|
||||
}
|
||||
|
||||
this.bounds = glmatrix.vec4.clone([minX, minY, maxX, maxY]);
|
||||
}
|
||||
|
||||
partition(): Promise<PathfinderMeshData> {
|
||||
// Make the request.
|
||||
return window.fetch(PARTITION_SVG_PATHS_ENDPOINT_URL, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(request),
|
||||
body: JSON.stringify({ paths: this.paths }),
|
||||
}).then(response => response.text()).then(responseText => {
|
||||
const response = JSON.parse(responseText);
|
||||
if (!('Ok' in response))
|
||||
|
@ -135,5 +152,6 @@ export class SVGLoader {
|
|||
private fileData: ArrayBuffer;
|
||||
|
||||
pathInstances: PathInstance[];
|
||||
private paths: any[];
|
||||
bounds: glmatrix.vec4;
|
||||
}
|
||||
|
|
|
@ -142,7 +142,7 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
|||
const editTextOkButton = unwrapNull(document.getElementById('pf-edit-text-ok-button'));
|
||||
editTextOkButton.addEventListener('click', () => this.updateText(), false);
|
||||
|
||||
this.loadInitialFile();
|
||||
this.loadInitialFile(this.builtinFileURI);
|
||||
}
|
||||
|
||||
showTextEditor() {
|
||||
|
|
Loading…
Reference in New Issue