diff --git a/demo/.gitignore b/demo/.gitignore
deleted file mode 100644
index a56a7ef4..00000000
--- a/demo/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-node_modules
-
diff --git a/demo/client/build/rustdoc-webpack-plugin/index.js b/demo/client/build/rustdoc-webpack-plugin/index.js
deleted file mode 100644
index a34ed592..00000000
--- a/demo/client/build/rustdoc-webpack-plugin/index.js
+++ /dev/null
@@ -1,46 +0,0 @@
-const {spawn} = require('child_process');
-const process = require('process');
-
-class RustdocPlugin {
- constructor(options) {
- this.options = Object.assign({directories: [], flags: {}}, options);
- }
-
- apply(compiler) {
- let rustdocFlags = [];
- for (let key in this.options.flags) {
- if (this.options.flags.hasOwnProperty(key))
- rustdocFlags.push("--" + key + "=" + this.options.flags[key]);
- }
- rustdocFlags = rustdocFlags.join(" ");
-
- compiler.plugin('after-compile', (compilation, done) => {
- let directoriesLeft = this.options.directories.length;
- for (const directory of this.options.directories) {
- console.log("Building documentation for `" + directory + "`...");
- const cargo = spawn("cargo", ["doc", "--no-deps"], {
- cwd: directory,
- env: Object.assign({RUSTDOCFLAGS: rustdocFlags}, process.env),
- });
- cargo.stdout.setEncoding('utf8');
- cargo.stderr.setEncoding('utf8');
- cargo.stdout.on('data', data => console.log(data));
- cargo.stderr.on('data', data => console.log(data));
- cargo.on('close', code => {
- if (code !== 0) {
- const message = "Failed to build documentation for `" + directory + "`!";
- console.error(message);
- throw new Error(message);
- }
-
- console.log("Built documentation for `" + directory + "`.");
- directoriesLeft--;
- if (directoriesLeft === 0)
- done();
- });
- }
- });
- }
-}
-
-module.exports = RustdocPlugin;
diff --git a/demo/client/build/rustdoc-webpack-plugin/package.json b/demo/client/build/rustdoc-webpack-plugin/package.json
deleted file mode 100644
index ee391a28..00000000
--- a/demo/client/build/rustdoc-webpack-plugin/package.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "rustdoc-webpack-plugin",
- "version": "0.1.0",
- "description": "Webpack plugin for building Rust package documentation",
- "main": "index.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "author": "Patrick Walton",
- "license": "MIT OR Apache-2.0"
-}
diff --git a/demo/client/css/pathfinder-doc.css b/demo/client/css/pathfinder-doc.css
deleted file mode 100644
index 9c83cf19..00000000
--- a/demo/client/css/pathfinder-doc.css
+++ /dev/null
@@ -1,43 +0,0 @@
-body {
- padding: 0 !important;
-}
-h1,
-h2,
-h3
-h4,
-.sidebar,
-a.source,
-.search-input,
-.content table :not(code) > a,
-.collapse-toggle,
-ul.item-list > li > .out-of-band {
- font-family: inherit !important;
-}
-.content {
- padding: 0 15px 0 0 !important;
-}
-nav.navbar {
- max-width: inherit !important;
- border: none !important;
- margin: 0 !important;
-}
-nav.sidebar {
- top: auto !important;
-}
-img {
- max-width: inherit !important;
-}
-h4 > span.invisible,
-.structfield > span.invisible,
-.variant > span.invisible {
- visibility: visible !important;
-}
-
-/*
- * Hide `generate_gamma_lut` and `pathfinder_server`.
- * FIXME(pcwalton): Is there a better way to do this?
- */
-.sidebar-elems > .block.crate > ul > li:first-child,
-.sidebar-elems > .block.crate > ul > li:last-child {
- display: none;
-}
diff --git a/demo/client/css/pathfinder.css b/demo/client/css/pathfinder.css
deleted file mode 100644
index 751b962a..00000000
--- a/demo/client/css/pathfinder.css
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * Fonts
- */
-
-@font-face {
- font-family: "Inter UI";
- font-weight: 400;
- src: url("/woff2/inter-ui/Inter-UI-Regular.woff2") format('woff2');
-}
-
-@font-face {
- font-family: "Inter UI";
- font-weight: 700;
- src: url("/woff2/inter-ui/Inter-UI-Bold.woff2") format('woff2');
-}
-
-@font-face {
- font-family: "Inter UI";
- font-weight: 400;
- font-style: italic;
- src: url("/woff2/inter-ui/Inter-UI-RegularItalic.woff2") format('woff2');
-}
-
-@font-face {
- font-family: "Inter UI";
- font-weight: 700;
- font-style: italic;
- src: url("/woff2/inter-ui/Inter-UI-BoldItalic.woff2") format('woff2');
-}
-
-@font-face {
- font-family: "Material Icons";
- font-style: normal;
- font-weight: 400;
- src: local('Material Icons'),
- local('MaterialIcons-Regular'),
- url("/woff2/material-icons/MaterialIcons-Regular.woff2") format('woff2');
-}
-
-/*
- * Core
- */
-
-body {
- font-family: "Inter UI", sans-serif;
-}
-body.pf-unscrollable {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
-}
-nav {
- -moz-user-select: none;
- user-select: none;
-}
-#pf-load-font-button-label,
-#pf-load-svg-button-label {
- left: 1em;
- margin: 0;
-}
-#pf-settings-container, #pf-rotate-slider-container {
- position: absolute;
- right: 1rem;
- bottom: 2rem;
- pointer-events: none;
- text-align: right;
-}
-#pf-toolbar {
- margin-right: 1rem;
-}
-#pf-settings, #pf-rotate-slider-card {
- text-align: initial;
- user-select: none;
- -moz-user-select: none;
- opacity: 1.0;
- transition: opacity 300ms, transform 300ms, visibility 300ms;
- transform: translateY(0);
-}
-#pf-settings:not(.pf-invisible), #pf-rotate-slider-card:not(.pf-invisible) {
- pointer-events: auto;
-}
-.pf-toolbar-button {
- pointer-events: auto;
-}
-#pf-test-pane {
- pointer-events: auto;
-}
-.pf-toolbar-button.btn-outline-secondary:not(:hover) {
- background: rgba(255, 255, 255, 0.75);
-}
-#pf-file-select {
- position: absolute;
- visibility: hidden;
-}
-#pf-settings.pf-invisible, #pf-rotate-slider-card.pf-invisible {
- opacity: 0.0;
- transform: translateY(1em);
- visibility: hidden;
-}
-button > svg {
- width: 1.25em;
- display: block;
-}
-button > svg path {
- fill: currentColor;
-}
-.pf-pointer-events-none {
- pointer-events: none;
-}
-.pf-pointer-events-auto {
- pointer-events: auto;
-}
-#pf-rendering-options-group {
- right: 1em;
-}
-.pf-maximized-canvas {
- display: block;
- position: absolute;
- touch-action: none;
-}
-#pf-canvas.pf-draggable {
- cursor: grab;
- cursor: -webkit-grab;
-}
-#pf-canvas.pf-draggable.pf-grabbing {
- cursor: grabbing;
- cursor: -webkit-grabbing;
-}
-#pf-fps-label {
- color: white;
- background: rgba(0, 0, 0, 0.75);
- width: 17em;
-}
-#pf-svg, #pf-svg * {
- visibility: hidden !important;
-}
-#pf-outer-container {
- display: flex;
- flex-direction: column;
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
-}
-#pf-inner-container {
- overflow: scroll;
-}
-.pf-spinner {
- position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
-}
-.pf-spinner-hidden {
- display: none !important;
-}
-.pf-display-none {
- display: none !important;
-}
-#pf-benchmark-results-modal .modal-body {
- overflow: scroll;
- max-height: 75vh;
-}
-.pf-benchmark-results-global {
- padding: 0.75rem;
-}
-.pf-benchmark-results-label {
- font-weight: bold;
-}
-.pf-material-icons {
- font-family: "Material Icons";
- display: block;
- font-size: 18px;
- margin: -3px 0 -3px;
-}
-#pf-rotate-slider {
- margin: 2px 0 0;
-}
-label.pf-disabled {
- color: gray;
-}
-.btn-group input[type="radio"] {
- -moz-appearance: none;
-}
-
-/*
- * Arrow
- *
- * http://www.cssarrowplease.com/
- */
-
-#pf-settings > .pf-arrow-box:after, #pf-settings .pf-arrow-box:before {
- right: 22px;
-}
-
-#pf-rotate-slider-card > .pf-arrow-box:after, #pf-rotate-slider-card .pf-arrow-box:before {
- right: 166px;
-}
-
-.pf-arrow-box:after, .pf-arrow-box:before {
- top: 100%;
- border: solid transparent;
- content: " ";
- height: 0;
- width: 0;
- position: absolute;
- pointer-events: none;
-}
-.pf-arrow-box:after {
- border-color: rgba(0, 0, 0, 0);
- border-top-color: white;
- border-width: 14px;
- margin-right: -14px;
-}
-.pf-arrow-box:before {
- border-color: rgba(0, 0, 0, 0);
- border-top-color: rgba(0, 0, 0, 0.125);
- border-width: 15px;
- margin-right: -15px;
-}
-
-#pf-masthead-logo {
- width: 30vw;
-}
-
-.github-corner {
- display: block;
- position: absolute;
- right: 0;
- top: 0;
- height: 100%;
- text-align: right;
-}
-.github-corner > svg {
- fill: #151513;
- color: #fff;
- height: 100%;
- width: auto;
-}
-.github-corner:hover .octo-arm {
- animation: octocat-wave 560ms ease-in-out;
-}
-@keyframes octocat-wave{
- 0%, 100% {
- transform: rotate(0);
- }
- 20%, 60% {
- transform: rotate(-25deg);
- }
- 40%, 80% {
- transform: rotate(10deg);
- }
-}
-@media (max-width: 500px) {
- .github-corner:hover .octo-arm {
- animation: none;
- }
- .github-corner .octo-arm {
- animation: octocat-wave 560ms ease-in-out;
- }
-}
-
-/*
- * Spinner
- *
- * http://tobiasahlin.com/spinkit/
- */
-
-.sk-fading-circle {
- margin: auto;
- width: 40px;
- height: 40px;
-}
-
-.sk-fading-circle .sk-circle {
- width: 100%;
- height: 100%;
- position: absolute;
- left: 0;
- top: 0;
-}
-
-.sk-fading-circle .sk-circle:before {
- content: '';
- display: block;
- margin: 0 auto;
- width: 15%;
- height: 15%;
- background-color: #333;
- border-radius: 100%;
- -webkit-animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;
- animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;
-}
-.sk-fading-circle .sk-circle2 {
- -webkit-transform: rotate(30deg);
- -ms-transform: rotate(30deg);
- transform: rotate(30deg);
-}
-.sk-fading-circle .sk-circle3 {
- -webkit-transform: rotate(60deg);
- -ms-transform: rotate(60deg);
- transform: rotate(60deg);
-}
-.sk-fading-circle .sk-circle4 {
- -webkit-transform: rotate(90deg);
- -ms-transform: rotate(90deg);
- transform: rotate(90deg);
-}
-.sk-fading-circle .sk-circle5 {
- -webkit-transform: rotate(120deg);
- -ms-transform: rotate(120deg);
- transform: rotate(120deg);
-}
-.sk-fading-circle .sk-circle6 {
- -webkit-transform: rotate(150deg);
- -ms-transform: rotate(150deg);
- transform: rotate(150deg);
-}
-.sk-fading-circle .sk-circle7 {
- -webkit-transform: rotate(180deg);
- -ms-transform: rotate(180deg);
- transform: rotate(180deg);
-}
-.sk-fading-circle .sk-circle8 {
- -webkit-transform: rotate(210deg);
- -ms-transform: rotate(210deg);
- transform: rotate(210deg);
-}
-.sk-fading-circle .sk-circle9 {
- -webkit-transform: rotate(240deg);
- -ms-transform: rotate(240deg);
- transform: rotate(240deg);
-}
-.sk-fading-circle .sk-circle10 {
- -webkit-transform: rotate(270deg);
- -ms-transform: rotate(270deg);
- transform: rotate(270deg);
-}
-.sk-fading-circle .sk-circle11 {
- -webkit-transform: rotate(300deg);
- -ms-transform: rotate(300deg);
- transform: rotate(300deg);
-}
-.sk-fading-circle .sk-circle12 {
- -webkit-transform: rotate(330deg);
- -ms-transform: rotate(330deg);
- transform: rotate(330deg);
-}
-.sk-fading-circle .sk-circle2:before {
- -webkit-animation-delay: -1.1s;
- animation-delay: -1.1s;
-}
-.sk-fading-circle .sk-circle3:before {
- -webkit-animation-delay: -1s;
- animation-delay: -1s;
-}
-.sk-fading-circle .sk-circle4:before {
- -webkit-animation-delay: -0.9s;
- animation-delay: -0.9s;
-}
-.sk-fading-circle .sk-circle5:before {
- -webkit-animation-delay: -0.8s;
- animation-delay: -0.8s;
-}
-.sk-fading-circle .sk-circle6:before {
- -webkit-animation-delay: -0.7s;
- animation-delay: -0.7s;
-}
-.sk-fading-circle .sk-circle7:before {
- -webkit-animation-delay: -0.6s;
- animation-delay: -0.6s;
-}
-.sk-fading-circle .sk-circle8:before {
- -webkit-animation-delay: -0.5s;
- animation-delay: -0.5s;
-}
-.sk-fading-circle .sk-circle9:before {
- -webkit-animation-delay: -0.4s;
- animation-delay: -0.4s;
-}
-.sk-fading-circle .sk-circle10:before {
- -webkit-animation-delay: -0.3s;
- animation-delay: -0.3s;
-}
-.sk-fading-circle .sk-circle11:before {
- -webkit-animation-delay: -0.2s;
- animation-delay: -0.2s;
-}
-.sk-fading-circle .sk-circle12:before {
- -webkit-animation-delay: -0.1s;
- animation-delay: -0.1s;
-}
-
-@-webkit-keyframes sk-circleFadeDelay {
- 0%, 39%, 100% { opacity: 0; }
- 40% { opacity: 1; }
-}
-
-@keyframes sk-circleFadeDelay {
- 0%, 39%, 100% { opacity: 0; }
- 40% { opacity: 1; }
-}
diff --git a/demo/client/html/3d-demo.html.hbs b/demo/client/html/3d-demo.html.hbs
deleted file mode 100644
index 7966ff65..00000000
--- a/demo/client/html/3d-demo.html.hbs
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
- 3D Demo — Pathfinder
-
- {{>partials/header.html}}
-
-
-
- {{>partials/navbar.html isDemo=true}}
-
-
- {{>partials/spinner.html}}
-
-
diff --git a/demo/client/html/benchmark.html.hbs b/demo/client/html/benchmark.html.hbs
deleted file mode 100644
index bd5dab81..00000000
--- a/demo/client/html/benchmark.html.hbs
+++ /dev/null
@@ -1,122 +0,0 @@
-
-
-
- Benchmark — Pathfinder
-
- {{>partials/header.html}}
-
-
-
- {{>partials/navbar.html isTool=true}}
-
-
-
-
-
-
-
-
-
- Partitioning:
-
- 0 µs/glyph
-
-
-
-
-
-
-
-
-
-
diff --git a/demo/client/html/doc-before-content.html.hbs b/demo/client/html/doc-before-content.html.hbs
deleted file mode 100644
index b79d2080..00000000
--- a/demo/client/html/doc-before-content.html.hbs
+++ /dev/null
@@ -1 +0,0 @@
-{{>partials/navbar.html isDoc=true}}
diff --git a/demo/client/html/doc-header.html.hbs b/demo/client/html/doc-header.html.hbs
deleted file mode 100644
index bc514e4d..00000000
--- a/demo/client/html/doc-header.html.hbs
+++ /dev/null
@@ -1,2 +0,0 @@
-{{>partials/header.html}}
-
diff --git a/demo/client/html/index.html.hbs b/demo/client/html/index.html.hbs
deleted file mode 100644
index ad870752..00000000
--- a/demo/client/html/index.html.hbs
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
- Pathfinder
-
- {{>partials/header.html}}
-
-
- {{>partials/navbar.html}}
-
-
-
-
-
Pathfinder
-
- A fast, high-quality, open source text and vector graphics renderer for
- GPUs.
-
-
-
-
-
-
diff --git a/demo/client/html/mesh-debugger.html.hbs b/demo/client/html/mesh-debugger.html.hbs
deleted file mode 100644
index cc32bd5d..00000000
--- a/demo/client/html/mesh-debugger.html.hbs
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
- Mesh Debugger — Pathfinder
-
- {{>partials/header.html}}
-
-
-
- {{>partials/navbar.html isTool=true}}
-
-
-
-
- {{>partials/spinner.html}}
-
-
diff --git a/demo/client/html/partials/header.html.hbs b/demo/client/html/partials/header.html.hbs
deleted file mode 100644
index e757f451..00000000
--- a/demo/client/html/partials/header.html.hbs
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/demo/client/html/partials/navbar.html.hbs b/demo/client/html/partials/navbar.html.hbs
deleted file mode 100644
index c7a10051..00000000
--- a/demo/client/html/partials/navbar.html.hbs
+++ /dev/null
@@ -1,42 +0,0 @@
-
diff --git a/demo/client/html/partials/rotate.html.hbs b/demo/client/html/partials/rotate.html.hbs
deleted file mode 100644
index 2b09ec10..00000000
--- a/demo/client/html/partials/rotate.html.hbs
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
diff --git a/demo/client/html/partials/spinner.html.hbs b/demo/client/html/partials/spinner.html.hbs
deleted file mode 100644
index 6ea99543..00000000
--- a/demo/client/html/partials/spinner.html.hbs
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/demo/client/html/partials/switch.html.hbs b/demo/client/html/partials/switch.html.hbs
deleted file mode 100644
index 9dbd24ec..00000000
--- a/demo/client/html/partials/switch.html.hbs
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
diff --git a/demo/client/html/reference-test.html.hbs b/demo/client/html/reference-test.html.hbs
deleted file mode 100644
index b1a444d2..00000000
--- a/demo/client/html/reference-test.html.hbs
+++ /dev/null
@@ -1,188 +0,0 @@
-
-
-
- Reference Test — Pathfinder
-
- {{>partials/header.html}}
-
-
-
-
-
-
- {{>partials/navbar.html isTool=true}}
-
-
-
-
-
diff --git a/demo/client/html/svg-demo.html.hbs b/demo/client/html/svg-demo.html.hbs
deleted file mode 100644
index 4963ce79..00000000
--- a/demo/client/html/svg-demo.html.hbs
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
- SVG Demo — Pathfinder
-
- {{>partials/header.html}}
-
-
-
- {{>partials/navbar.html isDemo=true}}
-
-
-
- {{>partials/spinner.html}}
-
-
diff --git a/demo/client/html/text-demo.html.hbs b/demo/client/html/text-demo.html.hbs
deleted file mode 100644
index e1ce6155..00000000
--- a/demo/client/html/text-demo.html.hbs
+++ /dev/null
@@ -1,133 +0,0 @@
-
-
-
- Text Demo — Pathfinder
-
- {{>partials/header.html}}
-
-
-
- {{>partials/navbar.html isDemo=true}}
-
-
-
- {{>partials/spinner.html}}
-
-
diff --git a/demo/client/package.json b/demo/client/package.json
deleted file mode 100644
index 00f2d33b..00000000
--- a/demo/client/package.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
- "name": "pathfinder-demo",
- "version": "0.1.0",
- "description": "Demo for Pathfinder 2",
- "scripts": {
- "build": "webpack",
- "build-watch": "webpack --watch",
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "author": "Patrick Walton ",
- "license": "MIT OR Apache-2.0",
- "dependencies": {
- "@types/base64-js": "^1.2.5",
- "@types/gl-matrix": "^2.4.0",
- "@types/lodash": "^4.14.104",
- "@types/node": "^8.9.4",
- "@types/opentype.js": "0.0.0",
- "@types/papaparse": "^4.1.33",
- "@types/webgl-ext": "0.0.29",
- "base64-js": "^1.2.3",
- "bootstrap": "^4.0.0",
- "gl-matrix": "^2.4.0",
- "handlebars": "^4.0.11",
- "handlebars-loader": "^1.6.0",
- "handlebars-webpack-plugin": "^1.3.2",
- "html-loader": "^0.5.5",
- "image-ssim": "^0.2.0",
- "jquery": "^3.3.1",
- "lodash": "^4.17.5",
- "opentype.js": "^0.7.3",
- "papaparse": "^4.3.7",
- "parse-color": "^1.0.0",
- "path-data-polyfill.js": "^1.0.2",
- "popper.js": "^1.13.0",
- "rustdoc-webpack-plugin": "file:./build/rustdoc-webpack-plugin",
- "ts-loader": "^2.3.7",
- "typescript": "^2.7.2"
- },
- "devDependencies": {
- "tslint": "^5.9.1",
- "tslint-loader": "^3.5.3",
- "webpack": "^3.11.0"
- }
-}
diff --git a/demo/client/src/3d-demo.ts b/demo/client/src/3d-demo.ts
deleted file mode 100644
index 684c72f0..00000000
--- a/demo/client/src/3d-demo.ts
+++ /dev/null
@@ -1,1143 +0,0 @@
-// pathfinder/client/src/3d-demo.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-import * as _ from "lodash";
-import * as opentype from "opentype.js";
-
-import {mat4, vec2} from "gl-matrix";
-import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
-import {SubpixelAAType} from "./aa-strategy";
-import {DemoAppController} from "./app-controller";
-import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey} from './atlas';
-import PathfinderBufferTexture from "./buffer-texture";
-import {CameraView, PerspectiveCamera} from "./camera";
-import {UniformMap} from './gl-utils';
-import {PathfinderMeshPack, PathfinderPackedMeshes} from "./meshes";
-import {PathTransformBuffers, Renderer} from './renderer';
-import {ShaderMap, ShaderProgramSource} from "./shader-loader";
-import SSAAStrategy from "./ssaa-strategy";
-import {BUILTIN_FONT_URI, ExpandedMeshData} from "./text";
-import {calculatePixelRectForGlyph, GlyphStore, Hint, PathfinderFont} from "./text";
-import {SimpleTextLayout, TextFrame, TextRun, UnitMetrics} from "./text";
-import {TextRenderContext, TextRenderer} from './text-renderer';
-import {assert, FLOAT32_SIZE, panic, PathfinderError, Range, UINT16_SIZE} from "./utils";
-import {unwrapNull} from "./utils";
-import {DemoView, Timings} from "./view";
-
-const TEXT_AVAILABLE_WIDTH: number = 150000;
-const TEXT_PADDING: number = 2000;
-
-const TEXT_SCALE: glmatrix.vec3 = glmatrix.vec3.fromValues(1.0 / 200.0, 1.0 / 200.0, 1.0 / 200.0);
-
-const TEXT_DATA_URI: string = "/data/mozmonument.json";
-
-const FONT: string = 'open-sans';
-
-const PIXELS_PER_UNIT: number = 1.0;
-
-const FOV: number = 45.0;
-const NEAR_CLIP_PLANE: number = 0.1;
-const FAR_CLIP_PLANE: number = 100000.0;
-
-const ATLAS_FONT_SIZE: number = 48;
-
-const MAX_DISTANCE: number = 200.0;
-
-const TEXT_TRANSLATION: number[] = [
- -TEXT_AVAILABLE_WIDTH * 0.5,
- 0.0,
- TEXT_AVAILABLE_WIDTH * 0.5 + TEXT_PADDING,
-];
-
-const MONUMENT_TRANSLATION: glmatrix.vec3 = glmatrix.vec3.fromValues(0.0, -690.0, 0.0);
-const MONUMENT_SCALE: glmatrix.vec3 =
- glmatrix.vec3.fromValues((TEXT_AVAILABLE_WIDTH * 0.5 + TEXT_PADDING) * TEXT_SCALE[0],
- 700.0,
- (TEXT_AVAILABLE_WIDTH * 0.5 + TEXT_PADDING) * TEXT_SCALE[2]);
-
-const TEXT_COLOR: Uint8Array = new Uint8Array([0xf2, 0xf8, 0xf8, 0xff]);
-
-const AMBIENT_COLOR: glmatrix.vec3 = glmatrix.vec3.clone([0.063, 0.063, 0.063]);
-const DIFFUSE_COLOR: glmatrix.vec3 = glmatrix.vec3.clone([0.356, 0.264, 0.136]);
-const SPECULAR_COLOR: glmatrix.vec3 = glmatrix.vec3.clone([0.490, 0.420, 0.324]);
-
-const MONUMENT_SHININESS: number = 32.0;
-
-const CUBE_VERTEX_POSITIONS: Float32Array = new Float32Array([
- -1.0, -1.0, -1.0, // 0
- 1.0, -1.0, -1.0, // 1
- -1.0, -1.0, 1.0, // 2
- 1.0, -1.0, 1.0, // 3
- -1.0, 1.0, -1.0, // 4
- 1.0, 1.0, -1.0, // 5
- -1.0, 1.0, 1.0, // 6
- 1.0, 1.0, 1.0, // 7
-]);
-
-const CUBE_INDICES: Uint16Array = new Uint16Array([
- 0, 1, 2, 2, 1, 3, // bottom
- 0, 5, 1, 0, 4, 5, // front
- 2, 4, 0, 2, 6, 4, // left
- 3, 5, 1, 3, 7, 5, // right
- 2, 7, 3, 2, 6, 7, // back
- 4, 5, 6, 6, 5, 7, // top
-]);
-
-const MONUMENT_NORMALS: glmatrix.vec4[] = [
- glmatrix.vec4.clone([ 0.0, -1.0, 0.0, 1.0]),
- glmatrix.vec4.clone([ 0.0, 0.0, -1.0, 1.0]),
- glmatrix.vec4.clone([-1.0, 0.0, 0.0, 1.0]),
- glmatrix.vec4.clone([ 1.0, 0.0, 0.0, 1.0]),
- glmatrix.vec4.clone([ 0.0, 0.0, 1.0, 1.0]),
- glmatrix.vec4.clone([ 0.0, 1.0, 0.0, 1.0]),
-];
-
-const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
- none: NoAAStrategy,
- ssaa: SSAAStrategy,
-};
-
-interface AntialiasingStrategyTable {
- none: typeof NoAAStrategy;
- ssaa: typeof SSAAStrategy;
-}
-
-interface TextLine {
- names: string[];
-}
-
-interface MonumentSide {
- lines: TextLine[];
-}
-
-interface MeshDescriptor {
- glyphID: number;
- textFrameIndex: number;
- positions: glmatrix.vec2[];
-}
-
-class ThreeDController extends DemoAppController {
- font!: PathfinderFont;
- textFrames!: TextFrame[];
- glyphStore!: GlyphStore;
- meshDescriptors!: MeshDescriptor[];
-
- atlasGlyphs!: AtlasGlyph[];
- atlas!: Atlas;
-
- baseMeshes!: PathfinderMeshPack;
- private expandedMeshes!: PathfinderPackedMeshes[];
-
- private monumentPromise!: Promise;
-
- start() {
- super.start();
-
- this.atlas = new Atlas;
-
- this.monumentPromise = window.fetch(TEXT_DATA_URI)
- .then(response => response.json())
- .then(textData => this.parseTextData(textData));
-
- this.loadInitialFile(this.builtinFileURI);
- }
-
- protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
- this.font = new PathfinderFont(fileData, builtinName);
- this.monumentPromise.then(monument => this.layoutMonument(fileData, monument));
- }
-
- protected createView(areaLUT: HTMLImageElement,
- gammaLUT: HTMLImageElement,
- commonShaderSource: string,
- shaderSources: ShaderMap):
- ThreeDView {
- return new ThreeDView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
- }
-
- protected get builtinFileURI(): string {
- return BUILTIN_FONT_URI;
- }
-
- protected get defaultFile(): string {
- return FONT;
- }
-
- private parseTextData(textData: any): MonumentSide[] {
- const sides = [];
- for (let sideIndex = 0; sideIndex < 4; sideIndex++)
- sides[sideIndex] = { upper: { lines: [] }, lower: { lines: [] } };
-
- for (const nameData of textData.monument) {
- const side = parseInt(nameData.side, 10) - 1;
- const row = parseInt(nameData.row, 10) - 1;
- const index = parseInt(nameData.number, 10) - 1;
-
- if (sides[side] == null)
- continue;
-
- const lines: TextLine[] = sides[side][nameData.panel as ('upper' | 'lower')].lines;
- if (lines[row] == null)
- lines[row] = { names: [] };
-
- lines[row].names[index] = nameData.name;
- }
-
- return sides.map(side => ({ lines: side.upper.lines.concat(side.lower.lines) }));
- }
-
- private layoutMonument(fileData: ArrayBuffer, monument: MonumentSide[]) {
- this.textFrames = [];
- let glyphsNeeded: number[] = [];
-
- for (const monumentSide of monument) {
- const textRuns = [];
- for (let lineNumber = 0; lineNumber < monumentSide.lines.length; lineNumber++) {
- const line = monumentSide.lines[lineNumber];
-
- const lineY = -lineNumber * this.font.opentypeFont.lineHeight();
- const lineGlyphs = line.names.map(name => {
- const glyphs = this.font.opentypeFont.stringToGlyphs(name);
- const glyphIDs = glyphs.map(glyph => (glyph as any).index);
- const width = _.sumBy(glyphs, glyph => glyph.advanceWidth);
- return { glyphs: glyphIDs, width: width };
- });
-
- const usedSpace = _.sumBy(lineGlyphs, 'width');
- const emptySpace = Math.max(TEXT_AVAILABLE_WIDTH - usedSpace, 0.0);
- const spacing = emptySpace / Math.max(lineGlyphs.length - 1, 1);
-
- let currentX = 0.0;
- for (const glyphInfo of lineGlyphs) {
- const textRunOrigin = [currentX, lineY];
- const textRun = new TextRun(glyphInfo.glyphs, textRunOrigin, this.font);
- textRun.layout();
- textRuns.push(textRun);
- currentX += glyphInfo.width + spacing;
- }
- }
-
- const textFrame = new TextFrame(textRuns, this.font);
- this.textFrames.push(textFrame);
- glyphsNeeded.push(...textFrame.allGlyphIDs);
- }
-
- glyphsNeeded.sort((a, b) => a - b);
- glyphsNeeded = _.sortedUniq(glyphsNeeded);
-
- this.glyphStore = new GlyphStore(this.font, glyphsNeeded);
- this.glyphStore.partition().then(result => {
- // Build the atlas glyphs needed.
- this.atlasGlyphs = [];
- for (const glyphID of glyphsNeeded) {
- const glyphKey = new GlyphKey(glyphID, null);
- const glyphStoreIndex = this.glyphStore.indexOfGlyphWithID(glyphID);
- if (glyphStoreIndex != null)
- this.atlasGlyphs.push(new AtlasGlyph(glyphStoreIndex, glyphKey));
- }
-
- const hint = new Hint(this.glyphStore.font, PIXELS_PER_UNIT, false);
-
- this.baseMeshes = result.meshes;
-
- this.meshDescriptors = [];
-
- for (let textFrameIndex = 0;
- textFrameIndex < this.textFrames.length;
- textFrameIndex++) {
- const textFrame = this.textFrames[textFrameIndex];
- const textBounds = textFrame.bounds;
-
- let glyphDescriptors = [];
- for (const run of textFrame.runs) {
- for (let glyphIndex = 0; glyphIndex < run.glyphIDs.length; glyphIndex++) {
- glyphDescriptors.push({
- glyphID: run.glyphIDs[glyphIndex],
- position: run.calculatePixelOriginForGlyphAt(glyphIndex,
- PIXELS_PER_UNIT,
- 0.0,
- hint,
- textBounds),
- });
- }
- }
-
- glyphDescriptors = _.sortBy(glyphDescriptors, descriptor => descriptor.glyphID);
-
- let currentMeshDescriptor: (MeshDescriptor | null) = null;
- for (const glyphDescriptor of glyphDescriptors) {
- if (currentMeshDescriptor == null ||
- glyphDescriptor.glyphID !== currentMeshDescriptor.glyphID) {
- if (currentMeshDescriptor != null)
- this.meshDescriptors.push(currentMeshDescriptor);
- currentMeshDescriptor = {
- glyphID: glyphDescriptor.glyphID,
- positions: [],
- textFrameIndex: textFrameIndex,
- };
- }
- currentMeshDescriptor.positions.push(glyphDescriptor.position);
- }
- if (currentMeshDescriptor != null)
- this.meshDescriptors.push(currentMeshDescriptor);
- }
-
- this.expandedMeshes = this.meshDescriptors.map(meshDescriptor => {
- const glyphIndex = _.sortedIndexOf(glyphsNeeded, meshDescriptor.glyphID);
- return new PathfinderPackedMeshes(this.baseMeshes, [glyphIndex + 1]);
- });
-
- this.view.then(view => view.attachMeshes(this.expandedMeshes));
- });
- }
-}
-
-class ThreeDView extends DemoView implements TextRenderContext {
- cameraView: CameraView;
- get atlas(): Atlas {
- return this.appController.atlas;
- }
-
- get atlasGlyphs(): AtlasGlyph[] {
- return this.appController.atlasGlyphs;
- }
-
- set atlasGlyphs(newAtlasGlyphs: AtlasGlyph[]) {
- this.appController.atlasGlyphs = newAtlasGlyphs;
- }
-
- get glyphStore(): GlyphStore {
- return this.appController.glyphStore;
- }
-
- get font(): PathfinderFont {
- return this.appController.font;
- }
-
- get fontSize(): number {
- return ATLAS_FONT_SIZE;
- }
-
- get useHinting(): boolean {
- return false;
- }
-
- get atlasPixelsPerUnit(): number {
- return ATLAS_FONT_SIZE / this.font.opentypeFont.unitsPerEm;
- }
-
- renderer: ThreeDRenderer;
-
- appController: ThreeDController;
-
- private vrDisplay: VRDisplay | null;
- private vrFrameData: VRFrameData | null;
- private inVRRAF: boolean;
-
- protected get camera(): PerspectiveCamera {
- return this.renderer.camera;
- }
-
- constructor(appController: ThreeDController,
- areaLUT: HTMLImageElement,
- gammaLUT: HTMLImageElement,
- commonShaderSource: string,
- shaderSources: ShaderMap) {
- super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
-
- this.cameraView = new ThreeDAtlasCameraView;
-
- this.appController = appController;
- this.renderer = new ThreeDRenderer(this);
-
- this.inVRRAF = false;
- this.vrDisplay = null;
- if ("VRFrameData" in window) {
- this.vrFrameData = new VRFrameData;
- } else {
- this.vrFrameData = null;
- }
-
- this.vrSetup();
-
- this.resizeToFit(true);
- }
-
- newTimingsReceived(timings: Timings): void {}
-
- vrSetup(): void {
- if (!('getVRDisplays' in navigator)) {
- return;
- }
-
- navigator.getVRDisplays().then(displays => {
- if (displays.length === 0) {
- return;
- }
- this.vrDisplay = displays[displays.length - 1];
-
- // It's heighly recommended that you set the near and far planes to
- // something appropriate for your scene so the projection matrices
- // WebVR produces have a well scaled depth buffer.
- this.renderer.setClipPlanes(this.vrDisplay);
- unwrapNull(document.getElementById('pf-vr')).style.display = "initial";
- });
-
- window.addEventListener('vrdisplaypresentchange', () => {
- if (this.vrDisplay == null)
- return;
-
- if (this.vrDisplay.isPresenting) {
- const eye = this.vrDisplay.getEyeParameters("left");
-
- this.vrDisplayHeight = eye.renderHeight;
- this.vrDisplayWidth = eye.renderWidth * 2;
- this.resizeToFit(true);
- const vrCallback = () => {
- if (this.vrDisplay == null || !this.renderer.inVR) {
- return;
- }
- this.vrDisplay.requestAnimationFrame(vrCallback);
- this.inVRRAF = true;
- this.redraw();
- this.inVRRAF = false;
- };
- this.vrDisplay.requestAnimationFrame(vrCallback);
- } else {
- this.renderer.inVR = false;
- this.resizeToFit(true);
- }
- });
- }
-
- enterVR(): void {
- if (this.vrDisplay != null) {
- this.renderer.inVR = true;
- this.vrDisplay.requestPresent([{ source: this.canvas }]);
- }
- }
-
- redrawVR(): void {
- if (this.vrDisplay == null ||
- this.vrFrameData == null) {
- this.renderer.redraw();
- return;
- }
- if (!this.inVRRAF) {
- // redraw() called outside of vr RAF, will get drawn later
- return;
- }
- this.vrDisplay.getFrameData(this.vrFrameData);
- this.renderer.redrawVR(this.vrFrameData);
- this.vrDisplay.submitFrame();
- }
-}
-
-class ThreeDRenderer extends Renderer {
- renderContext!: ThreeDView;
-
- camera: PerspectiveCamera;
-
- needsStencil: boolean = false;
- vrRightEye: boolean = false;
-
- get isMulticolor(): boolean {
- return false;
- }
-
- get destFramebuffer(): WebGLFramebuffer | null {
- return null;
- }
-
- get destAllocatedSize(): glmatrix.vec2 {
- let width = this.renderContext.canvas.width;
- if (this.inVR) {
- width = width / 2;
- }
- return glmatrix.vec2.clone([
- width,
- this.renderContext.canvas.height,
- ]);
- }
-
- get destUsedSize(): glmatrix.vec2 {
- return this.destAllocatedSize;
- }
-
- get allowSubpixelAA(): boolean {
- return false;
- }
-
- get backgroundColor(): glmatrix.vec4 {
- return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
- }
-
- protected get pathIDsAreInstanced(): boolean {
- return true;
- }
-
- protected get worldTransform() {
- return this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
- }
-
- protected get usedSizeFactor(): glmatrix.vec2 {
- return glmatrix.vec2.clone([1.0, 1.0]);
- }
-
- protected get objectCount(): number {
- return this.meshBuffers == null ? 0 : this.meshBuffers.length;
- }
-
- private cubeVertexPositionBuffer: WebGLBuffer;
- private cubeIndexBuffer: WebGLBuffer;
-
- private glyphPositionsBuffer!: WebGLBuffer;
- private glyphPositions: number[];
- private glyphPositionRanges: Range[];
- private glyphTexCoords: glmatrix.vec4[];
- private glyphSizes: glmatrix.vec2[];
-
- private distantGlyphVAO: WebGLVertexArrayObjectOES | null;
-
- private vrProjectionMatrix: Float32Array | null;
-
- constructor(renderContext: ThreeDView) {
- super(renderContext);
-
- const gl = renderContext.gl;
-
- this.glyphPositions = [];
- this.glyphPositionRanges = [];
- this.glyphTexCoords = [];
- this.glyphSizes = [];
-
- this.distantGlyphVAO = null;
- this.vrProjectionMatrix = null;
-
- this.camera = new PerspectiveCamera(renderContext.canvas, {
- innerCollisionExtent: MONUMENT_SCALE[0],
- });
- this.camera.onChange = () => renderContext.setDirty();
-
- this.cubeVertexPositionBuffer = unwrapNull(gl.createBuffer());
- gl.bindBuffer(gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, CUBE_VERTEX_POSITIONS, gl.STATIC_DRAW);
-
- this.cubeIndexBuffer = unwrapNull(gl.createBuffer());
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer);
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, CUBE_INDICES, gl.STATIC_DRAW);
- }
-
- attachMeshes(expandedMeshes: PathfinderPackedMeshes[]) {
- super.attachMeshes(expandedMeshes);
-
- this.renderAtlasGlyphs(this.renderContext.appController.atlasGlyphs);
-
- this.uploadPathColors(expandedMeshes.length);
- this.uploadPathTransforms(expandedMeshes.length);
-
- this.uploadGlyphPositions();
- }
-
- pathCountForObject(objectIndex: number): number {
- return this.renderContext.appController.meshDescriptors[objectIndex].positions.length;
- }
-
- pathBoundingRects(objectIndex: number): Float32Array {
- panic("ThreeDRenderer.pathBoundingRects(): TODO");
- return glmatrix.vec4.create();
- }
-
- setHintsUniform(uniforms: UniformMap): void {
- this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
- }
-
- redrawVR(frame: VRFrameData): void {
- this.clearDestFramebuffer(true);
- this.vrProjectionMatrix = frame.leftProjectionMatrix;
- this.vrRightEye = false;
- this.camera.setView((glmatrix.mat4.clone as any)(frame.leftViewMatrix), frame.pose);
- this.redraw();
- this.vrRightEye = true;
- this.vrProjectionMatrix = frame.rightProjectionMatrix;
- this.camera.setView((glmatrix.mat4.clone as any)(frame.rightViewMatrix), frame.pose);
- this.redraw();
- }
-
- setDrawViewport() {
- let offset = 0;
- if (this.vrRightEye) {
- offset = this.destAllocatedSize[0];
- }
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
- gl.viewport(offset, 0, this.destAllocatedSize[0], this.destAllocatedSize[1]);
- }
-
- setClipPlanes(display: VRDisplay) {
- display.depthNear = NEAR_CLIP_PLANE;
- display.depthFar = FAR_CLIP_PLANE;
- }
-
- pathTransformsForObject(objectIndex: number): PathTransformBuffers {
- const meshDescriptor = this.renderContext.appController.meshDescriptors[objectIndex];
- const pathCount = this.pathCountForObject(objectIndex);
- const pathTransforms = this.createPathTransformBuffers(pathCount);
- for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
- const glyphOrigin = meshDescriptor.positions[pathIndex];
- pathTransforms.st.set([1, 1, glyphOrigin[0], glyphOrigin[1]], (pathIndex + 1) * 4);
- }
- return pathTransforms;
- }
-
- protected clearColorForObject(objectIndex: number): glmatrix.vec4 | null {
- return null;
- }
-
- protected drawSceneryIfNecessary(): void {
- const gl = this.renderContext.gl;
-
- // Set up the depth buffer for drawing the monument.
- gl.clearDepth(1.0);
- gl.clear(gl.DEPTH_BUFFER_BIT);
-
- this.drawMonument();
-
- // Clear to avoid Z-fighting.
- gl.clearDepth(1.0);
- gl.clear(gl.DEPTH_BUFFER_BIT);
-
- this.drawDistantGlyphs();
-
- // Set up the depth buffer for direct rendering.
- gl.clearDepth(0.0);
- gl.clear(gl.DEPTH_BUFFER_BIT);
- }
-
- protected compositeIfNecessary(): void {}
-
- protected pathColorsForObject(objectIndex: number): Uint8Array {
- return TEXT_COLOR;
- }
-
- protected meshInstanceCountForObject(objectIndex: number): number {
- return this.renderContext.appController.meshDescriptors[objectIndex].positions.length;
- }
-
- protected createAAStrategy(aaType: AntialiasingStrategyName,
- aaLevel: number,
- subpixelAA: SubpixelAAType):
- AntialiasingStrategy {
- if (aaType !== 'xcaa')
- return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
- throw new PathfinderError("Unsupported antialiasing type!");
- }
-
- protected clearDestFramebuffer(force: boolean): void {
- const gl = this.renderContext.gl;
-
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.destFramebuffer);
- // clear the entire viewport
- gl.viewport(0, 0, this.renderContext.canvas.width, this.renderContext.canvas.height);
-
- gl.clearColor(1.0, 1.0, 1.0, 1.0);
- gl.clearDepth(1.0);
- gl.depthMask(true);
- if (force || this.vrProjectionMatrix == null) {
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
- }
- }
-
- protected getModelviewTransform(objectIndex: number): glmatrix.mat4 {
- const textFrameIndex = this.renderContext
- .appController
- .meshDescriptors[objectIndex]
- .textFrameIndex;
-
- const transform = glmatrix.mat4.create();
- glmatrix.mat4.rotateY(transform, transform, Math.PI / 2.0 * textFrameIndex);
- glmatrix.mat4.translate(transform, transform, TEXT_TRANSLATION);
- return transform;
- }
-
- protected instanceRangeForObject(glyphDescriptorIndex: number): Range {
- if (!this.objectIsVisible(glyphDescriptorIndex))
- return new Range(0, 0);
-
- const totalLength =
- this.renderContext.appController.meshDescriptors[glyphDescriptorIndex].positions.length;
-
- const cameraTransform = this.calculateCameraTransform(glmatrix.vec3.create(), TEXT_SCALE);
- const worldTransform = this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
- const glyphsTransform = glmatrix.mat4.clone(cameraTransform);
- const renderTransform = glmatrix.mat4.clone(worldTransform);
- const modelviewTransform = this.getModelviewTransform(glyphDescriptorIndex);
- glmatrix.mat4.mul(glyphsTransform, cameraTransform, modelviewTransform);
- glmatrix.mat4.mul(renderTransform, renderTransform, modelviewTransform);
-
- const nearbyRange = this.findNearbyGlyphPositions(glyphsTransform, glyphDescriptorIndex);
- const glyphPositionRange = this.glyphPositionRanges[glyphDescriptorIndex];
- nearbyRange.start -= glyphPositionRange.start;
- nearbyRange.end -= glyphPositionRange.start;
- return nearbyRange;
- }
-
- protected newTimingsReceived(): void {
- const newTimings: Partial = _.pick(this.lastTimings, ['rendering']);
- this.renderContext.appController.newTimingsReceived(newTimings);
- }
-
- protected directCurveProgramName(): keyof ShaderMap {
- return 'direct3DCurve';
- }
-
- protected directInteriorProgramName(): keyof ShaderMap {
- return 'direct3DInterior';
- }
-
- // Cheap but effective backface culling.
- private objectIsVisible(objectIndex: number): boolean {
- const textFrameIndex = this.renderContext
- .appController
- .meshDescriptors[objectIndex]
- .textFrameIndex;
-
- const translation = this.camera.translation;
- const extent = TEXT_TRANSLATION[2] * TEXT_SCALE[2];
- switch (textFrameIndex) {
- case 0: return translation[2] < -extent;
- case 1: return translation[0] < -extent;
- case 2: return translation[2] > extent;
- default: return translation[0] > extent;
- }
- }
-
- private uploadGlyphPositions(): void {
- const gl = this.renderContext.gl;
- const font = this.renderContext.font;
- const meshDescriptors = this.renderContext.appController.meshDescriptors;
-
- this.glyphPositions = [];
- this.glyphPositionRanges = [];
- for (const meshDescriptor of meshDescriptors) {
- const glyphIndex = this.renderContext.atlasGlyphs.findIndex(atlasGlyph => {
- return atlasGlyph.glyphKey.id === meshDescriptor.glyphID;
- });
- const glyph = this.renderContext.atlasGlyphs[glyphIndex];
- const glyphMetrics = unwrapNull(font.metricsForGlyph(glyph.glyphKey.id));
- const glyphUnitMetrics = new UnitMetrics(glyphMetrics, 0.0, glmatrix.vec2.create());
-
- const firstPosition = this.glyphPositions.length / 2;
-
- for (const position of meshDescriptor.positions) {
- this.glyphPositions.push(position[0] + glyphUnitMetrics.left,
- position[1] + glyphUnitMetrics.descent);
- }
-
- const lastPosition = this.glyphPositions.length / 2;
- this.glyphPositionRanges.push(new Range(firstPosition, lastPosition));
- }
-
- this.glyphPositionsBuffer = unwrapNull(gl.createBuffer());
- gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.glyphPositions), gl.STATIC_DRAW);
- }
-
- private drawMonument(): void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- // Set up the cube VBO.
- const monumentProgram = this.renderContext.shaderPrograms.demo3DMonument;
- gl.useProgram(monumentProgram.program);
- gl.bindBuffer(gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer);
- gl.vertexAttribPointer(monumentProgram.attributes.aPosition, 3, gl.FLOAT, false, 0, 0);
- gl.enableVertexAttribArray(monumentProgram.attributes.aPosition);
-
- // Set uniforms for the monument.
- const projection = this.calculateProjectionTransform();
- const modelview = this.calculateModelviewTransform(MONUMENT_TRANSLATION, MONUMENT_SCALE);
- gl.uniformMatrix4fv(monumentProgram.uniforms.uProjection, false, projection);
- gl.uniformMatrix4fv(monumentProgram.uniforms.uModelview, false, modelview);
- const cameraModelview = this.calculateCameraModelviewTransform();
- const lightPosition = glmatrix.vec4.clone([-1750.0, -700.0, 1750.0, 1.0]);
- glmatrix.vec4.transformMat4(lightPosition, lightPosition, cameraModelview);
- gl.uniform3f(monumentProgram.uniforms.uLightPosition,
- lightPosition[0] / lightPosition[3],
- lightPosition[1] / lightPosition[3],
- lightPosition[2] / lightPosition[3]);
- gl.uniform3fv(monumentProgram.uniforms.uAmbientColor, AMBIENT_COLOR);
- gl.uniform3fv(monumentProgram.uniforms.uDiffuseColor, DIFFUSE_COLOR);
- gl.uniform3fv(monumentProgram.uniforms.uSpecularColor, SPECULAR_COLOR);
- gl.uniform1f(monumentProgram.uniforms.uShininess, MONUMENT_SHININESS);
-
- // Set state for the monument.
- gl.enable(gl.DEPTH_TEST);
- gl.depthFunc(gl.LESS);
- gl.depthMask(true);
- gl.disable(gl.SCISSOR_TEST);
- gl.disable(gl.BLEND);
-
- // Loop over each face.
- for (let face = 0; face < 6; face++) {
- // Set the uniforms for this face.
- const normal = glmatrix.vec4.clone(MONUMENT_NORMALS[face]);
- glmatrix.vec4.transformMat4(normal, normal, this.camera.rotationMatrix);
- gl.uniform3f(monumentProgram.uniforms.uNormal,
- normal[0] / normal[3],
- normal[1] / normal[3],
- normal[2] / normal[3]);
- gl.uniform1f(monumentProgram.uniforms.uEnableLighting, this.inVR ? 0 : 1);
- // Draw the face!
- gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, face * 6 * UINT16_SIZE);
- }
- }
-
- private drawDistantGlyphs(): void {
- const appController = this.renderContext.appController;
- const gl = this.renderContext.gl;
-
- // Prepare the distant glyph VAO.
- if (this.distantGlyphVAO == null)
- this.distantGlyphVAO = this.renderContext.vertexArrayObjectExt.createVertexArrayOES();
- this.renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.distantGlyphVAO);
- const distantGlyphProgram = this.renderContext.shaderPrograms.demo3DDistantGlyph;
- gl.useProgram(distantGlyphProgram.program);
- gl.bindBuffer(gl.ARRAY_BUFFER, this.renderContext.quadPositionsBuffer);
- gl.vertexAttribPointer(distantGlyphProgram.attributes.aQuadPosition,
- 2,
- gl.FLOAT,
- false,
- 0,
- 0);
- gl.enableVertexAttribArray(distantGlyphProgram.attributes.aQuadPosition);
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.renderContext.quadElementsBuffer);
-
- // Set global uniforms.
- gl.uniform4fv(distantGlyphProgram.uniforms.uColor,
- _.map(TEXT_COLOR, number => number / 0xff));
- const atlasTexture = this.renderContext.atlas.ensureTexture(this.renderContext);
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, atlasTexture);
- gl.uniform1i(distantGlyphProgram.uniforms.uAtlas, 0);
-
- // Set state.
- gl.disable(gl.DEPTH_TEST);
- gl.disable(gl.SCISSOR_TEST);
- gl.enable(gl.BLEND);
- gl.blendEquation(gl.FUNC_ADD);
- gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
-
- // Draw textures for distant glyphs.
- const cameraTransform = this.calculateCameraTransform(glmatrix.vec3.create(), TEXT_SCALE);
- const worldTransform = this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
-
- for (let glyphDescriptorIndex = 0;
- glyphDescriptorIndex < this.glyphPositionRanges.length;
- glyphDescriptorIndex++) {
- if (!this.objectIsVisible(glyphDescriptorIndex))
- continue;
-
- const meshDescriptor = appController.meshDescriptors[glyphDescriptorIndex];
- const glyphIndex = this.renderContext.atlasGlyphs.findIndex(glyph => {
- return glyph.glyphKey.id === meshDescriptor.glyphID;
- });
-
- // Calculate transforms.
- const glyphsTransform = glmatrix.mat4.clone(cameraTransform);
- const renderTransform = glmatrix.mat4.clone(worldTransform);
- const modelviewTransform = this.getModelviewTransform(glyphDescriptorIndex);
- glmatrix.mat4.mul(glyphsTransform, cameraTransform, modelviewTransform);
- glmatrix.mat4.mul(renderTransform, renderTransform, modelviewTransform);
-
- const glyphPositionRange = this.glyphPositionRanges[glyphDescriptorIndex];
- const nearbyGlyphPositionRange = this.findNearbyGlyphPositions(glyphsTransform,
- glyphDescriptorIndex);
-
- // Set uniforms.
- gl.uniformMatrix4fv(distantGlyphProgram.uniforms.uTransform, false, renderTransform);
-
- const glyphTexCoords = this.glyphTexCoords[glyphIndex];
- gl.uniform4f(distantGlyphProgram.uniforms.uGlyphTexCoords,
- glyphTexCoords[0],
- glyphTexCoords[1],
- glyphTexCoords[2],
- glyphTexCoords[3]);
- const glyphSize = this.glyphSizes[glyphIndex];
- gl.uniform2f(distantGlyphProgram.uniforms.uGlyphSize, glyphSize[0], glyphSize[1]);
-
- const rangeBefore = new Range(glyphPositionRange.start,
- nearbyGlyphPositionRange.start);
- if (!rangeBefore.isEmpty) {
- // Would be nice to have `glDrawElementsInstancedBaseInstance`...
- // FIXME(pcwalton): Cache VAOs?
- gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
- gl.vertexAttribPointer(distantGlyphProgram.attributes.aPosition,
- 2,
- gl.FLOAT,
- false,
- 0,
- rangeBefore.start * FLOAT32_SIZE * 2);
- gl.enableVertexAttribArray(distantGlyphProgram.attributes.aPosition);
- this.renderContext
- .instancedArraysExt
- .vertexAttribDivisorANGLE(distantGlyphProgram.attributes.aPosition, 1);
-
- this.renderContext
- .instancedArraysExt
- .drawElementsInstancedANGLE(gl.TRIANGLES,
- 6,
- gl.UNSIGNED_BYTE,
- 0,
- rangeBefore.length);
- }
-
- const rangeAfter = new Range(nearbyGlyphPositionRange.end, glyphPositionRange.end);
- if (!rangeAfter.isEmpty) {
- gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
- gl.vertexAttribPointer(distantGlyphProgram.attributes.aPosition,
- 2,
- gl.FLOAT,
- false,
- 0,
- rangeAfter.start * FLOAT32_SIZE * 2);
- gl.enableVertexAttribArray(distantGlyphProgram.attributes.aPosition);
- this.renderContext
- .instancedArraysExt
- .vertexAttribDivisorANGLE(distantGlyphProgram.attributes.aPosition, 1);
-
- this.renderContext
- .instancedArraysExt
- .drawElementsInstancedANGLE(gl.TRIANGLES,
- 6,
- gl.UNSIGNED_BYTE,
- 0,
- rangeAfter.length);
- }
- }
-
- this.renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
- }
-
- private calculateProjectionTransform(): glmatrix.mat4 {
- if (this.vrProjectionMatrix != null)
- return (glmatrix.mat4.clone as any)(this.vrProjectionMatrix);
-
- const canvas = this.renderContext.canvas;
- const projection = glmatrix.mat4.create();
- glmatrix.mat4.perspective(projection,
- FOV / 180.0 * Math.PI,
- canvas.width / canvas.height,
- NEAR_CLIP_PLANE,
- FAR_CLIP_PLANE);
- return projection;
- }
-
- private calculateCameraModelviewTransform(): glmatrix.mat4 {
- const modelview = glmatrix.mat4.create();
- glmatrix.mat4.mul(modelview, modelview, this.camera.rotationMatrix);
- glmatrix.mat4.translate(modelview, modelview, this.camera.translation);
- return modelview;
- }
-
- private calculateModelviewTransform(modelviewTranslation: glmatrix.vec3,
- modelviewScale: glmatrix.vec3):
- glmatrix.mat4 {
- const modelview = this.calculateCameraModelviewTransform();
- glmatrix.mat4.translate(modelview, modelview, modelviewTranslation);
- glmatrix.mat4.scale(modelview, modelview, modelviewScale);
- return modelview;
- }
-
- private calculateWorldTransform(modelviewTranslation: glmatrix.vec3,
- modelviewScale: glmatrix.vec3):
- glmatrix.mat4 {
- const projection = this.calculateProjectionTransform();
- const modelview = this.calculateModelviewTransform(modelviewTranslation, modelviewScale);
-
- const transform = glmatrix.mat4.create();
- glmatrix.mat4.mul(transform, projection, modelview);
- return transform;
- }
-
- private calculateCameraTransform(modelviewTranslation: glmatrix.vec3,
- modelviewScale: glmatrix.vec3):
- glmatrix.mat4 {
- const transform = glmatrix.mat4.create();
- glmatrix.mat4.translate(transform, transform, this.camera.translation);
- glmatrix.mat4.translate(transform, transform, modelviewTranslation);
- glmatrix.mat4.scale(transform, transform, modelviewScale);
- return transform;
- }
-
- private renderAtlasGlyphs(atlasGlyphs: AtlasGlyph[]): void {
- const hint = new Hint(this.renderContext.font,
- this.renderContext.atlasPixelsPerUnit,
- false);
- this.renderContext.atlas.layoutGlyphs(atlasGlyphs,
- this.renderContext.font,
- this.renderContext.atlasPixelsPerUnit,
- 0.0,
- hint,
- glmatrix.vec2.create());
-
- const atlasRenderer = new ThreeDAtlasRenderer(this.renderContext, atlasGlyphs);
- const baseMeshes = this.renderContext.appController.baseMeshes;
- const expandedMeshes = new PathfinderPackedMeshes(baseMeshes);
- atlasRenderer.attachMeshes([expandedMeshes]);
- atlasRenderer.renderAtlas();
- this.glyphTexCoords = atlasRenderer.glyphTexCoords;
- this.glyphSizes = atlasRenderer.glyphSizes;
- }
-
- private findNearbyGlyphPositions(transform: glmatrix.mat4, glyphDescriptorIndex: number):
- Range {
- const glyphPositionRange = this.glyphPositionRanges[glyphDescriptorIndex];
- const startPosition = this.findFirstGlyphPositionInRange(transform,
- glyphPositionRange,
- -MAX_DISTANCE);
- const endPosition = this.findFirstGlyphPositionInRange(transform,
- new Range(startPosition,
- glyphPositionRange.end),
- MAX_DISTANCE);
- return new Range(startPosition, endPosition);
- }
-
- private findFirstGlyphPositionInRange(transform: glmatrix.mat4,
- range: Range,
- maxDistance: number):
- number {
- let lo = range.start, hi = range.end;
- while (lo < hi) {
- const mid = lo + ((hi - lo) >> 1);
- const glyphPosition = this.calculateTransformedGlyphPosition(transform, mid);
- const glyphDistance = -glyphPosition[1];
- if (glyphDistance < maxDistance)
- lo = mid + 1;
- else
- hi = mid;
- }
- return lo;
- }
-
- private calculateTransformedGlyphPosition(transform: glmatrix.mat4,
- glyphPositionIndex: number):
- glmatrix.vec4 {
- const position = glmatrix.vec4.clone([
- this.glyphPositions[glyphPositionIndex * 2 + 0],
- this.glyphPositions[glyphPositionIndex * 2 + 1],
- 0.0,
- 1.0,
- ]);
- glmatrix.vec4.transformMat4(position, position, transform);
- return position;
- }
-}
-
-class ThreeDAtlasRenderer extends TextRenderer {
- glyphTexCoords: glmatrix.vec4[];
- glyphSizes: glmatrix.vec2[];
-
- private allAtlasGlyphs: AtlasGlyph[];
-
- get backgroundColor(): glmatrix.vec4 {
- return glmatrix.vec4.create();
- }
-
- constructor(renderContext: ThreeDView, atlasGlyphs: AtlasGlyph[]) {
- super(renderContext);
- this.allAtlasGlyphs = atlasGlyphs;
- this.glyphTexCoords = [];
- this.glyphSizes = [];
- }
-
- renderAtlas(): void {
- this.createAtlasFramebuffer();
- this.buildAtlasGlyphs(this.allAtlasGlyphs);
- this.redraw();
- this.calculateGlyphTexCoords();
- }
-
- protected compositeIfNecessary(): void {}
-
- private calculateGlyphTexCoords(): void {
- const pixelsPerUnit = this.pixelsPerUnit;
- const glyphCount = this.renderContext.atlasGlyphs.length;
- const font = this.renderContext.font;
- const hint = this.createHint();
-
- this.glyphTexCoords = [];
- this.glyphSizes = [];
-
- for (let glyphIndex = 0; glyphIndex < glyphCount; glyphIndex++) {
- const glyph = this.renderContext.atlasGlyphs[glyphIndex];
- const glyphPixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
- const glyphMetrics = font.metricsForGlyph(glyph.glyphKey.id);
- if (glyphMetrics == null)
- continue;
-
- const glyphUnitMetrics = new UnitMetrics(glyphMetrics, 0.0, glmatrix.vec2.create());
- const atlasGlyphRect = calculatePixelRectForGlyph(glyphUnitMetrics,
- glyphPixelOrigin,
- pixelsPerUnit,
- hint);
-
- this.glyphSizes.push(glmatrix.vec2.clone([
- glyphUnitMetrics.right - glyphUnitMetrics.left,
- glyphUnitMetrics.ascent - glyphUnitMetrics.descent,
- ]));
-
- 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);
-
- this.glyphTexCoords.push(glmatrix.vec4.clone([
- atlasGlyphBL[0],
- atlasGlyphBL[1],
- atlasGlyphTR[0],
- atlasGlyphTR[1],
- ]));
- }
- }
-}
-
-class ThreeDAtlasCameraView implements CameraView {
- get width(): number {
- return ATLAS_SIZE[0];
- }
-
- get height(): number {
- return ATLAS_SIZE[1];
- }
-
- get classList(): DOMTokenList | null {
- return null;
- }
-
- addEventListener(type: K,
- listener: (this: HTMLCanvasElement,
- ev: HTMLElementEventMap[K]) =>
- any,
- useCapture?: boolean): void {}
-
- getBoundingClientRect(): ClientRect {
- return new ClientRect();
- }
-}
-
-function main() {
- const controller = new ThreeDController;
- window.addEventListener('load', () => controller.start(), false);
-}
-
-main();
diff --git a/demo/client/src/aa-strategy.ts b/demo/client/src/aa-strategy.ts
deleted file mode 100644
index bf05aad8..00000000
--- a/demo/client/src/aa-strategy.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-// pathfinder/client/src/aa-strategy.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-
-import {createFramebuffer, createFramebufferColorTexture, UniformMap} from './gl-utils';
-import {createFramebufferDepthTexture} from './gl-utils';
-import {Renderer} from './renderer';
-import {unwrapNull} from './utils';
-import {DemoView} from './view';
-
-const SUBPIXEL_AA_KERNELS: {readonly [kind in SubpixelAAType]: glmatrix.vec4} = {
- // These intentionally do not precisely match what Core Graphics does (a Lanczos function),
- // because we don't want any ringing artefacts.
- 'core-graphics': glmatrix.vec4.clone([0.033165660, 0.102074051, 0.221434336, 0.286651906]),
- 'freetype': glmatrix.vec4.clone([0.0, 0.031372549, 0.301960784, 0.337254902]),
- 'none': glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]),
-};
-
-export type AntialiasingStrategyName = 'none' | 'ssaa' | 'xcaa';
-
-export type DirectRenderingMode = 'none' | 'conservative' | 'color';
-
-export type SubpixelAAType = 'none' | 'freetype' | 'core-graphics';
-
-export type GammaCorrectionMode = 'off' | 'on';
-
-export type StemDarkeningMode = 'none' | 'dark';
-
-export interface TileInfo {
- size: glmatrix.vec2;
- position: glmatrix.vec2;
-}
-
-export abstract class AntialiasingStrategy {
- // The type of direct rendering that should occur, if any.
- abstract readonly directRenderingMode: DirectRenderingMode;
-
- // How many rendering passes this AA strategy requires.
- abstract readonly passCount: number;
-
- protected subpixelAA: SubpixelAAType;
-
- constructor(subpixelAA: SubpixelAAType) {
- this.subpixelAA = subpixelAA;
- }
-
- // Prepares any OpenGL data. This is only called on startup and canvas resize.
- init(renderer: Renderer): void {
- this.setFramebufferSize(renderer);
- }
-
- // Uploads any mesh data. This is called whenever a new set of meshes is supplied.
- abstract attachMeshes(renderer: Renderer): void;
-
- // This is called whenever the framebuffer has changed.
- abstract setFramebufferSize(renderer: Renderer): void;
-
- // Returns the transformation matrix that should be applied when directly rendering.
- abstract get transform(): glmatrix.mat4;
-
- // Called before rendering.
- //
- // Typically, this redirects rendering to a framebuffer of some sort.
- abstract prepareForRendering(renderer: Renderer): void;
-
- // Called before directly rendering.
- //
- // Typically, this redirects rendering to a framebuffer of some sort.
- abstract prepareForDirectRendering(renderer: Renderer): void;
-
- // Called before directly rendering a single object.
- abstract prepareToRenderObject(renderer: Renderer, objectIndex: number): void;
-
- abstract finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void;
-
- // Called after direct rendering.
- //
- // This usually performs the actual antialiasing.
- abstract antialiasObject(renderer: Renderer, objectIndex: number): void;
-
- // Called after antialiasing each object.
- abstract finishAntialiasingObject(renderer: Renderer, objectIndex: number): void;
-
- // Called before rendering each object directly.
- abstract resolveAAForObject(renderer: Renderer, objectIndex: number): void;
-
- // Called after antialiasing.
- //
- // This usually blits to the real framebuffer.
- abstract resolve(pass: number, renderer: Renderer): void;
-
- setSubpixelAAKernelUniform(renderer: Renderer, uniforms: UniformMap): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- const kernel = SUBPIXEL_AA_KERNELS[this.subpixelAA];
- gl.uniform4f(uniforms.uKernel, kernel[0], kernel[1], kernel[2], kernel[3]);
- }
-
- worldTransformForPass(renderer: Renderer, pass: number): glmatrix.mat4 {
- return glmatrix.mat4.create();
- }
-}
-
-export class NoAAStrategy extends AntialiasingStrategy {
- framebufferSize: glmatrix.vec2;
-
- get passCount(): number {
- return 1;
- }
-
- constructor(level: number, subpixelAA: SubpixelAAType) {
- super(subpixelAA);
- this.framebufferSize = glmatrix.vec2.create();
- }
-
- attachMeshes(renderer: Renderer) {}
-
- setFramebufferSize(renderer: Renderer) {
- this.framebufferSize = renderer.destAllocatedSize;
- }
-
- get transform(): glmatrix.mat4 {
- return glmatrix.mat4.create();
- }
-
- prepareForRendering(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
- gl.bindFramebuffer(gl.FRAMEBUFFER, renderer.destFramebuffer);
- renderer.setDrawViewport();
- gl.disable(gl.SCISSOR_TEST);
- }
-
- prepareForDirectRendering(renderer: Renderer): void {}
-
- prepareToRenderObject(renderer: Renderer, objectIndex: number): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- gl.bindFramebuffer(gl.FRAMEBUFFER, renderer.destFramebuffer);
- renderer.setDrawViewport();
- gl.disable(gl.SCISSOR_TEST);
- }
-
- finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void {}
-
- antialiasObject(renderer: Renderer, objectIndex: number): void {}
-
- finishAntialiasingObject(renderer: Renderer, objectIndex: number): void {}
-
- resolveAAForObject(renderer: Renderer): void {}
-
- resolve(pass: number, renderer: Renderer): void {}
-
- get directRenderingMode(): DirectRenderingMode {
- return 'color';
- }
-}
diff --git a/demo/client/src/app-controller.ts b/demo/client/src/app-controller.ts
deleted file mode 100644
index 1ac86945..00000000
--- a/demo/client/src/app-controller.ts
+++ /dev/null
@@ -1,398 +0,0 @@
-// pathfinder/client/src/app-controller.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import {AntialiasingStrategyName, GammaCorrectionMode, StemDarkeningMode} from "./aa-strategy";
-import {SubpixelAAType} from "./aa-strategy";
-import {FilePickerView} from "./file-picker";
-import {ShaderLoader, ShaderMap, ShaderProgramSource} from './shader-loader';
-import {expectNotNull, unwrapNull, unwrapUndef} from './utils';
-import {DemoView, Timings, TIMINGS} from "./view";
-
-const AREA_LUT_URI: string = "/textures/area-lut.png";
-const GAMMA_LUT_URI: string = "/textures/gamma-lut.png";
-
-const SWITCHES: SwitchMap = {
- gammaCorrection: {
- defaultValue: 'on',
- id: 'pf-gamma-correction',
- offValue: 'off',
- onValue: 'on',
- switchInputsName: 'gammaCorrectionSwitchInputs',
- },
- stemDarkening: {
- defaultValue: 'dark',
- id: 'pf-stem-darkening',
- offValue: 'none',
- onValue: 'dark',
- switchInputsName: 'stemDarkeningSwitchInputs',
- },
-};
-
-interface SwitchDescriptor {
- id: string;
- switchInputsName: keyof Switches;
- onValue: string;
- offValue: string;
- defaultValue: string;
-}
-
-interface SwitchMap {
- gammaCorrection: SwitchDescriptor;
- stemDarkening: SwitchDescriptor;
-}
-
-export interface AAOptions {
- gammaCorrection: GammaCorrectionMode;
- stemDarkening: StemDarkeningMode;
- subpixelAA: SubpixelAAType;
-}
-
-export interface SwitchInputs {
- on: HTMLInputElement;
- off: HTMLInputElement;
-}
-
-interface Switches {
- gammaCorrectionSwitchInputs: SwitchInputs | null;
- stemDarkeningSwitchInputs: SwitchInputs | null;
-}
-
-export abstract class AppController {
- protected canvas!: HTMLCanvasElement;
- protected selectFileElement: HTMLSelectElement | null = null;
- protected screenshotButton: HTMLButtonElement | null = null;
-
- start(): void {
- this.selectFileElement = document.getElementById('pf-select-file') as HTMLSelectElement |
- null;
- }
-
- protected loadInitialFile(builtinFileURI: string): void {
- if (this.selectFileElement != null) {
- const selectedOption = this.selectFileElement.selectedOptions[0] as HTMLOptionElement;
- this.fetchFile(selectedOption.value, builtinFileURI);
- } else {
- this.fetchFile(this.defaultFile, builtinFileURI);
- }
- }
-
- protected fetchFile(file: string, builtinFileURI: string): Promise {
- return new Promise(resolve => {
- window.fetch(`${builtinFileURI}/${file}`)
- .then(response => response.arrayBuffer())
- .then(data => {
- this.fileLoaded(data, file);
- resolve();
- });
- });
- }
-
- protected abstract fileLoaded(data: ArrayBuffer, builtinName: string | null): void;
-
- protected abstract get defaultFile(): string;
-}
-
-export abstract class DemoAppController extends AppController
- implements Switches {
- view!: Promise;
-
- gammaCorrectionSwitchInputs: SwitchInputs | null = null;
- stemDarkeningSwitchInputs: SwitchInputs | null = null;
-
- protected abstract readonly builtinFileURI: string;
-
- protected filePickerView: FilePickerView | null = null;
-
- protected aaLevelSelect: HTMLSelectElement | null = null;
- protected subpixelAASelect: HTMLSelectElement | null = null;
-
- private fpsLabel: HTMLElement | null = null;
-
- constructor() {
- super();
- }
-
- start() {
- super.start();
-
- this.initPopup('pf-settings', 'pf-settings-button', 'pf-settings-close-button');
- this.initPopup('pf-rotate-slider-card', 'pf-rotate-button', 'pf-rotate-close-button');
-
- const screenshotButton = document.getElementById('pf-screenshot-button') as
- HTMLButtonElement | null;
- if (screenshotButton != null) {
- screenshotButton.addEventListener('click', () => {
- this.view.then(view => view.queueScreenshot());
- }, false);
- }
-
- const zoomInButton = document.getElementById('pf-zoom-in-button') as HTMLButtonElement |
- null;
- if (zoomInButton != null) {
- zoomInButton.addEventListener('click', () => {
- this.view.then(view => view.zoomIn());
- }, false);
- }
-
- const zoomOutButton = document.getElementById('pf-zoom-out-button') as HTMLButtonElement |
- null;
- if (zoomOutButton != null) {
- zoomOutButton.addEventListener('click', () => {
- this.view.then(view => view.zoomOut());
- }, false);
- }
-
- const zoomPulseButton = document.getElementById('pf-zoom-pulse-button') as
- HTMLButtonElement | null;
- if (zoomPulseButton != null) {
- zoomPulseButton.addEventListener('click', () => {
- this.view.then(view => view.zoomPulse());
- }, false);
- }
-
- const rotateSlider = document.getElementById('pf-rotate-slider') as HTMLInputElement |
- null;
- if (rotateSlider != null) {
- rotateSlider.addEventListener('input', event => {
- this.view.then(view => {
- view.rotate((event.target as HTMLInputElement).valueAsNumber);
- });
- }, false);
- }
-
- const vrButton = document.getElementById('pf-vr') as HTMLInputElement |
- null;
- if (vrButton != null) {
- vrButton.addEventListener('click', event => {
- this.view.then(view => {
- view.enterVR();
- });
- }, false);
- }
-
- this.filePickerView = FilePickerView.create();
- if (this.filePickerView != null) {
- this.filePickerView.onFileLoaded = fileData => this.fileLoaded(fileData, null);
- }
-
- if (this.selectFileElement != null) {
- this.selectFileElement
- .addEventListener('click', event => this.fileSelectionChanged(event), false);
- }
-
- this.fpsLabel = document.getElementById('pf-fps-label');
-
- const shaderLoader = new ShaderLoader;
- shaderLoader.load();
-
- const areaLUTPromise = this.loadTexture(AREA_LUT_URI);
- const gammaLUTPromise = this.loadTexture(GAMMA_LUT_URI);
-
- const promises: any[] = [
- areaLUTPromise,
- gammaLUTPromise,
- shaderLoader.common,
- shaderLoader.shaders,
- ];
- this.view = Promise.all(promises).then(assets => {
- return this.createView(assets[0], assets[1], assets[2], assets[3]);
- });
-
- this.aaLevelSelect = document.getElementById('pf-aa-level-select') as
- (HTMLSelectElement | null);
- if (this.aaLevelSelect != null)
- this.aaLevelSelect.addEventListener('change', () => this.updateAALevel(), false);
-
- this.subpixelAASelect = document.getElementById('pf-subpixel-aa-select') as
- (HTMLSelectElement | null);
- if (this.subpixelAASelect != null)
- this.subpixelAASelect.addEventListener('change', () => this.updateAALevel(), false);
-
- // The event listeners here use `window.setTimeout()` because jQuery won't fire the "live"
- // click listener that Bootstrap sets up until the event bubbles up to the document. This
- // click listener is what toggles the `checked` attribute, so we have to wait until it
- // fires before updating the antialiasing settings.
- for (const switchName of Object.keys(SWITCHES) as Array) {
- const switchInputsName = SWITCHES[switchName].switchInputsName;
- const switchID = SWITCHES[switchName].id;
- const switchOnInput = document.getElementById(`${switchID}-select-on`);
- const switchOffInput = document.getElementById(`${switchID}-select-off`);
- if (switchOnInput != null && switchOffInput != null) {
- this[switchInputsName] = {
- off: switchOffInput as HTMLInputElement,
- on: switchOnInput as HTMLInputElement,
- };
- } else {
- this[switchInputsName] = null;
- }
-
- const buttons = document.getElementById(`${switchID}-buttons`) as HTMLElement | null;
- if (buttons == null)
- continue;
-
- buttons.addEventListener('click', () => {
- window.setTimeout(() => this.updateAALevel(), 0);
- }, false);
- }
-
- this.updateAALevel();
- }
-
- newTimingsReceived(timings: Partial) {
- if (this.fpsLabel == null)
- return;
-
- while (this.fpsLabel.lastChild != null)
- this.fpsLabel.removeChild(this.fpsLabel.lastChild);
-
- for (const timing of Object.keys(timings) as Array) {
- const tr = document.createElement('div');
- tr.classList.add('row');
-
- const keyTD = document.createElement('div');
- const valueTD = document.createElement('div');
- keyTD.classList.add('col');
- valueTD.classList.add('col');
- keyTD.appendChild(document.createTextNode(TIMINGS[timing]));
- valueTD.appendChild(document.createTextNode(timings[timing] + " ms"));
-
- tr.appendChild(keyTD);
- tr.appendChild(valueTD);
- this.fpsLabel.appendChild(tr);
- }
-
- this.fpsLabel.classList.remove('invisible');
- }
-
- protected updateAALevel(): Promise {
- let aaType: AntialiasingStrategyName, aaLevel: number;
- if (this.aaLevelSelect != null) {
- const selectedOption = this.aaLevelSelect.selectedOptions[0];
- const aaValues = unwrapNull(/^([a-z-]+)(?:-([0-9]+))?$/.exec(selectedOption.value));
- aaType = aaValues[1] as AntialiasingStrategyName;
- aaLevel = aaValues[2] === "" ? 1 : parseInt(aaValues[2], 10);
- } else {
- aaType = 'none';
- aaLevel = 0;
- }
-
- this.updateUIForAALevelChange(aaType, aaLevel);
-
- const aaOptions: Partial = {};
- for (const switchName of Object.keys(SWITCHES) as Array) {
- const switchDescriptor = SWITCHES[switchName];
- const switchInputsName = switchDescriptor.switchInputsName;
- const switchInputs = this[switchInputsName];
- if (switchInputs == null)
- aaOptions[switchName] = switchDescriptor.defaultValue as any;
- else if (switchInputs.on.checked && !switchInputs.on.disabled)
- aaOptions[switchName] = switchDescriptor.onValue as any;
- else
- aaOptions[switchName] = switchDescriptor.offValue as any;
- }
- if (this.subpixelAASelect != null) {
- const selectedOption = this.subpixelAASelect.selectedOptions[0];
- aaOptions.subpixelAA = selectedOption.value as SubpixelAAType;
- } else {
- aaOptions.subpixelAA = 'none';
- }
-
- return this.view.then(view => {
- view.setAntialiasingOptions(aaType, aaLevel, aaOptions as AAOptions);
- });
- }
-
- protected updateUIForAALevelChange(aaType: AntialiasingStrategyName, aaLevel: number): void {
- // Overridden by subclasses.
- }
-
- protected abstract createView(areaLUT: HTMLImageElement,
- gammaLUT: HTMLImageElement,
- commonShaderSource: string,
- shaderSources: ShaderMap):
- View;
-
- private initPopup(cardID: string, popupButtonID: string, closeButtonID: string): void {
- const card = document.getElementById(cardID) as HTMLElement | null;
- const button = document.getElementById(popupButtonID) as HTMLButtonElement | null;
- const closeButton = document.getElementById(closeButtonID) as HTMLButtonElement | null;
-
- if (button != null) {
- button.addEventListener('click', event => {
- event.stopPropagation();
- unwrapNull(card).classList.toggle('pf-invisible');
- }, false);
- }
- if (closeButton != null) {
- closeButton.addEventListener('click', () => {
- unwrapNull(card).classList.add('pf-invisible');
- }, false);
- }
-
- if (card == null)
- return;
-
- document.body.addEventListener('click', event => {
- let element = event.target as Element | null;
- while (element != null) {
- if (element === card)
- return;
- element = element.parentElement;
- }
-
- card.classList.add('pf-invisible');
- }, false);
- }
-
- private loadTexture(uri: string): Promise {
- return window.fetch(uri)
- .then(response => response.blob())
- .then(blob => {
- const imgElement = document.createElement('img');
- imgElement.src = URL.createObjectURL(blob);
- const promise: Promise = new Promise(resolve => {
- imgElement.addEventListener('load', () => {
- resolve(imgElement);
- }, false);
- });
- return promise;
- });
- }
-
- private fileSelectionChanged(event: Event): void {
- const selectFileElement = event.currentTarget as HTMLSelectElement;
- const selectedOption = selectFileElement.selectedOptions[0] as HTMLOptionElement;
-
- if (selectedOption.value === 'load-custom' && this.filePickerView != null) {
- this.filePickerView.open();
-
- const oldSelectedIndex = selectFileElement.selectedIndex;
- const newOption = document.createElement('option');
- newOption.id = 'pf-custom-option-placeholder';
- newOption.appendChild(document.createTextNode("Custom"));
- selectFileElement.insertBefore(newOption, selectedOption);
- selectFileElement.selectedIndex = oldSelectedIndex;
- return;
- }
-
- // Remove the "Custom…" placeholder if it exists.
- const placeholder = document.getElementById('pf-custom-option-placeholder');
- if (placeholder != null)
- selectFileElement.removeChild(placeholder);
-
- // Fetch the file.
- this.fetchFile(selectedOption.value, this.builtinFileURI);
- }
-}
-
-export function setSwitchInputsValue(switchInputs: SwitchInputs, on: boolean): void {
- switchInputs.on.checked = on;
- switchInputs.off.checked = !on;
-}
diff --git a/demo/client/src/atlas.ts b/demo/client/src/atlas.ts
deleted file mode 100644
index 784af743..00000000
--- a/demo/client/src/atlas.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-// pathfinder/client/src/atlas.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-import * as _ from 'lodash';
-
-import {setTextureParameters} from './gl-utils';
-import {calculatePixelDescent, calculatePixelRectForGlyph, calculatePixelXMin, Hint} from './text';
-import {PathfinderFont, UnitMetrics} from './text';
-import {unwrapNull} from './utils';
-import {RenderContext} from './view';
-
-export const SUBPIXEL_GRANULARITY: number = 4;
-
-export const ATLAS_SIZE: glmatrix.vec2 = glmatrix.vec2.fromValues(2048, 4096);
-
-export class Atlas {
- private _texture: WebGLTexture | null;
- private _usedSize: glmatrix.vec2;
-
- constructor() {
- this._texture = null;
- this._usedSize = glmatrix.vec2.create();
- }
-
- layoutGlyphs(glyphs: AtlasGlyph[],
- font: PathfinderFont,
- pixelsPerUnit: number,
- rotationAngle: number,
- hint: Hint,
- emboldenAmount: glmatrix.vec2):
- void {
- let nextOrigin = glmatrix.vec2.fromValues(1.0, 1.0);
- let shelfBottom = 2.0;
-
- for (const glyph of glyphs) {
- // Place the glyph, and advance the origin.
- const metrics = font.metricsForGlyph(glyph.glyphKey.id);
- if (metrics == null)
- continue;
-
- const unitMetrics = new UnitMetrics(metrics, rotationAngle, emboldenAmount);
- glyph.setPixelLowerLeft(nextOrigin, unitMetrics, pixelsPerUnit);
-
- let pixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
- nextOrigin[0] = calculatePixelRectForGlyph(unitMetrics,
- pixelOrigin,
- pixelsPerUnit,
- hint)[2] + 1.0;
-
- // If the glyph overflowed the shelf, make a new one and reposition the glyph.
- if (nextOrigin[0] > ATLAS_SIZE[0]) {
- nextOrigin = glmatrix.vec2.clone([1.0, shelfBottom + 1.0]);
- glyph.setPixelLowerLeft(nextOrigin, unitMetrics, pixelsPerUnit);
- pixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
- nextOrigin[0] = calculatePixelRectForGlyph(unitMetrics,
- pixelOrigin,
- pixelsPerUnit,
- hint)[2] + 1.0;
- }
-
- // Grow the shelf as necessary.
- const glyphBottom = calculatePixelRectForGlyph(unitMetrics,
- pixelOrigin,
- pixelsPerUnit,
- hint)[3];
- shelfBottom = Math.max(shelfBottom, glyphBottom + 1.0);
- }
-
- // FIXME(pcwalton): Could be more precise if we don't have a full row.
- this._usedSize = glmatrix.vec2.clone([ATLAS_SIZE[0], shelfBottom]);
- }
-
- ensureTexture(renderContext: RenderContext): WebGLTexture {
- if (this._texture != null)
- return this._texture;
-
- const gl = renderContext.gl;
- 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;
- }
-}
-
-export class AtlasGlyph {
- readonly glyphStoreIndex: number;
- readonly glyphKey: GlyphKey;
- readonly origin: glmatrix.vec2;
-
- constructor(glyphStoreIndex: number, glyphKey: GlyphKey) {
- this.glyphStoreIndex = glyphStoreIndex;
- this.glyphKey = glyphKey;
- this.origin = glmatrix.vec2.create();
- }
-
- calculateSubpixelOrigin(pixelsPerUnit: number): glmatrix.vec2 {
- const pixelOrigin = glmatrix.vec2.create();
- glmatrix.vec2.scale(pixelOrigin, this.origin, pixelsPerUnit);
- glmatrix.vec2.round(pixelOrigin, pixelOrigin);
- if (this.glyphKey.subpixel != null)
- pixelOrigin[0] += this.glyphKey.subpixel / SUBPIXEL_GRANULARITY;
- return pixelOrigin;
- }
-
- setPixelLowerLeft(pixelLowerLeft: glmatrix.vec2, metrics: UnitMetrics, pixelsPerUnit: number):
- void {
- const pixelXMin = calculatePixelXMin(metrics, pixelsPerUnit);
- const pixelDescent = calculatePixelDescent(metrics, pixelsPerUnit);
- const pixelOrigin = glmatrix.vec2.clone([pixelLowerLeft[0] - pixelXMin,
- pixelLowerLeft[1] - pixelDescent]);
- this.setPixelOrigin(pixelOrigin, pixelsPerUnit);
- }
-
- private setPixelOrigin(pixelOrigin: glmatrix.vec2, pixelsPerUnit: number): void {
- glmatrix.vec2.scale(this.origin, pixelOrigin, 1.0 / pixelsPerUnit);
- }
-
- get pathID(): number {
- if (this.glyphKey.subpixel == null)
- return this.glyphStoreIndex + 1;
- return this.glyphStoreIndex * SUBPIXEL_GRANULARITY + this.glyphKey.subpixel + 1;
- }
-}
-
-export class GlyphKey {
- readonly id: number;
- readonly subpixel: number | null;
-
- constructor(id: number, subpixel: number | null) {
- this.id = id;
- this.subpixel = subpixel;
- }
-
- get sortKey(): number {
- return this.subpixel == null ? this.id : this.id * SUBPIXEL_GRANULARITY + this.subpixel;
- }
-}
diff --git a/demo/client/src/benchmark.ts b/demo/client/src/benchmark.ts
deleted file mode 100644
index 6a1b9347..00000000
--- a/demo/client/src/benchmark.ts
+++ /dev/null
@@ -1,653 +0,0 @@
-// pathfinder/client/src/benchmark.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-import * as _ from 'lodash';
-import * as opentype from "opentype.js";
-
-import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
-import {SubpixelAAType} from "./aa-strategy";
-import {AppController, DemoAppController, setSwitchInputsValue} from "./app-controller";
-import PathfinderBufferTexture from './buffer-texture';
-import {OrthographicCamera} from './camera';
-import {UniformMap} from './gl-utils';
-import {PathfinderMeshPack, PathfinderPackedMeshes} from "./meshes";
-import {PathTransformBuffers, Renderer} from './renderer';
-import {ShaderMap, ShaderProgramSource} from "./shader-loader";
-import SSAAStrategy from './ssaa-strategy';
-import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
-import {SVGRenderer} from './svg-renderer';
-import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStore, PathfinderFont, TextFrame} from "./text";
-import {computeStemDarkeningAmount, TextRun} from "./text";
-import {assert, lerp, PathfinderError, unwrapNull, unwrapUndef} from "./utils";
-import {DemoView, Timings} from "./view";
-import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy';
-
-const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-
-const DEFAULT_FONT: string = 'nimbus-sans';
-const DEFAULT_SVG_FILE: string = 'tiger';
-
-const TEXT_COLOR: number[] = [0, 0, 0, 255];
-
-// In milliseconds.
-const MIN_RUNTIME: number = 100;
-const MAX_RUNTIME: number = 3000;
-
-const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
- none: NoAAStrategy,
- ssaa: SSAAStrategy,
- xcaa: AdaptiveStencilMeshAAAStrategy,
-};
-
-interface BenchmarkModeMap {
- text: T;
- svg: T;
-}
-
-type BenchmarkMode = 'text' | 'svg';
-
-interface AntialiasingStrategyTable {
- none: typeof NoAAStrategy;
- ssaa: typeof SSAAStrategy;
- xcaa: typeof AdaptiveStencilMeshAAAStrategy;
-}
-
-interface TestParameter {
- start: number;
- stop: number;
- step: number;
-}
-
-const DISPLAY_HEADER_LABELS: BenchmarkModeMap = {
- svg: ["Size (px)", "GPU time (ms)"],
- text: ["Font size (px)", "GPU time per glyph (µs)"],
-};
-
-const TEST_SIZES: BenchmarkModeMap = {
- svg: { start: 64, stop: 2048, step: 16 },
- text: { start: 6, stop: 200, step: 1 },
-};
-
-class BenchmarkAppController extends DemoAppController {
- font: PathfinderFont | null = null;
- textRun: TextRun | null = null;
-
- svgLoader!: SVGLoader;
-
- mode!: BenchmarkMode;
-
- protected get defaultFile(): string {
- if (this.mode === 'text')
- return DEFAULT_FONT;
- return DEFAULT_SVG_FILE;
- }
-
- protected get builtinFileURI(): string {
- if (this.mode === 'text')
- return BUILTIN_FONT_URI;
- return BUILTIN_SVG_URI;
- }
-
- private optionsModal!: HTMLDivElement;
-
- private resultsModal!: HTMLDivElement;
- private resultsTableHeader!: HTMLTableSectionElement;
- private resultsTableBody!: HTMLTableSectionElement;
- private resultsPartitioningTimeLabel!: HTMLSpanElement;
-
- private glyphStore!: GlyphStore;
- private baseMeshes!: PathfinderMeshPack;
- private expandedMeshes!: ExpandedMeshData;
-
- private size!: number;
- private currentRun!: number;
- private startTime!: number;
- private elapsedTimes!: ElapsedTime[];
- private partitionTime!: number;
-
- start(): void {
- super.start();
-
- this.mode = 'text';
-
- this.optionsModal = unwrapNull(document.getElementById('pf-benchmark-modal')) as
- HTMLDivElement;
-
- this.resultsModal = unwrapNull(document.getElementById('pf-benchmark-results-modal')) as
- HTMLDivElement;
- this.resultsTableHeader =
- unwrapNull(document.getElementById('pf-benchmark-results-table-header')) as
- HTMLTableSectionElement;
- this.resultsTableBody =
- unwrapNull(document.getElementById('pf-benchmark-results-table-body')) as
- HTMLTableSectionElement;
- this.resultsPartitioningTimeLabel =
- unwrapNull(document.getElementById('pf-benchmark-results-partitioning-time')) as
- HTMLSpanElement;
-
- const resultsSaveCSVButton =
- unwrapNull(document.getElementById('pf-benchmark-results-save-csv-button'));
- resultsSaveCSVButton.addEventListener('click', () => this.saveCSV(), false);
-
- const resultsCloseButton =
- unwrapNull(document.getElementById('pf-benchmark-results-close-button'));
- resultsCloseButton.addEventListener('click', () => {
- window.jQuery(this.resultsModal).modal('hide');
- }, false);
-
- const runBenchmarkButton = unwrapNull(document.getElementById('pf-run-benchmark-button'));
- runBenchmarkButton.addEventListener('click', () => this.runBenchmark(), false);
-
- const aaLevelFormGroup = unwrapNull(document.getElementById('pf-aa-level-form-group')) as
- HTMLDivElement;
- const benchmarkTextForm = unwrapNull(document.getElementById('pf-benchmark-text-form')) as
- HTMLFormElement;
- const benchmarkSVGForm = unwrapNull(document.getElementById('pf-benchmark-svg-form')) as
- HTMLFormElement;
-
- window.jQuery(this.optionsModal).modal();
-
- const benchmarkTextTab = document.getElementById('pf-benchmark-text-tab') as
- HTMLAnchorElement;
- const benchmarkSVGTab = document.getElementById('pf-benchmark-svg-tab') as
- HTMLAnchorElement;
- window.jQuery(benchmarkTextTab).on('shown.bs.tab', event => {
- this.mode = 'text';
- if (aaLevelFormGroup.parentElement != null)
- aaLevelFormGroup.parentElement.removeChild(aaLevelFormGroup);
- benchmarkTextForm.insertBefore(aaLevelFormGroup, benchmarkTextForm.firstChild);
- this.modeChanged();
- });
- window.jQuery(benchmarkSVGTab).on('shown.bs.tab', event => {
- this.mode = 'svg';
- if (aaLevelFormGroup.parentElement != null)
- aaLevelFormGroup.parentElement.removeChild(aaLevelFormGroup);
- benchmarkSVGForm.insertBefore(aaLevelFormGroup, benchmarkSVGForm.firstChild);
- this.modeChanged();
- });
-
- this.loadInitialFile(this.builtinFileURI);
- }
-
- protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
- switch (this.mode) {
- case 'text':
- this.textFileLoaded(fileData, builtinName);
- return;
- case 'svg':
- this.svgFileLoaded(fileData, builtinName);
- return;
- }
- }
-
- protected createView(areaLUT: HTMLImageElement,
- gammaLUT: HTMLImageElement,
- commonShaderSource: string,
- shaderSources: ShaderMap):
- BenchmarkTestView {
- return new BenchmarkTestView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
- }
-
- private modeChanged(): void {
- this.loadInitialFile(this.builtinFileURI);
- if (this.aaLevelSelect != null)
- this.aaLevelSelect.selectedIndex = 0;
- if (this.subpixelAASelect != null)
- this.subpixelAASelect.selectedIndex = 0;
- this.updateAALevel();
- }
-
- private textFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
- const font = new PathfinderFont(fileData, builtinName);
- this.font = font;
-
- const textRun = new TextRun(STRING, [0, 0], font);
- textRun.layout();
- this.textRun = textRun;
- const textFrame = new TextFrame([textRun], font);
-
- const glyphIDs = textFrame.allGlyphIDs;
- glyphIDs.sort((a, b) => a - b);
- this.glyphStore = new GlyphStore(font, glyphIDs);
-
- this.glyphStore.partition().then(result => {
- this.baseMeshes = result.meshes;
-
- const partitionTime = result.time / this.glyphStore.glyphIDs.length * 1e6;
- const timeLabel = this.resultsPartitioningTimeLabel;
- while (timeLabel.firstChild != null)
- timeLabel.removeChild(timeLabel.firstChild);
- timeLabel.appendChild(document.createTextNode("" + partitionTime));
-
- const expandedMeshes = textFrame.expandMeshes(this.baseMeshes, glyphIDs);
- this.expandedMeshes = expandedMeshes;
-
- this.view.then(view => {
- view.recreateRenderer();
- view.attachMeshes([expandedMeshes.meshes]);
- });
- });
- }
-
- private svgFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
- this.svgLoader = new SVGLoader;
- this.svgLoader.loadFile(fileData);
- this.svgLoader.partition().then(meshes => {
- this.view.then(view => {
- view.recreateRenderer();
- view.attachMeshes([new PathfinderPackedMeshes(meshes)]);
- view.initCameraBounds(this.svgLoader.svgViewBox);
- });
- });
- }
-
- private reset(): void {
- this.currentRun = 0;
- this.startTime = Date.now();
- }
-
- private runBenchmark(): void {
- window.jQuery(this.optionsModal).modal('hide');
-
- this.reset();
- this.elapsedTimes = [];
- this.size = TEST_SIZES[this.mode].start;
- this.view.then(view => this.runOneBenchmarkTest(view));
- }
-
- private runDone(): boolean {
- const totalElapsedTime = Date.now() - this.startTime;
- if (totalElapsedTime < MIN_RUNTIME)
- return false;
- if (totalElapsedTime >= MAX_RUNTIME)
- return true;
-
- // Compute median absolute devation.
- const elapsedTime = unwrapUndef(_.last(this.elapsedTimes));
- elapsedTime.times.sort((a, b) => a - b);
- const median = unwrapNull(computeMedian(elapsedTime.times));
- const absoluteDeviations = elapsedTime.times.map(time => Math.abs(time - median));
- absoluteDeviations.sort((a, b) => a - b);
- const medianAbsoluteDeviation = unwrapNull(computeMedian(absoluteDeviations));
- const medianAbsoluteDeviationFraction = medianAbsoluteDeviation / median;
- return medianAbsoluteDeviationFraction <= 0.01;
- }
-
- private runOneBenchmarkTest(view: BenchmarkTestView): void {
- const renderedPromise = new Promise((resolve, reject) => {
- view.renderingPromiseCallback = resolve;
- view.size = this.size;
- });
- renderedPromise.then(elapsedTime => {
- if (this.currentRun === 0)
- this.elapsedTimes.push(new ElapsedTime(this.size));
- unwrapUndef(_.last(this.elapsedTimes)).times.push(elapsedTime);
-
- this.currentRun++;
- if (this.runDone()) {
- this.reset();
-
- if (this.size >= TEST_SIZES[this.mode].stop) {
- this.showResults();
- return;
- }
-
- this.size += TEST_SIZES[this.mode].step;
- }
-
- this.runOneBenchmarkTest(view);
- });
- }
-
- private showResults(): void {
- while (this.resultsTableHeader.lastChild != null)
- this.resultsTableHeader.removeChild(this.resultsTableHeader.lastChild);
- while (this.resultsTableBody.lastChild != null)
- this.resultsTableBody.removeChild(this.resultsTableBody.lastChild);
-
- const tr = document.createElement('tr');
- for (const headerLabel of DISPLAY_HEADER_LABELS[this.mode]) {
- const th = document.createElement('th');
- th.appendChild(document.createTextNode(headerLabel));
- tr.appendChild(th);
- }
- this.resultsTableHeader.appendChild(tr);
-
- for (const elapsedTime of this.elapsedTimes) {
- const tr = document.createElement('tr');
- const sizeTH = document.createElement('th');
- const timeTD = document.createElement('td');
- sizeTH.appendChild(document.createTextNode("" + elapsedTime.size));
- const time = this.mode === 'svg' ? elapsedTime.timeInMS : elapsedTime.time;
- timeTD.appendChild(document.createTextNode("" + time));
- sizeTH.scope = 'row';
- tr.appendChild(sizeTH);
- tr.appendChild(timeTD);
- this.resultsTableBody.appendChild(tr);
- }
-
- window.jQuery(this.resultsModal).modal();
- }
-
- private saveCSV(): void {
- let output = "Size,Time\n";
- for (const elapsedTime of this.elapsedTimes) {
- const time = this.mode === 'svg' ? elapsedTime.timeInMS : elapsedTime.time;
- output += `${elapsedTime.size},${time}\n`;
- }
-
- // https://stackoverflow.com/a/30832210
- const file = new Blob([output], {type: 'text/csv'});
- const a = document.createElement('a');
- const url = URL.createObjectURL(file);
- a.href = url;
- a.download = "pathfinder-benchmark-results.csv";
- document.body.appendChild(a);
- a.click();
-
- window.setTimeout(() => {
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- }, 0);
- }
-}
-
-class BenchmarkTestView extends DemoView {
- renderer!: BenchmarkTextRenderer | BenchmarkSVGRenderer;
-
- readonly appController: BenchmarkAppController;
-
- renderingPromiseCallback: ((time: number) => void) | null = null;
-
- get camera(): OrthographicCamera {
- return this.renderer.camera;
- }
-
- set size(newSize: number) {
- if (this.renderer instanceof BenchmarkTextRenderer) {
- this.renderer.pixelsPerEm = newSize;
- } else if (this.renderer instanceof BenchmarkSVGRenderer) {
- const camera = this.renderer.camera;
- camera.zoomToSize(newSize);
- camera.center();
- }
- }
-
- constructor(appController: BenchmarkAppController,
- areaLUT: HTMLImageElement,
- gammaLUT: HTMLImageElement,
- commonShaderSource: string,
- shaderSources: ShaderMap) {
- super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
- this.appController = appController;
- this.recreateRenderer();
- this.resizeToFit(true);
- }
-
- recreateRenderer(): void {
- switch (this.appController.mode) {
- case 'svg':
- this.renderer = new BenchmarkSVGRenderer(this);
- break;
- case 'text':
- this.renderer = new BenchmarkTextRenderer(this);
- break;
- }
- }
-
- initCameraBounds(viewBox: glmatrix.vec4): void {
- if (this.renderer instanceof BenchmarkSVGRenderer)
- this.renderer.initCameraBounds(viewBox);
- }
-
- protected renderingFinished(): void {
- if (this.renderingPromiseCallback == null)
- return;
-
- const appController = this.appController;
- let time = this.renderer.lastTimings.rendering * 1000.0;
- if (appController.mode === 'text')
- time /= unwrapNull(appController.textRun).glyphIDs.length;
- this.renderingPromiseCallback(time);
- }
-}
-
-class BenchmarkTextRenderer extends Renderer {
- renderContext!: BenchmarkTestView;
-
- camera: OrthographicCamera;
-
- needsStencil: boolean = false;
- isMulticolor: boolean = false;
-
- get destFramebuffer(): WebGLFramebuffer | null {
- return null;
- }
-
- get bgColor(): glmatrix.vec4 {
- return glmatrix.vec4.clone([1.0, 1.0, 1.0, 0.0]);
- }
-
- get fgColor(): glmatrix.vec4 {
- return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
- }
-
- get destAllocatedSize(): glmatrix.vec2 {
- const canvas = this.renderContext.canvas;
- return glmatrix.vec2.clone([canvas.width, canvas.height]);
- }
-
- get destUsedSize(): glmatrix.vec2 {
- return this.destAllocatedSize;
- }
-
- get allowSubpixelAA(): boolean {
- return true;
- }
-
- get emboldenAmount(): glmatrix.vec2 {
- return this.stemDarkeningAmount;
- }
-
- get pixelsPerEm(): number {
- return this._pixelsPerEm;
- }
-
- set pixelsPerEm(newPixelsPerEm: number) {
- this._pixelsPerEm = newPixelsPerEm;
- this.uploadPathTransforms(1);
- this.renderContext.setDirty();
- }
-
- protected get usedSizeFactor(): glmatrix.vec2 {
- return glmatrix.vec2.clone([1.0, 1.0]);
- }
-
- protected get worldTransform(): glmatrix.mat4 {
- const canvas = this.renderContext.canvas;
-
- const transform = glmatrix.mat4.create();
- const translation = this.camera.translation;
- glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
- glmatrix.mat4.scale(transform, transform, [2.0 / canvas.width, 2.0 / canvas.height, 1.0]);
- glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
- glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
-
- const pixelsPerUnit = this.pixelsPerUnit;
- glmatrix.mat4.scale(transform, transform, [pixelsPerUnit, pixelsPerUnit, 1.0]);
-
- return transform;
- }
-
- protected get objectCount(): number {
- return this.meshBuffers == null ? 0 : this.meshBuffers.length;
- }
-
- private _pixelsPerEm: number = 32.0;
-
- private get pixelsPerUnit(): number {
- const font = unwrapNull(this.renderContext.appController.font);
- return this._pixelsPerEm / font.opentypeFont.unitsPerEm;
- }
-
- private get stemDarkeningAmount(): glmatrix.vec2 {
- return computeStemDarkeningAmount(this._pixelsPerEm, this.pixelsPerUnit);
- }
-
- constructor(renderContext: BenchmarkTestView) {
- super(renderContext);
-
- this.camera = new OrthographicCamera(renderContext.canvas, { fixed: true });
- this.camera.onPan = () => renderContext.setDirty();
- this.camera.onZoom = () => renderContext.setDirty();
- }
-
- attachMeshes(meshes: PathfinderPackedMeshes[]): void {
- super.attachMeshes(meshes);
-
- this.uploadPathColors(1);
- this.uploadPathTransforms(1);
- }
-
- pathCountForObject(objectIndex: number): number {
- return STRING.length;
- }
-
- pathBoundingRects(objectIndex: number): Float32Array {
- const appController = this.renderContext.appController;
- const font = unwrapNull(appController.font);
-
- const boundingRects = new Float32Array((STRING.length + 1) * 4);
-
- for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
- const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex];
-
- const metrics = font.metricsForGlyph(glyphID);
- if (metrics == null)
- continue;
-
- boundingRects[(glyphIndex + 1) * 4 + 0] = metrics.xMin;
- boundingRects[(glyphIndex + 1) * 4 + 1] = metrics.yMin;
- boundingRects[(glyphIndex + 1) * 4 + 2] = metrics.xMax;
- boundingRects[(glyphIndex + 1) * 4 + 3] = metrics.yMax;
- }
-
- return boundingRects;
- }
-
- setHintsUniform(uniforms: UniformMap): void {
- this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
- }
-
- pathTransformsForObject(objectIndex: number): PathTransformBuffers {
- const appController = this.renderContext.appController;
- const canvas = this.renderContext.canvas;
- const font = unwrapNull(appController.font);
-
- const pathTransforms = this.createPathTransformBuffers(STRING.length);
-
- let currentX = 0, currentY = 0;
- const availableWidth = canvas.width / this.pixelsPerUnit;
- const lineHeight = font.opentypeFont.lineHeight();
-
- for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
- const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex];
- pathTransforms.st.set([1, 1, currentX, currentY], (glyphIndex + 1) * 4);
-
- currentX += font.opentypeFont.glyphs.get(glyphID).advanceWidth;
- if (currentX > availableWidth) {
- currentX = 0;
- currentY += lineHeight;
- }
- }
-
- return pathTransforms;
- }
-
- protected createAAStrategy(aaType: AntialiasingStrategyName,
- aaLevel: number,
- subpixelAA: SubpixelAAType):
- AntialiasingStrategy {
- return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
- }
-
- protected compositeIfNecessary(): void {}
-
- protected updateTimings(timings: Timings): void {
- // TODO(pcwalton)
- }
-
- protected pathColorsForObject(objectIndex: number): Uint8Array {
- const pathColors = new Uint8Array(4 * (STRING.length + 1));
- for (let pathIndex = 0; pathIndex < STRING.length; pathIndex++)
- pathColors.set(TEXT_COLOR, (pathIndex + 1) * 4);
- return pathColors;
- }
-
- protected directCurveProgramName(): keyof ShaderMap {
- return 'directCurve';
- }
-
- protected directInteriorProgramName(): keyof ShaderMap {
- return 'directInterior';
- }
-}
-
-class BenchmarkSVGRenderer extends SVGRenderer {
- renderContext!: BenchmarkTestView;
-
- protected get loader(): SVGLoader {
- return this.renderContext.appController.svgLoader;
- }
-
- protected get canvas(): HTMLCanvasElement {
- return this.renderContext.canvas;
- }
-
- constructor(renderContext: BenchmarkTestView) {
- super(renderContext, {sizeToFit: false});
- }
-}
-
-function computeMedian(values: number[]): number | null {
- if (values.length === 0)
- return null;
- const mid = values.length / 2;
- if (values.length % 2 === 1)
- return values[Math.floor(mid)];
- return lerp(values[mid - 1], values[mid], 0.5);
-}
-
-class ElapsedTime {
- readonly size: number;
- readonly times: number[];
-
- constructor(size: number) {
- this.size = size;
- this.times = [];
- }
-
- get time(): number {
- const median = computeMedian(this.times);
- return median == null ? 0.0 : median;
- }
-
- get timeInMS(): number {
- return this.time / 1000.0;
- }
-}
-
-function main() {
- const controller = new BenchmarkAppController;
- window.addEventListener('load', () => controller.start(), false);
-}
-
-main();
diff --git a/demo/client/src/buffer-texture.ts b/demo/client/src/buffer-texture.ts
deleted file mode 100644
index e93ddab8..00000000
--- a/demo/client/src/buffer-texture.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-// pathfinder/client/src/buffer-texture.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-
-import {setTextureParameters, UniformMap} from './gl-utils';
-import {assert, expectNotNull} from './utils';
-
-export default class PathfinderBufferTexture {
- readonly texture: WebGLTexture;
- readonly uniformName: string;
-
- private size: glmatrix.vec2;
- private glType: number;
- private destroyed: boolean;
-
- constructor(gl: WebGLRenderingContext, uniformName: string) {
- this.texture = expectNotNull(gl.createTexture(), "Failed to create buffer texture!");
- this.size = glmatrix.vec2.create();
- this.uniformName = uniformName;
- this.glType = 0;
- this.destroyed = false;
- }
-
- destroy(gl: WebGLRenderingContext): void {
- assert(!this.destroyed, "Buffer texture destroyed!");
- gl.deleteTexture(this.texture);
- this.destroyed = true;
- }
-
- upload(gl: WebGLRenderingContext, data: Float32Array | Uint8Array): void {
- assert(!this.destroyed, "Buffer texture destroyed!");
-
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, this.texture);
-
- const glType = data instanceof Float32Array ? gl.FLOAT : gl.UNSIGNED_BYTE;
- const areaNeeded = Math.ceil(data.length / 4);
- if (glType !== this.glType || areaNeeded > this.area) {
- const sideLength = nextPowerOfTwo(Math.ceil(Math.sqrt(areaNeeded)));
- this.size = glmatrix.vec2.clone([sideLength, sideLength]);
- this.glType = glType;
-
- gl.texImage2D(gl.TEXTURE_2D,
- 0,
- gl.RGBA,
- sideLength,
- sideLength,
- 0,
- gl.RGBA,
- glType,
- null);
- setTextureParameters(gl, gl.NEAREST);
- }
-
- const mainDimensions = glmatrix.vec4.clone([
- 0,
- 0,
- this.size[0],
- (areaNeeded / this.size[0]) | 0,
- ]);
- const remainderDimensions = glmatrix.vec4.clone([
- 0,
- mainDimensions[3],
- areaNeeded % this.size[0],
- 1,
- ]);
- const splitIndex = mainDimensions[2] * mainDimensions[3] * 4;
-
- if (mainDimensions[2] > 0 && mainDimensions[3] > 0) {
- gl.texSubImage2D(gl.TEXTURE_2D,
- 0,
- mainDimensions[0],
- mainDimensions[1],
- mainDimensions[2],
- mainDimensions[3],
- gl.RGBA,
- this.glType,
- data.slice(0, splitIndex));
- }
-
- if (remainderDimensions[2] > 0) {
- // Round data up to a multiple of 4 elements if necessary.
- let remainderLength = data.length - splitIndex;
- let remainder: Float32Array | Uint8Array;
- if (remainderLength % 4 === 0) {
- remainder = data.slice(splitIndex);
- } else {
- remainderLength += 4 - remainderLength % 4;
- remainder = new (data.constructor as any)(remainderLength);
- remainder.set(data.slice(splitIndex));
- }
-
- gl.texSubImage2D(gl.TEXTURE_2D,
- 0,
- remainderDimensions[0],
- remainderDimensions[1],
- remainderDimensions[2],
- remainderDimensions[3],
- gl.RGBA,
- this.glType,
- remainder);
- }
- }
-
- bind(gl: WebGLRenderingContext, uniforms: UniformMap, textureUnit: number): void {
- assert(!this.destroyed, "Buffer texture destroyed!");
-
- gl.activeTexture(gl.TEXTURE0 + textureUnit);
- gl.bindTexture(gl.TEXTURE_2D, this.texture);
- gl.uniform2i(uniforms[`${this.uniformName}Dimensions`], this.size[0], this.size[1]);
- gl.uniform1i(uniforms[this.uniformName], textureUnit);
- }
-
- private get area(): number {
- assert(!this.destroyed, "Buffer texture destroyed!");
- return this.size[0] * this.size[1];
- }
-}
-
-/// https://stackoverflow.com/a/466242
-function nextPowerOfTwo(n: number): number {
- n--;
- n |= n >> 1;
- n |= n >> 2;
- n |= n >> 4;
- n |= n >> 8;
- n |= n >> 16;
- return n + 1;
-}
diff --git a/demo/client/src/camera.ts b/demo/client/src/camera.ts
deleted file mode 100644
index 505e5178..00000000
--- a/demo/client/src/camera.ts
+++ /dev/null
@@ -1,506 +0,0 @@
-// pathfinder/client/src/camera.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-import * as _ from 'lodash';
-
-import {EPSILON, unwrapNull} from "./utils";
-import {PathfinderView} from "./view";
-
-const PIXELS_PER_LINE: number = 16.0;
-
-const ORTHOGRAPHIC_ZOOM_SPEED: number = 1.0 / 100.0;
-
-const ORTHOGRAPHIC_ZOOM_IN_FACTOR: number = 1.2;
-const ORTHOGRAPHIC_ZOOM_OUT_FACTOR: number = 1.0 / ORTHOGRAPHIC_ZOOM_IN_FACTOR;
-
-const ORTHOGRAPHIC_DEFAULT_MIN_SCALE: number = 0.01;
-const ORTHOGRAPHIC_DEFAULT_MAX_SCALE: number = 1000.0;
-
-const PERSPECTIVE_MOVEMENT_SPEED: number = 10.0;
-const PERSPECTIVE_ROTATION_SPEED: number = 1.0 / 300.0;
-
-const PERSPECTIVE_MOVEMENT_VECTORS: PerspectiveMovementVectors = _.fromPairs([
- ['W'.charCodeAt(0), glmatrix.vec3.fromValues(0, 0, PERSPECTIVE_MOVEMENT_SPEED)],
- ['A'.charCodeAt(0), glmatrix.vec3.fromValues(PERSPECTIVE_MOVEMENT_SPEED, 0, 0)],
- ['S'.charCodeAt(0), glmatrix.vec3.fromValues(0, 0, -PERSPECTIVE_MOVEMENT_SPEED)],
- ['D'.charCodeAt(0), glmatrix.vec3.fromValues(-PERSPECTIVE_MOVEMENT_SPEED, 0, 0)],
-]);
-
-const PERSPECTIVE_MOVEMENT_INTERVAL_DELAY: number = 10;
-
-const PERSPECTIVE_INITIAL_TRANSLATION: glmatrix.vec3 =
- glmatrix.vec3.clone([1750.0, 700.0, -1750.0]);
-const PERSPECTIVE_INITIAL_ROTATION: glmatrix.vec2 = glmatrix.vec2.clone([Math.PI * 0.25, 0.0]);
-
-const PERSPECTIVE_OUTER_COLLISION_EXTENT: number = 3000.0;
-const PERSPECTIVE_HITBOX_RADIUS: number = 1.0;
-
-const KEYCODES = ["W", "A", "S", "D"].map(x => x.charCodeAt(0));
-
-export interface OrthographicCameraOptions {
- fixed?: boolean;
- minScale?: number;
- maxScale?: number;
- scaleBounds?: boolean;
- ignoreBounds?: boolean;
-}
-
-export interface PerspectiveCameraOptions {
- innerCollisionExtent?: number;
-}
-
-interface PerspectiveMovementVectors {
- [keyCode: number]: glmatrix.vec3;
-}
-
-interface PerspectiveMovementKeys {
- [keyCode: number]: boolean;
-}
-
-export interface CameraView {
- readonly width: number;
- readonly height: number;
- readonly classList: DOMTokenList | null;
-
- addEventListener(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;
- }
-
- abstract zoom(scale: number): void;
- abstract zoomIn(): void;
- abstract zoomOut(): void;
- abstract rotate(newAngle: number): void;
-}
-
-export class OrthographicCamera extends Camera {
- onPan: (() => void) | null;
- onZoom: (() => void) | null;
- onRotate: (() => void) | null;
-
- translation!: glmatrix.vec2;
- scale!: number;
- rotationAngle!: number;
-
- private _bounds: glmatrix.vec4;
-
- private readonly fixed: boolean;
- private readonly minScale: number;
- private readonly maxScale: number;
- private readonly scaleBounds: boolean;
- private readonly ignoreBounds: boolean;
-
- constructor(canvas: CameraView, options?: OrthographicCameraOptions) {
- super(canvas);
-
- if (options == null)
- options = {};
-
- this.fixed = !!options.fixed;
- this.minScale = _.defaultTo(options.minScale, ORTHOGRAPHIC_DEFAULT_MIN_SCALE);
- this.maxScale = _.defaultTo(options.maxScale, ORTHOGRAPHIC_DEFAULT_MAX_SCALE);
- this.scaleBounds = !!options.scaleBounds;
- this.ignoreBounds = !!options.ignoreBounds;
-
- this.reset();
-
- this._bounds = glmatrix.vec4.create();
-
- if (!this.fixed) {
- this.canvas.addEventListener('wheel', event => this.onWheel(event), false);
- this.canvas.addEventListener('mousedown', event => this.onMouseDown(event), false);
- this.canvas.addEventListener('mouseup', event => this.onMouseUp(event), false);
- this.canvas.addEventListener('mousemove', event => this.onMouseMove(event), false);
- if (this.canvas.classList != null)
- this.canvas.classList.add('pf-draggable');
- } else {
- if (this.canvas.classList != null)
- this.canvas.classList.remove('pf-draggable');
- }
-
- this.onPan = null;
- this.onZoom = null;
- this.onRotate = null;
- }
-
- onWheel(event: MouseWheelEvent): void {
- if (this.canvas == null)
- throw new Error("onWheel() with no canvas?!");
-
- event.preventDefault();
-
- if (!event.ctrlKey) {
- const delta = glmatrix.vec2.fromValues(-event.deltaX, event.deltaY);
- if (event.deltaMode === event.DOM_DELTA_LINE)
- glmatrix.vec2.scale(delta, delta, PIXELS_PER_LINE);
- this.pan(delta);
- return;
- }
-
- // Zoom event: see https://developer.mozilla.org/en-US/docs/Web/Events/wheel
- const mouseLocation = glmatrix.vec2.fromValues(event.clientX, event.clientY);
- const canvasLocation = this.canvas.getBoundingClientRect();
- mouseLocation[0] -= canvasLocation.left;
- mouseLocation[1] = canvasLocation.bottom - mouseLocation[1];
- glmatrix.vec2.scale(mouseLocation, mouseLocation, window.devicePixelRatio);
-
- const scale = 1.0 - event.deltaY * window.devicePixelRatio * ORTHOGRAPHIC_ZOOM_SPEED;
- this.doZoom(scale, mouseLocation);
- }
-
- zoomToFit(): void {
- const size = this.objectSize();
- this.scale = Math.min(this.canvas.width / size[0], this.canvas.height / size[1]);
- this.center();
- }
-
- center(): void {
- const upperLeft = glmatrix.vec2.clone([this._bounds[0], this._bounds[1]]);
- const lowerRight = glmatrix.vec2.clone([this._bounds[2], this._bounds[3]]);
-
- this.translation = glmatrix.vec2.create();
- glmatrix.vec2.lerp(this.translation, upperLeft, lowerRight, 0.5);
- glmatrix.vec2.scale(this.translation, this.translation, -this.scale);
- this.translation[0] += this.canvas.width * 0.5;
- this.translation[1] += this.canvas.height * 0.5;
- }
-
- zoomToSize(newSize: number): void {
- this.reset();
-
- const size = this.objectSize();
- const length = Math.max(size[0], size[1]);
- this.zoom(newSize / length);
- }
-
- zoom(scale: number): void {
- this.doZoom(scale, this.centerPoint);
- }
-
- zoomIn(): void {
- this.doZoom(ORTHOGRAPHIC_ZOOM_IN_FACTOR, this.centerPoint);
- }
-
- zoomOut(): void {
- this.doZoom(ORTHOGRAPHIC_ZOOM_OUT_FACTOR, this.centerPoint);
- }
-
- rotate(newAngle: number): void {
- this.rotationAngle = newAngle;
-
- if (this.onRotate != null)
- this.onRotate();
- }
-
- private objectSize(): glmatrix.vec2 {
- const upperLeft = glmatrix.vec2.clone([this._bounds[0], this._bounds[1]]);
- const lowerRight = glmatrix.vec2.clone([this._bounds[2], this._bounds[3]]);
- const width = this._bounds[2] - this._bounds[0];
- const height = Math.abs(this._bounds[1] - this._bounds[3]);
- return glmatrix.vec2.clone([width, height]);
- }
-
- private reset(): void {
- this.translation = glmatrix.vec2.create();
- this.scale = 1.0;
- this.rotationAngle = 0.0;
- }
-
- private onMouseDown(event: MouseEvent): void {
- if (this.canvas.classList != null)
- this.canvas.classList.add('pf-grabbing');
- }
-
- private onMouseUp(event: MouseEvent): void {
- if (this.canvas.classList != null)
- this.canvas.classList.remove('pf-grabbing');
- }
-
- private onMouseMove(event: MouseEvent): void {
- if ((event.buttons & 1) !== 0)
- this.pan(glmatrix.vec2.fromValues(event.movementX, -event.movementY));
- }
-
- private pan(delta: glmatrix.vec2): void {
- // Pan event.
- glmatrix.vec2.scale(delta, delta, window.devicePixelRatio);
- glmatrix.vec2.add(this.translation, this.translation, delta);
-
- this.clampViewport();
-
- if (this.onPan != null)
- this.onPan();
- }
-
- private clampViewport() {
- if (this.ignoreBounds)
- return;
-
- const bounds = glmatrix.vec4.clone(this.bounds);
- if (!this.scaleBounds)
- glmatrix.vec4.scale(bounds, bounds, this.scale);
-
- for (let axis = 0; axis < 2; axis++) {
- const viewportLength = axis === 0 ? this.canvas.width : this.canvas.height;
- const axisBounds = [bounds[axis + 0], bounds[axis + 2]];
- const boundsLength = axisBounds[1] - axisBounds[0];
- if (viewportLength < boundsLength) {
- // Viewport must be inside bounds.
- this.translation[axis] = _.clamp(this.translation[axis],
- viewportLength - axisBounds[1],
- -axisBounds[0]);
- } else {
- // Bounds must be inside viewport.
- this.translation[axis] = _.clamp(this.translation[axis],
- -axisBounds[0],
- viewportLength - axisBounds[1]);
- }
- }
- }
-
- private doZoom(scale: number, point: glmatrix.vec2): void {
- const absoluteTranslation = glmatrix.vec2.create();
- glmatrix.vec2.sub(absoluteTranslation, this.translation, point);
- glmatrix.vec2.scale(absoluteTranslation, absoluteTranslation, 1.0 / this.scale);
-
- this.scale = _.clamp(this.scale * scale, this.minScale, this.maxScale);
-
- glmatrix.vec2.scale(absoluteTranslation, absoluteTranslation, this.scale);
- glmatrix.vec2.add(this.translation, absoluteTranslation, point);
-
- this.clampViewport();
-
- if (this.onZoom != null)
- this.onZoom();
- }
-
- private get centerPoint(): glmatrix.vec2 {
- return glmatrix.vec2.clone([this.canvas.width * 0.5, this.canvas.height * 0.5]);
- }
-
- get bounds(): glmatrix.vec4 {
- const bounds = glmatrix.vec4.clone(this._bounds);
- if (this.scaleBounds)
- glmatrix.vec4.scale(bounds, bounds, this.scale);
- return bounds;
- }
-
- set bounds(newBounds: glmatrix.vec4) {
- this._bounds = glmatrix.vec4.clone(newBounds);
- }
-}
-
-export class PerspectiveCamera extends Camera {
- canvas!: HTMLCanvasElement;
-
- onChange: (() => void) | null;
-
- translation: glmatrix.vec3;
-
- /// Yaw and pitch Euler angles.
- rotation: glmatrix.vec2;
-
- private movementDelta: glmatrix.vec3;
- // If W, A, S, D are pressed
- private wasdPress: PerspectiveMovementKeys;
- private movementInterval: number | null;
-
- private readonly innerCollisionExtent: number;
-
- private vrRotationMatrix: glmatrix.mat4 | null;
-
- constructor(canvas: HTMLCanvasElement, options?: PerspectiveCameraOptions) {
- super(canvas);
-
- if (options == null)
- options = {};
- this.innerCollisionExtent = options.innerCollisionExtent || 0.0;
-
- this.translation = glmatrix.vec3.clone(PERSPECTIVE_INITIAL_TRANSLATION);
- this.rotation = glmatrix.vec2.clone(PERSPECTIVE_INITIAL_ROTATION);
- this.movementDelta = glmatrix.vec3.create();
- this.movementInterval = null;
-
- this.canvas.addEventListener('mousedown', event => this.onMouseDown(event), false);
- this.canvas.addEventListener('mouseup', event => this.onMouseUp(event), false);
- this.canvas.addEventListener('mousemove', event => this.onMouseMove(event), false);
-
- window.addEventListener('keydown', event => this.onKeyDown(event), false);
- window.addEventListener('keyup', event => this.onKeyUp(event), false);
-
- this.onChange = null;
- this.vrRotationMatrix = null;
-
- this.wasdPress = _.fromPairs([
- ['W'.charCodeAt(0), false],
- ['A'.charCodeAt(0), false],
- ['S'.charCodeAt(0), false],
- ['D'.charCodeAt(0), false],
- ]);
- }
-
- zoom(scale: number): void {
- // TODO(pcwalton)
- }
-
- zoomIn(): void {
- // TODO(pcwalton)
- }
-
- zoomOut(): void {
- // TODO(pcwalton)
- }
-
- rotate(newAngle: number): void {
- // TODO(pcwalton)
- }
-
- setView(rotation: glmatrix.mat4, pose: VRPose): void {
- this.vrRotationMatrix = rotation;
- }
-
- private onMouseDown(event: MouseEvent): void {
- if (document.pointerLockElement !== this.canvas) {
- this.canvas.requestPointerLock();
- return;
- }
-
- this.movementDelta = glmatrix.vec3.fromValues(0.0, 0.0, PERSPECTIVE_MOVEMENT_SPEED);
- if (event.button !== 1)
- this.movementDelta[0] = -this.movementDelta[0];
-
- this.startMoving();
- }
-
- private onMouseUp(event: MouseEvent): void {
- this.stopMoving();
- }
-
- private onMouseMove(event: MouseEvent): void {
- if (document.pointerLockElement !== this.canvas)
- return;
-
- this.rotation[0] += event.movementX * PERSPECTIVE_ROTATION_SPEED;
-
- const trialYRotation = this.rotation[1] + event.movementY * PERSPECTIVE_ROTATION_SPEED;
- this.rotation[1] = _.clamp(trialYRotation, -Math.PI * 0.5, Math.PI * 0.5);
-
- if (this.onChange != null)
- this.onChange();
- }
-
- private onKeyDown(event: KeyboardEvent): void {
- if (PERSPECTIVE_MOVEMENT_VECTORS.hasOwnProperty(event.keyCode)) {
- // keyDown will be repeated on prolonged holds of the key,
- // don't do extra computation in that case
- if (this.wasdPress[event.keyCode])
- return;
- this.wasdPress[event.keyCode] = true;
- this.updateMovementDelta();
- this.startMoving();
- }
- }
-
- private onKeyUp(event: KeyboardEvent): void {
- if (PERSPECTIVE_MOVEMENT_VECTORS.hasOwnProperty(event.keyCode)) {
- this.wasdPress[event.keyCode] = false;
- if (this.updateMovementDelta()) {
- this.stopMoving();
- }
- }
- }
-
- // Updates the movementDelta vector based on what keys are currently pressed
- // Returns true if the vector is now empty
- private updateMovementDelta(): boolean {
- this.movementDelta = glmatrix.vec3.create();
- let empty = true;
- for (const key of KEYCODES) {
- if (this.wasdPress[key]) {
- glmatrix.vec3.add(this.movementDelta,
- this.movementDelta,
- PERSPECTIVE_MOVEMENT_VECTORS[key]);
- empty = false;
- }
- }
- return empty;
- }
-
- private startMoving(): void {
- if (this.movementInterval == null)
- this.movementInterval = window.setInterval(() => this.move(),
- PERSPECTIVE_MOVEMENT_INTERVAL_DELAY);
- }
-
- private stopMoving(): void {
- if (this.movementInterval != null) {
- window.clearInterval(this.movementInterval);
- this.movementInterval = null;
- this.movementDelta = glmatrix.vec3.create();
- }
- }
-
- private move() {
- const invRotationMatrix = glmatrix.mat4.create();
- glmatrix.mat4.invert(invRotationMatrix, this.rotationMatrix);
-
- const delta = glmatrix.vec3.clone(this.movementDelta);
- glmatrix.vec3.transformMat4(delta, delta, invRotationMatrix);
-
- const trialTranslation = glmatrix.vec3.create();
- glmatrix.vec3.add(trialTranslation, this.translation, delta);
-
- // TODO(pcwalton): Sliding…
- const absoluteTrialTranslationX = Math.abs(trialTranslation[0]);
- const absoluteTrialTranslationZ = Math.abs(trialTranslation[2]);
- if (absoluteTrialTranslationX < this.innerCollisionExtent + PERSPECTIVE_HITBOX_RADIUS &&
- absoluteTrialTranslationZ < this.innerCollisionExtent + PERSPECTIVE_HITBOX_RADIUS) {
- return;
- }
-
- if (absoluteTrialTranslationX > PERSPECTIVE_OUTER_COLLISION_EXTENT -
- PERSPECTIVE_HITBOX_RADIUS) {
- trialTranslation[0] = Math.sign(trialTranslation[0]) *
- (PERSPECTIVE_OUTER_COLLISION_EXTENT - PERSPECTIVE_HITBOX_RADIUS);
- }
- if (absoluteTrialTranslationZ > PERSPECTIVE_OUTER_COLLISION_EXTENT -
- PERSPECTIVE_HITBOX_RADIUS) {
- trialTranslation[2] = Math.sign(trialTranslation[2]) *
- (PERSPECTIVE_OUTER_COLLISION_EXTENT - PERSPECTIVE_HITBOX_RADIUS);
- }
-
- this.translation = trialTranslation;
-
- if (this.onChange != null)
- this.onChange();
- }
-
- get rotationMatrix(): glmatrix.mat4 {
- if (this.vrRotationMatrix != null) {
- // This actually is a rotation + translation matrix, but it works
- return this.vrRotationMatrix;
- }
- const matrix = glmatrix.mat4.create();
- glmatrix.mat4.fromXRotation(matrix, this.rotation[1]);
- glmatrix.mat4.rotateY(matrix, matrix, this.rotation[0]);
- return matrix;
- }
-}
diff --git a/demo/client/src/file-picker.ts b/demo/client/src/file-picker.ts
deleted file mode 100644
index cd0d3b07..00000000
--- a/demo/client/src/file-picker.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-// pathfinder/client/src/file-picker.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import {expectNotNull} from "./utils";
-
-export class FilePickerView {
- static create(): FilePickerView | null {
- const element = document.getElementById('pf-file-select') as (HTMLInputElement | null);
- return element == null ? null : new FilePickerView(element);
- }
-
- onFileLoaded: ((fileData: ArrayBuffer) => void) | null;
-
- private readonly element: HTMLInputElement;
-
- private constructor(element: HTMLInputElement) {
- this.element = element;
- this.onFileLoaded = null;
- element.addEventListener('change', event => this.loadFile(event), false);
- }
-
- open() {
- this.element.click();
- }
-
- private loadFile(event: Event) {
- const element = event.target as HTMLInputElement;
- const file = expectNotNull(element.files, "No file selected!")[0];
- const reader = new FileReader;
- reader.addEventListener('loadend', () => {
- if (this.onFileLoaded != null)
- this.onFileLoaded(reader.result);
- }, false);
- reader.readAsArrayBuffer(file);
- }
-}
diff --git a/demo/client/src/gl-utils.ts b/demo/client/src/gl-utils.ts
deleted file mode 100644
index 735f79b2..00000000
--- a/demo/client/src/gl-utils.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-// pathfinder/client/src/gl-utils.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-import * as _ from 'lodash';
-
-import {assert, UINT32_SIZE, unwrapNull} from './utils';
-import {ColorAlphaFormat, DemoView} from './view';
-
-export type WebGLVertexArrayObject = any;
-
-export interface AttributeMap {
- [attributeName: string]: number;
-}
-
-export interface UniformMap {
- [uniformName: string]: WebGLUniformLocation;
-}
-
-export interface EXTDisjointTimerQuery {
- readonly QUERY_COUNTER_BITS_EXT: GLenum;
- readonly CURRENT_QUERY_EXT: GLenum;
- readonly QUERY_RESULT_EXT: GLenum;
- readonly QUERY_RESULT_AVAILABLE_EXT: GLenum;
- readonly TIME_ELAPSED_EXT: GLenum;
- readonly TIMESTAMP_EXT: GLenum;
- readonly GPU_DISJOINT_EXT: GLenum;
- createQueryEXT(): WebGLQuery;
- deleteQueryEXT(query: WebGLQuery): void;
- isQueryEXT(query: any): GLboolean;
- beginQueryEXT(target: GLenum, query: WebGLQuery): void;
- endQueryEXT(target: GLenum): void;
- queryCounterEXT(query: WebGLQuery, target: GLenum): void;
- getQueryEXT(target: GLenum, pname: GLenum): any;
- getQueryObjectEXT(query: WebGLQuery, pname: GLenum): any;
-}
-
-export class WebGLQuery {}
-
-export const QUAD_ELEMENTS: Uint8Array = new Uint8Array([0, 1, 2, 1, 3, 2]);
-
-export function createFramebufferColorTexture(gl: WebGLRenderingContext,
- size: glmatrix.vec2,
- colorAlphaFormat: ColorAlphaFormat,
- filter?: number):
- WebGLTexture {
- // Firefox seems to have a bug whereby textures don't get marked as initialized when cleared
- // if they're anything other than the first attachment of an FBO. To work around this, supply
- // zero data explicitly when initializing the texture.
- let format, type, internalFormat, bitDepth, zeroes;
- if (colorAlphaFormat === 'RGBA8') {
- format = internalFormat = gl.RGBA;
- type = gl.UNSIGNED_BYTE;
- bitDepth = 32;
- zeroes = new Uint8Array(size[0] * size[1] * 4);
- } else {
- format = internalFormat = gl.RGBA;
- type = gl.UNSIGNED_SHORT_5_5_5_1;
- bitDepth = 16;
- zeroes = new Uint16Array(size[0] * size[1] * 4);
- }
-
- const texture = unwrapNull(gl.createTexture());
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, texture);
- gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, size[0], size[1], 0, format, type, zeroes);
- setTextureParameters(gl, _.defaultTo(filter, gl.NEAREST));
- return texture;
-}
-
-export function createFramebufferDepthTexture(gl: WebGLRenderingContext, size: glmatrix.vec2):
- WebGLTexture {
- const texture = unwrapNull(gl.createTexture());
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, texture);
- gl.texImage2D(gl.TEXTURE_2D,
- 0,
- gl.DEPTH_COMPONENT,
- size[0],
- size[1],
- 0,
- gl.DEPTH_COMPONENT,
- gl.UNSIGNED_INT,
- null);
- setTextureParameters(gl, gl.NEAREST);
- return texture;
-}
-
-export function setTextureParameters(gl: WebGLRenderingContext, filter: number) {
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
-}
-
-export function createFramebuffer(gl: WebGLRenderingContext,
- colorAttachment: WebGLTexture,
- depthAttachment: WebGLTexture | null):
- WebGLFramebuffer {
- const framebuffer = unwrapNull(gl.createFramebuffer());
- gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
-
- gl.framebufferTexture2D(gl.FRAMEBUFFER,
- gl.COLOR_ATTACHMENT0,
- gl.TEXTURE_2D,
- colorAttachment,
- 0);
-
- if (depthAttachment != null) {
- gl.framebufferTexture2D(gl.FRAMEBUFFER,
- gl.DEPTH_ATTACHMENT,
- gl.TEXTURE_2D,
- depthAttachment,
- 0);
- assert(gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER,
- gl.DEPTH_ATTACHMENT,
- gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE) ===
- gl.TEXTURE,
- "Failed to attach depth texture!");
-
- }
-
- assert(gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE,
- "Framebuffer was incomplete!");
- return framebuffer;
-}
diff --git a/demo/client/src/mesh-debugger.ts b/demo/client/src/mesh-debugger.ts
deleted file mode 100644
index 40947ca4..00000000
--- a/demo/client/src/mesh-debugger.ts
+++ /dev/null
@@ -1,564 +0,0 @@
-// pathfinder/client/src/mesh-debugger.ts
-//
-// Copyright © 2018 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-import * as opentype from "opentype.js";
-
-import {Font} from 'opentype.js';
-import {AppController} from "./app-controller";
-import {OrthographicCamera} from "./camera";
-import {FilePickerView} from './file-picker';
-import {B_QUAD_UPPER_RIGHT_VERTEX_OFFSET, PathfinderMeshPack} from "./meshes";
-import {B_QUAD_LOWER_LEFT_VERTEX_OFFSET, B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET} from "./meshes";
-import {B_QUAD_LOWER_RIGHT_VERTEX_OFFSET} from "./meshes";
-import {B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET, PathfinderPackedMeshes} from "./meshes";
-import {B_QUAD_SIZE, B_QUAD_UPPER_LEFT_VERTEX_OFFSET} from "./meshes";
-import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
-import {BUILTIN_FONT_URI, TextRun} from "./text";
-import {GlyphStore, PathfinderFont, TextFrame} from "./text";
-import {assert, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils";
-import {PathfinderView} from "./view";
-
-const CHARACTER: string = 'A';
-
-const FONT: string = 'eb-garamond';
-
-const POINT_LABEL_FONT: string = "sans-serif";
-const POINT_LABEL_FONT_SIZE: number = 12.0;
-const POINT_LABEL_OFFSET: glmatrix.vec2 = glmatrix.vec2.fromValues(12.0, 12.0);
-const POINT_RADIUS: number = 2.0;
-
-const SEGMENT_POINT_RADIUS: number = 3.0;
-const SEGMENT_STROKE_WIDTH: number = 1.0;
-const SEGMENT_CONTROL_POINT_STROKE_WIDTH: number = 1.0;
-
-const NORMAL_LENGTHS: NormalStyleParameter = {
- bVertex: 10.0,
- edge: 14.0,
-};
-
-const NORMAL_ARROWHEAD_LENGTH: number = 4.0;
-const NORMAL_ARROWHEAD_ANGLE: number = Math.PI * 5.0 / 6.0;
-
-const SEGMENT_POINT_FILL_STYLE: string = "rgb(0, 0, 128)";
-
-const LIGHT_STROKE_STYLE: string = "rgb(192, 192, 192)";
-const LINE_STROKE_STYLE: string = "rgb(0, 128, 0)";
-const CURVE_STROKE_STYLE: string = "rgb(128, 0, 0)";
-const SEGMENT_LINE_STROKE_STYLE: string = "rgb(128, 192, 128)";
-const SEGMENT_CONTROL_POINT_FILL_STYLE: string = "rgb(255, 255, 255)";
-const SEGMENT_CONTROL_POINT_STROKE_STYLE: string = "rgb(0, 0, 128)";
-const SEGMENT_CONTROL_POINT_HULL_STROKE_STYLE: string = "rgba(128, 128, 128, 0.5)";
-
-const NORMAL_STROKE_STYLES: NormalStyleParameter = {
- bVertex: '#e6aa00',
- edge: '#cc5500',
-};
-
-const BUILTIN_URIS = {
- font: BUILTIN_FONT_URI,
- svg: BUILTIN_SVG_URI,
-};
-
-const SVG_SCALE: number = 1.0;
-
-type FileType = 'font' | 'svg';
-
-type NormalType = 'edge' | 'bVertex';
-
-interface NormalStyleParameter {
- edge: T;
- bVertex: T;
-}
-
-interface NormalsTable {
- lowerCurve: T;
- lowerLine: T;
- upperCurve: T;
- upperLine: T;
-}
-
-class MeshDebuggerAppController extends AppController {
- meshes: PathfinderPackedMeshes | null = null;
-
- protected readonly defaultFile: string = FONT;
-
- private file: PathfinderFont | SVGLoader | null = null;
- private fileType!: FileType;
- private fileData: ArrayBuffer | null = null;
-
- private openModal!: HTMLElement;
- private openFileSelect!: HTMLSelectElement;
- private fontPathSelectGroup!: HTMLElement;
- private fontPathSelect!: HTMLSelectElement;
-
- private filePicker!: FilePickerView;
- private view!: MeshDebuggerView;
-
- start() {
- super.start();
-
- this.fileType = 'font';
-
- this.view = new MeshDebuggerView(this);
-
- this.filePicker = unwrapNull(FilePickerView.create());
- this.filePicker.onFileLoaded = fileData => this.fileLoaded(fileData, null);
-
- this.openModal = unwrapNull(document.getElementById('pf-open-modal'));
- this.fontPathSelectGroup =
- unwrapNull(document.getElementById('pf-font-path-select-group'));
- this.fontPathSelect = unwrapNull(document.getElementById('pf-font-path-select')) as
- HTMLSelectElement;
-
- this.openFileSelect = unwrapNull(document.getElementById('pf-open-file-select')) as
- HTMLSelectElement;
- this.openFileSelect.addEventListener('click', () => this.openSelectedFile(), false);
-
- const openButton = unwrapNull(document.getElementById('pf-open-button'));
- openButton.addEventListener('click', () => this.showOpenDialog(), false);
-
- const openOKButton = unwrapNull(document.getElementById('pf-open-ok-button'));
- openOKButton.addEventListener('click', () => this.loadPath(), false);
-
- this.loadInitialFile(BUILTIN_FONT_URI);
- }
-
- protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
- while (this.fontPathSelect.lastChild != null)
- this.fontPathSelect.removeChild(this.fontPathSelect.lastChild);
-
- this.fontPathSelectGroup.classList.remove('pf-display-none');
-
- if (this.fileType === 'font')
- this.fontLoaded(fileData, builtinName);
- else if (this.fileType === 'svg')
- this.svgLoaded(fileData);
- }
-
- protected loadPath(opentypeGlyph?: opentype.Glyph | null) {
- window.jQuery(this.openModal).modal('hide');
-
- let promise: Promise;
-
- if (this.file instanceof PathfinderFont && this.fileData != null) {
- if (opentypeGlyph == null) {
- const glyphIndex = parseInt(this.fontPathSelect.selectedOptions[0].value, 10);
- opentypeGlyph = this.file.opentypeFont.glyphs.get(glyphIndex);
- }
-
- const glyphStorage = new GlyphStore(this.file, [(opentypeGlyph as any).index]);
- promise = glyphStorage.partition().then(result => result.meshes);
- } else if (this.file instanceof SVGLoader) {
- promise = this.file.partition(this.fontPathSelect.selectedIndex);
- } else {
- return;
- }
-
- promise.then(meshes => {
- this.meshes = new PathfinderPackedMeshes(meshes);
- this.view.attachMeshes();
- });
- }
-
- private showOpenDialog(): void {
- window.jQuery(this.openModal).modal();
- }
-
- private openSelectedFile(): void {
- const selectedOption = this.openFileSelect.selectedOptions[0] as HTMLOptionElement;
- const optionValue = selectedOption.value;
-
- this.fontPathSelectGroup.classList.add('pf-display-none');
-
- const results = unwrapNull(/^([a-z]+)-(.*)$/.exec(optionValue));
- this.fileType = results[1] as FileType;
-
- const filename = results[2];
- if (filename === 'custom')
- this.filePicker.open();
- else
- this.fetchFile(results[2], BUILTIN_URIS[this.fileType]);
- }
-
- private fontLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
- this.file = new PathfinderFont(fileData, builtinName);
- this.fileData = fileData;
-
- const glyphCount = this.file.opentypeFont.numGlyphs;
- for (let glyphIndex = 1; glyphIndex < glyphCount; glyphIndex++) {
- const newOption = document.createElement('option');
- newOption.value = "" + glyphIndex;
- const glyphName = this.file.opentypeFont.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.file.opentypeFont.charToGlyph(CHARACTER));
- }
-
- private svgLoaded(fileData: ArrayBuffer): void {
- this.file = new SVGLoader;
- this.file.scale = SVG_SCALE;
- this.file.loadFile(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);
- }
- }
-}
-
-class MeshDebuggerView extends PathfinderView {
- camera: OrthographicCamera;
-
- private appController: MeshDebuggerAppController;
-
- private drawControl: boolean = true;
- private drawNormals: boolean = true;
- private drawVertices: boolean = true;
- private drawSegments: boolean = false;
-
- constructor(appController: MeshDebuggerAppController) {
- super();
-
- this.appController = appController;
- this.camera = new OrthographicCamera(this.canvas, { ignoreBounds: true });
-
- this.camera.onPan = () => this.setDirty();
- this.camera.onZoom = () => this.setDirty();
-
- window.addEventListener('keypress', event => this.onKeyPress(event), false);
-
- this.resizeToFit(true);
- }
-
- attachMeshes() {
- this.setDirty();
- }
-
- redraw() {
- super.redraw();
-
- const meshes = this.appController.meshes;
- if (meshes == null)
- return;
-
- const context = unwrapNull(this.canvas.getContext('2d'));
- context.clearRect(0, 0, this.canvas.width, this.canvas.height);
-
- context.save();
- context.translate(this.camera.translation[0],
- this.canvas.height - this.camera.translation[1]);
- context.scale(this.camera.scale, this.camera.scale);
-
- const invScaleFactor = window.devicePixelRatio / this.camera.scale;
- context.font = `12px ${POINT_LABEL_FONT}`;
- context.lineWidth = invScaleFactor;
-
- const bQuadVertexPositions = new Float32Array(meshes.bQuadVertexPositions);
-
- const normals: NormalsTable = {
- lowerCurve: new Float32Array(0),
- lowerLine: new Float32Array(0),
- upperCurve: new Float32Array(0),
- upperLine: new Float32Array(0),
- };
-
- // Draw B-quads.
- for (let bQuadIndex = 0; bQuadIndex < meshes.bQuadVertexPositions.length; bQuadIndex++) {
- const bQuadStartOffset = (B_QUAD_SIZE * bQuadIndex) / UINT32_SIZE;
-
- const upperLeftPosition = getPosition(bQuadVertexPositions, bQuadIndex, 0);
- const upperControlPointPosition = getPosition(bQuadVertexPositions, bQuadIndex, 1);
- const upperRightPosition = getPosition(bQuadVertexPositions, bQuadIndex, 2);
- const lowerRightPosition = getPosition(bQuadVertexPositions, bQuadIndex, 3);
- const lowerControlPointPosition = getPosition(bQuadVertexPositions, bQuadIndex, 4);
- const lowerLeftPosition = getPosition(bQuadVertexPositions, bQuadIndex, 5);
-
- if (this.drawVertices) {
- drawVertexIfNecessary(context, upperLeftPosition, invScaleFactor);
- drawVertexIfNecessary(context, upperRightPosition, invScaleFactor);
- drawVertexIfNecessary(context, lowerLeftPosition, invScaleFactor);
- drawVertexIfNecessary(context, lowerRightPosition, invScaleFactor);
- }
-
- context.beginPath();
- context.moveTo(upperLeftPosition[0], -upperLeftPosition[1]);
- if (upperControlPointPosition != null) {
- context.strokeStyle = CURVE_STROKE_STYLE;
- context.quadraticCurveTo(upperControlPointPosition[0],
- -upperControlPointPosition[1],
- upperRightPosition[0],
- -upperRightPosition[1]);
- } else {
- context.strokeStyle = LINE_STROKE_STYLE;
- context.lineTo(upperRightPosition[0], -upperRightPosition[1]);
- }
- context.stroke();
-
- context.strokeStyle = LIGHT_STROKE_STYLE;
- context.beginPath();
- context.moveTo(upperRightPosition[0], -upperRightPosition[1]);
- context.lineTo(lowerRightPosition[0], -lowerRightPosition[1]);
- context.stroke();
-
- context.beginPath();
- context.moveTo(lowerRightPosition[0], -lowerRightPosition[1]);
- if (lowerControlPointPosition != null) {
- context.strokeStyle = CURVE_STROKE_STYLE;
- context.quadraticCurveTo(lowerControlPointPosition[0],
- -lowerControlPointPosition[1],
- lowerLeftPosition[0],
- -lowerLeftPosition[1]);
- } else {
- context.strokeStyle = LINE_STROKE_STYLE;
- context.lineTo(lowerLeftPosition[0], -lowerLeftPosition[1]);
- }
- context.stroke();
-
- context.strokeStyle = LIGHT_STROKE_STYLE;
- context.beginPath();
- context.moveTo(lowerLeftPosition[0], -lowerLeftPosition[1]);
- context.lineTo(upperLeftPosition[0], -upperLeftPosition[1]);
- context.stroke();
- }
-
- // Draw segments.
- if (this.drawVertices) {
- drawSegmentVertices(context,
- new Float32Array(meshes.stencilSegments),
- new Float32Array(meshes.stencilNormals),
- meshes.count('stencilSegments'),
- [0, 2],
- 1,
- 3,
- invScaleFactor,
- this.drawControl,
- this.drawNormals,
- this.drawSegments);
- }
- context.restore();
- }
-
- private onKeyPress(event: KeyboardEvent): void {
- if (event.key === "c") {
- this.drawControl = !this.drawControl;
- } else if (event.key === "n") {
- this.drawNormals = !this.drawNormals;
- } else if (event.key === "v") {
- this.drawVertices = !this.drawVertices;
- } else if (event.key === "r") {
- // Reset
- this.drawControl = true;
- this.drawNormals = true;
- this.drawVertices = true;
- }
- this.setDirty();
- }
-}
-
-function getPosition(positions: Float32Array, bQuadIndex: number, vertexIndex: number):
- glmatrix.vec2 {
- return glmatrix.vec2.clone([
- positions[(bQuadIndex * 6 + vertexIndex) * 2 + 0],
- positions[(bQuadIndex * 6 + vertexIndex) * 2 + 1],
- ]);
-}
-
-function getNormal(normals: Float32Array, bQuadIndex: number, vertexIndex: number): number {
- return normals[bQuadIndex * 6 + vertexIndex];
-}
-
-function getNormals(normals: NormalsTable,
- normalIndices: NormalsTable,
- isCurve: boolean,
- side: 'upper' | 'lower'):
- { left: number, right: number } {
- const key: keyof NormalsTable = (side + (isCurve ? 'Curve' : 'Line')) as keyof
- NormalsTable;
- const startOffset = normalIndices[key];
- normalIndices[key]++;
- return {
- left: normals[key][startOffset * 2 + 0],
- right: normals[key][startOffset * 2 + 1],
- };
-}
-
-function drawSegmentVertices(context: CanvasRenderingContext2D,
- segments: Float32Array,
- normals: Float32Array,
- segmentCount: number,
- endpointOffsets: number[],
- controlPointOffset: number | null,
- segmentSize: number,
- invScaleFactor: number,
- drawControl: boolean,
- drawNormals: boolean,
- drawSegments: boolean) {
- for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++) {
- const positionStartOffset = segmentSize * 2 * segmentIndex;
- const normalStartOffset = segmentSize * 2 * segmentIndex;
-
- const position0 =
- glmatrix.vec2.clone([segments[positionStartOffset + endpointOffsets[0] * 2 + 0],
- segments[positionStartOffset + endpointOffsets[0] * 2 + 1]]);
- const position1 =
- glmatrix.vec2.clone([segments[positionStartOffset + endpointOffsets[1] * 2 + 0],
- segments[positionStartOffset + endpointOffsets[1] * 2 + 1]]);
-
- let controlPoint: glmatrix.vec2 | null;
- if (controlPointOffset != null) {
- controlPoint =
- glmatrix.vec2.clone([segments[positionStartOffset + controlPointOffset * 2 + 0],
- segments[positionStartOffset + controlPointOffset * 2 + 1]]);
- } else {
- controlPoint = null;
- }
-
- const normal0 =
- glmatrix.vec2.clone([normals[normalStartOffset + endpointOffsets[0] * 2 + 0],
- normals[normalStartOffset + endpointOffsets[0] * 2 + 1]]);
- const normal1 =
- glmatrix.vec2.clone([normals[normalStartOffset + endpointOffsets[1] * 2 + 0],
- normals[normalStartOffset + endpointOffsets[1] * 2 + 1]]);
-
- let normalControlPoint: glmatrix.vec2 | null;
- if (controlPointOffset != null) {
- normalControlPoint =
- glmatrix.vec2.clone([normals[normalStartOffset + controlPointOffset * 2 + 0],
- normals[normalStartOffset + controlPointOffset * 2 + 1]]);
- } else {
- normalControlPoint = null;
- }
-
- if (drawNormals) {
- drawNormal(context, position0, normal0, invScaleFactor, 'edge');
- drawNormal(context, position1, normal1, invScaleFactor, 'edge');
- if (drawControl && controlPoint != null && normalControlPoint != null)
- drawNormal(context, controlPoint, normalControlPoint, invScaleFactor, 'edge');
- }
-
- drawSegmentVertex(context, position0, invScaleFactor);
- drawSegmentVertex(context, position1, invScaleFactor);
- if (drawControl && controlPoint != null) {
- context.save();
- context.strokeStyle = SEGMENT_CONTROL_POINT_HULL_STROKE_STYLE;
- context.setLineDash([2 * invScaleFactor, 2 * invScaleFactor]);
- context.beginPath();
- context.moveTo(position0[0], -position0[1]);
- context.lineTo(controlPoint[0], -controlPoint[1]);
- context.lineTo(position1[0], -position1[1]);
- context.stroke();
- context.restore();
- drawSegmentControlPoint(context, controlPoint, invScaleFactor);
- }
-
- // TODO(pcwalton): Draw the curves too.
- if (drawSegments) {
- context.save();
- context.strokeStyle = SEGMENT_LINE_STROKE_STYLE;
- context.beginPath();
- context.moveTo(position0[0], -position0[1]);
- context.lineTo(position1[0], -position1[1]);
- context.stroke();
- context.restore();
- }
- }
-}
-
-function drawVertexIfNecessary(context: CanvasRenderingContext2D,
- position: Float32Array,
- invScaleFactor: number) {
- context.beginPath();
- context.moveTo(position[0], -position[1]);
- context.arc(position[0], -position[1], POINT_RADIUS * invScaleFactor, 0, 2.0 * Math.PI);
- context.fill();
-}
-
-function drawSegmentVertex(context: CanvasRenderingContext2D,
- position: glmatrix.vec2,
- invScaleFactor: number) {
- context.save();
- context.fillStyle = SEGMENT_POINT_FILL_STYLE;
- context.lineWidth = invScaleFactor * SEGMENT_STROKE_WIDTH;
- context.beginPath();
- context.arc(position[0],
- -position[1],
- SEGMENT_POINT_RADIUS * invScaleFactor,
- 0,
- 2.0 * Math.PI);
- context.fill();
- context.restore();
-}
-
-function drawSegmentControlPoint(context: CanvasRenderingContext2D,
- position: glmatrix.vec2,
- invScaleFactor: number) {
- context.save();
- context.strokeStyle = SEGMENT_CONTROL_POINT_STROKE_STYLE;
- context.fillStyle = SEGMENT_CONTROL_POINT_FILL_STYLE;
- context.lineWidth = invScaleFactor * SEGMENT_CONTROL_POINT_STROKE_WIDTH;
- context.beginPath();
- context.arc(position[0],
- -position[1],
- SEGMENT_POINT_RADIUS * invScaleFactor,
- 0,
- 2.0 * Math.PI);
- context.fill();
- context.stroke();
- context.restore();
-}
-
-function drawNormal(context: CanvasRenderingContext2D,
- position: glmatrix.vec2,
- normalVector: glmatrix.vec2,
- invScaleFactor: number,
- normalType: NormalType) {
- const length = invScaleFactor * NORMAL_LENGTHS[normalType];
- const arrowheadLength = invScaleFactor * NORMAL_ARROWHEAD_LENGTH;
- const endpoint = glmatrix.vec2.clone([position[0] + length * normalVector[0],
- -position[1] + length * -normalVector[1]]);
-
- context.save();
- context.strokeStyle = NORMAL_STROKE_STYLES[normalType];
- context.beginPath();
- context.moveTo(position[0], -position[1]);
- context.lineTo(endpoint[0], endpoint[1]);
- context.lineTo(endpoint[0] + arrowheadLength *
- (Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[0] +
- Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[1]),
- endpoint[1] + arrowheadLength *
- (Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[0] -
- Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[1]));
- context.stroke();
- context.beginPath();
- context.moveTo(endpoint[0], endpoint[1]);
- context.lineTo(endpoint[0] + arrowheadLength *
- (Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[0] -
- Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[1]),
- endpoint[1] - arrowheadLength *
- (Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[1] +
- Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[0]));
- context.stroke();
- context.restore();
-}
-
-function main() {
- const controller = new MeshDebuggerAppController;
- window.addEventListener('load', () => controller.start(), false);
-}
-
-main();
diff --git a/demo/client/src/meshes.ts b/demo/client/src/meshes.ts
deleted file mode 100644
index adcadf60..00000000
--- a/demo/client/src/meshes.ts
+++ /dev/null
@@ -1,391 +0,0 @@
-// pathfinder/client/src/meshes.ts
-//
-// Copyright © 2018 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as base64js from 'base64-js';
-
-import * as _ from 'lodash';
-import {expectNotNull, FLOAT32_SIZE, panic, PathfinderError, Range, UINT16_SIZE} from './utils';
-import {UINT32_MAX, UINT32_SIZE, UINT8_SIZE, unwrapNull, unwrapUndef} from './utils';
-
-interface BufferTypeFourCCTable {
- [fourCC: string]: keyof MeshLike;
-}
-
-interface PathRangeTypeFourCCTable {
- [fourCC: string]: keyof PathRanges;
-}
-
-interface RangeToCountTable {
- [rangeKey: string]: keyof MeshDataCounts;
-}
-
-type PathIDBufferTable = Partial>;
-
-interface ArrayLike {
- readonly length: number;
-}
-
-interface VertexCopyResult {
- originalStartIndex: number;
- originalEndIndex: number;
- expandedStartIndex: number;
- expandedEndIndex: number;
-}
-
-type PrimitiveType = 'Uint16' | 'Uint32' | 'Float32';
-
-type PrimitiveTypeArray = Float32Array | Uint16Array | Uint32Array;
-
-type MeshBufferType = keyof MeshLike;
-
-type PackedMeshBufferType = keyof PackedMeshLike;
-
-interface MeshBufferTypeDescriptor {
- type: PrimitiveType;
- size: number;
-}
-
-const PRIMITIVE_TYPE_ARRAY_CONSTRUCTORS = {
- Float32: Float32Array,
- Uint16: Uint16Array,
- Uint32: Uint32Array,
-};
-
-export const B_QUAD_SIZE: number = 4 * 8;
-export const B_QUAD_UPPER_LEFT_VERTEX_OFFSET: number = 4 * 0;
-export const B_QUAD_UPPER_RIGHT_VERTEX_OFFSET: number = 4 * 1;
-export const B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET: number = 4 * 2;
-export const B_QUAD_LOWER_LEFT_VERTEX_OFFSET: number = 4 * 4;
-export const B_QUAD_LOWER_RIGHT_VERTEX_OFFSET: number = 4 * 5;
-export const B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET: number = 4 * 6;
-export const B_QUAD_UPPER_INDICES_OFFSET: number = B_QUAD_UPPER_LEFT_VERTEX_OFFSET;
-export const B_QUAD_LOWER_INDICES_OFFSET: number = B_QUAD_LOWER_LEFT_VERTEX_OFFSET;
-
-const B_QUAD_FIELD_COUNT: number = B_QUAD_SIZE / UINT32_SIZE;
-
-// FIXME(pcwalton): This duplicates information below in `MESH_TYPES`.
-const INDEX_SIZE: number = 4;
-const B_QUAD_VERTEX_POSITION_SIZE: number = 12 * 4;
-const B_VERTEX_POSITION_SIZE: number = 4 * 2;
-
-const MESH_TYPES: PackedMeshLike = {
- bBoxPathIDs: { type: 'Uint16', size: 1 },
- bBoxes: { type: 'Float32', size: 20 },
- bQuadVertexInteriorIndices: { type: 'Uint32', size: 1 },
- bQuadVertexPositionPathIDs: { type: 'Uint16', size: 1 },
- bQuadVertexPositions: { type: 'Float32', size: 2 },
- stencilNormals: { type: 'Float32', size: 6 },
- stencilSegmentPathIDs: { type: 'Uint16', size: 1 },
- stencilSegments: { type: 'Float32', size: 6 },
-};
-
-const BUFFER_TYPES: PackedMeshLike = {
- bBoxPathIDs: 'ARRAY_BUFFER',
- bBoxes: 'ARRAY_BUFFER',
- bQuadVertexInteriorIndices: 'ELEMENT_ARRAY_BUFFER',
- bQuadVertexPositionPathIDs: 'ARRAY_BUFFER',
- bQuadVertexPositions: 'ARRAY_BUFFER',
- stencilNormals: 'ARRAY_BUFFER',
- stencilSegmentPathIDs: 'ARRAY_BUFFER',
- stencilSegments: 'ARRAY_BUFFER',
-};
-
-const EDGE_BUFFER_NAMES = ['UpperLine', 'UpperCurve', 'LowerLine', 'LowerCurve'];
-
-const RIFF_FOURCC: string = 'RIFF';
-
-const MESH_PACK_FOURCC: string = 'PFMP';
-
-const MESH_FOURCC: string = 'mesh';
-
-// Must match the FourCCs in `pathfinder_partitioner::mesh_library::MeshLibrary::serialize_into()`.
-const BUFFER_TYPE_FOURCCS: BufferTypeFourCCTable = {
- bbox: 'bBoxes',
- bqii: 'bQuadVertexInteriorIndices',
- bqvp: 'bQuadVertexPositions',
- snor: 'stencilNormals',
- sseg: 'stencilSegments',
-};
-
-const RANGE_TO_COUNT_TABLE: RangeToCountTable = {
- bBoxPathRanges: 'bBoxCount',
- bQuadVertexInteriorIndexPathRanges: 'bQuadVertexInteriorIndexCount',
- bQuadVertexPositionPathRanges: 'bQuadVertexPositionCount',
- stencilSegmentPathRanges: 'stencilSegmentCount',
-};
-
-const INDEX_TYPE_DESCRIPTOR_TABLE: {[P in MeshBufferType]?: IndexTypeDescriptor} = {
- bQuadVertexInteriorIndices: {
- bufferType: 'bQuadVertexPositions',
- },
-};
-
-const PATH_ID_BUFFER_TABLE: PathIDBufferTable = {
- bBoxes: 'bBoxPathIDs',
- bQuadVertexPositions: 'bQuadVertexPositionPathIDs',
- stencilSegments: 'stencilSegmentPathIDs',
-};
-
-const PATH_RANGE_TO_BUFFER_TYPE_TABLE: {[P in keyof PathRanges]: MeshBufferType} = {
- bBoxPathRanges: 'bBoxes',
- bQuadVertexInteriorIndexPathRanges: 'bQuadVertexInteriorIndices',
- bQuadVertexPositionPathRanges: 'bQuadVertexPositions',
- stencilSegmentPathRanges: 'stencilSegments',
-};
-
-type BufferType = 'ARRAY_BUFFER' | 'ELEMENT_ARRAY_BUFFER';
-
-export interface MeshBuilder {
- bQuadVertexPositions: T;
- bQuadVertexInteriorIndices: T;
- bBoxes: T;
- stencilSegments: T;
- stencilNormals: T;
-}
-
-export interface PackedMeshBuilder extends MeshBuilder {
- bBoxPathIDs: T;
- bQuadVertexPositionPathIDs: T;
- stencilSegmentPathIDs: T;
-}
-
-export type MeshLike = {
- readonly [P in keyof MeshBuilder]: T;
-};
-
-export type PackedMeshLike = {
- readonly [P in keyof PackedMeshBuilder]: T;
-};
-
-interface PathRanges {
- readonly bBoxPathRanges: Range[];
- readonly bQuadVertexInteriorIndexPathRanges: Range[];
- readonly bQuadVertexPositionPathRanges: Range[];
- readonly stencilSegmentPathRanges: Range[];
-}
-
-interface MeshDataCounts {
- readonly bQuadVertexPositionCount: number;
- readonly bQuadVertexInteriorIndexCount: number;
- readonly bBoxCount: number;
- readonly stencilSegmentCount: number;
-}
-
-interface IndexTypeDescriptor {
- bufferType: MeshBufferType;
-}
-
-export class PathfinderMeshPack {
- meshes: PathfinderMesh[];
-
- constructor(meshes: ArrayBuffer) {
- this.meshes = [];
-
- // RIFF encoded data.
- if (toFourCC(meshes, 0) !== RIFF_FOURCC)
- panic("Supplied array buffer is not a mesh library (no RIFF header)!");
- if (toFourCC(meshes, 8) !== MESH_PACK_FOURCC)
- panic("Supplied array buffer is not a mesh library (no PFMP header)!");
-
- let offset = 12;
- while (offset < meshes.byteLength) {
- const fourCC = toFourCC(meshes, offset);
- const chunkLength = readUInt32(meshes, offset + 4);
- const startOffset = offset + 8;
- const endOffset = startOffset + chunkLength;
-
- if (fourCC === MESH_FOURCC)
- this.meshes.push(new PathfinderMesh(meshes.slice(startOffset, endOffset)));
-
- offset = endOffset;
- }
- }
-}
-
-export class PathfinderMesh implements MeshLike {
- bQuadVertexPositions!: ArrayBuffer;
- bQuadVertexInteriorIndices!: ArrayBuffer;
- bBoxes!: ArrayBuffer;
- stencilSegments!: ArrayBuffer;
- stencilNormals!: ArrayBuffer;
-
- constructor(data: ArrayBuffer) {
- let offset = 0;
- while (offset < data.byteLength) {
- const fourCC = toFourCC(data, offset);
- const chunkLength = readUInt32(data, offset + 4);
- const startOffset = offset + 8;
- const endOffset = startOffset + chunkLength;
-
- if (BUFFER_TYPE_FOURCCS.hasOwnProperty(fourCC))
- this[BUFFER_TYPE_FOURCCS[fourCC]] = data.slice(startOffset, endOffset);
-
- offset = endOffset;
- }
-
- for (const type of Object.keys(BUFFER_TYPE_FOURCCS) as Array>) {
- if (this[type] == null)
- this[type] = new ArrayBuffer(0);
- }
- }
-}
-
-export class PathfinderPackedMeshes implements PackedMeshLike, PathRanges {
- readonly bBoxes!: Float32Array;
- readonly bQuadVertexInteriorIndices!: Uint32Array;
- readonly bQuadVertexPositions!: Float32Array;
- readonly stencilSegments!: Float32Array;
- readonly stencilNormals!: Float32Array;
-
- readonly bBoxPathIDs!: Uint16Array;
- readonly bQuadVertexPositionPathIDs!: Uint16Array;
- readonly stencilSegmentPathIDs!: Uint16Array;
-
- readonly bBoxPathRanges!: Range[];
- readonly bQuadVertexInteriorIndexPathRanges!: Range[];
- readonly bQuadVertexPositionPathRanges!: Range[];
- readonly stencilSegmentPathRanges!: Range[];
-
- /// NB: Mesh indices are 1-indexed.
- constructor(meshPack: PathfinderMeshPack, meshIndices?: number[]) {
- if (meshIndices == null)
- meshIndices = meshPack.meshes.map((value, index) => index + 1);
-
- const meshData: PackedMeshBuilder = {
- bBoxPathIDs: [],
- bBoxes: [],
- bQuadVertexInteriorIndices: [],
- bQuadVertexPositionPathIDs: [],
- bQuadVertexPositions: [],
- stencilNormals: [],
- stencilSegmentPathIDs: [],
- stencilSegments: [],
- };
- const pathRanges: PathRanges = {
- bBoxPathRanges: [],
- bQuadVertexInteriorIndexPathRanges: [],
- bQuadVertexPositionPathRanges: [],
- stencilSegmentPathRanges: [],
- };
-
- for (let destMeshIndex = 0; destMeshIndex < meshIndices.length; destMeshIndex++) {
- const srcMeshIndex = meshIndices[destMeshIndex];
- const mesh = meshPack.meshes[srcMeshIndex - 1];
-
- for (const pathRangeType of Object.keys(pathRanges) as Array) {
- const bufferType = PATH_RANGE_TO_BUFFER_TYPE_TABLE[pathRangeType];
- const startIndex = bufferCount(meshData, bufferType);
- pathRanges[pathRangeType].push(new Range(startIndex, startIndex));
- }
-
- for (const indexType of Object.keys(BUFFER_TYPES) as MeshBufferType[]) {
- if (BUFFER_TYPES[indexType] !== 'ELEMENT_ARRAY_BUFFER')
- continue;
- const indexTypeDescriptor = unwrapUndef(INDEX_TYPE_DESCRIPTOR_TABLE[indexType]);
- const offset = bufferCount(meshData, indexTypeDescriptor.bufferType);
- for (const index of new Uint32Array(mesh[indexType]))
- meshData[indexType].push(index + offset);
- }
- for (const bufferType of Object.keys(BUFFER_TYPES) as MeshBufferType[]) {
- if (BUFFER_TYPES[bufferType] !== 'ARRAY_BUFFER')
- continue;
- meshData[bufferType].push(...new Float32Array(mesh[bufferType]));
-
- const pathIDBufferType = PATH_ID_BUFFER_TABLE[bufferType];
- if (pathIDBufferType != null) {
- const length = bufferCount(meshData, bufferType);
- while (meshData[pathIDBufferType].length < length)
- meshData[pathIDBufferType].push(destMeshIndex + 1);
- }
- }
-
- for (const pathRangeType of Object.keys(PATH_RANGE_TO_BUFFER_TYPE_TABLE) as
- Array) {
- const bufferType = PATH_RANGE_TO_BUFFER_TYPE_TABLE[pathRangeType];
- const endIndex = bufferCount(meshData, bufferType);
- unwrapUndef(_.last(pathRanges[pathRangeType])).end = endIndex;
- }
- }
-
- for (const bufferType of Object.keys(BUFFER_TYPES) as PackedMeshBufferType[]) {
- const arrayCtor = PRIMITIVE_TYPE_ARRAY_CONSTRUCTORS[MESH_TYPES[bufferType].type];
- this[bufferType] = (new arrayCtor(meshData[bufferType])) as any;
- }
- _.assign(this, pathRanges);
- }
-
- count(bufferType: MeshBufferType): number {
- return bufferCount(this, bufferType);
- }
-}
-
-export class PathfinderPackedMeshBuffers implements PackedMeshLike, PathRanges {
- readonly bBoxes!: WebGLBuffer;
- readonly bQuadVertexInteriorIndices!: WebGLBuffer;
- readonly bQuadVertexPositions!: WebGLBuffer;
- readonly stencilSegments!: WebGLBuffer;
- readonly stencilNormals!: WebGLBuffer;
-
- readonly bBoxPathIDs!: WebGLBuffer;
- readonly bQuadVertexPositionPathIDs!: WebGLBuffer;
- readonly stencilSegmentPathIDs!: WebGLBuffer;
-
- readonly bBoxPathRanges!: Range[];
- readonly bQuadVertexInteriorIndexPathRanges!: Range[];
- readonly bQuadVertexPositionPathRanges!: Range[];
- readonly stencilSegmentPathRanges!: Range[];
-
- constructor(gl: WebGLRenderingContext, packedMeshes: PathfinderPackedMeshes) {
- for (const bufferName of Object.keys(BUFFER_TYPES) as PackedMeshBufferType[]) {
- const bufferType = gl[BUFFER_TYPES[bufferName]];
- const buffer = expectNotNull(gl.createBuffer(), "Failed to create buffer!");
- gl.bindBuffer(bufferType, buffer);
- gl.bufferData(bufferType, packedMeshes[bufferName], gl.STATIC_DRAW);
- this[bufferName] = buffer;
- }
-
- for (const rangeName of Object.keys(PATH_RANGE_TO_BUFFER_TYPE_TABLE) as
- Array) {
- this[rangeName] = packedMeshes[rangeName];
- }
- }
-}
-
-function bufferCount(mesh: MeshLike, bufferType: MeshBufferType): number {
- return mesh[bufferType].length / MESH_TYPES[bufferType].size;
-}
-
-function sizeOfPrimitive(primitiveType: PrimitiveType): number {
- switch (primitiveType) {
- case 'Uint16': return UINT16_SIZE;
- case 'Uint32': return UINT32_SIZE;
- case 'Float32': return FLOAT32_SIZE;
- }
-}
-
-function toFourCC(buffer: ArrayBuffer, position: number): string {
- let result = "";
- const bytes = new Uint8Array(buffer, position, 4);
- for (const byte of bytes)
- result += String.fromCharCode(byte);
- return result;
-}
-
-export function parseServerTiming(headers: Headers): number {
- if (!headers.has('Server-Timing'))
- return 0.0;
- const timing = headers.get('Server-Timing')!;
- const matches = /^Partitioning\s*=\s*([0-9.]+)$/.exec(timing);
- return matches != null ? parseFloat(matches[1]) / 1000.0 : 0.0;
-}
-
-function readUInt32(buffer: ArrayBuffer, offset: number): number {
- return (new Uint32Array(buffer.slice(offset, offset + 4)))[0];
-}
diff --git a/demo/client/src/reference-test.ts b/demo/client/src/reference-test.ts
deleted file mode 100644
index 7782946a..00000000
--- a/demo/client/src/reference-test.ts
+++ /dev/null
@@ -1,917 +0,0 @@
-// pathfinder/client/src/reference-test.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-import * as imageSSIM from 'image-ssim';
-import * as _ from 'lodash';
-import * as papaparse from 'papaparse';
-
-import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy';
-import {SubpixelAAType} from './aa-strategy';
-import {DemoAppController, setSwitchInputsValue} from "./app-controller";
-import {SUBPIXEL_GRANULARITY} from './atlas';
-import {OrthographicCamera} from './camera';
-import {UniformMap} from './gl-utils';
-import {B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET, PathfinderMeshPack} from './meshes';
-import {PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from './meshes';
-import {PathTransformBuffers, Renderer} from "./renderer";
-import {ShaderMap, ShaderProgramSource} from "./shader-loader";
-import SSAAStrategy from './ssaa-strategy';
-import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
-import {SVGRenderer} from './svg-renderer';
-import {BUILTIN_FONT_URI, computeStemDarkeningAmount, ExpandedMeshData, GlyphStore} from "./text";
-import {Hint} from "./text";
-import {PathfinderFont, TextFrame, TextRun} from "./text";
-import {MAX_SUBPIXEL_AA_FONT_SIZE} from './text-renderer';
-import {unwrapNull} from "./utils";
-import {DemoView} from "./view";
-import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy';
-
-const FONT: string = 'open-sans';
-const TEXT_COLOR: number[] = [0, 0, 0, 255];
-
-const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
- none: NoAAStrategy,
- ssaa: SSAAStrategy,
- xcaa: AdaptiveStencilMeshAAAStrategy,
-};
-
-const RENDER_REFERENCE_URIS: PerTestType = {
- font: "/render-reference/text",
- svg: "/render-reference/svg",
-};
-
-const TEST_DATA_URI: string = "/test-data/reference-test-text.csv";
-
-const SSIM_TOLERANCE: number = 0.01;
-const SSIM_WINDOW_SIZE: number = 8;
-
-const FILES: PerTestType = {
- font: [
- { id: 'open-sans', title: "Open Sans" },
- { id: 'eb-garamond', title: "EB Garamond" },
- { id: 'nimbus-sans', title: "Nimbus Sans" },
- ],
- svg: [
- { id: 'tiger', title: "Ghostscript Tiger" },
- ],
-};
-
-interface ReferenceTestGroup {
- font: string;
- tests: ReferenceTestCase[];
-}
-
-interface ReferenceTestCase {
- size: number;
- character: string;
- aaMode: keyof AntialiasingStrategyTable;
- subpixel: boolean;
- referenceRenderer: ReferenceRenderer;
- expectedSSIM: number;
-}
-
-interface PerTestType {
- font: T;
- svg: T;
-}
-
-interface File {
- id: string;
- title: string;
-}
-
-type ReferenceRenderer = 'core-graphics' | 'freetype';
-
-interface AntialiasingStrategyTable {
- none: typeof NoAAStrategy;
- ssaa: typeof SSAAStrategy;
- xcaa: typeof AdaptiveStencilMeshAAAStrategy;
-}
-
-class ReferenceTestAppController extends DemoAppController {
- font: PathfinderFont | null = null;
- textRun: TextRun | null = null;
-
- svgLoader!: SVGLoader;
- builtinSvgName: string | null = null;
-
- referenceCanvas!: HTMLCanvasElement;
-
- tests!: Promise;
-
- currentTestType!: 'font' | 'svg';
-
- protected readonly defaultFile: string = FONT;
-
- protected get builtinFileURI(): string {
- if (this.currentTestType === 'font')
- return BUILTIN_FONT_URI;
- return BUILTIN_SVG_URI;
- }
-
- private glyphStore!: GlyphStore;
- private baseMeshes!: PathfinderMeshPack;
- private expandedMeshes!: ExpandedMeshData;
-
- private fontSizeInput!: HTMLInputElement;
- private characterInput!: HTMLInputElement;
- private referenceRendererSelect!: HTMLSelectElement;
-
- private differenceCanvas!: HTMLCanvasElement;
-
- private aaLevelGroup!: HTMLElement;
-
- private customTabs!: PerTestType;
- private customTestForms!: PerTestType;
- private selectFileGroups!: PerTestType;
- private runTestsButtons!: PerTestType;
- private ssimGroups!: PerTestType;
- private ssimLabels!: PerTestType;
- private resultsTables!: PerTestType;
-
- private currentTestGroupIndex: number | null = null;
- private currentTestCaseIndex: number | null = null;
- private currentGlobalTestCaseIndex: number | null = null;
-
- get currentFontSize(): number {
- return parseInt(this.fontSizeInput.value, 10);
- }
-
- set currentFontSize(newFontSize: number) {
- this.fontSizeInput.value = "" + newFontSize;
- }
-
- get currentCharacter(): string {
- return this.characterInput.value;
- }
-
- set currentCharacter(newCharacter: string) {
- this.characterInput.value = newCharacter;
- }
-
- get currentReferenceRenderer(): ReferenceRenderer {
- return this.referenceRendererSelect.value as ReferenceRenderer;
- }
-
- set currentReferenceRenderer(newReferenceRenderer: ReferenceRenderer) {
- this.referenceRendererSelect.value = newReferenceRenderer;
- }
-
- start(): void {
- this.referenceRendererSelect =
- unwrapNull(document.getElementById('pf-font-reference-renderer')) as HTMLSelectElement;
- this.referenceRendererSelect.addEventListener('change', () => {
- this.view.then(view => this.runSingleTest(view));
- }, false);
-
- super.start();
-
- this.currentTestGroupIndex = null;
- this.currentTestCaseIndex = null;
- this.currentGlobalTestCaseIndex = null;
-
- this.referenceCanvas = unwrapNull(document.getElementById('pf-reference-canvas')) as
- HTMLCanvasElement;
-
- this.fontSizeInput = unwrapNull(document.getElementById('pf-font-size')) as
- HTMLInputElement;
- this.fontSizeInput.addEventListener('change', () => {
- this.view.then(view => this.runSingleTest(view));
- }, false);
-
- this.characterInput = unwrapNull(document.getElementById('pf-character')) as
- HTMLInputElement;
- this.characterInput.addEventListener('change', () => {
- this.view.then(view => this.runSingleTest(view));
- }, false);
-
- this.aaLevelGroup = unwrapNull(document.getElementById('pf-aa-level-group')) as
- HTMLElement;
-
- this.differenceCanvas = unwrapNull(document.getElementById('pf-difference-canvas')) as
- HTMLCanvasElement;
-
- this.customTabs = {
- font: unwrapNull(document.getElementById('pf-font-custom-test-tab')) as HTMLElement,
- svg: unwrapNull(document.getElementById('pf-svg-custom-test-tab')) as HTMLElement,
- };
- this.customTestForms = {
- font: unwrapNull(document.getElementById('pf-font-custom-form')) as HTMLFormElement,
- svg: unwrapNull(document.getElementById('pf-svg-custom-form')) as HTMLFormElement,
- };
- this.selectFileGroups = {
- font: unwrapNull(document.getElementById('pf-font-select-file-group')) as HTMLElement,
- svg: unwrapNull(document.getElementById('pf-svg-select-file-group')) as HTMLElement,
- };
- this.runTestsButtons = {
- font: unwrapNull(document.getElementById('pf-run-font-tests-button')) as
- HTMLButtonElement,
- svg: unwrapNull(document.getElementById('pf-run-svg-tests-button')) as
- HTMLButtonElement,
- };
- this.ssimGroups = {
- font: unwrapNull(document.getElementById('pf-font-ssim-group')) as HTMLElement,
- svg: unwrapNull(document.getElementById('pf-svg-ssim-group')) as HTMLElement,
- };
- this.ssimLabels = {
- font: unwrapNull(document.getElementById('pf-font-ssim-label')) as HTMLElement,
- svg: unwrapNull(document.getElementById('pf-svg-ssim-label')) as HTMLElement,
- };
- this.resultsTables = {
- font: unwrapNull(document.getElementById('pf-font-results-table')) as HTMLTableElement,
- svg: unwrapNull(document.getElementById('pf-svg-results-table')) as HTMLTableElement,
- };
-
- this.customTabs.font.addEventListener('click',
- () => this.showCustomTabPane('font'),
- false);
- this.customTabs.svg.addEventListener('click', () => this.showCustomTabPane('svg'), false);
-
- this.runTestsButtons.font.addEventListener('click', () => {
- this.view.then(view => this.runTests());
- }, false);
- this.runTestsButtons.svg.addEventListener('click', () => {
- this.view.then(view => this.runTests());
- }, false);
-
- this.currentTestType = 'font';
-
- this.loadTestData();
- this.populateResultsTable();
- this.populateFilesSelect();
-
- this.loadInitialFile(this.builtinFileURI);
- }
-
- runNextTestIfNecessary(view: ReferenceTestView, tests: ReferenceTestGroup[]): void {
- if (this.currentTestGroupIndex == null || this.currentTestCaseIndex == null ||
- this.currentGlobalTestCaseIndex == null) {
- return;
- }
-
- this.currentTestCaseIndex++;
- this.currentGlobalTestCaseIndex++;
- if (this.currentTestCaseIndex === tests[this.currentTestGroupIndex].tests.length) {
- this.currentTestCaseIndex = 0;
- this.currentTestGroupIndex++;
- if (this.currentTestGroupIndex === tests.length) {
- // Done running tests.
- this.currentTestCaseIndex = null;
- this.currentTestGroupIndex = null;
- this.currentGlobalTestCaseIndex = null;
- this.view.then(view => view.suppressAutomaticRedraw = false);
- return;
- }
- }
-
- this.loadFontForTestGroupIfNecessary(tests).then(() => {
- this.setOptionsForCurrentTest(tests).then(() => this.runSingleTest(view));
- });
- }
-
- recordSSIMResult(tests: ReferenceTestGroup[], ssimResult: imageSSIM.IResult): void {
- const formattedSSIM: string = "" + (Math.round(ssimResult.ssim * 1000.0) / 1000.0);
- this.ssimLabels[this.currentTestType].textContent = formattedSSIM;
-
- if (this.currentTestGroupIndex == null || this.currentTestCaseIndex == null ||
- this.currentGlobalTestCaseIndex == null) {
- return;
- }
-
- const testGroup = tests[this.currentTestGroupIndex];
- const expectedSSIM = testGroup.tests[this.currentTestCaseIndex].expectedSSIM;
- const passed = Math.abs(expectedSSIM - ssimResult.ssim) <= SSIM_TOLERANCE;
-
- const resultsBody: Element = unwrapNull(this.resultsTables[this.currentTestType]
- .lastElementChild);
- let resultsRow = unwrapNull(resultsBody.firstElementChild);
- for (let rowIndex = 0; rowIndex < this.currentGlobalTestCaseIndex; rowIndex++)
- resultsRow = unwrapNull(resultsRow.nextElementSibling);
-
- const passCell = unwrapNull(resultsRow.firstElementChild);
- const resultsCell = unwrapNull(resultsRow.lastElementChild);
- resultsCell.textContent = formattedSSIM;
- passCell.textContent = passed ? "✓" : "✗";
-
- resultsRow.classList.remove('table-success', 'table-danger');
- resultsRow.classList.add(passed ? 'table-success' : 'table-danger');
- }
-
- drawDifferenceImage(differenceImage: imageSSIM.IImage): void {
- const canvas = this.differenceCanvas;
- const context = unwrapNull(canvas.getContext('2d'));
- context.fillStyle = 'white';
- context.fillRect(0, 0, canvas.width, canvas.height);
-
- const data = new Uint8ClampedArray(differenceImage.data);
- const imageData = new ImageData(data, differenceImage.width, differenceImage.height);
- context.putImageData(imageData, 0, 0);
- }
-
- protected createView(areaLUT: HTMLImageElement,
- gammaLUT: HTMLImageElement,
- commonShaderSource: string,
- shaderSources: ShaderMap):
- ReferenceTestView {
- return new ReferenceTestView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
- }
-
- protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
- switch (this.currentTestType) {
- case 'font':
- this.textFileLoaded(fileData, builtinName);
- break;
- case 'svg':
- this.svgFileLoaded(fileData, builtinName);
- break;
- }
-
- // Don't automatically run the test unless this is a custom test.
- if (this.currentGlobalTestCaseIndex == null)
- this.view.then(view => this.runSingleTest(view));
- }
-
- private textFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
- const font = new PathfinderFont(fileData, builtinName);
- this.font = font;
- }
-
- private svgFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
- this.builtinSvgName = builtinName;
- this.svgLoader = new SVGLoader;
- this.svgLoader.loadFile(fileData);
- }
-
- private populateFilesSelect(): void {
- const selectFileElement = unwrapNull(this.selectFileElement);
- while (selectFileElement.lastChild != null)
- selectFileElement.removeChild(selectFileElement.lastChild);
-
- for (const file of FILES[this.currentTestType]) {
- const option = document.createElement('option');
- option.value = file.id;
- option.appendChild(document.createTextNode(file.title));
- selectFileElement.appendChild(option);
- }
- }
-
- private loadTestData(): void {
- this.tests = window.fetch(TEST_DATA_URI)
- .then(response => response.text())
- .then(testDataText => {
- const fontNames = [];
- const groups: {[font: string]: ReferenceTestCase[]} = {};
-
- const testData = papaparse.parse(testDataText, {
- comments: "#",
- header: true,
- skipEmptyLines: true,
- });
-
- for (const row of testData.data) {
- if (!groups.hasOwnProperty(row.Font)) {
- fontNames.push(row.Font);
- groups[row.Font] = [];
- }
- groups[row.Font].push({
- aaMode: row['AA Mode'] as keyof AntialiasingStrategyTable,
- character: row.Character,
- expectedSSIM: parseFloat(row['Expected SSIM']),
- referenceRenderer: row['Reference Renderer'],
- size: parseInt(row.Size, 10),
- subpixel: !!row.Subpixel,
- });
- }
-
- return fontNames.map(fontName => {
- return {
- font: fontName,
- tests: groups[fontName],
- };
- });
- });
- }
-
- private populateResultsTable(): void {
- this.tests.then(tests => {
- const resultsBody: Element = unwrapNull(this.resultsTables[this.currentTestType]
- .lastElementChild);
- for (const testGroup of tests) {
- for (const test of testGroup.tests) {
- const row = document.createElement('tr');
- addCell(row, "");
- addCell(row, testGroup.font);
- addCell(row, test.character);
- addCell(row, "" + test.size);
- addCell(row, "" + test.aaMode);
- addCell(row, test.subpixel ? "Y" : "N");
- addCell(row, test.referenceRenderer);
- addCell(row, "" + test.expectedSSIM);
- addCell(row, "");
- resultsBody.appendChild(row);
- }
- }
- });
- }
-
- private runSingleTest(view: ReferenceTestView): void {
- if (this.currentTestType === 'font')
- this.setUpTextRun();
-
- this.loadReference(view).then(() => this.loadRendering());
- }
-
- private runTests(): void {
- this.view.then(view => {
- view.suppressAutomaticRedraw = true;
- this.tests.then(tests => {
- this.currentTestGroupIndex = 0;
- this.currentTestCaseIndex = 0;
- this.currentGlobalTestCaseIndex = 0;
-
- this.loadFontForTestGroupIfNecessary(tests).then(() => {
- this.setOptionsForCurrentTest(tests).then(() => this.runSingleTest(view));
- });
- });
- });
- }
-
- private loadFontForTestGroupIfNecessary(tests: ReferenceTestGroup[]): Promise {
- return new Promise(resolve => {
- if (this.currentTestGroupIndex == null) {
- resolve();
- return;
- }
-
- this.fetchFile(tests[this.currentTestGroupIndex].font, BUILTIN_FONT_URI).then(() => {
- resolve();
- });
- });
- }
-
- private setOptionsForCurrentTest(tests: ReferenceTestGroup[]): Promise {
- if (this.currentTestGroupIndex == null || this.currentTestCaseIndex == null)
- return new Promise(resolve => resolve());
-
- const currentTestCase = tests[this.currentTestGroupIndex].tests[this.currentTestCaseIndex];
- this.currentFontSize = currentTestCase.size;
- this.currentCharacter = currentTestCase.character;
- this.currentReferenceRenderer = currentTestCase.referenceRenderer;
-
- const aaLevelSelect = unwrapNull(this.aaLevelSelect);
- aaLevelSelect.selectedIndex = _.findIndex(aaLevelSelect.options, option => {
- return option.value.startsWith(currentTestCase.aaMode);
- });
-
- const subpixelAASelect = unwrapNull(this.subpixelAASelect);
- subpixelAASelect.selectedIndex = currentTestCase.subpixel ? 1 : 0;
- return this.updateAALevel();
- }
-
- private setUpTextRun(): void {
- const font = unwrapNull(this.font);
-
- const textRun = new TextRun(this.currentCharacter, [0, 0], font);
- textRun.layout();
- this.textRun = textRun;
-
- this.glyphStore = new GlyphStore(font, [textRun.glyphIDs[0]]);
- }
-
- private loadRendering(): void {
- switch (this.currentTestType) {
- case 'font':
- this.loadTextRendering();
- break;
- case 'svg':
- this.loadSVGRendering();
- break;
- }
- }
-
- private loadTextRendering(): void {
- this.glyphStore.partition().then(result => {
- const textRun = unwrapNull(this.textRun);
-
- this.baseMeshes = result.meshes;
-
- const textFrame = new TextFrame([textRun], unwrapNull(this.font));
- const expandedMeshes = textFrame.expandMeshes(this.baseMeshes, [textRun.glyphIDs[0]]);
- this.expandedMeshes = expandedMeshes;
-
- this.view.then(view => {
- view.recreateRenderer();
- view.attachMeshes([expandedMeshes.meshes]);
- view.redraw();
- });
- });
- }
-
- private loadSVGRendering(): void {
- this.svgLoader.partition().then(meshes => {
- this.view.then(view => {
- view.recreateRenderer();
- view.attachMeshes([new PathfinderPackedMeshes(meshes)]);
- view.initCameraBounds(this.svgLoader.svgViewBox);
- });
- });
- }
-
- private loadReference(view: ReferenceTestView): Promise {
- let request;
- switch (this.currentTestType) {
- case 'font':
- request = {
- face: {
- Builtin: unwrapNull(this.font).builtinFontName,
- },
- fontIndex: 0,
- glyph: this.glyphStore.glyphIDs[0],
- pointSize: this.currentFontSize,
- renderer: this.currentReferenceRenderer,
- };
- break;
- case 'svg':
- // TODO(pcwalton): Custom SVGs.
- request = {
- name: unwrapNull(this.builtinSvgName),
- renderer: 'pixman',
- scale: 1.0,
- };
- break;
- }
-
- return window.fetch(RENDER_REFERENCE_URIS[this.currentTestType], {
- body: JSON.stringify(request),
- headers: {'Content-Type': 'application/json'} as any,
- method: 'POST',
- }).then(response => response.blob()).then(blob => {
- const imgElement = document.createElement('img');
- imgElement.src = URL.createObjectURL(blob);
- imgElement.addEventListener('load', () => {
- const canvas = this.referenceCanvas;
- const context = unwrapNull(canvas.getContext('2d'));
- context.fillStyle = 'white';
- context.fillRect(0, 0, canvas.width, canvas.height);
- context.drawImage(imgElement, 0, 0);
- }, false);
- });
- }
-
- private showCustomTabPane(testType: 'font' | 'svg'): void {
- this.currentTestType = testType;
-
- const selectFileElement = unwrapNull(this.selectFileElement);
- const aaLevelGroup = unwrapNull(this.aaLevelGroup);
-
- const customTestForm = this.customTestForms[testType];
- const selectFileGroup = this.selectFileGroups[testType];
- const ssimGroup = this.ssimGroups[testType];
-
- unwrapNull(selectFileElement.parentNode).removeChild(selectFileElement);
- unwrapNull(aaLevelGroup.parentNode).removeChild(aaLevelGroup);
-
- selectFileGroup.appendChild(selectFileElement);
- customTestForm.insertBefore(aaLevelGroup, ssimGroup);
-
- this.populateFilesSelect();
- }
-}
-
-class ReferenceTestView extends DemoView {
- renderer!: ReferenceTestTextRenderer | ReferenceTestSVGRenderer;
- readonly appController: ReferenceTestAppController;
-
- get camera(): OrthographicCamera {
- return this.renderer.camera;
- }
-
- constructor(appController: ReferenceTestAppController,
- areaLUT: HTMLImageElement,
- gammaLUT: HTMLImageElement,
- commonShaderSource: string,
- shaderSources: ShaderMap) {
- super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
-
- this.appController = appController;
- this.recreateRenderer();
-
- this.resizeToFit(true);
- }
-
- recreateRenderer(): void {
- switch (this.appController.currentTestType) {
- case 'svg':
- this.renderer = new ReferenceTestSVGRenderer(this);
- break;
- case 'font':
- this.renderer = new ReferenceTestTextRenderer(this);
- break;
- }
- }
-
- initCameraBounds(viewBox: glmatrix.vec4): void {
- if (this.renderer instanceof ReferenceTestSVGRenderer)
- this.renderer.initCameraBounds(viewBox);
- }
-
- protected renderingFinished(): void {
- const gl = this.renderContext.gl;
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
-
- // TODO(pcwalton): Get the SVG pixel rect.
- let pixelRect: glmatrix.vec4 = glmatrix.vec4.create();
- if (this.renderer instanceof ReferenceTestTextRenderer)
- pixelRect = this.renderer.getPixelRectForGlyphAt(0);
-
- const canvasHeight = this.canvas.height;
- const width = pixelRect[2] - pixelRect[0], height = pixelRect[3] - pixelRect[1];
- const originY = Math.max(canvasHeight - height, 0);
- const flippedBuffer = new Uint8Array(width * height * 4);
- gl.readPixels(0, originY, width, height, gl.RGBA, gl.UNSIGNED_BYTE, flippedBuffer);
-
- const buffer = new Uint8Array(width * height * 4);
- for (let y = 0; y < height; y++) {
- const destRowStart = y * width * 4;
- const srcRowStart = (height - y - 1) * width * 4;
- buffer.set(flippedBuffer.slice(srcRowStart, srcRowStart + width * 4),
- destRowStart);
- }
-
- const renderedImage = createSSIMImage(buffer, pixelRect);
-
- this.appController.tests.then(tests => {
- const referenceImage = createSSIMImage(this.appController.referenceCanvas,
- pixelRect);
- const ssimResult = imageSSIM.compare(referenceImage, renderedImage, SSIM_WINDOW_SIZE);
- const differenceImage = generateDifferenceImage(referenceImage, renderedImage);
- this.appController.recordSSIMResult(tests, ssimResult);
- this.appController.drawDifferenceImage(differenceImage);
- this.appController.runNextTestIfNecessary(this, tests);
- });
- }
-}
-
-class ReferenceTestTextRenderer extends Renderer {
- renderContext!: ReferenceTestView;
- camera: OrthographicCamera;
-
- needsStencil: boolean = false;
- isMulticolor: boolean = false;
-
- get destFramebuffer(): WebGLFramebuffer | null {
- return null;
- }
-
- get bgColor(): glmatrix.vec4 {
- return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
- }
-
- get fgColor(): glmatrix.vec4 {
- return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
- }
-
- get destAllocatedSize(): glmatrix.vec2 {
- const canvas = this.renderContext.canvas;
- return glmatrix.vec2.clone([canvas.width, canvas.height]);
- }
-
- get destUsedSize(): glmatrix.vec2 {
- return this.destAllocatedSize;
- }
-
- get emboldenAmount(): glmatrix.vec2 {
- return this.stemDarkeningAmount;
- }
-
- get allowSubpixelAA(): boolean {
- const appController = this.renderContext.appController;
- return appController.currentFontSize <= MAX_SUBPIXEL_AA_FONT_SIZE;
- }
-
- protected get objectCount(): number {
- return this.meshBuffers == null ? 0 : this.meshBuffers.length;
- }
-
- protected get usedSizeFactor(): glmatrix.vec2 {
- return glmatrix.vec2.clone([1.0, 1.0]);
- }
-
- protected get worldTransform(): glmatrix.mat4 {
- const canvas = this.renderContext.canvas;
-
- const transform = glmatrix.mat4.create();
- const translation = this.camera.translation;
- glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
- glmatrix.mat4.scale(transform, transform, [2.0 / canvas.width, 2.0 / canvas.height, 1.0]);
- glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
- glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
-
- const pixelsPerUnit = this.pixelsPerUnit;
- glmatrix.mat4.scale(transform, transform, [pixelsPerUnit, pixelsPerUnit, 1.0]);
-
- return transform;
- }
-
- private get pixelsPerUnit(): number {
- const appController = this.renderContext.appController;
- const font = unwrapNull(appController.font);
- return appController.currentFontSize / font.opentypeFont.unitsPerEm;
- }
-
- private get stemDarkeningAmount(): glmatrix.vec2 {
- const appController = this.renderContext.appController;
- return computeStemDarkeningAmount(appController.currentFontSize, this.pixelsPerUnit);
- }
-
- constructor(renderContext: ReferenceTestView) {
- super(renderContext);
-
- this.camera = new OrthographicCamera(renderContext.canvas, { fixed: true });
- this.camera.onPan = () => renderContext.setDirty();
- this.camera.onZoom = () => renderContext.setDirty();
- }
-
- attachMeshes(meshes: PathfinderPackedMeshes[]): void {
- super.attachMeshes(meshes);
-
- this.uploadPathColors(1);
- this.uploadPathTransforms(1);
- }
-
- pathCountForObject(objectIndex: number): number {
- return 1;
- }
-
- pathBoundingRects(objectIndex: number): Float32Array {
- const appController = this.renderContext.appController;
- const font = unwrapNull(appController.font);
-
- const boundingRects = new Float32Array(2 * 4);
-
- const glyphID = unwrapNull(appController.textRun).glyphIDs[0];
-
- const metrics = unwrapNull(font.metricsForGlyph(glyphID));
-
- boundingRects[4 + 0] = metrics.xMin;
- boundingRects[4 + 1] = metrics.yMin;
- boundingRects[4 + 2] = metrics.xMax;
- boundingRects[4 + 3] = metrics.yMax;
-
- return boundingRects;
- }
-
- setHintsUniform(uniforms: UniformMap): void {
- this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
- }
-
- getPixelRectForGlyphAt(glyphIndex: number): glmatrix.vec4 {
- const textRun = unwrapNull(this.renderContext.appController.textRun);
- return textRun.pixelRectForGlyphAt(glyphIndex);
- }
-
- pathTransformsForObject(objectIndex: number): PathTransformBuffers {
- const appController = this.renderContext.appController;
- const canvas = this.renderContext.canvas;
- const font = unwrapNull(appController.font);
- const hint = new Hint(font, this.pixelsPerUnit, true);
-
- const pathTransforms = this.createPathTransformBuffers(1);
-
- const textRun = unwrapNull(appController.textRun);
- const glyphID = textRun.glyphIDs[0];
- textRun.recalculatePixelRects(this.pixelsPerUnit,
- 0.0,
- hint,
- glmatrix.vec2.create(),
- SUBPIXEL_GRANULARITY,
- glmatrix.vec4.create());
- const pixelRect = textRun.pixelRectForGlyphAt(0);
-
- const x = -pixelRect[0] / this.pixelsPerUnit;
- const y = (canvas.height - (pixelRect[3] - pixelRect[1])) / this.pixelsPerUnit;
-
- pathTransforms.st.set([1, 1, x, y], 1 * 4);
-
- return pathTransforms;
- }
-
- protected createAAStrategy(aaType: AntialiasingStrategyName,
- aaLevel: number,
- subpixelAA: SubpixelAAType):
- AntialiasingStrategy {
- return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
- }
-
- protected compositeIfNecessary(): void {}
-
- protected pathColorsForObject(objectIndex: number): Uint8Array {
- const pathColors = new Uint8Array(4 * 2);
- pathColors.set(TEXT_COLOR, 1 * 4);
- return pathColors;
- }
-
- protected directCurveProgramName(): keyof ShaderMap {
- return 'directCurve';
- }
-
- protected directInteriorProgramName(): keyof ShaderMap {
- return 'directInterior';
- }
-}
-
-class ReferenceTestSVGRenderer extends SVGRenderer {
- renderContext!: ReferenceTestView;
-
- protected get loader(): SVGLoader {
- return this.renderContext.appController.svgLoader;
- }
-
- protected get canvas(): HTMLCanvasElement {
- return this.renderContext.canvas;
- }
-
- constructor(renderContext: ReferenceTestView) {
- super(renderContext, { sizeToFit: false, fixed: true });
- }
-}
-
-function createSSIMImage(image: HTMLCanvasElement | Uint8Array, rect: glmatrix.vec4):
- imageSSIM.IImage {
- const size = glmatrix.vec2.clone([rect[2] - rect[0], rect[3] - rect[1]]);
-
- let data;
- if (image instanceof HTMLCanvasElement) {
- const context = unwrapNull(image.getContext('2d'));
- data = new Uint8Array(context.getImageData(0, 0, size[0], size[1]).data);
- } else {
- data = image;
- }
-
- return {
- channels: imageSSIM.Channels.RGBAlpha,
- data: data,
- height: size[1],
- width: size[0],
- };
-}
-
-function generateDifferenceImage(referenceImage: imageSSIM.IImage,
- renderedImage: imageSSIM.IImage):
- imageSSIM.IImage {
- const differenceImage = new Uint8Array(referenceImage.width * referenceImage.height * 4);
- for (let y = 0; y < referenceImage.height; y++) {
- const rowStart = y * referenceImage.width * 4;
- for (let x = 0; x < referenceImage.width; x++) {
- const pixelStart = rowStart + x * 4;
-
- let differenceSum = 0;
- for (let channel = 0; channel < 3; channel++) {
- differenceSum += Math.abs(referenceImage.data[pixelStart + channel] -
- renderedImage.data[pixelStart + channel]);
- }
-
- if (differenceSum === 0) {
- // Lighten to indicate no difference.
- for (let channel = 0; channel < 4; channel++) {
- differenceImage[pixelStart + channel] =
- Math.floor(referenceImage.data[pixelStart + channel] / 2) + 128;
- }
- continue;
- }
-
- // Draw differences in red.
- const differenceMean = differenceSum / 3;
- differenceImage[pixelStart + 0] = 127 + Math.round(differenceMean / 2);
- differenceImage[pixelStart + 1] = differenceImage[pixelStart + 2] = 0;
- differenceImage[pixelStart + 3] = 255;
- }
- }
- return {
- channels: referenceImage.channels,
- data: differenceImage,
- height: referenceImage.height,
- width: referenceImage.width,
- };
-}
-
-function addCell(row: HTMLTableRowElement, text: string): void {
- const tableCell = document.createElement('td');
- tableCell.textContent = text;
- row.appendChild(tableCell);
-}
-
-function main() {
- const controller = new ReferenceTestAppController;
- window.addEventListener('load', () => controller.start(), false);
-}
-
-main();
diff --git a/demo/client/src/renderer.ts b/demo/client/src/renderer.ts
deleted file mode 100644
index de6cde78..00000000
--- a/demo/client/src/renderer.ts
+++ /dev/null
@@ -1,847 +0,0 @@
-// pathfinder/client/src/renderer.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-import * as _ from 'lodash';
-
-import {AntialiasingStrategy, AntialiasingStrategyName, DirectRenderingMode} from './aa-strategy';
-import {GammaCorrectionMode} from './aa-strategy';
-import {TileInfo} from './aa-strategy';
-import {NoAAStrategy, StemDarkeningMode, SubpixelAAType} from './aa-strategy';
-import {AAOptions} from './app-controller';
-import PathfinderBufferTexture from "./buffer-texture";
-import {UniformMap, WebGLQuery} from './gl-utils';
-import {PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from "./meshes";
-import {ShaderMap} from './shader-loader';
-import {FLOAT32_SIZE, Range, UINT16_SIZE, UINT32_SIZE, unwrapNull, unwrapUndef} from './utils';
-import {RenderContext, Timings} from "./view";
-
-const MAX_PATHS: number = 65535;
-
-const MAX_VERTICES: number = 4 * 1024 * 1024;
-
-const TIME_INTERVAL_DELAY: number = 32;
-
-const B_LOOP_BLINN_DATA_SIZE: number = 4;
-const B_LOOP_BLINN_DATA_TEX_COORD_OFFSET: number = 0;
-const B_LOOP_BLINN_DATA_SIGN_OFFSET: number = 2;
-
-export interface PathTransformBuffers {
- st: T;
- ext: T;
-}
-
-export abstract class Renderer {
- readonly renderContext: RenderContext;
-
- readonly pathTransformBufferTextures: Array>;
-
- meshBuffers: PathfinderPackedMeshBuffers[] | null;
- meshes: PathfinderPackedMeshes[] | null;
-
- lastTimings: Timings;
-
- inVR: boolean = false;
-
- get emboldenAmount(): glmatrix.vec2 {
- return glmatrix.vec2.create();
- }
-
- get bgColor(): glmatrix.vec4 {
- return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
- }
-
- get fgColor(): glmatrix.vec4 | null {
- return null;
- }
-
- get backgroundColor(): glmatrix.vec4 {
- return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
- }
-
- get meshesAttached(): boolean {
- return this.meshBuffers != null && this.meshes != null;
- }
-
- abstract get isMulticolor(): boolean;
- abstract get needsStencil(): boolean;
- abstract get allowSubpixelAA(): boolean;
-
- abstract get destFramebuffer(): WebGLFramebuffer | null;
- abstract get destAllocatedSize(): glmatrix.vec2;
- abstract get destUsedSize(): glmatrix.vec2;
-
- protected antialiasingStrategy: AntialiasingStrategy | null;
- protected pathColorsBufferTextures: PathfinderBufferTexture[];
-
- protected gammaCorrectionMode: GammaCorrectionMode;
-
- protected get pathIDsAreInstanced(): boolean {
- return false;
- }
-
- protected abstract get objectCount(): number;
- protected abstract get usedSizeFactor(): glmatrix.vec2;
- protected abstract get worldTransform(): glmatrix.mat4;
-
- private implicitCoverInteriorVAO: WebGLVertexArrayObjectOES | null = null;
- private implicitCoverCurveVAO: WebGLVertexArrayObjectOES | null = null;
-
- private gammaLUTTexture: WebGLTexture | null = null;
- private areaLUTTexture: WebGLTexture | null = null;
-
- private instancedPathIDVBO: WebGLBuffer | null = null;
- private vertexIDVBO: WebGLBuffer | null = null;
- private timerQueryPollInterval: number | null = null;
-
- constructor(renderContext: RenderContext) {
- this.renderContext = renderContext;
-
- this.meshes = null;
- this.meshBuffers = null;
-
- this.lastTimings = { rendering: 0, compositing: 0 };
-
- this.gammaCorrectionMode = 'on';
-
- this.pathTransformBufferTextures = [];
- this.pathColorsBufferTextures = [];
-
- if (this.pathIDsAreInstanced)
- this.initInstancedPathIDVBO();
-
- this.initVertexIDVBO();
- this.initLUTTexture('gammaLUT', 'gammaLUTTexture');
- this.initLUTTexture('areaLUT', 'areaLUTTexture');
-
- this.antialiasingStrategy = new NoAAStrategy(0, 'none');
- this.antialiasingStrategy.init(this);
- this.antialiasingStrategy.setFramebufferSize(this);
- }
-
- attachMeshes(meshes: PathfinderPackedMeshes[]): void {
- const renderContext = this.renderContext;
- this.meshes = meshes;
- this.meshBuffers = meshes.map(meshes => {
- return new PathfinderPackedMeshBuffers(renderContext.gl, meshes);
- });
- unwrapNull(this.antialiasingStrategy).attachMeshes(this);
- }
-
- abstract pathBoundingRects(objectIndex: number): Float32Array;
- abstract setHintsUniform(uniforms: UniformMap): void;
- abstract pathTransformsForObject(objectIndex: number): PathTransformBuffers;
-
- redrawVR(frame: VRFrameData): void {
- this.redraw();
- }
-
- setDrawViewport() {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
- gl.viewport(0, 0, this.destAllocatedSize[0], this.destAllocatedSize[1]);
- }
-
- setClipPlanes(display: VRDisplay) {
- }
-
- redraw(): void {
- const renderContext = this.renderContext;
-
- if (this.meshBuffers == null)
- return;
-
- this.clearDestFramebuffer(false);
-
- // Start timing rendering.
- if (this.timerQueryPollInterval == null &&
- renderContext.timerQueryExt != null &&
- renderContext.atlasRenderingTimerQuery != null) {
- renderContext.timerQueryExt.beginQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT,
- renderContext.atlasRenderingTimerQuery);
- }
-
- const antialiasingStrategy = unwrapNull(this.antialiasingStrategy);
- antialiasingStrategy.prepareForRendering(this);
-
- // Draw "scenery" (used in the 3D view).
- this.drawSceneryIfNecessary();
-
- const passCount = antialiasingStrategy.passCount;
- for (let pass = 0; pass < passCount; pass++) {
- if (antialiasingStrategy.directRenderingMode !== 'none')
- antialiasingStrategy.prepareForDirectRendering(this);
-
- const objectCount = this.objectCount;
- for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
- if (antialiasingStrategy.directRenderingMode !== 'none') {
- // Prepare for direct rendering.
- antialiasingStrategy.prepareToRenderObject(this, objectIndex);
-
- // Clear.
- this.clearForDirectRendering(objectIndex);
-
- // Perform direct rendering (Loop-Blinn).
- this.directlyRenderObject(pass, objectIndex);
- }
-
- // Antialias.
- antialiasingStrategy.antialiasObject(this, objectIndex);
-
- // End the timer, and start a new one.
- // FIXME(pcwalton): This is kinda bogus for multipass.
- if (this.timerQueryPollInterval == null &&
- objectIndex === objectCount - 1 &&
- pass === passCount - 1 &&
- renderContext.timerQueryExt != null &&
- renderContext.compositingTimerQuery != null) {
- renderContext.timerQueryExt
- .endQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT);
- renderContext.timerQueryExt
- .beginQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT,
- renderContext.compositingTimerQuery);
- }
-
- // Perform post-antialiasing tasks.
- antialiasingStrategy.finishAntialiasingObject(this, objectIndex);
-
- antialiasingStrategy.resolveAAForObject(this, objectIndex);
- }
-
- antialiasingStrategy.resolve(pass, this);
- }
-
- // Draw the glyphs with the resolved atlas to the default framebuffer.
- this.compositeIfNecessary();
-
- // Finish timing.
- this.finishTiming();
- }
-
- setAntialiasingOptions(aaType: AntialiasingStrategyName,
- aaLevel: number,
- aaOptions: AAOptions):
- void {
- this.gammaCorrectionMode = aaOptions.gammaCorrection;
-
- this.antialiasingStrategy = this.createAAStrategy(aaType,
- aaLevel,
- aaOptions.subpixelAA,
- aaOptions.stemDarkening);
-
- this.antialiasingStrategy.init(this);
- if (this.meshes != null)
- this.antialiasingStrategy.attachMeshes(this);
- this.antialiasingStrategy.setFramebufferSize(this);
-
- this.renderContext.setDirty();
- }
-
- canvasResized(): void {
- if (this.antialiasingStrategy != null)
- this.antialiasingStrategy.init(this);
- }
-
- setFramebufferSizeUniform(uniforms: UniformMap): void {
- const gl = this.renderContext.gl;
- gl.uniform2i(uniforms.uFramebufferSize,
- this.destAllocatedSize[0],
- this.destAllocatedSize[1]);
- }
-
- setTransformAndTexScaleUniformsForDest(uniforms: UniformMap, tileInfo?: TileInfo): void {
- const renderContext = this.renderContext;
- const usedSize = this.usedSizeFactor;
-
- let tileSize, tilePosition;
- if (tileInfo == null) {
- tileSize = glmatrix.vec2.clone([1.0, 1.0]);
- tilePosition = glmatrix.vec2.create();
- } else {
- tileSize = tileInfo.size;
- tilePosition = tileInfo.position;
- }
-
- const transform = glmatrix.mat4.create();
- glmatrix.mat4.fromTranslation(transform, [
- -1.0 + tilePosition[0] / tileSize[0] * 2.0,
- -1.0 + tilePosition[1] / tileSize[1] * 2.0,
- 0.0,
- ]);
- glmatrix.mat4.scale(transform, transform, [2.0 * usedSize[0], 2.0 * usedSize[1], 1.0]);
- glmatrix.mat4.scale(transform, transform, [1.0 / tileSize[0], 1.0 / tileSize[1], 1.0]);
- renderContext.gl.uniformMatrix4fv(uniforms.uTransform, false, transform);
-
- renderContext.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
- }
-
- setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap): void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- const usedSize = this.usedSizeFactor;
- gl.uniform4f(uniforms.uTransformST, 2.0 * usedSize[0], 2.0 * usedSize[1], -1.0, -1.0);
- gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
- }
-
- setTransformUniform(uniforms: UniformMap, pass: number, objectIndex: number): void {
- const transform = this.computeTransform(pass, objectIndex);
- this.renderContext.gl.uniformMatrix4fv(uniforms.uTransform, false, transform);
- }
-
- setTransformSTUniform(uniforms: UniformMap, objectIndex: number): void {
- // FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an ST matrix is ugly and fragile.
- // Refactor.
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- const transform = this.computeTransform(0, objectIndex);
-
- gl.uniform4f(uniforms.uTransformST,
- transform[0],
- transform[5],
- transform[12],
- transform[13]);
- }
-
- affineTransform(objectIndex: number): glmatrix.mat2d {
- // FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an affine matrix is ugly and
- // fragile. Refactor.
- const transform = this.computeTransform(0, objectIndex);
- return glmatrix.mat2d.fromValues(transform[0], transform[1],
- transform[4], transform[5],
- transform[12], transform[13]);
-
- }
-
- setTransformAffineUniforms(uniforms: UniformMap, objectIndex: number): void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- const transform = this.affineTransform(objectIndex);
- gl.uniform4f(uniforms.uTransformST,
- transform[0],
- transform[3],
- transform[4],
- transform[5]);
- gl.uniform2f(uniforms.uTransformExt, transform[1], transform[2]);
- }
-
- uploadPathColors(objectCount: number): void {
- const renderContext = this.renderContext;
- for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
- const pathColors = this.pathColorsForObject(objectIndex);
-
- let pathColorsBufferTexture;
- if (objectIndex >= this.pathColorsBufferTextures.length) {
- pathColorsBufferTexture = new PathfinderBufferTexture(renderContext.gl,
- 'uPathColors');
- this.pathColorsBufferTextures[objectIndex] = pathColorsBufferTexture;
- } else {
- pathColorsBufferTexture = this.pathColorsBufferTextures[objectIndex];
- }
-
- pathColorsBufferTexture.upload(renderContext.gl, pathColors);
- }
- }
-
- uploadPathTransforms(objectCount: number): void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
- const pathTransforms = this.pathTransformsForObject(objectIndex);
-
- let pathTransformBufferTextures;
- if (objectIndex >= this.pathTransformBufferTextures.length) {
- pathTransformBufferTextures = {
- ext: new PathfinderBufferTexture(gl, 'uPathTransformExt'),
- st: new PathfinderBufferTexture(gl, 'uPathTransformST'),
- };
- this.pathTransformBufferTextures[objectIndex] = pathTransformBufferTextures;
- } else {
- pathTransformBufferTextures = this.pathTransformBufferTextures[objectIndex];
- }
-
- pathTransformBufferTextures.st.upload(gl, pathTransforms.st);
- pathTransformBufferTextures.ext.upload(gl, pathTransforms.ext);
- }
- }
-
- setPathColorsUniform(objectIndex: number, uniforms: UniformMap, textureUnit: number): void {
- const gl = this.renderContext.gl;
- const meshIndex = this.meshIndexForObject(objectIndex);
- this.pathColorsBufferTextures[meshIndex].bind(gl, uniforms, textureUnit);
- }
-
- setEmboldenAmountUniform(objectIndex: number, uniforms: UniformMap): void {
- const gl = this.renderContext.gl;
- const emboldenAmount = this.emboldenAmount;
- gl.uniform2f(uniforms.uEmboldenAmount, emboldenAmount[0], emboldenAmount[1]);
- }
-
- meshIndexForObject(objectIndex: number): number {
- return objectIndex;
- }
-
- pathRangeForObject(objectIndex: number): Range {
- if (this.meshBuffers == null)
- return new Range(0, 0);
- const bVertexPathRanges = this.meshBuffers[objectIndex].bQuadVertexPositionPathRanges;
- return new Range(1, bVertexPathRanges.length + 1);
- }
-
- bindAreaLUT(textureUnit: number, uniforms: UniformMap): void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- gl.activeTexture(gl.TEXTURE0 + textureUnit);
- gl.bindTexture(gl.TEXTURE_2D, this.areaLUTTexture);
- gl.uniform1i(uniforms.uAreaLUT, textureUnit);
- }
-
- protected clearColorForObject(objectIndex: number): glmatrix.vec4 | null {
- return null;
- }
-
- protected bindGammaLUT(bgColor: glmatrix.vec3, textureUnit: number, uniforms: UniformMap):
- void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- gl.activeTexture(gl.TEXTURE0 + textureUnit);
- gl.bindTexture(gl.TEXTURE_2D, this.gammaLUTTexture);
- gl.uniform1i(uniforms.uGammaLUT, textureUnit);
-
- gl.uniform3f(uniforms.uBGColor, bgColor[0], bgColor[1], bgColor[2]);
- }
-
- protected abstract createAAStrategy(aaType: AntialiasingStrategyName,
- aaLevel: number,
- subpixelAA: SubpixelAAType,
- stemDarkening: StemDarkeningMode):
- AntialiasingStrategy;
- protected abstract compositeIfNecessary(): void;
- protected abstract pathColorsForObject(objectIndex: number): Uint8Array;
-
- protected abstract directCurveProgramName(): keyof ShaderMap;
- protected abstract directInteriorProgramName(renderingMode: DirectRenderingMode):
- keyof ShaderMap;
-
- protected drawSceneryIfNecessary(): void {}
-
- protected clearDestFramebuffer(force: boolean): void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- const clearColor = this.backgroundColor;
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.destFramebuffer);
- gl.depthMask(true);
- gl.viewport(0, 0, this.destAllocatedSize[0], this.destAllocatedSize[1]);
- gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
- gl.clearDepth(0.0);
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
- }
-
- protected clearForDirectRendering(objectIndex: number): void {
- const renderingMode = unwrapNull(this.antialiasingStrategy).directRenderingMode;
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- const clearColor = this.clearColorForObject(objectIndex);
- if (clearColor == null)
- return;
-
- gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
- gl.clearDepth(0.0);
- gl.depthMask(true);
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
- }
-
- protected getModelviewTransform(pathIndex: number): glmatrix.mat4 {
- return glmatrix.mat4.create();
- }
-
- /// If non-instanced, returns instance 0. An empty range skips rendering the object entirely.
- protected instanceRangeForObject(objectIndex: number): Range {
- return new Range(0, 1);
- }
-
- /// Called whenever new GPU timing statistics are available.
- protected newTimingsReceived(): void {}
-
- protected createPathTransformBuffers(pathCount: number): PathTransformBuffers {
- pathCount += 1;
- return {
- ext: new Float32Array((pathCount + (pathCount & 1)) * 2),
- st: new Float32Array(pathCount * 4),
- };
- }
-
- private directlyRenderObject(pass: number, objectIndex: number): void {
- if (this.meshBuffers == null || this.meshes == null)
- return;
-
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- const antialiasingStrategy = unwrapNull(this.antialiasingStrategy);
- const renderingMode = antialiasingStrategy.directRenderingMode;
- const objectCount = this.objectCount;
-
- const instanceRange = this.instanceRangeForObject(objectIndex);
- if (instanceRange.isEmpty)
- return;
-
- const pathRange = this.pathRangeForObject(objectIndex);
- const meshIndex = this.meshIndexForObject(objectIndex);
-
- const meshes = this.meshBuffers![meshIndex];
- const meshData = this.meshes![meshIndex];
-
- // Set up implicit cover state.
- gl.depthFunc(gl.GREATER);
- gl.depthMask(true);
- gl.enable(gl.DEPTH_TEST);
- gl.disable(gl.BLEND);
- gl.cullFace(gl.BACK);
- gl.frontFace(gl.CCW);
- gl.enable(gl.CULL_FACE);
-
- // Set up the implicit cover interior VAO.
- const directInteriorProgramName = this.directInteriorProgramName(renderingMode);
- const directInteriorProgram = renderContext.shaderPrograms[directInteriorProgramName];
- if (this.implicitCoverInteriorVAO == null) {
- this.implicitCoverInteriorVAO = renderContext.vertexArrayObjectExt
- .createVertexArrayOES();
- }
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.implicitCoverInteriorVAO);
- this.initImplicitCoverInteriorVAO(objectIndex, instanceRange, renderingMode);
-
- // Draw direct interior parts.
- if (renderingMode === 'conservative')
- this.setTransformAffineUniforms(directInteriorProgram.uniforms, objectIndex);
- else
- this.setTransformUniform(directInteriorProgram.uniforms, pass, objectIndex);
- this.setFramebufferSizeUniform(directInteriorProgram.uniforms);
- this.setHintsUniform(directInteriorProgram.uniforms);
- this.setPathColorsUniform(objectIndex, directInteriorProgram.uniforms, 0);
- this.setEmboldenAmountUniform(objectIndex, directInteriorProgram.uniforms);
- this.pathTransformBufferTextures[meshIndex].st.bind(gl, directInteriorProgram.uniforms, 1);
- this.pathTransformBufferTextures[meshIndex]
- .ext
- .bind(gl, directInteriorProgram.uniforms, 2);
- const bQuadInteriorRange = getMeshIndexRange(meshes.bQuadVertexInteriorIndexPathRanges,
- pathRange);
- if (!this.pathIDsAreInstanced) {
- gl.drawElements(gl.TRIANGLES,
- bQuadInteriorRange.length,
- gl.UNSIGNED_INT,
- bQuadInteriorRange.start * UINT32_SIZE);
- } else {
- renderContext.instancedArraysExt
- .drawElementsInstancedANGLE(gl.TRIANGLES,
- bQuadInteriorRange.length,
- gl.UNSIGNED_INT,
- 0,
- instanceRange.length);
- }
-
- gl.disable(gl.CULL_FACE);
-
- // Render curves, if applicable.
- if (renderingMode !== 'conservative') {
- // Set up direct curve state.
- gl.depthMask(false);
- gl.enable(gl.BLEND);
- gl.blendEquation(gl.FUNC_ADD);
- gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
-
- // Set up the direct curve VAO.
- //
- // TODO(pcwalton): Cache these.
- const directCurveProgramName = this.directCurveProgramName();
- const directCurveProgram = renderContext.shaderPrograms[directCurveProgramName];
- if (this.implicitCoverCurveVAO == null) {
- this.implicitCoverCurveVAO = renderContext.vertexArrayObjectExt
- .createVertexArrayOES();
- }
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.implicitCoverCurveVAO);
- this.initImplicitCoverCurveVAO(objectIndex, instanceRange);
-
- // Draw direct curve parts.
- this.setTransformUniform(directCurveProgram.uniforms, pass, objectIndex);
- this.setFramebufferSizeUniform(directCurveProgram.uniforms);
- this.setHintsUniform(directCurveProgram.uniforms);
- this.setPathColorsUniform(objectIndex, directCurveProgram.uniforms, 0);
- this.setEmboldenAmountUniform(objectIndex, directCurveProgram.uniforms);
- this.pathTransformBufferTextures[meshIndex]
- .st
- .bind(gl, directCurveProgram.uniforms, 1);
- this.pathTransformBufferTextures[meshIndex]
- .ext
- .bind(gl, directCurveProgram.uniforms, 2);
- const coverCurveRange = getMeshIndexRange(meshes.bQuadVertexPositionPathRanges,
- pathRange);
- if (!this.pathIDsAreInstanced) {
- gl.drawArrays(gl.TRIANGLES, coverCurveRange.start * 6, coverCurveRange.length * 6);
- } else {
- renderContext.instancedArraysExt
- .drawArraysInstancedANGLE(gl.TRIANGLES,
- 0,
- coverCurveRange.length * 6,
- instanceRange.length);
- }
- }
-
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
-
- // Finish direct rendering. Right now, this performs compositing if necessary.
- antialiasingStrategy.finishDirectlyRenderingObject(this, objectIndex);
- }
-
- private finishTiming(): void {
- const renderContext = this.renderContext;
-
- if (this.timerQueryPollInterval != null ||
- renderContext.timerQueryExt == null ||
- renderContext.atlasRenderingTimerQuery == null ||
- renderContext.compositingTimerQuery == null) {
- return;
- }
-
- renderContext.timerQueryExt.endQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT);
-
- this.timerQueryPollInterval = window.setInterval(() => {
- if (renderContext.timerQueryExt == null ||
- renderContext.atlasRenderingTimerQuery == null ||
- renderContext.compositingTimerQuery == null) {
- return;
- }
-
- for (const queryName of ['atlasRenderingTimerQuery', 'compositingTimerQuery'] as
- Array<'atlasRenderingTimerQuery' | 'compositingTimerQuery'>) {
- if (renderContext.timerQueryExt
- .getQueryObjectEXT(renderContext[queryName] as WebGLQuery,
- renderContext.timerQueryExt
- .QUERY_RESULT_AVAILABLE_EXT) ===
- 0) {
- return;
- }
- }
-
- const atlasRenderingTime =
- renderContext.timerQueryExt
- .getQueryObjectEXT(renderContext.atlasRenderingTimerQuery,
- renderContext.timerQueryExt.QUERY_RESULT_EXT);
- const compositingTime =
- renderContext.timerQueryExt
- .getQueryObjectEXT(renderContext.compositingTimerQuery,
- renderContext.timerQueryExt.QUERY_RESULT_EXT);
- this.lastTimings = {
- compositing: compositingTime / 1000000.0,
- rendering: atlasRenderingTime / 1000000.0,
- };
-
- this.newTimingsReceived();
-
- window.clearInterval(this.timerQueryPollInterval!);
- this.timerQueryPollInterval = null;
- }, TIME_INTERVAL_DELAY);
- }
-
- private initLUTTexture(imageName: 'gammaLUT' | 'areaLUT',
- textureName: 'gammaLUTTexture' | 'areaLUTTexture'):
- void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- const image = renderContext[imageName];
- const texture = unwrapNull(gl.createTexture());
- gl.bindTexture(gl.TEXTURE_2D, texture);
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, gl.LUMINANCE, gl.UNSIGNED_BYTE, image);
- const filter = imageName === 'gammaLUT' ? gl.NEAREST : gl.LINEAR;
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
-
- this[textureName] = texture;
- }
-
- private initImplicitCoverCurveVAO(objectIndex: number, instanceRange: Range): void {
- if (this.meshBuffers == null)
- return;
-
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- const meshIndex = this.meshIndexForObject(objectIndex);
- const meshes = this.meshBuffers[meshIndex];
- const meshData = unwrapNull(this.meshes)[meshIndex];
-
- const directCurveProgramName = this.directCurveProgramName();
- const directCurveProgram = renderContext.shaderPrograms[directCurveProgramName];
- gl.useProgram(directCurveProgram.program);
- gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositions);
- gl.vertexAttribPointer(directCurveProgram.attributes.aPosition, 2, gl.FLOAT, false, 0, 0);
- gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexIDVBO);
- gl.vertexAttribPointer(directCurveProgram.attributes.aVertexID, 1, gl.FLOAT, false, 0, 0);
-
- if (this.pathIDsAreInstanced)
- gl.bindBuffer(gl.ARRAY_BUFFER, this.instancedPathIDVBO);
- else
- gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositionPathIDs);
- gl.vertexAttribPointer(directCurveProgram.attributes.aPathID,
- 1,
- gl.UNSIGNED_SHORT,
- false,
- 0,
- instanceRange.start * UINT16_SIZE);
- if (this.pathIDsAreInstanced) {
- renderContext.instancedArraysExt
- .vertexAttribDivisorANGLE(directCurveProgram.attributes.aPathID, 1);
- }
-
- gl.enableVertexAttribArray(directCurveProgram.attributes.aPosition);
- gl.enableVertexAttribArray(directCurveProgram.attributes.aVertexID);
- gl.enableVertexAttribArray(directCurveProgram.attributes.aPathID);
- }
-
- private initImplicitCoverInteriorVAO(objectIndex: number,
- instanceRange: Range,
- renderingMode: DirectRenderingMode):
- void {
- if (this.meshBuffers == null)
- return;
-
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- const meshIndex = this.meshIndexForObject(objectIndex);
- const meshes = this.meshBuffers[meshIndex];
-
- const directInteriorProgramName = this.directInteriorProgramName(renderingMode);
- const directInteriorProgram = renderContext.shaderPrograms[directInteriorProgramName];
- gl.useProgram(directInteriorProgram.program);
- gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositions);
- gl.vertexAttribPointer(directInteriorProgram.attributes.aPosition,
- 2,
- gl.FLOAT,
- false,
- 0,
- 0);
-
- if (this.pathIDsAreInstanced)
- gl.bindBuffer(gl.ARRAY_BUFFER, this.instancedPathIDVBO);
- else
- gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositionPathIDs);
- gl.vertexAttribPointer(directInteriorProgram.attributes.aPathID,
- 1,
- gl.UNSIGNED_SHORT,
- false,
- 0,
- instanceRange.start * UINT16_SIZE);
- if (this.pathIDsAreInstanced) {
- renderContext.instancedArraysExt
- .vertexAttribDivisorANGLE(directInteriorProgram.attributes.aPathID, 1);
- }
-
- if (directInteriorProgramName === 'conservativeInterior') {
- gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexIDVBO);
- gl.vertexAttribPointer(directInteriorProgram.attributes.aVertexID,
- 1,
- gl.FLOAT,
- false,
- 0,
- 0);
- }
-
- gl.enableVertexAttribArray(directInteriorProgram.attributes.aPosition);
- gl.enableVertexAttribArray(directInteriorProgram.attributes.aPathID);
- if (directInteriorProgramName === 'conservativeInterior')
- gl.enableVertexAttribArray(directInteriorProgram.attributes.aVertexID);
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, meshes.bQuadVertexInteriorIndices);
- }
-
- private initInstancedPathIDVBO(): void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- const pathIDs = new Uint16Array(MAX_PATHS);
- for (let pathIndex = 0; pathIndex < MAX_PATHS; pathIndex++)
- pathIDs[pathIndex] = pathIndex + 1;
-
- this.instancedPathIDVBO = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, this.instancedPathIDVBO);
- gl.bufferData(gl.ARRAY_BUFFER, pathIDs, gl.STATIC_DRAW);
- gl.bindBuffer(gl.ARRAY_BUFFER, null);
- }
-
- private initVertexIDVBO(): void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- const vertexIDs = new Float32Array(MAX_VERTICES);
- for (let vertexID = 0; vertexID < MAX_VERTICES; vertexID++)
- vertexIDs[vertexID] = vertexID;
-
- this.vertexIDVBO = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexIDVBO);
- gl.bufferData(gl.ARRAY_BUFFER, vertexIDs, gl.STATIC_DRAW);
- gl.bindBuffer(gl.ARRAY_BUFFER, null);
- }
-
- private computeTransform(pass: number, objectIndex: number): glmatrix.mat4 {
- let transform;
- if (this.antialiasingStrategy == null)
- transform = glmatrix.mat4.create();
- else
- transform = this.antialiasingStrategy.worldTransformForPass(this, pass);
-
- glmatrix.mat4.mul(transform, transform, this.worldTransform);
- glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex));
- return transform;
- }
-}
-
-function getMeshIndexRange(indexRanges: Range[], pathRange: Range): Range {
- if (indexRanges.length === 0)
- return new Range(0, 0);
-
- const lastIndexRange = unwrapUndef(_.last(indexRanges));
- const descending = indexRanges[0].start > lastIndexRange.start;
-
- pathRange = new Range(pathRange.start - 1, pathRange.end - 1);
-
- let startIndex;
- if (pathRange.start >= indexRanges.length)
- startIndex = lastIndexRange.end;
- else if (!descending)
- startIndex = indexRanges[pathRange.start].start;
- else
- startIndex = indexRanges[pathRange.start].end;
-
- let endIndex;
- if (descending)
- endIndex = indexRanges[pathRange.end - 1].start;
- else if (pathRange.end >= indexRanges.length)
- endIndex = lastIndexRange.end;
- else
- endIndex = indexRanges[pathRange.end].start;
-
- if (descending) {
- const tmp = startIndex;
- startIndex = endIndex;
- endIndex = tmp;
- }
-
- return new Range(startIndex, endIndex);
-}
diff --git a/demo/client/src/shader-loader.ts b/demo/client/src/shader-loader.ts
deleted file mode 100644
index ad2e9e66..00000000
--- a/demo/client/src/shader-loader.ts
+++ /dev/null
@@ -1,189 +0,0 @@
-// pathfinder/demo/client/src/shader-loader.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import {AttributeMap, UniformMap} from './gl-utils';
-import {expectNotNull, PathfinderError, unwrapNull} from './utils';
-
-export interface ShaderMap {
- blitLinear: T;
- blitGamma: T;
- conservativeInterior: T;
- demo3DDistantGlyph: T;
- demo3DMonument: T;
- directCurve: T;
- directInterior: T;
- direct3DCurve: T;
- direct3DInterior: T;
- mcaa: T;
- ssaaSubpixelResolve: T;
- stencilAAA: T;
- xcaaMonoResolve: T;
- xcaaMonoSubpixelResolve: T;
-}
-
-export interface UnlinkedShaderProgram {
- vertex: WebGLShader;
- fragment: WebGLShader;
-}
-
-const COMMON_SHADER_URL: string = '/glsl/gles2/common.inc.glsl';
-
-export const SHADER_NAMES: Array> = [
- 'blitLinear',
- 'blitGamma',
- 'conservativeInterior',
- 'directCurve',
- 'directInterior',
- 'direct3DCurve',
- 'direct3DInterior',
- 'ssaaSubpixelResolve',
- 'mcaa',
- 'stencilAAA',
- 'xcaaMonoResolve',
- 'xcaaMonoSubpixelResolve',
- 'demo3DDistantGlyph',
- 'demo3DMonument',
-];
-
-const SHADER_URLS: ShaderMap = {
- blitGamma: {
- fragment: "/glsl/gles2/blit-gamma.fs.glsl",
- vertex: "/glsl/gles2/blit.vs.glsl",
- },
- blitLinear: {
- fragment: "/glsl/gles2/blit-linear.fs.glsl",
- vertex: "/glsl/gles2/blit.vs.glsl",
- },
- conservativeInterior: {
- fragment: "/glsl/gles2/direct-interior.fs.glsl",
- vertex: "/glsl/gles2/conservative-interior.vs.glsl",
- },
- demo3DDistantGlyph: {
- fragment: "/glsl/gles2/demo-3d-distant-glyph.fs.glsl",
- vertex: "/glsl/gles2/demo-3d-distant-glyph.vs.glsl",
- },
- demo3DMonument: {
- fragment: "/glsl/gles2/demo-3d-monument.fs.glsl",
- vertex: "/glsl/gles2/demo-3d-monument.vs.glsl",
- },
- direct3DCurve: {
- fragment: "/glsl/gles2/direct-curve.fs.glsl",
- vertex: "/glsl/gles2/direct-3d-curve.vs.glsl",
- },
- direct3DInterior: {
- fragment: "/glsl/gles2/direct-interior.fs.glsl",
- vertex: "/glsl/gles2/direct-3d-interior.vs.glsl",
- },
- directCurve: {
- fragment: "/glsl/gles2/direct-curve.fs.glsl",
- vertex: "/glsl/gles2/direct-curve.vs.glsl",
- },
- directInterior: {
- fragment: "/glsl/gles2/direct-interior.fs.glsl",
- vertex: "/glsl/gles2/direct-interior.vs.glsl",
- },
- mcaa: {
- fragment: "/glsl/gles2/mcaa.fs.glsl",
- vertex: "/glsl/gles2/mcaa.vs.glsl",
- },
- ssaaSubpixelResolve: {
- fragment: "/glsl/gles2/ssaa-subpixel-resolve.fs.glsl",
- vertex: "/glsl/gles2/blit.vs.glsl",
- },
- stencilAAA: {
- fragment: "/glsl/gles2/stencil-aaa.fs.glsl",
- vertex: "/glsl/gles2/stencil-aaa.vs.glsl",
- },
- xcaaMonoResolve: {
- fragment: "/glsl/gles2/xcaa-mono-resolve.fs.glsl",
- vertex: "/glsl/gles2/xcaa-mono-resolve.vs.glsl",
- },
- xcaaMonoSubpixelResolve: {
- fragment: "/glsl/gles2/xcaa-mono-subpixel-resolve.fs.glsl",
- vertex: "/glsl/gles2/xcaa-mono-subpixel-resolve.vs.glsl",
- },
-};
-
-export interface ShaderProgramSource {
- vertex: string;
- fragment: string;
-}
-
-interface ShaderProgramURLs {
- vertex: string;
- fragment: string;
-}
-
-export class ShaderLoader {
- common!: Promise;
- shaders!: Promise>;
-
- load(): void {
- this.common = window.fetch(COMMON_SHADER_URL).then(response => response.text());
-
- const shaderKeys = Object.keys(SHADER_URLS) as Array>;
- const promises = [];
- for (const shaderKey of shaderKeys) {
- promises.push(Promise.all([
- window.fetch(SHADER_URLS[shaderKey].vertex).then(response => response.text()),
- window.fetch(SHADER_URLS[shaderKey].fragment).then(response => response.text()),
- ]).then(results => ({ vertex: results[0], fragment: results[1] })));
- }
-
- this.shaders = Promise.all(promises).then(promises => {
- const shaderMap: Partial> = {};
- for (let keyIndex = 0; keyIndex < shaderKeys.length; keyIndex++)
- shaderMap[shaderKeys[keyIndex]] = promises[keyIndex];
- return shaderMap as ShaderMap;
- });
- }
-}
-
-export class PathfinderShaderProgram {
- readonly uniforms: UniformMap;
- readonly attributes: AttributeMap;
- readonly program: WebGLProgram;
- readonly programName: string;
-
- constructor(gl: WebGLRenderingContext,
- programName: string,
- unlinkedShaderProgram: UnlinkedShaderProgram) {
- this.programName = programName;
-
- this.program = expectNotNull(gl.createProgram(), "Failed to create shader program!");
- for (const compiledShader of Object.values(unlinkedShaderProgram))
- gl.attachShader(this.program, compiledShader);
- gl.linkProgram(this.program);
-
- if (gl.getProgramParameter(this.program, gl.LINK_STATUS) === 0) {
- const infoLog = gl.getProgramInfoLog(this.program);
- throw new PathfinderError(`Failed to link program "${programName}":\n${infoLog}`);
- }
-
- const uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
- const attributeCount = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES);
-
- const uniforms: UniformMap = {};
- const attributes: AttributeMap = {};
-
- for (let uniformIndex = 0; uniformIndex < uniformCount; uniformIndex++) {
- const uniformName = unwrapNull(gl.getActiveUniform(this.program, uniformIndex)).name;
- uniforms[uniformName] = expectNotNull(gl.getUniformLocation(this.program, uniformName),
- `Didn't find uniform "${uniformName}"!`);
- }
- for (let attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) {
- const attributeName = unwrapNull(gl.getActiveAttrib(this.program, attributeIndex)).name;
- attributes[attributeName] = attributeIndex;
- }
-
- this.uniforms = uniforms;
- this.attributes = attributes;
- }
-}
diff --git a/demo/client/src/ssaa-strategy.ts b/demo/client/src/ssaa-strategy.ts
deleted file mode 100644
index 61d9aa40..00000000
--- a/demo/client/src/ssaa-strategy.ts
+++ /dev/null
@@ -1,202 +0,0 @@
-// pathfinder/demo/client/src/ssaa-strategy.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-
-import {AntialiasingStrategy, DirectRenderingMode, SubpixelAAType, TileInfo} from './aa-strategy';
-import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
-import {createFramebufferDepthTexture, setTextureParameters} from './gl-utils';
-import {Renderer} from './renderer';
-import {unwrapNull} from './utils';
-import {DemoView} from './view';
-
-export default class SSAAStrategy extends AntialiasingStrategy {
- get passCount(): number {
- switch (this.level) {
- case 16:
- return 4;
- case 8:
- return 2;
- }
- return 1;
- }
-
- private level: number;
-
- private destFramebufferSize: glmatrix.vec2;
- private supersampledFramebufferSize: glmatrix.vec2;
- private supersampledColorTexture!: WebGLTexture;
- private supersampledDepthTexture!: WebGLTexture;
- private supersampledFramebuffer!: WebGLFramebuffer;
-
- constructor(level: number, subpixelAA: SubpixelAAType) {
- super(subpixelAA);
-
- this.level = level;
- this.subpixelAA = subpixelAA;
- this.destFramebufferSize = glmatrix.vec2.create();
- this.supersampledFramebufferSize = glmatrix.vec2.create();
- }
-
- attachMeshes(renderer: Renderer): void {}
-
- setFramebufferSize(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- this.destFramebufferSize = glmatrix.vec2.clone(renderer.destAllocatedSize);
-
- this.supersampledFramebufferSize = glmatrix.vec2.create();
- glmatrix.vec2.mul(this.supersampledFramebufferSize,
- this.destFramebufferSize,
- this.supersampleScale);
-
- this.supersampledColorTexture =
- createFramebufferColorTexture(gl,
- this.supersampledFramebufferSize,
- renderContext.colorAlphaFormat,
- gl.LINEAR);
-
- this.supersampledDepthTexture =
- createFramebufferDepthTexture(gl, this.supersampledFramebufferSize);
-
- this.supersampledFramebuffer = createFramebuffer(gl,
- this.supersampledColorTexture,
- this.supersampledDepthTexture);
-
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
- }
-
- get transform(): glmatrix.mat4 {
- const scale = glmatrix.vec2.create();
- glmatrix.vec2.div(scale, this.supersampledFramebufferSize, this.destFramebufferSize);
-
- const transform = glmatrix.mat4.create();
- glmatrix.mat4.fromScaling(transform, [scale[0], scale[1], 1.0]);
- return transform;
- }
-
- prepareForRendering(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- const framebufferSize = this.supersampledFramebufferSize;
- const usedSize = this.usedSupersampledFramebufferSize(renderer);
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.supersampledFramebuffer);
- gl.viewport(0, 0, framebufferSize[0], framebufferSize[1]);
- gl.scissor(0, 0, usedSize[0], usedSize[1]);
- gl.enable(gl.SCISSOR_TEST);
-
- const clearColor = renderer.backgroundColor;
- gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
- gl.clearDepth(0.0);
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
- }
-
- prepareForDirectRendering(renderer: Renderer): void {}
-
- prepareToRenderObject(renderer: Renderer, objectIndex: number): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.supersampledFramebuffer);
- gl.viewport(0,
- 0,
- this.supersampledFramebufferSize[0],
- this.supersampledFramebufferSize[1]);
- gl.disable(gl.SCISSOR_TEST);
- }
-
- finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void {}
-
- antialiasObject(renderer: Renderer): void {}
-
- finishAntialiasingObject(renderer: Renderer, objectIndex: number): void {}
-
- resolveAAForObject(renderer: Renderer): void {}
-
- resolve(pass: number, renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- gl.bindFramebuffer(gl.FRAMEBUFFER, renderer.destFramebuffer);
- renderer.setDrawViewport();
- gl.disable(gl.DEPTH_TEST);
- gl.disable(gl.BLEND);
-
- // Set up the blit program VAO.
- let resolveProgram;
- if (this.subpixelAA !== 'none')
- resolveProgram = renderContext.shaderPrograms.ssaaSubpixelResolve;
- else
- resolveProgram = renderContext.shaderPrograms.blitLinear;
- gl.useProgram(resolveProgram.program);
- renderContext.initQuadVAO(resolveProgram.attributes);
-
- // Resolve framebuffer.
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, this.supersampledColorTexture);
- gl.uniform1i(resolveProgram.uniforms.uSource, 0);
- gl.uniform2i(resolveProgram.uniforms.uSourceDimensions,
- this.supersampledFramebufferSize[0],
- this.supersampledFramebufferSize[1]);
- const tileInfo = this.tileInfoForPass(pass);
- renderer.setTransformAndTexScaleUniformsForDest(resolveProgram.uniforms, tileInfo);
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer);
- gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
- }
-
- worldTransformForPass(renderer: Renderer, pass: number): glmatrix.mat4 {
- const tileInfo = this.tileInfoForPass(pass);
- const usedSize = renderer.destUsedSize;
- const transform = glmatrix.mat4.create();
- glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
- glmatrix.mat4.scale(transform, transform, [tileInfo.size[0], tileInfo.size[1], 1.0]);
- glmatrix.mat4.translate(transform, transform, [
- -tileInfo.position[0] / tileInfo.size[0] * 2.0,
- -tileInfo.position[1] / tileInfo.size[1] * 2.0,
- 0.0,
- ]);
- glmatrix.mat4.translate(transform, transform, [1.0, 1.0, 0.0]);
- return transform;
- }
-
- private tileInfoForPass(pass: number): TileInfo {
- const tileSize = this.tileSize;
- return {
- position: glmatrix.vec2.clone([pass % tileSize[0], Math.floor(pass / tileSize[0])]),
- size: tileSize,
- };
- }
-
- get directRenderingMode(): DirectRenderingMode {
- return 'color';
- }
-
- private get supersampleScale(): glmatrix.vec2 {
- return glmatrix.vec2.clone([this.subpixelAA !== 'none' ? 3 : 2, this.level === 2 ? 1 : 2]);
- }
-
- private get tileSize(): glmatrix.vec2 {
- switch (this.level) {
- case 16:
- return glmatrix.vec2.clone([2.0, 2.0]);
- case 8:
- return glmatrix.vec2.clone([2.0, 1.0]);
- }
- return glmatrix.vec2.clone([1.0, 1.0]);
- }
-
- private usedSupersampledFramebufferSize(renderer: Renderer): glmatrix.vec2 {
- const result = glmatrix.vec2.create();
- glmatrix.vec2.mul(result, renderer.destUsedSize, this.supersampleScale);
- return result;
- }
-}
diff --git a/demo/client/src/svg-demo.ts b/demo/client/src/svg-demo.ts
deleted file mode 100644
index e62d6c6e..00000000
--- a/demo/client/src/svg-demo.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-// pathfinder/demo/client/src/svg-demo.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-import * as _ from 'lodash';
-
-import {DemoAppController} from './app-controller';
-import {OrthographicCamera} from "./camera";
-import {PathfinderPackedMeshes} from "./meshes";
-import {ShaderMap, ShaderProgramSource} from './shader-loader';
-import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
-import {SVGRenderer} from './svg-renderer';
-import {DemoView} from './view';
-
-const parseColor = require('parse-color');
-
-const SVG_NS: string = "http://www.w3.org/2000/svg";
-
-const DEFAULT_FILE: string = 'tiger';
-
-class SVGDemoController extends DemoAppController {
- loader!: SVGLoader;
-
- protected readonly builtinFileURI: string = BUILTIN_SVG_URI;
-
- private meshes!: PathfinderPackedMeshes;
-
- start() {
- super.start();
-
- this.loader = new SVGLoader;
-
- this.loadInitialFile(this.builtinFileURI);
- }
-
- protected fileLoaded(fileData: ArrayBuffer) {
- this.loader.loadFile(fileData);
- this.loader.partition().then(meshes => {
- this.meshes = new PathfinderPackedMeshes(meshes);
- this.meshesReceived();
- });
- }
-
- protected createView(areaLUT: HTMLImageElement,
- gammaLUT: HTMLImageElement,
- commonShaderSource: string,
- shaderSources: ShaderMap):
- SVGDemoView {
- return new SVGDemoView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
- }
-
- protected get defaultFile(): string {
- return DEFAULT_FILE;
- }
-
- private meshesReceived(): void {
- this.view.then(view => {
- view.attachMeshes([this.meshes]);
- view.initCameraBounds(this.loader.svgViewBox);
- });
- }
-}
-
-class SVGDemoView extends DemoView {
- renderer: SVGDemoRenderer;
- appController: SVGDemoController;
-
- get camera(): OrthographicCamera {
- return this.renderer.camera;
- }
-
- constructor(appController: SVGDemoController,
- areaLUT: HTMLImageElement,
- gammaLUT: HTMLImageElement,
- commonShaderSource: string,
- shaderSources: ShaderMap) {
- super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
-
- this.appController = appController;
- this.renderer = new SVGDemoRenderer(this, {sizeToFit: true});
-
- this.resizeToFit(true);
- }
-
- initCameraBounds(viewBox: glmatrix.vec4): void {
- this.renderer.initCameraBounds(viewBox);
- }
-}
-
-class SVGDemoRenderer extends SVGRenderer {
- renderContext!: SVGDemoView;
-
- needsStencil: boolean = false;
-
- protected get loader(): SVGLoader {
- return this.renderContext.appController.loader;
- }
-
- protected get canvas(): HTMLCanvasElement {
- return this.renderContext.canvas;
- }
-
- protected newTimingsReceived(): void {
- this.renderContext.appController.newTimingsReceived(this.lastTimings);
- }
-}
-
-function main() {
- const controller = new SVGDemoController;
- window.addEventListener('load', () => controller.start(), false);
-}
-
-main();
diff --git a/demo/client/src/svg-loader.ts b/demo/client/src/svg-loader.ts
deleted file mode 100644
index a813d1b8..00000000
--- a/demo/client/src/svg-loader.ts
+++ /dev/null
@@ -1,254 +0,0 @@
-// pathfinder/client/src/svg-loader.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as base64js from 'base64-js';
-import * as glmatrix from 'gl-matrix';
-import * as _ from 'lodash';
-
-import 'path-data-polyfill.js';
-import {parseServerTiming, PathfinderMeshPack, PathfinderPackedMeshes} from "./meshes";
-import {lerp, panic, Range, unwrapNull, unwrapUndef} from "./utils";
-
-export const BUILTIN_SVG_URI: string = "/svg/demo";
-
-const parseColor = require('parse-color');
-
-const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths";
-
-declare class SVGPathSegment {
- type: string;
- values: number[];
-}
-
-declare global {
- interface SVGPathElement {
- getPathData(settings: any): SVGPathSegment[];
- }
-}
-
-type FillRule = 'evenodd' | 'winding';
-
-export abstract class SVGPath {
- element: SVGPathElement;
- color: glmatrix.vec4;
-
- constructor(element: SVGPathElement, colorProperty: keyof CSSStyleDeclaration) {
- this.element = element;
-
- const style = window.getComputedStyle(element);
- this.color = unwrapNull(colorFromStyle(style[colorProperty]));
- }
-}
-
-export class SVGFill extends SVGPath {
- fillRule: FillRule;
-
- get pathfinderFillRule(): string {
- return { evenodd: 'EvenOdd', winding: 'Winding' }[this.fillRule];
- }
-
- constructor(element: SVGPathElement) {
- super(element, 'fill');
-
- const style = window.getComputedStyle(element);
- this.fillRule = style.fillRule === 'evenodd' ? 'evenodd' : 'winding';
- }
-}
-
-export class SVGStroke extends SVGPath {
- width: number;
-
- constructor(element: SVGPathElement) {
- super(element, 'stroke');
-
- const style = window.getComputedStyle(element);
- const ctm = unwrapNull(element.getCTM());
-
- const strokeWidthString = unwrapNull(style.strokeWidth);
- const matches = /^(\d+\.?\d*)(.*)$/.exec(strokeWidthString);
- let strokeWidth: number;
- if (matches == null) {
- strokeWidth = 0.0;
- } else {
- strokeWidth = unwrapNull(parseFloat(matches[1]));
- if (matches[2] === 'px')
- strokeWidth *= lerp(ctm.a, ctm.d, 0.5);
- }
-
- this.width = strokeWidth;
- }
-}
-
-interface ClipPathIDTable {
- [id: string]: number;
-}
-
-export class SVGLoader {
- pathInstances: SVGPath[];
- scale: number;
- pathBounds: glmatrix.vec4[];
- svgBounds: glmatrix.vec4;
- svgViewBox: glmatrix.vec4;
- isMonochrome: boolean;
-
- private svg: SVGSVGElement;
- private fileData!: ArrayBuffer;
-
- private paths: any[];
- private clipPathIDs: ClipPathIDTable;
-
- constructor() {
- this.scale = 1.0;
- this.pathInstances = [];
- this.pathBounds = [];
- this.paths = [];
- this.clipPathIDs = {};
- this.svgBounds = glmatrix.vec4.create();
- this.svgViewBox = glmatrix.vec4.create();
- this.svg = unwrapNull(document.getElementById('pf-svg')) as Element as SVGSVGElement;
- this.isMonochrome = false;
- }
-
- 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;
- this.attachSVG(svgElement);
- }
-
- partition(pathIndex?: number | undefined): Promise {
- // Make the request.
- const paths = pathIndex == null ? this.paths : [this.paths[pathIndex]];
- let time = 0;
- return window.fetch(PARTITION_SVG_PATHS_ENDPOINT_URL, {
- body: JSON.stringify({
- paths: paths,
- viewBoxHeight: this.svgViewBox[3] - this.svgViewBox[1],
- viewBoxWidth: this.svgViewBox[2] - this.svgViewBox[0],
- }),
- headers: {'Content-Type': 'application/json'} as any,
- method: 'POST',
- }).then(response => {
- time = parseServerTiming(response.headers);
- return response.arrayBuffer();
- }).then(buffer => new PathfinderMeshPack(buffer));
- }
-
- private attachSVG(svgElement: SVGSVGElement) {
- // Clear out the current document.
- let kid;
- while ((kid = this.svg.firstChild) != null)
- this.svg.removeChild(kid);
-
- // Add all kids of the incoming SVG document.
- while ((kid = svgElement.firstChild) != null)
- this.svg.appendChild(kid);
-
- const viewBox = svgElement.viewBox.baseVal;
- this.svg.setAttribute('width', "" + viewBox.width);
- this.svg.setAttribute('height', "" + viewBox.height);
-
- // Scan for geometry elements.
- this.pathInstances.length = 0;
- this.clipPathIDs = {};
- this.scanElement(this.svg);
-
- this.paths = [];
-
- for (const instance of this.pathInstances) {
- const element = instance.element;
- const svgCTM = unwrapNull(element.getCTM());
- const ctm = glmatrix.mat2d.fromValues(svgCTM.a, -svgCTM.b,
- svgCTM.c, -svgCTM.d,
- svgCTM.e, viewBox.height - svgCTM.f);
- glmatrix.mat2d.scale(ctm, ctm, [this.scale, this.scale]);
-
- const bottomLeft = glmatrix.vec2.create();
- const topRight = glmatrix.vec2.create();
-
- const segments = element.getPathData({ normalize: true }).map(segment => {
- const newValues = _.flatMap(_.chunk(segment.values, 2), coords => {
- const point = glmatrix.vec2.create();
- glmatrix.vec2.transformMat2d(point, coords, ctm);
-
- glmatrix.vec2.min(bottomLeft, bottomLeft, point);
- glmatrix.vec2.max(topRight, topRight, point);
-
- return [point[0], point[1]];
- });
- return {
- type: segment.type,
- values: newValues,
- };
- });
-
- const pathBounds = glmatrix.vec4.clone([
- bottomLeft[0], bottomLeft[1],
- topRight[0], topRight[1],
- ]);
-
- if (instance instanceof SVGFill) {
- this.paths.push({
- kind: { Fill: instance.pathfinderFillRule },
- segments: segments,
- });
- this.pathBounds.push(pathBounds);
- } else if (instance instanceof SVGStroke) {
- this.paths.push({ kind: { Stroke: instance.width }, segments: segments });
- this.pathBounds.push(pathBounds);
- }
- }
-
- this.isMonochrome = this.pathInstances.every(pathInstance => {
- return glmatrix.vec4.equals(pathInstance.color, this.pathInstances[0].color);
- });
-
- this.svgViewBox = glmatrix.vec4.clone([
- viewBox.x, viewBox.y, viewBox.x + viewBox.width, viewBox.y + viewBox.height,
- ]);
- }
-
- private scanElement(element: Element): void {
- const style = window.getComputedStyle(element);
-
- if (element instanceof SVGPathElement) {
- if (colorFromStyle(style.fill) != null)
- this.addPathInstance(new SVGFill(element));
- if (colorFromStyle(style.stroke) != null)
- this.addPathInstance(new SVGStroke(element));
- }
-
- for (const kid of element.childNodes) {
- if (kid instanceof Element)
- this.scanElement(kid);
- }
- }
-
- private addPathInstance(pathInstance: SVGPath): void {
- this.pathInstances.push(pathInstance);
- }
-}
-
-function colorFromStyle(style: string | null): glmatrix.vec4 | null {
- if (style == null || style === 'none')
- return null;
-
- // TODO(pcwalton): Gradients?
- const color = parseColor(style);
- if (color.rgba == null)
- return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
-
- if (color.rgba[3] === 0.0)
- return null;
- return glmatrix.vec4.clone(color.rgba);
-}
diff --git a/demo/client/src/svg-renderer.ts b/demo/client/src/svg-renderer.ts
deleted file mode 100644
index 1947e080..00000000
--- a/demo/client/src/svg-renderer.ts
+++ /dev/null
@@ -1,217 +0,0 @@
-// pathfinder/demo/client/src/svg-renderer.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-
-import {AntialiasingStrategy, AntialiasingStrategyName, DirectRenderingMode} from './aa-strategy';
-import {NoAAStrategy} from './aa-strategy';
-import {SubpixelAAType} from './aa-strategy';
-import {OrthographicCamera} from "./camera";
-import {UniformMap} from './gl-utils';
-import {PathfinderPackedMeshes} from './meshes';
-import {PathTransformBuffers, Renderer} from "./renderer";
-import {ShaderMap} from './shader-loader';
-import SSAAStrategy from './ssaa-strategy';
-import {SVGLoader} from './svg-loader';
-import {Range} from './utils';
-import {RenderContext} from './view';
-import {MCAAStrategy, XCAAStrategy} from './xcaa-strategy';
-
-interface AntialiasingStrategyTable {
- none: typeof NoAAStrategy;
- ssaa: typeof SSAAStrategy;
- xcaa: typeof MCAAStrategy;
-}
-
-const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
- none: NoAAStrategy,
- ssaa: SSAAStrategy,
- xcaa: MCAAStrategy,
-};
-
-export interface SVGRendererOptions {
- sizeToFit?: boolean;
- fixed?: boolean;
-}
-
-export abstract class SVGRenderer extends Renderer {
- renderContext!: RenderContext;
-
- camera: OrthographicCamera;
-
- needsStencil: boolean = false;
-
- private options: SVGRendererOptions;
-
- get isMulticolor(): boolean {
- return !this.loader.isMonochrome;
- }
-
- get bgColor(): glmatrix.vec4 {
- return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
- }
-
- get fgColor(): glmatrix.vec4 {
- return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
- }
-
- get destAllocatedSize(): glmatrix.vec2 {
- const canvas = this.canvas;
- return glmatrix.vec2.clone([canvas.width, canvas.height]);
- }
-
- get destFramebuffer(): WebGLFramebuffer | null {
- return null;
- }
-
- get destUsedSize(): glmatrix.vec2 {
- return this.destAllocatedSize;
- }
-
- get backgroundColor(): glmatrix.vec4 {
- return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
- }
-
- get allowSubpixelAA(): boolean {
- return false;
- }
-
- protected get objectCount(): number {
- return 1;
- }
-
- protected abstract get loader(): SVGLoader;
- protected abstract get canvas(): HTMLCanvasElement;
-
- constructor(renderContext: RenderContext, options: SVGRendererOptions) {
- super(renderContext);
-
- this.options = options;
-
- // FIXME(pcwalton): Get the canvas a better way?
- this.camera = new OrthographicCamera((this as any).canvas, {
- fixed: !!this.options.fixed,
- scaleBounds: true,
- });
-
- this.camera.onPan = () => this.renderContext.setDirty();
- this.camera.onZoom = () => this.renderContext.setDirty();
- this.camera.onRotate = () => this.renderContext.setDirty();
- }
-
- setHintsUniform(uniforms: UniformMap): void {
- this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
- }
-
- pathBoundingRects(objectIndex: number): Float32Array {
- const loader = this.loader;
- const boundingRectsBuffer = new Float32Array((loader.pathBounds.length + 1) * 4);
- for (let pathIndex = 0; pathIndex < loader.pathBounds.length; pathIndex++)
- boundingRectsBuffer.set(loader.pathBounds[pathIndex], (pathIndex + 1) * 4);
- return boundingRectsBuffer;
- }
-
- attachMeshes(meshes: PathfinderPackedMeshes[]): void {
- super.attachMeshes(meshes);
- this.uploadPathColors(1);
- this.uploadPathTransforms(1);
- }
-
- initCameraBounds(svgViewBox: glmatrix.vec4): void {
- // The SVG origin is in the upper left, but the camera origin is in the lower left.
- this.camera.bounds = svgViewBox;
-
- if (this.options.sizeToFit)
- this.camera.zoomToFit();
- }
-
- meshIndexForObject(objectIndex: number): number {
- return 0;
- }
-
- pathRangeForObject(objectIndex: number): Range {
- return new Range(1, this.loader.pathInstances.length + 1);
- }
-
- pathTransformsForObject(objectIndex: number): PathTransformBuffers {
- const instances = this.loader.pathInstances;
- const pathTransforms = this.createPathTransformBuffers(instances.length);
-
- for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
- // TODO(pcwalton): Set transform.
- const startOffset = (pathIndex + 1) * 4;
- pathTransforms.st.set([1, 1, 0, 0], startOffset);
- }
-
- return pathTransforms;
- }
-
- protected get usedSizeFactor(): glmatrix.vec2 {
- return glmatrix.vec2.clone([1.0, 1.0]);
- }
-
- protected get worldTransform(): glmatrix.mat4 {
- const canvas = this.canvas;
-
- const transform = glmatrix.mat4.create();
-
- glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
- glmatrix.mat4.scale(transform, transform, [2.0 / canvas.width, 2.0 / canvas.height, 1.0]);
-
- const centerPoint = glmatrix.vec3.clone([canvas.width * 0.5, canvas.height * 0.5, 0.0]);
- glmatrix.mat4.translate(transform, transform, centerPoint);
- glmatrix.mat4.rotateZ(transform, transform, this.camera.rotationAngle);
- glmatrix.vec3.negate(centerPoint, centerPoint);
- glmatrix.mat4.translate(transform, transform, centerPoint);
-
- const translation = this.camera.translation;
- glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
- glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
- return transform;
- }
-
- protected clearColorForObject(objectIndex: number): glmatrix.vec4 | null {
- return glmatrix.vec4.create();
- }
-
- protected directCurveProgramName(): keyof ShaderMap {
- return 'directCurve';
- }
-
- protected directInteriorProgramName(renderingMode: DirectRenderingMode):
- keyof ShaderMap {
- return renderingMode === 'conservative' ? 'conservativeInterior' : 'directInterior';
- }
-
- protected pathColorsForObject(objectIndex: number): Uint8Array {
- const instances = this.loader.pathInstances;
- const pathColors = new Uint8Array(4 * (instances.length + 1));
-
- for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
- const startOffset = (pathIndex + 1) * 4;
-
- // Set color.
- const color: ArrayLike = instances[pathIndex].color;
- pathColors.set(instances[pathIndex].color, startOffset);
- pathColors[startOffset + 3] = color[3] * 255;
- }
-
- return pathColors;
- }
-
- protected createAAStrategy(aaType: AntialiasingStrategyName,
- aaLevel: number,
- subpixelAA: SubpixelAAType):
- AntialiasingStrategy {
- return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
- }
-
- protected compositeIfNecessary(): void {}
-}
diff --git a/demo/client/src/text-demo.ts b/demo/client/src/text-demo.ts
deleted file mode 100644
index b6b0e0d4..00000000
--- a/demo/client/src/text-demo.ts
+++ /dev/null
@@ -1,470 +0,0 @@
-// pathfinder/client/src/text-demo.ts
-//
-// Copyright © 2018 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as base64js from 'base64-js';
-import * as glmatrix from 'gl-matrix';
-import * as _ from 'lodash';
-import * as opentype from 'opentype.js';
-
-import {Metrics} from 'opentype.js';
-import {StemDarkeningMode, SubpixelAAType} from './aa-strategy';
-import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
-import {AAOptions, DemoAppController, setSwitchInputsValue, SwitchInputs} from './app-controller';
-import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey, SUBPIXEL_GRANULARITY} from './atlas';
-import PathfinderBufferTexture from './buffer-texture';
-import {CameraView, OrthographicCamera} from "./camera";
-import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
-import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from './gl-utils';
-import {UniformMap} from './gl-utils';
-import {PathfinderMeshPack, PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from './meshes';
-import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
-import SSAAStrategy from './ssaa-strategy';
-import {calculatePixelRectForGlyph, PathfinderFont} from "./text";
-import {BUILTIN_FONT_URI, calculatePixelXMin, computeStemDarkeningAmount} from "./text";
-import {GlyphStore, Hint, SimpleTextLayout, UnitMetrics} from "./text";
-import {SimpleTextLayoutRenderContext, SimpleTextLayoutRenderer} from './text-renderer';
-import {TextRenderContext, TextRenderer} from './text-renderer';
-import {assert, expectNotNull, panic, PathfinderError, scaleRect, UINT32_SIZE} from './utils';
-import {unwrapNull} from './utils';
-import {DemoView, RenderContext, Timings, TIMINGS} from './view';
-
-const DEFAULT_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.`;
-
-const INITIAL_FONT_SIZE: number = 72.0;
-
-const DEFAULT_FONT: string = 'open-sans';
-
-const B_POSITION_SIZE: number = 8;
-
-const B_PATH_INDEX_SIZE: number = 2;
-
-declare global {
- interface Window {
- jQuery(element: HTMLElement): JQuerySubset;
- }
-}
-
-interface TabShownEvent {
- target: EventTarget;
- relatedTarget: EventTarget;
-}
-
-interface JQuerySubset {
- modal(options?: any): void;
- on(name: 'shown.bs.tab', handler: (event: TabShownEvent) => void): void;
-}
-
-type Matrix4D = Float32Array;
-
-type Rect = glmatrix.vec4;
-
-interface Point2D {
- x: number;
- y: number;
-}
-
-type Size2D = glmatrix.vec2;
-
-type ShaderType = number;
-
-// `opentype.js` monkey patches
-
-declare module 'opentype.js' {
- interface Font {
- isSupported(): boolean;
- lineHeight(): number;
- }
- interface Glyph {
- getIndex(): number;
- }
-}
-
-class TextDemoController extends DemoAppController {
- font!: PathfinderFont;
- layout!: SimpleTextLayout;
- glyphStore!: GlyphStore;
- atlasGlyphs!: AtlasGlyph[];
-
- private hintingSelect!: HTMLSelectElement;
- private emboldenInput!: HTMLInputElement;
-
- private editTextModal!: HTMLElement;
- private editTextArea!: HTMLTextAreaElement;
-
- private _atlas: Atlas;
-
- private meshes!: PathfinderPackedMeshes;
-
- private _fontSize!: number;
- private _rotationAngle!: number;
- private _emboldenAmount!: number;
-
- private text: string;
-
- constructor() {
- super();
- this.text = DEFAULT_TEXT;
- this._atlas = new Atlas;
- }
-
- start(): void {
- this._fontSize = INITIAL_FONT_SIZE;
- this._rotationAngle = 0.0;
- this._emboldenAmount = 0.0;
-
- this.hintingSelect = unwrapNull(document.getElementById('pf-hinting-select')) as
- HTMLSelectElement;
- this.hintingSelect.addEventListener('change', () => this.hintingChanged(), false);
-
- this.emboldenInput = unwrapNull(document.getElementById('pf-embolden')) as
- HTMLInputElement;
- this.emboldenInput.addEventListener('input', () => this.emboldenAmountChanged(), false);
-
- this.editTextModal = unwrapNull(document.getElementById('pf-edit-text-modal'));
- this.editTextArea = unwrapNull(document.getElementById('pf-edit-text-area')) as
- HTMLTextAreaElement;
-
- const editTextOkButton = unwrapNull(document.getElementById('pf-edit-text-ok-button'));
- editTextOkButton.addEventListener('click', () => this.updateText(), false);
-
- super.start();
-
- this.loadInitialFile(this.builtinFileURI);
- }
-
- showTextEditor(): void {
- this.editTextArea.value = this.text;
-
- window.jQuery(this.editTextModal).modal();
- }
-
- get emboldenAmount(): number {
- return this._emboldenAmount;
- }
-
- protected updateUIForAALevelChange(aaType: AntialiasingStrategyName, aaLevel: number): void {
- const gammaCorrectionSwitchInputs = unwrapNull(this.gammaCorrectionSwitchInputs);
- const stemDarkeningSwitchInputs = unwrapNull(this.stemDarkeningSwitchInputs);
- const emboldenInput = unwrapNull(this.emboldenInput);
- const emboldenLabel = getLabelFor(emboldenInput);
-
- switch (aaType) {
- case 'none':
- case 'ssaa':
- enableSwitchInputs(gammaCorrectionSwitchInputs, false);
- enableSwitchInputs(stemDarkeningSwitchInputs, false);
- emboldenInput.value = "0";
- emboldenInput.disabled = true;
- enableLabel(emboldenLabel, false);
- break;
-
- case 'xcaa':
- enableSwitchInputs(gammaCorrectionSwitchInputs, true);
- enableSwitchInputs(stemDarkeningSwitchInputs, true);
- emboldenInput.value = "0";
- emboldenInput.disabled = false;
- enableLabel(emboldenLabel, true);
- break;
- }
-
- function enableSwitchInputs(switchInputs: SwitchInputs, enabled: boolean): void {
- switchInputs.off.disabled = switchInputs.on.disabled = !enabled;
- enableLabel(getLabelFor(switchInputs.on), enabled);
- }
-
- function enableLabel(label: HTMLLabelElement | null, enabled: boolean): void {
- if (label == null)
- return;
-
- if (enabled)
- label.classList.remove('pf-disabled');
- else
- label.classList.add('pf-disabled');
- }
-
- function getLabelFor(element: HTMLElement): HTMLLabelElement | null {
- return document.querySelector(`label[for="${element.id}"]`) as HTMLLabelElement | null;
- }
- }
-
- protected createView(areaLUT: HTMLImageElement,
- gammaLUT: HTMLImageElement,
- commonShaderSource: string,
- shaderSources: ShaderMap):
- TextDemoView {
- return new TextDemoView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
- }
-
- protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null) {
- const font = new PathfinderFont(fileData, builtinName);
- this.recreateLayout(font);
- }
-
- private hintingChanged(): void {
- this.view.then(view => view.renderer.updateHinting());
- }
-
- private emboldenAmountChanged(): void {
- this._emboldenAmount = parseFloat(this.emboldenInput.value);
- this.view.then(view => view.renderer.updateEmboldenAmount());
- }
-
- private updateText(): void {
- this.text = this.editTextArea.value;
- window.jQuery(this.editTextModal).modal('hide');
-
- this.recreateLayout(this.font);
- }
-
- private recreateLayout(font: PathfinderFont) {
- const newLayout = new SimpleTextLayout(font, this.text);
-
- let uniqueGlyphIDs = newLayout.textFrame.allGlyphIDs;
- uniqueGlyphIDs.sort((a, b) => a - b);
- uniqueGlyphIDs = _.sortedUniq(uniqueGlyphIDs);
-
- const glyphStore = new GlyphStore(font, uniqueGlyphIDs);
- glyphStore.partition().then(result => {
- const meshes = this.expandMeshes(result.meshes, uniqueGlyphIDs.length);
-
- this.view.then(view => {
- this.font = font;
- this.layout = newLayout;
- this.glyphStore = glyphStore;
- this.meshes = meshes;
-
- view.attachText();
- view.attachMeshes([this.meshes]);
- });
- });
- }
-
- private expandMeshes(meshes: PathfinderMeshPack, glyphCount: number): PathfinderPackedMeshes {
- const pathIDs = [];
- for (let glyphIndex = 0; glyphIndex < glyphCount; glyphIndex++) {
- for (let subpixel = 0; subpixel < SUBPIXEL_GRANULARITY; subpixel++)
- pathIDs.push(glyphIndex + 1);
- }
- return new PathfinderPackedMeshes(meshes, pathIDs);
- }
-
- get atlas(): Atlas {
- return this._atlas;
- }
-
- /// 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.renderer.relayoutText());
- }
-
- get rotationAngle(): number {
- return this._rotationAngle;
- }
-
- set rotationAngle(newRotationAngle: number) {
- this._rotationAngle = newRotationAngle;
- this.view.then(view => view.renderer.relayoutText());
- }
-
- get unitsPerEm(): number {
- return this.font.opentypeFont.unitsPerEm;
- }
-
- get pixelsPerUnit(): number {
- return this._fontSize / this.unitsPerEm;
- }
-
- get useHinting(): boolean {
- return this.hintingSelect.selectedIndex !== 0;
- }
-
- get pathCount(): number {
- return this.glyphStore.glyphIDs.length * SUBPIXEL_GRANULARITY;
- }
-
- protected get builtinFileURI(): string {
- return BUILTIN_FONT_URI;
- }
-
- protected get defaultFile(): string {
- return DEFAULT_FONT;
- }
-}
-
-class TextDemoView extends DemoView implements SimpleTextLayoutRenderContext {
- renderer: TextDemoRenderer;
-
- appController: TextDemoController;
-
- get cameraView(): CameraView {
- return this.canvas;
- }
-
- get atlasGlyphs(): AtlasGlyph[] {
- return this.appController.atlasGlyphs;
- }
-
- set atlasGlyphs(newAtlasGlyphs: AtlasGlyph[]) {
- this.appController.atlasGlyphs = newAtlasGlyphs;
- }
-
- get atlas(): Atlas {
- return this.appController.atlas;
- }
-
- get glyphStore(): GlyphStore {
- return this.appController.glyphStore;
- }
-
- get font(): PathfinderFont {
- return this.appController.font;
- }
-
- get fontSize(): number {
- return this.appController.fontSize;
- }
-
- get pathCount(): number {
- return this.appController.pathCount;
- }
-
- get pixelsPerUnit(): number {
- return this.appController.pixelsPerUnit;
- }
-
- get useHinting(): boolean {
- return this.appController.useHinting;
- }
-
- get layout(): SimpleTextLayout {
- return this.appController.layout;
- }
-
- get rotationAngle(): number {
- return this.appController.rotationAngle;
- }
-
- get emboldenAmount(): number {
- return this.appController.emboldenAmount;
- }
-
- get unitsPerEm(): number {
- return this.appController.unitsPerEm;
- }
-
- protected get camera(): OrthographicCamera {
- return this.renderer.camera;
- }
-
- constructor(appController: TextDemoController,
- areaLUT: HTMLImageElement,
- gammaLUT: HTMLImageElement,
- commonShaderSource: string,
- shaderSources: ShaderMap) {
- super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
-
- this.appController = appController;
- this.renderer = new TextDemoRenderer(this);
-
- this.canvas.addEventListener('dblclick', () => this.appController.showTextEditor(), false);
-
- this.resizeToFit(true);
- }
-
- attachText() {
- this.panZoomEventsEnabled = false;
- this.renderer.prepareToAttachText();
- this.renderer.camera.zoomToFit();
- this.appController.fontSize = this.renderer.camera.scale *
- this.appController.font.opentypeFont.unitsPerEm;
- this.renderer.finishAttachingText();
- this.panZoomEventsEnabled = true;
- }
-
- newTimingsReceived(newTimings: Timings) {
- this.appController.newTimingsReceived(newTimings);
- }
-
- protected onPan(): void {
- this.renderer.viewPanned();
- }
-
- protected onZoom(): void {
- this.appController.fontSize = this.renderer.camera.scale *
- this.appController.font.opentypeFont.unitsPerEm;
- }
-
- protected onRotate(): void {
- this.appController.rotationAngle = this.renderer.camera.rotationAngle;
- }
-
- private set panZoomEventsEnabled(flag: boolean) {
- if (flag) {
- this.renderer.camera.onPan = () => this.onPan();
- this.renderer.camera.onZoom = () => this.onZoom();
- this.renderer.camera.onRotate = () => this.onRotate();
- } else {
- this.renderer.camera.onPan = null;
- this.renderer.camera.onZoom = null;
- this.renderer.camera.onRotate = null;
- }
- }
-}
-
-class TextDemoRenderer extends SimpleTextLayoutRenderer {
- renderContext!: TextDemoView;
-}
-
-function main(): void {
- const controller = new TextDemoController;
- window.addEventListener('load', () => controller.start(), false);
-}
-
-main();
diff --git a/demo/client/src/text-renderer.ts b/demo/client/src/text-renderer.ts
deleted file mode 100644
index aee4c1aa..00000000
--- a/demo/client/src/text-renderer.ts
+++ /dev/null
@@ -1,649 +0,0 @@
-// pathfinder/client/src/text-renderer.ts
-//
-// Copyright © 2018 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-import * as _ from 'lodash';
-
-import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy';
-import {StemDarkeningMode, SubpixelAAType} from './aa-strategy';
-import {AAOptions} from './app-controller';
-import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey, SUBPIXEL_GRANULARITY} from './atlas';
-import {CameraView, OrthographicCamera} from './camera';
-import {createFramebuffer, createFramebufferDepthTexture, QUAD_ELEMENTS} from './gl-utils';
-import {UniformMap} from './gl-utils';
-import {PathTransformBuffers, Renderer} from './renderer';
-import {ShaderMap} from './shader-loader';
-import SSAAStrategy from './ssaa-strategy';
-import {calculatePixelRectForGlyph, computeStemDarkeningAmount, GlyphStore, Hint} from "./text";
-import {MAX_STEM_DARKENING_PIXELS_PER_EM, PathfinderFont, SimpleTextLayout} from "./text";
-import {UnitMetrics} from "./text";
-import {unwrapNull} from './utils';
-import {RenderContext, Timings} from "./view";
-import {StencilAAAStrategy} from './xcaa-strategy';
-
-interface AntialiasingStrategyTable {
- none: typeof NoAAStrategy;
- ssaa: typeof SSAAStrategy;
- xcaa: typeof StencilAAAStrategy;
-}
-
-const SQRT_1_2: number = 1.0 / Math.sqrt(2.0);
-
-const MIN_SCALE: number = 0.0025;
-const MAX_SCALE: number = 0.5;
-
-export const MAX_SUBPIXEL_AA_FONT_SIZE: number = 48.0;
-
-const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
- none: NoAAStrategy,
- ssaa: SSAAStrategy,
- xcaa: StencilAAAStrategy,
-};
-
-export interface TextRenderContext extends RenderContext {
- atlasGlyphs: AtlasGlyph[];
-
- readonly cameraView: CameraView;
- readonly atlas: Atlas;
- readonly glyphStore: GlyphStore;
- readonly font: PathfinderFont;
- readonly fontSize: number;
- readonly useHinting: boolean;
-
- newTimingsReceived(timings: Timings): void;
-}
-
-export abstract class TextRenderer extends Renderer {
- renderContext!: TextRenderContext;
-
- camera: OrthographicCamera;
-
- atlasFramebuffer!: WebGLFramebuffer;
- atlasDepthTexture!: WebGLTexture;
-
- get isMulticolor(): boolean {
- return false;
- }
-
- get needsStencil(): boolean {
- return this.renderContext.fontSize <= MAX_STEM_DARKENING_PIXELS_PER_EM;
- }
-
- get destFramebuffer(): WebGLFramebuffer {
- return this.atlasFramebuffer;
- }
-
- get destAllocatedSize(): glmatrix.vec2 {
- return ATLAS_SIZE;
- }
-
- get destUsedSize(): glmatrix.vec2 {
- return this.renderContext.atlas.usedSize;
- }
-
- get emboldenAmount(): glmatrix.vec2 {
- const emboldenAmount = glmatrix.vec2.create();
- glmatrix.vec2.add(emboldenAmount, this.extraEmboldenAmount, this.stemDarkeningAmount);
- return emboldenAmount;
- }
-
- get bgColor(): glmatrix.vec4 {
- return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
- }
-
- get fgColor(): glmatrix.vec4 {
- return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
- }
-
- get rotationAngle(): number {
- return 0.0;
- }
-
- get allowSubpixelAA(): boolean {
- return this.renderContext.fontSize <= MAX_SUBPIXEL_AA_FONT_SIZE;
- }
-
- protected get pixelsPerUnit(): number {
- return this.renderContext.fontSize / this.renderContext.font.opentypeFont.unitsPerEm;
- }
-
- protected get worldTransform(): glmatrix.mat4 {
- const transform = glmatrix.mat4.create();
- glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
- glmatrix.mat4.scale(transform, transform, [2.0 / ATLAS_SIZE[0], 2.0 / ATLAS_SIZE[1], 1.0]);
- return transform;
- }
-
- protected get stemDarkeningAmount(): glmatrix.vec2 {
- if (this.stemDarkening === 'dark')
- return computeStemDarkeningAmount(this.renderContext.fontSize, this.pixelsPerUnit);
- return glmatrix.vec2.create();
- }
-
- protected get usedSizeFactor(): glmatrix.vec2 {
- const usedSize = glmatrix.vec2.create();
- glmatrix.vec2.div(usedSize, this.renderContext.atlas.usedSize, ATLAS_SIZE);
- return usedSize;
- }
-
- private get pathCount(): number {
- return this.renderContext.glyphStore.glyphIDs.length * SUBPIXEL_GRANULARITY;
- }
-
- protected get objectCount(): number {
- return this.meshBuffers == null ? 0 : this.meshBuffers.length;
- }
-
- protected get extraEmboldenAmount(): glmatrix.vec2 {
- return glmatrix.vec2.create();
- }
-
- private stemDarkening!: StemDarkeningMode;
- private subpixelAA!: SubpixelAAType;
-
- constructor(renderContext: TextRenderContext) {
- super(renderContext);
-
- this.camera = new OrthographicCamera(this.renderContext.cameraView, {
- maxScale: MAX_SCALE,
- minScale: MIN_SCALE,
- });
- }
-
- setHintsUniform(uniforms: UniformMap): void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- const hint = this.createHint();
- gl.uniform4f(uniforms.uHints,
- hint.xHeight,
- hint.hintedXHeight,
- hint.stemHeight,
- hint.hintedStemHeight);
- }
-
- pathBoundingRects(objectIndex: number): Float32Array {
- const pathCount = this.pathCount;
- const atlasGlyphs = this.renderContext.atlasGlyphs;
- const pixelsPerUnit = this.pixelsPerUnit;
- const rotationAngle = this.rotationAngle;
- const font = this.renderContext.font;
- const hint = this.createHint();
-
- const boundingRects = new Float32Array((pathCount + 1) * 4);
-
- for (const glyph of atlasGlyphs) {
- const atlasGlyphMetrics = font.metricsForGlyph(glyph.glyphKey.id);
- if (atlasGlyphMetrics == null)
- continue;
- const atlasUnitMetrics = new UnitMetrics(atlasGlyphMetrics, 0.0, this.emboldenAmount);
-
- const pathID = glyph.pathID;
- boundingRects[pathID * 4 + 0] = atlasUnitMetrics.left;
- boundingRects[pathID * 4 + 1] = atlasUnitMetrics.descent;
- boundingRects[pathID * 4 + 2] = atlasUnitMetrics.right;
- boundingRects[pathID * 4 + 3] = atlasUnitMetrics.ascent;
- }
-
- return boundingRects;
- }
-
- pathTransformsForObject(objectIndex: number): PathTransformBuffers {
- const pathCount = this.pathCount;
- const atlasGlyphs = this.renderContext.atlasGlyphs;
- const pixelsPerUnit = this.pixelsPerUnit;
- const rotationAngle = this.rotationAngle;
-
- // FIXME(pcwalton): This is a hack that tries to preserve the vertical extents of the glyph
- // after stem darkening. It's better than nothing, but we should really do better.
- //
- // This hack seems to produce *better* results than what macOS does on sans-serif fonts;
- // the ascenders and x-heights of the glyphs are pixel snapped, while they aren't on macOS.
- // But we should really figure out what macOS does…
- const ascender = this.renderContext.font.opentypeFont.ascender;
- const emboldenAmount = this.emboldenAmount;
- const stemDarkeningYScale = (ascender + emboldenAmount[1]) / ascender;
-
- const stemDarkeningOffset = glmatrix.vec2.clone(emboldenAmount);
- glmatrix.vec2.scale(stemDarkeningOffset, stemDarkeningOffset, pixelsPerUnit);
- glmatrix.vec2.scale(stemDarkeningOffset, stemDarkeningOffset, SQRT_1_2);
- glmatrix.vec2.mul(stemDarkeningOffset, stemDarkeningOffset, [1, stemDarkeningYScale]);
-
- const transform = glmatrix.mat2d.create();
- const transforms = this.createPathTransformBuffers(pathCount);
-
- for (const glyph of atlasGlyphs) {
- const pathID = glyph.pathID;
- const atlasOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
-
- glmatrix.mat2d.identity(transform);
- glmatrix.mat2d.translate(transform, transform, atlasOrigin);
- glmatrix.mat2d.translate(transform, transform, stemDarkeningOffset);
- glmatrix.mat2d.rotate(transform, transform, rotationAngle);
- glmatrix.mat2d.scale(transform,
- transform,
- [pixelsPerUnit, pixelsPerUnit * stemDarkeningYScale]);
-
- transforms.st[pathID * 4 + 0] = transform[0];
- transforms.st[pathID * 4 + 1] = transform[3];
- transforms.st[pathID * 4 + 2] = transform[4];
- transforms.st[pathID * 4 + 3] = transform[5];
-
- transforms.ext[pathID * 2 + 0] = transform[1];
- transforms.ext[pathID * 2 + 1] = transform[2];
- }
-
- return transforms;
- }
-
- protected createAtlasFramebuffer(): void {
- const atlasColorTexture = this.renderContext.atlas.ensureTexture(this.renderContext);
- this.atlasDepthTexture = createFramebufferDepthTexture(this.renderContext.gl, ATLAS_SIZE);
- this.atlasFramebuffer = createFramebuffer(this.renderContext.gl,
- atlasColorTexture,
- this.atlasDepthTexture);
-
- // Allow the antialiasing strategy to set up framebuffers as necessary.
- if (this.antialiasingStrategy != null)
- this.antialiasingStrategy.setFramebufferSize(this);
- }
-
- protected createAAStrategy(aaType: AntialiasingStrategyName,
- aaLevel: number,
- subpixelAA: SubpixelAAType,
- stemDarkening: StemDarkeningMode):
- AntialiasingStrategy {
- this.subpixelAA = subpixelAA;
- this.stemDarkening = stemDarkening;
- return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
- }
-
- protected clearForDirectRendering(): void {}
-
- protected buildAtlasGlyphs(atlasGlyphs: AtlasGlyph[]): void {
- const renderContext = this.renderContext;
- const font = renderContext.font;
- const pixelsPerUnit = this.pixelsPerUnit;
- const rotationAngle = this.rotationAngle;
- const hint = this.createHint();
-
- atlasGlyphs.sort((a, b) => a.glyphKey.sortKey - b.glyphKey.sortKey);
- atlasGlyphs = _.sortedUniqBy(atlasGlyphs, glyph => glyph.glyphKey.sortKey);
- if (atlasGlyphs.length === 0)
- return;
-
- renderContext.atlasGlyphs = atlasGlyphs;
- renderContext.atlas.layoutGlyphs(atlasGlyphs,
- font,
- pixelsPerUnit,
- rotationAngle,
- hint,
- this.emboldenAmount);
-
- this.uploadPathTransforms(1);
- this.uploadPathColors(1);
- }
-
- protected pathColorsForObject(objectIndex: number): Uint8Array {
- const pathCount = this.pathCount;
-
- const pathColors = new Uint8Array(4 * (pathCount + 1));
-
- for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
- for (let channel = 0; channel < 3; channel++)
- pathColors[(pathIndex + 1) * 4 + channel] = 0xff; // RGB
- pathColors[(pathIndex + 1) * 4 + 3] = 0xff; // alpha
- }
-
- return pathColors;
- }
-
- protected newTimingsReceived(): void {
- this.renderContext.newTimingsReceived(this.lastTimings);
- }
-
- protected createHint(): Hint {
- return new Hint(this.renderContext.font,
- this.pixelsPerUnit,
- this.renderContext.useHinting);
- }
-
- protected directCurveProgramName(): keyof ShaderMap {
- return 'directCurve';
- }
-
- protected directInteriorProgramName(): keyof ShaderMap {
- return 'directInterior';
- }
-}
-
-export interface SimpleTextLayoutRenderContext extends TextRenderContext {
- readonly layout: SimpleTextLayout;
- readonly rotationAngle: number;
- readonly emboldenAmount: number;
- readonly unitsPerEm: number;
-}
-
-export abstract class SimpleTextLayoutRenderer extends TextRenderer {
- abstract get renderContext(): SimpleTextLayoutRenderContext;
-
- glyphPositionsBuffer!: WebGLBuffer;
- glyphTexCoordsBuffer!: WebGLBuffer;
- glyphElementsBuffer!: WebGLBuffer;
-
- private glyphBounds!: Float32Array;
-
- get layout(): SimpleTextLayout {
- return this.renderContext.layout;
- }
-
- get backgroundColor(): glmatrix.vec4 {
- return glmatrix.vec4.create();
- }
-
- get rotationAngle(): number {
- return this.renderContext.rotationAngle;
- }
-
- protected get extraEmboldenAmount(): glmatrix.vec2 {
- const emboldenLength = this.renderContext.emboldenAmount * this.renderContext.unitsPerEm;
- return glmatrix.vec2.clone([emboldenLength, emboldenLength]);
- }
-
- prepareToAttachText(): void {
- if (this.atlasFramebuffer == null)
- this.createAtlasFramebuffer();
-
- this.layoutText();
- }
-
- finishAttachingText(): void {
- this.buildGlyphs();
- this.renderContext.setDirty();
- }
-
- setAntialiasingOptions(aaType: AntialiasingStrategyName,
- aaLevel: number,
- aaOptions: AAOptions):
- void {
- super.setAntialiasingOptions(aaType, aaLevel, aaOptions);
-
- // Need to relayout because changing AA options can cause font dilation to change...
- this.layoutText();
- this.buildGlyphs();
- this.renderContext.setDirty();
- }
-
- relayoutText(): void {
- this.layoutText();
- this.buildGlyphs();
- this.renderContext.setDirty();
- }
-
- updateHinting(): void {
- // Need to relayout the text because the pixel bounds of the glyphs can change from this...
- this.layoutText();
- this.buildGlyphs();
- this.renderContext.setDirty();
- }
-
- updateEmboldenAmount(): void {
- // Likewise, need to relayout the text.
- this.layoutText();
- this.buildGlyphs();
- this.renderContext.setDirty();
- }
-
- viewPanned(): void {
- this.buildGlyphs();
- this.renderContext.setDirty();
- }
-
- protected compositeIfNecessary(): void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- // Set up composite state.
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
- gl.viewport(0, 0, renderContext.cameraView.width, renderContext.cameraView.height);
- gl.disable(gl.DEPTH_TEST);
- gl.disable(gl.SCISSOR_TEST);
- gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT);
- gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE);
- gl.enable(gl.BLEND);
-
- // Clear.
- gl.clearColor(1.0, 1.0, 1.0, 1.0);
- gl.clear(gl.COLOR_BUFFER_BIT);
-
- // Set the appropriate program.
- const programName = this.gammaCorrectionMode === 'off' ? 'blitLinear' : 'blitGamma';
- const blitProgram = this.renderContext.shaderPrograms[programName];
-
- // Set up the composite VAO.
- const attributes = blitProgram.attributes;
- gl.useProgram(blitProgram.program);
- gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
- gl.vertexAttribPointer(attributes.aPosition, 2, gl.FLOAT, false, 0, 0);
- gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
- gl.vertexAttribPointer(attributes.aTexCoord, 2, gl.FLOAT, false, 0, 0);
- gl.enableVertexAttribArray(attributes.aPosition);
- gl.enableVertexAttribArray(attributes.aTexCoord);
- gl.bindBuffer(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.renderContext.cameraView.width,
- 2.0 / this.renderContext.cameraView.height,
- 1.0,
- ]);
- glmatrix.mat4.translate(transform,
- transform,
- [this.camera.translation[0], this.camera.translation[1], 0.0]);
-
- // Blit.
- gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform);
- gl.activeTexture(gl.TEXTURE0);
- const destTexture = this.renderContext
- .atlas
- .ensureTexture(this.renderContext);
- gl.bindTexture(gl.TEXTURE_2D, destTexture);
- gl.uniform1i(blitProgram.uniforms.uSource, 0);
- this.setIdentityTexScaleUniform(blitProgram.uniforms);
- this.bindGammaLUT(glmatrix.vec3.clone([1.0, 1.0, 1.0]), 1, blitProgram.uniforms);
- const totalGlyphCount = this.layout.textFrame.totalGlyphCount;
- gl.drawElements(gl.TRIANGLES, totalGlyphCount * 6, gl.UNSIGNED_INT, 0);
- }
-
- private layoutText(): void {
- const renderContext = this.renderContext;
- const gl = renderContext.gl;
-
- this.layout.layoutRuns();
-
- const textBounds = this.layout.textFrame.bounds;
- this.camera.bounds = textBounds;
-
- const totalGlyphCount = this.layout.textFrame.totalGlyphCount;
- const glyphPositions = new Float32Array(totalGlyphCount * 8);
- const glyphIndices = new Uint32Array(totalGlyphCount * 6);
-
- const hint = this.createHint();
- const pixelsPerUnit = this.pixelsPerUnit;
- const rotationAngle = this.rotationAngle;
-
- let globalGlyphIndex = 0;
- for (const run of this.layout.textFrame.runs) {
- run.recalculatePixelRects(pixelsPerUnit,
- rotationAngle,
- hint,
- this.emboldenAmount,
- SUBPIXEL_GRANULARITY,
- textBounds);
-
- for (let glyphIndex = 0;
- glyphIndex < run.glyphIDs.length;
- glyphIndex++, globalGlyphIndex++) {
- const rect = run.pixelRectForGlyphAt(glyphIndex);
- glyphPositions.set([
- rect[0], rect[3],
- rect[2], rect[3],
- rect[0], rect[1],
- rect[2], rect[1],
- ], globalGlyphIndex * 8);
-
- for (let glyphIndexIndex = 0;
- glyphIndexIndex < QUAD_ELEMENTS.length;
- glyphIndexIndex++) {
- glyphIndices[glyphIndexIndex + globalGlyphIndex * 6] =
- QUAD_ELEMENTS[glyphIndexIndex] + 4 * globalGlyphIndex;
- }
- }
- }
-
- this.glyphPositionsBuffer = unwrapNull(gl.createBuffer());
- gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, glyphPositions, gl.STATIC_DRAW);
- this.glyphElementsBuffer = unwrapNull(gl.createBuffer());
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer);
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, glyphIndices, gl.STATIC_DRAW);
- }
-
- private buildGlyphs(): void {
- const font = this.renderContext.font;
- const glyphStore = this.renderContext.glyphStore;
- const pixelsPerUnit = this.pixelsPerUnit;
- const rotationAngle = this.rotationAngle;
-
- const textFrame = this.layout.textFrame;
- const textBounds = textFrame.bounds;
- const hint = this.createHint();
-
- // Only build glyphs in view.
- const translation = this.camera.translation;
- const canvasRect = glmatrix.vec4.clone([
- -translation[0],
- -translation[1],
- -translation[0] + this.renderContext.cameraView.width,
- -translation[1] + this.renderContext.cameraView.height,
- ]);
-
- const atlasGlyphs = [];
- for (const run of textFrame.runs) {
- for (let glyphIndex = 0; glyphIndex < run.glyphIDs.length; glyphIndex++) {
- const pixelRect = run.pixelRectForGlyphAt(glyphIndex);
- if (!rectsIntersect(pixelRect, canvasRect))
- continue;
-
- const glyphID = run.glyphIDs[glyphIndex];
- const glyphStoreIndex = glyphStore.indexOfGlyphWithID(glyphID);
- if (glyphStoreIndex == null)
- continue;
-
- const subpixel = run.subpixelForGlyphAt(glyphIndex,
- pixelsPerUnit,
- rotationAngle,
- hint,
- SUBPIXEL_GRANULARITY,
- textBounds);
- const glyphKey = new GlyphKey(glyphID, subpixel);
- atlasGlyphs.push(new AtlasGlyph(glyphStoreIndex, glyphKey));
- }
- }
-
- this.buildAtlasGlyphs(atlasGlyphs);
-
- // TODO(pcwalton): Regenerate the IBOs to include only the glyphs we care about.
- this.setGlyphTexCoords();
- }
-
- private setGlyphTexCoords(): void {
- const gl = this.renderContext.gl;
-
- const textFrame = this.layout.textFrame;
- const textBounds = textFrame.bounds;
-
- const font = this.renderContext.font;
- const atlasGlyphs = this.renderContext.atlasGlyphs;
-
- const hint = this.createHint();
- const pixelsPerUnit = this.pixelsPerUnit;
- const rotationAngle = this.rotationAngle;
-
- const atlasGlyphKeys = atlasGlyphs.map(atlasGlyph => atlasGlyph.glyphKey.sortKey);
-
- this.glyphBounds = new Float32Array(textFrame.totalGlyphCount * 8);
-
- let globalGlyphIndex = 0;
- for (const run of textFrame.runs) {
- for (let glyphIndex = 0;
- glyphIndex < run.glyphIDs.length;
- glyphIndex++, globalGlyphIndex++) {
- const textGlyphID = run.glyphIDs[glyphIndex];
-
- const subpixel = run.subpixelForGlyphAt(glyphIndex,
- pixelsPerUnit,
- rotationAngle,
- hint,
- SUBPIXEL_GRANULARITY,
- textBounds);
-
- const glyphKey = new GlyphKey(textGlyphID, subpixel);
-
- const atlasGlyphIndex = _.sortedIndexOf(atlasGlyphKeys, glyphKey.sortKey);
- if (atlasGlyphIndex < 0)
- continue;
-
- // Set texture coordinates.
- const atlasGlyph = atlasGlyphs[atlasGlyphIndex];
- const atlasGlyphMetrics = font.metricsForGlyph(atlasGlyph.glyphKey.id);
- if (atlasGlyphMetrics == null)
- continue;
-
- const atlasGlyphUnitMetrics = new UnitMetrics(atlasGlyphMetrics,
- rotationAngle,
- this.emboldenAmount);
-
- const atlasGlyphPixelOrigin =
- atlasGlyph.calculateSubpixelOrigin(pixelsPerUnit);
- const atlasGlyphRect = calculatePixelRectForGlyph(atlasGlyphUnitMetrics,
- atlasGlyphPixelOrigin,
- pixelsPerUnit,
- hint);
- 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);
-
- this.glyphBounds.set([
- atlasGlyphBL[0], atlasGlyphTR[1],
- atlasGlyphTR[0], atlasGlyphTR[1],
- atlasGlyphBL[0], atlasGlyphBL[1],
- atlasGlyphTR[0], atlasGlyphBL[1],
- ], globalGlyphIndex * 8);
- }
- }
-
- this.glyphTexCoordsBuffer = unwrapNull(gl.createBuffer());
- gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, this.glyphBounds, gl.STATIC_DRAW);
- }
-
- private setIdentityTexScaleUniform(uniforms: UniformMap): void {
- this.renderContext.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
- }
-}
-
-/// 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];
-}
diff --git a/demo/client/src/text.ts b/demo/client/src/text.ts
deleted file mode 100644
index 268a9556..00000000
--- a/demo/client/src/text.ts
+++ /dev/null
@@ -1,453 +0,0 @@
-// pathfinder/client/src/text.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as base64js from 'base64-js';
-import * as glmatrix from 'gl-matrix';
-import * as _ from 'lodash';
-import * as opentype from "opentype.js";
-import {Metrics} from 'opentype.js';
-
-import {B_QUAD_SIZE, parseServerTiming, PathfinderMeshPack} from "./meshes";
-import {PathfinderPackedMeshes} from "./meshes";
-import {assert, lerp, panic, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils";
-
-export const BUILTIN_FONT_URI: string = "/otf/demo";
-
-const SQRT_2: number = Math.sqrt(2.0);
-
-// Should match macOS 10.13 High Sierra.
-//
-// We multiply by sqrt(2) to compensate for the fact that dilation amounts are relative to the
-// pixel square on macOS and relative to the vertex normal in Pathfinder.
-const STEM_DARKENING_FACTORS: glmatrix.vec2 = glmatrix.vec2.clone([
- 0.0121 * SQRT_2,
- 0.0121 * 1.25 * SQRT_2,
-]);
-
-// Likewise.
-const MAX_STEM_DARKENING_AMOUNT: glmatrix.vec2 = glmatrix.vec2.clone([0.3 * SQRT_2, 0.3 * SQRT_2]);
-
-// This value is a subjective cutoff. Above this ppem value, no stem darkening is performed.
-export const MAX_STEM_DARKENING_PIXELS_PER_EM: number = 72.0;
-
-const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font";
-
-export interface ExpandedMeshData {
- meshes: PathfinderPackedMeshes;
-}
-
-export interface PartitionResult {
- meshes: PathfinderMeshPack;
- time: number;
-}
-
-export interface PixelMetrics {
- left: number;
- right: number;
- ascent: number;
- descent: number;
-}
-
-opentype.Font.prototype.isSupported = function() {
- return (this as any).supported;
-};
-
-opentype.Font.prototype.lineHeight = function() {
- const os2Table = this.tables.os2;
- return os2Table.sTypoAscender - os2Table.sTypoDescender + os2Table.sTypoLineGap;
-};
-
-export class PathfinderFont {
- readonly opentypeFont: opentype.Font;
- readonly data: ArrayBuffer;
- readonly builtinFontName: string | null;
-
- private metricsCache: Metrics[];
-
- constructor(data: ArrayBuffer, builtinFontName: string | null) {
- this.data = data;
- this.builtinFontName = builtinFontName != null ? builtinFontName : null;
-
- this.opentypeFont = opentype.parse(data);
- if (!this.opentypeFont.isSupported())
- panic("Unsupported font!");
-
- this.metricsCache = [];
- }
-
- metricsForGlyph(glyphID: number): Metrics | null {
- if (this.metricsCache[glyphID] == null)
- this.metricsCache[glyphID] = this.opentypeFont.glyphs.get(glyphID).getMetrics();
- return this.metricsCache[glyphID];
- }
-}
-
-export class TextRun {
- readonly glyphIDs: number[];
- advances: number[];
- readonly origin: number[];
-
- private readonly font: PathfinderFont;
- private pixelRects: glmatrix.vec4[];
-
- constructor(text: number[] | string, origin: number[], font: PathfinderFont) {
- if (typeof(text) === 'string') {
- this.glyphIDs = font.opentypeFont
- .stringToGlyphs(text)
- .map(glyph => (glyph as any).index);
- } else {
- this.glyphIDs = text;
- }
-
- this.origin = origin;
- this.advances = [];
- this.font = font;
- this.pixelRects = [];
- }
-
- layout() {
- this.advances = [];
- let currentX = 0;
- for (const glyphID of this.glyphIDs) {
- this.advances.push(currentX);
- currentX += this.font.opentypeFont.glyphs.get(glyphID).advanceWidth;
- }
- }
-
- calculatePixelOriginForGlyphAt(index: number,
- pixelsPerUnit: number,
- rotationAngle: number,
- hint: Hint,
- textFrameBounds: glmatrix.vec4):
- glmatrix.vec2 {
- const textFrameCenter = glmatrix.vec2.clone([
- 0.5 * (textFrameBounds[0] + textFrameBounds[2]),
- 0.5 * (textFrameBounds[1] + textFrameBounds[3]),
- ]);
-
- const transform = glmatrix.mat2d.create();
- glmatrix.mat2d.fromTranslation(transform, textFrameCenter);
- glmatrix.mat2d.rotate(transform, transform, -rotationAngle);
- glmatrix.vec2.negate(textFrameCenter, textFrameCenter);
- glmatrix.mat2d.translate(transform, transform, textFrameCenter);
-
- const textGlyphOrigin = glmatrix.vec2.create();
- glmatrix.vec2.add(textGlyphOrigin, [this.advances[index], 0.0], this.origin);
- glmatrix.vec2.transformMat2d(textGlyphOrigin, textGlyphOrigin, transform);
-
- glmatrix.vec2.scale(textGlyphOrigin, textGlyphOrigin, pixelsPerUnit);
- return textGlyphOrigin;
- }
-
- pixelRectForGlyphAt(index: number): glmatrix.vec4 {
- return this.pixelRects[index];
- }
-
- subpixelForGlyphAt(index: number,
- pixelsPerUnit: number,
- rotationAngle: number,
- hint: Hint,
- subpixelGranularity: number,
- textFrameBounds: glmatrix.vec4):
- number {
- const textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index,
- pixelsPerUnit,
- rotationAngle,
- hint,
- textFrameBounds)[0];
- return Math.abs(Math.round(textGlyphOrigin * subpixelGranularity) % subpixelGranularity);
- }
-
- recalculatePixelRects(pixelsPerUnit: number,
- rotationAngle: number,
- hint: Hint,
- emboldenAmount: glmatrix.vec2,
- subpixelGranularity: number,
- textFrameBounds: glmatrix.vec4):
- void {
- for (let index = 0; index < this.glyphIDs.length; index++) {
- const metrics = unwrapNull(this.font.metricsForGlyph(this.glyphIDs[index]));
- const unitMetrics = new UnitMetrics(metrics, rotationAngle, emboldenAmount);
- const textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index,
- pixelsPerUnit,
- rotationAngle,
- hint,
- textFrameBounds);
-
- textGlyphOrigin[0] *= subpixelGranularity;
- glmatrix.vec2.round(textGlyphOrigin, textGlyphOrigin);
- textGlyphOrigin[0] /= subpixelGranularity;
-
- const pixelRect = calculatePixelRectForGlyph(unitMetrics,
- textGlyphOrigin,
- pixelsPerUnit,
- hint);
-
- this.pixelRects[index] = pixelRect;
- }
- }
-
- get measure(): number {
- const lastGlyphID = _.last(this.glyphIDs), lastAdvance = _.last(this.advances);
- if (lastGlyphID == null || lastAdvance == null)
- return 0.0;
- return lastAdvance + this.font.opentypeFont.glyphs.get(lastGlyphID).advanceWidth;
- }
-}
-
-export class TextFrame {
- readonly runs: TextRun[];
- readonly origin: glmatrix.vec3;
-
- private readonly font: PathfinderFont;
-
- constructor(runs: TextRun[], font: PathfinderFont) {
- this.runs = runs;
- this.origin = glmatrix.vec3.create();
- this.font = font;
- }
-
- expandMeshes(meshes: PathfinderMeshPack, glyphIDs: number[]): ExpandedMeshData {
- const pathIDs = [];
- for (const textRun of this.runs) {
- for (const glyphID of textRun.glyphIDs) {
- if (glyphID === 0)
- continue;
- const pathID = _.sortedIndexOf(glyphIDs, glyphID);
- pathIDs.push(pathID + 1);
- }
- }
-
- return {
- meshes: new PathfinderPackedMeshes(meshes, pathIDs),
- };
- }
-
- get bounds(): glmatrix.vec4 {
- if (this.runs.length === 0)
- return glmatrix.vec4.create();
-
- const upperLeft = glmatrix.vec2.clone(this.runs[0].origin);
- const lowerRight = glmatrix.vec2.clone(_.last(this.runs)!.origin);
-
- const lowerLeft = glmatrix.vec2.clone([upperLeft[0], lowerRight[1]]);
- const upperRight = glmatrix.vec2.clone([lowerRight[0], upperLeft[1]]);
-
- const lineHeight = this.font.opentypeFont.lineHeight();
- lowerLeft[1] -= lineHeight;
- upperRight[1] += lineHeight * 2.0;
-
- upperRight[0] = _.defaultTo(_.max(this.runs.map(run => run.measure)), 0.0);
-
- return glmatrix.vec4.clone([lowerLeft[0], lowerLeft[1], upperRight[0], upperRight[1]]);
- }
-
- get totalGlyphCount(): number {
- return _.sumBy(this.runs, run => run.glyphIDs.length);
- }
-
- get allGlyphIDs(): number[] {
- const glyphIDs = [];
- for (const run of this.runs)
- glyphIDs.push(...run.glyphIDs);
- return glyphIDs;
- }
-}
-
-/// Stores one copy of each glyph.
-export class GlyphStore {
- readonly font: PathfinderFont;
- readonly glyphIDs: number[];
-
- constructor(font: PathfinderFont, glyphIDs: number[]) {
- this.font = font;
- this.glyphIDs = glyphIDs;
- }
-
- partition(): Promise {
- // Build the partitioning request to the server.
- let fontFace;
- if (this.font.builtinFontName != null)
- fontFace = { Builtin: this.font.builtinFontName };
- else
- fontFace = { Custom: base64js.fromByteArray(new Uint8Array(this.font.data)) };
-
- const request = {
- face: fontFace,
- fontIndex: 0,
- glyphs: this.glyphIDs.map(id => ({ id: id, transform: [1, 0, 0, 1, 0, 0] })),
- pointSize: this.font.opentypeFont.unitsPerEm,
- };
-
- // Make the request.
- let time = 0;
- return window.fetch(PARTITION_FONT_ENDPOINT_URI, {
- body: JSON.stringify(request),
- headers: {'Content-Type': 'application/json'} as any,
- method: 'POST',
- }).then(response => {
- time = parseServerTiming(response.headers);
- return response.arrayBuffer();
- }).then(buffer => {
- return {
- meshes: new PathfinderMeshPack(buffer),
- time: time,
- };
- });
- }
-
- indexOfGlyphWithID(glyphID: number): number | null {
- const index = _.sortedIndexOf(this.glyphIDs, glyphID);
- return index >= 0 ? index : null;
- }
-}
-
-export class SimpleTextLayout {
- readonly textFrame: TextFrame;
-
- constructor(font: PathfinderFont, text: string) {
- const lineHeight = font.opentypeFont.lineHeight();
- const textRuns: TextRun[] = text.split("\n").map((line, lineNumber) => {
- return new TextRun(line, [0.0, -lineHeight * lineNumber], font);
- });
- this.textFrame = new TextFrame(textRuns, font);
- }
-
- layoutRuns() {
- this.textFrame.runs.forEach(textRun => textRun.layout());
- }
-}
-
-export class Hint {
- readonly xHeight: number;
- readonly hintedXHeight: number;
- readonly stemHeight: number;
- readonly hintedStemHeight: number;
-
- private useHinting: boolean;
-
- constructor(font: PathfinderFont, pixelsPerUnit: number, useHinting: boolean) {
- useHinting = false;
- this.useHinting = useHinting;
-
- const os2Table = font.opentypeFont.tables.os2;
- this.xHeight = os2Table.sxHeight != null ? os2Table.sxHeight : 0;
- this.stemHeight = os2Table.sCapHeight != null ? os2Table.sCapHeight : 0;
-
- if (!useHinting) {
- this.hintedXHeight = this.xHeight;
- this.hintedStemHeight = this.stemHeight;
- } else {
- this.hintedXHeight = Math.round(Math.round(this.xHeight * pixelsPerUnit) /
- pixelsPerUnit);
- this.hintedStemHeight = Math.round(Math.round(this.stemHeight * pixelsPerUnit) /
- pixelsPerUnit);
- }
- }
-
- /// NB: This must match `hintPosition()` in `common.inc.glsl`.
- hintPosition(position: glmatrix.vec2): glmatrix.vec2 {
- if (!this.useHinting)
- return position;
-
- if (position[1] >= this.stemHeight) {
- const y = position[1] - this.stemHeight + this.hintedStemHeight;
- return glmatrix.vec2.clone([position[0], y]);
- }
-
- if (position[1] >= this.xHeight) {
- const y = lerp(this.hintedXHeight, this.hintedStemHeight,
- (position[1] - this.xHeight) / (this.stemHeight - this.xHeight));
- return glmatrix.vec2.clone([position[0], y]);
- }
-
- if (position[1] >= 0.0) {
- const y = lerp(0.0, this.hintedXHeight, position[1] / this.xHeight);
- return glmatrix.vec2.clone([position[0], y]);
- }
-
- return position;
- }
-}
-
-export class UnitMetrics {
- left: number;
- right: number;
- ascent: number;
- descent: number;
-
- constructor(metrics: Metrics, rotationAngle: number, emboldenAmount: glmatrix.vec2) {
- const left = metrics.xMin;
- const bottom = metrics.yMin;
- const right = metrics.xMax + emboldenAmount[0] * 2;
- const top = metrics.yMax + emboldenAmount[1] * 2;
-
- const transform = glmatrix.mat2.create();
- glmatrix.mat2.fromRotation(transform, -rotationAngle);
-
- const lowerLeft = glmatrix.vec2.clone([Infinity, Infinity]);
- const upperRight = glmatrix.vec2.clone([-Infinity, -Infinity]);
- const points = [[left, bottom], [left, top], [right, top], [right, bottom]];
- const transformedPoint = glmatrix.vec2.create();
- for (const point of points) {
- glmatrix.vec2.transformMat2(transformedPoint, point, transform);
- glmatrix.vec2.min(lowerLeft, lowerLeft, transformedPoint);
- glmatrix.vec2.max(upperRight, upperRight, transformedPoint);
- }
-
- this.left = lowerLeft[0];
- this.right = upperRight[0];
- this.ascent = upperRight[1];
- this.descent = lowerLeft[1];
- }
-}
-
-export function calculatePixelXMin(metrics: UnitMetrics, pixelsPerUnit: number): number {
- return Math.floor(metrics.left * pixelsPerUnit);
-}
-
-export function calculatePixelDescent(metrics: UnitMetrics, pixelsPerUnit: number): number {
- return Math.floor(metrics.descent * pixelsPerUnit);
-}
-
-function calculateSubpixelMetricsForGlyph(metrics: UnitMetrics, pixelsPerUnit: number, hint: Hint):
- PixelMetrics {
- const ascent = hint.hintPosition(glmatrix.vec2.fromValues(0, metrics.ascent))[1];
- return {
- ascent: ascent * pixelsPerUnit,
- descent: metrics.descent * pixelsPerUnit,
- left: metrics.left * pixelsPerUnit,
- right: metrics.right * pixelsPerUnit,
- };
-}
-
-export function calculatePixelRectForGlyph(metrics: UnitMetrics,
- subpixelOrigin: glmatrix.vec2,
- pixelsPerUnit: number,
- hint: Hint):
- glmatrix.vec4 {
- const pixelMetrics = calculateSubpixelMetricsForGlyph(metrics, pixelsPerUnit, hint);
- return glmatrix.vec4.clone([Math.floor(subpixelOrigin[0] + pixelMetrics.left),
- Math.floor(subpixelOrigin[1] + pixelMetrics.descent),
- Math.ceil(subpixelOrigin[0] + pixelMetrics.right),
- Math.ceil(subpixelOrigin[1] + pixelMetrics.ascent)]);
-}
-
-export function computeStemDarkeningAmount(pixelsPerEm: number, pixelsPerUnit: number):
- glmatrix.vec2 {
- const amount = glmatrix.vec2.create();
- if (pixelsPerEm > MAX_STEM_DARKENING_PIXELS_PER_EM)
- return amount;
-
- glmatrix.vec2.scale(amount, STEM_DARKENING_FACTORS, pixelsPerEm);
- glmatrix.vec2.min(amount, amount, MAX_STEM_DARKENING_AMOUNT);
- glmatrix.vec2.scale(amount, amount, 1.0 / pixelsPerUnit);
- return amount;
-}
diff --git a/demo/client/src/utils.ts b/demo/client/src/utils.ts
deleted file mode 100644
index eda91b7b..00000000
--- a/demo/client/src/utils.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-// pathfinder/client/src/utils.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-
-export const FLOAT32_SIZE: number = 4;
-export const UINT16_SIZE: number = 2;
-export const UINT8_SIZE: number = 1;
-
-export const UINT32_MAX: number = 0xffffffff;
-export const UINT32_SIZE: number = 4;
-
-export const EPSILON: number = 0.001;
-
-export function panic(message: string): never {
- throw new PathfinderError(message);
-}
-
-export function assert(value: boolean, message: string) {
- if (!value)
- panic(message);
-}
-
-export function expectNotNull(value: T | null, message: string): T {
- if (value === null)
- throw new PathfinderError(message);
- return value;
-}
-
-function expectNotUndef(value: T | undefined, message: string): T {
- if (value === undefined)
- throw new PathfinderError(message);
- return value;
-}
-
-export function unwrapNull(value: T | null): T {
- return expectNotNull(value, "Unexpected null!");
-}
-
-export function unwrapUndef(value: T | undefined): T {
- return expectNotUndef(value, "Unexpected `undefined`!");
-}
-
-export function scaleRect(rect: glmatrix.vec4, scale: number): glmatrix.vec4 {
- const upperLeft = glmatrix.vec2.clone([rect[0], rect[1]]);
- const lowerRight = glmatrix.vec2.clone([rect[2], rect[3]]);
- glmatrix.vec2.scale(upperLeft, upperLeft, scale);
- glmatrix.vec2.scale(lowerRight, lowerRight, scale);
- return glmatrix.vec4.clone([upperLeft[0], upperLeft[1], lowerRight[0], lowerRight[1]]);
-}
-
-export function lerp(a: number, b: number, t: number): number {
- return a + (b - a) * t;
-}
-
-export class PathfinderError extends Error {
- constructor(message?: string | undefined) {
- super(message);
- }
-}
-
-export class Range {
- start: number;
- end: number;
-
- get isEmpty(): boolean {
- return this.start >= this.end;
- }
-
- get length(): number {
- return this.end - this.start;
- }
-
- constructor(start: number, end: number) {
- this.start = start;
- this.end = end;
- }
-}
diff --git a/demo/client/src/view.ts b/demo/client/src/view.ts
deleted file mode 100644
index 60969ae8..00000000
--- a/demo/client/src/view.ts
+++ /dev/null
@@ -1,427 +0,0 @@
-// pathfinder/client/src/view.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-
-import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
-import {StemDarkeningMode, SubpixelAAType} from "./aa-strategy";
-import {AAOptions} from './app-controller';
-import PathfinderBufferTexture from './buffer-texture';
-import {Camera} from "./camera";
-import {EXTDisjointTimerQuery, QUAD_ELEMENTS, UniformMap} from './gl-utils';
-import {PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from './meshes';
-import {Renderer} from './renderer';
-import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader';
-import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader';
-import {expectNotNull, PathfinderError, UINT32_SIZE, unwrapNull} from './utils';
-
-const QUAD_POSITIONS: Float32Array = new Float32Array([
- 0.0, 0.0,
- 1.0, 0.0,
- 0.0, 1.0,
- 1.0, 1.0,
-]);
-
-const QUAD_TEX_COORDS: Float32Array = new Float32Array([
- 0.0, 0.0,
- 1.0, 0.0,
- 0.0, 1.0,
- 1.0, 1.0,
-]);
-
-export const TIMINGS: {[name: string]: string} = {
- compositing: "Compositing",
- rendering: "Rendering",
-};
-
-export interface Timings {
- compositing: number;
- rendering: number;
-}
-
-export type ColorAlphaFormat = 'RGBA8' | 'RGB5_A1';
-
-declare class WebGLQuery {}
-
-export abstract class PathfinderView {
- canvas: HTMLCanvasElement;
-
- suppressAutomaticRedraw: boolean;
-
- vrDisplayWidth: number | null;
- vrDisplayHeight: number | null;
-
- protected abstract get camera(): Camera;
-
- private dirty: boolean;
-
- private pulseHandle: number;
-
- constructor() {
- this.dirty = false;
- this.pulseHandle = 0;
- this.suppressAutomaticRedraw = false;
- this.canvas = unwrapNull(document.getElementById('pf-canvas')) as HTMLCanvasElement;
- window.addEventListener('resize', () => this.resizeToFit(false), false);
-
- this.vrDisplayHeight = null;
- this.vrDisplayWidth = null;
- }
-
- setDirty(): void {
- if (this.dirty || this.suppressAutomaticRedraw)
- return;
- this.dirty = true;
- window.requestAnimationFrame(() => this.redraw());
- }
-
- zoomIn(): void {
- this.camera.zoomIn();
- }
-
- zoomOut(): void {
- this.camera.zoomOut();
- }
-
- zoomPulse(): void {
- if (this.pulseHandle) {
- window.cancelAnimationFrame(this.pulseHandle);
- this.pulseHandle = 0;
- return;
- }
- let c = 0;
- let d = 0.005;
- const self = this;
- function tick() {
- self.camera.zoom(1 + d);
- if (c++ % 200 === 0) {
- d *= -1;
- }
- self.pulseHandle = window.requestAnimationFrame(tick);
- }
- this.pulseHandle = window.requestAnimationFrame(tick);
- }
-
- rotate(newAngle: number): void {
- this.camera.rotate(newAngle);
- }
-
- redraw(): void {
- this.dirty = false;
- }
-
- protected resized(): void {
- this.setDirty();
- }
-
- protected inVR(): boolean {
- return false;
- }
-
- protected resizeToFit(initialSize: boolean): void {
- if (!this.canvas.classList.contains('pf-no-autoresize')) {
-
- if (this.inVR()) {
- const width = unwrapNull(this.vrDisplayWidth);
- const height = unwrapNull(this.vrDisplayHeight);
- // these are already in device pixel units, no need to multiply
- this.canvas.style.width = width + 'px';
- this.canvas.style.height = height + 'px';
- this.canvas.width = width;
- this.canvas.height = height;
- } else {
- const width = window.innerWidth;
- const canvasTop = this.canvas.getBoundingClientRect().top;
- const height = window.scrollY + window.innerHeight - canvasTop;
- const devicePixelRatio = window.devicePixelRatio;
- const canvasSize = new Float32Array([width, height]) as glmatrix.vec2;
- glmatrix.vec2.scale(canvasSize, canvasSize, devicePixelRatio);
- glmatrix.vec2.round(canvasSize, canvasSize);
-
- this.canvas.style.width = width + 'px';
- this.canvas.style.height = height + 'px';
- this.canvas.width = canvasSize[0];
- this.canvas.height = canvasSize[1];
- }
-
- }
- this.resized();
- }
-}
-
-export abstract class DemoView extends PathfinderView implements RenderContext {
- readonly renderer!: Renderer;
-
- gl!: WebGLRenderingContext;
-
- shaderPrograms: ShaderMap;
- areaLUT: HTMLImageElement;
- gammaLUT: HTMLImageElement;
-
- instancedArraysExt!: ANGLE_instanced_arrays;
- textureHalfFloatExt!: OESTextureHalfFloat;
- timerQueryExt!: EXTDisjointTimerQuery | null;
- vertexArrayObjectExt!: OESVertexArrayObject;
-
- quadPositionsBuffer!: WebGLBuffer;
- quadTexCoordsBuffer!: WebGLBuffer;
- quadElementsBuffer!: WebGLBuffer;
-
- atlasRenderingTimerQuery!: WebGLQuery | null;
- compositingTimerQuery!: WebGLQuery | null;
-
- meshes: PathfinderPackedMeshBuffers[];
- meshData: PathfinderPackedMeshes[];
-
- get colorAlphaFormat(): ColorAlphaFormat {
- // On macOS, RGBA framebuffers seem to cause driver stalls when switching between rendering
- // and texturing. Work around this by using RGB5A1 instead.
- return navigator.platform === 'MacIntel' ? 'RGB5_A1' : 'RGBA8';
- }
-
- get renderContext(): RenderContext {
- return this;
- }
-
- protected colorBufferHalfFloatExt: any;
-
- private wantsScreenshot: boolean;
-
- /// NB: All subclasses are responsible for creating a renderer in their constructors.
- constructor(areaLUT: HTMLImageElement,
- gammaLUT: HTMLImageElement,
- commonShaderSource: string,
- shaderSources: ShaderMap) {
- super();
-
- this.meshes = [];
- this.meshData = [];
-
- this.initContext();
-
- const shaderSource = this.compileShaders(commonShaderSource, shaderSources);
- this.shaderPrograms = this.linkShaders(shaderSource);
-
- this.areaLUT = areaLUT;
- this.gammaLUT = gammaLUT;
-
- this.wantsScreenshot = false;
-
- }
-
- attachMeshes(meshes: PathfinderPackedMeshes[]): void {
- this.renderer.attachMeshes(meshes);
- this.setDirty();
- }
-
- initQuadVAO(attributes: any): void {
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadPositionsBuffer);
- this.gl.vertexAttribPointer(attributes.aPosition, 2, this.gl.FLOAT, false, 0, 0);
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadTexCoordsBuffer);
- 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.quadElementsBuffer);
- }
-
- queueScreenshot(): void {
- this.wantsScreenshot = true;
- this.setDirty();
- }
-
- setAntialiasingOptions(aaType: AntialiasingStrategyName,
- aaLevel: number,
- aaOptions: AAOptions):
- void {
- this.renderer.setAntialiasingOptions(aaType, aaLevel, aaOptions);
- }
-
- snapshot(rect: glmatrix.vec4): Uint8Array {
- const gl = this.renderContext.gl;
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
-
- const canvasHeight = this.canvas.height;
- const width = rect[2] - rect[0], height = rect[3] - rect[1];
- const originX = Math.max(rect[0], 0);
- const originY = Math.max(canvasHeight - height, 0);
- const flippedBuffer = new Uint8Array(width * height * 4);
- gl.readPixels(originX, originY, width, height, gl.RGBA, gl.UNSIGNED_BYTE, flippedBuffer);
-
- const buffer = new Uint8Array(width * height * 4);
- for (let y = 0; y < height; y++) {
- const destRowStart = y * width * 4;
- const srcRowStart = (height - y - 1) * width * 4;
- buffer.set(flippedBuffer.slice(srcRowStart, srcRowStart + width * 4),
- destRowStart);
- }
-
- return buffer;
- }
-
- enterVR(): void {}
- redrawVR(): void {
- this.renderer.redraw();
- }
-
- redraw(): void {
- super.redraw();
-
- if (!this.renderer.meshesAttached)
- return;
-
- if (!this.renderer.inVR) {
- this.renderer.redraw();
- } else {
- this.redrawVR();
- }
-
- // Invoke the post-render hook.
- this.renderingFinished();
-
- // Take a screenshot if desired.
- if (this.wantsScreenshot) {
- this.wantsScreenshot = false;
- this.takeScreenshot();
- }
- }
-
- protected resized(): void {
- super.resized();
- this.renderer.canvasResized();
- }
-
- protected inVR(): boolean {
- return this.renderer.inVR;
- }
-
- protected initContext(): void {
- // Initialize the OpenGL context.
- this.gl = expectNotNull(this.canvas.getContext('webgl', { antialias: false, depth: true }),
- "Failed to initialize WebGL! Check that your browser supports it.");
- this.colorBufferHalfFloatExt = this.gl.getExtension('EXT_color_buffer_half_float');
- this.instancedArraysExt = unwrapNull(this.gl.getExtension('ANGLE_instanced_arrays'));
- this.textureHalfFloatExt = unwrapNull(this.gl.getExtension('OES_texture_half_float'));
- this.timerQueryExt = this.gl.getExtension('EXT_disjoint_timer_query');
- this.vertexArrayObjectExt = unwrapNull(this.gl.getExtension('OES_vertex_array_object'));
- this.gl.getExtension('EXT_frag_depth');
- this.gl.getExtension('OES_element_index_uint');
- this.gl.getExtension('OES_standard_derivatives');
- this.gl.getExtension('OES_texture_float');
- this.gl.getExtension('WEBGL_depth_texture');
-
- // Upload quad buffers.
- this.quadPositionsBuffer = unwrapNull(this.gl.createBuffer());
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadPositionsBuffer);
- this.gl.bufferData(this.gl.ARRAY_BUFFER, QUAD_POSITIONS, this.gl.STATIC_DRAW);
- this.quadTexCoordsBuffer = unwrapNull(this.gl.createBuffer());
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadTexCoordsBuffer);
- this.gl.bufferData(this.gl.ARRAY_BUFFER, QUAD_TEX_COORDS, this.gl.STATIC_DRAW);
- this.quadElementsBuffer = unwrapNull(this.gl.createBuffer());
- this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.quadElementsBuffer);
- this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, QUAD_ELEMENTS, this.gl.STATIC_DRAW);
-
- // Set up our timer queries for profiling.
- if (this.timerQueryExt != null) {
- this.atlasRenderingTimerQuery = this.timerQueryExt.createQueryEXT();
- this.compositingTimerQuery = this.timerQueryExt.createQueryEXT();
- } else {
- this.atlasRenderingTimerQuery = null;
- this.compositingTimerQuery = null;
- }
- }
-
- protected renderingFinished(): void {}
-
- private compileShaders(commonSource: string, shaderSources: ShaderMap):
- ShaderMap {
- const shaders: Partial>> = {};
-
- for (const shaderKey of SHADER_NAMES) {
- for (const typeName of ['vertex', 'fragment'] as Array<'vertex' | 'fragment'>) {
- const type = {
- fragment: this.gl.FRAGMENT_SHADER,
- vertex: this.gl.VERTEX_SHADER,
- }[typeName];
-
- const source = shaderSources[shaderKey][typeName];
- const shader = this.gl.createShader(type);
- if (shader == null)
- throw new PathfinderError("Failed to create shader!");
-
- this.gl.shaderSource(shader, commonSource + "\n#line 1\n" + source);
- this.gl.compileShader(shader);
- if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
- const infoLog = this.gl.getShaderInfoLog(shader);
- throw new PathfinderError(`Failed to compile ${typeName} shader ` +
- `"${shaderKey}":\n${infoLog}`);
- }
-
- if (shaders[shaderKey] == null)
- shaders[shaderKey] = {};
- shaders[shaderKey]![typeName] = shader;
- }
- }
-
- return shaders as ShaderMap;
- }
-
- private linkShaders(shaders: ShaderMap):
- ShaderMap {
- const shaderProgramMap: Partial> = {};
- for (const shaderName of Object.keys(shaders) as Array>) {
- shaderProgramMap[shaderName] = new PathfinderShaderProgram(this.gl,
- shaderName,
- shaders[shaderName]);
- }
- return shaderProgramMap as ShaderMap;
- }
-
- private takeScreenshot(): void {
- const width = this.canvas.width, height = this.canvas.height;
- const scratchCanvas = document.createElement('canvas');
- scratchCanvas.width = width;
- scratchCanvas.height = height;
- const scratch2DContext = unwrapNull(scratchCanvas.getContext('2d'));
- scratch2DContext.drawImage(this.canvas, 0, 0, width, height);
-
- const scratchLink = document.createElement('a');
- scratchLink.download = 'pathfinder-screenshot.png';
- scratchLink.href = scratchCanvas.toDataURL();
- scratchLink.style.position = 'absolute';
- document.body.appendChild(scratchLink);
- scratchLink.click();
- document.body.removeChild(scratchLink);
- }
-}
-
-export interface RenderContext {
- /// The OpenGL context.
- readonly gl: WebGLRenderingContext;
-
- readonly instancedArraysExt: ANGLEInstancedArrays;
- readonly textureHalfFloatExt: OESTextureHalfFloat;
- readonly timerQueryExt: EXTDisjointTimerQuery | null;
- readonly vertexArrayObjectExt: OESVertexArrayObject;
-
- readonly colorAlphaFormat: ColorAlphaFormat;
-
- readonly shaderPrograms: ShaderMap;
- readonly areaLUT: HTMLImageElement;
- readonly gammaLUT: HTMLImageElement;
-
- readonly quadPositionsBuffer: WebGLBuffer;
- readonly quadElementsBuffer: WebGLBuffer;
-
- readonly atlasRenderingTimerQuery: WebGLQuery | null;
- readonly compositingTimerQuery: WebGLQuery | null;
-
- initQuadVAO(attributes: any): void;
- setDirty(): void;
-}
diff --git a/demo/client/src/xcaa-strategy.ts b/demo/client/src/xcaa-strategy.ts
deleted file mode 100644
index f1323b4e..00000000
--- a/demo/client/src/xcaa-strategy.ts
+++ /dev/null
@@ -1,886 +0,0 @@
-// pathfinder/demo/client/src/xcaa-strategy.ts
-//
-// Copyright © 2017 The Pathfinder Project Developers.
-//
-// Licensed under the Apache License, Version 2.0 or the MIT license
-// , at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-import * as glmatrix from 'gl-matrix';
-import * as _ from 'lodash';
-
-import {AntialiasingStrategy, DirectRenderingMode, SubpixelAAType} from './aa-strategy';
-import PathfinderBufferTexture from './buffer-texture';
-import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
-import {createFramebufferDepthTexture, setTextureParameters, UniformMap} from './gl-utils';
-import {WebGLVertexArrayObject} from './gl-utils';
-import {B_QUAD_LOWER_INDICES_OFFSET, B_QUAD_SIZE, B_QUAD_UPPER_INDICES_OFFSET} from './meshes';
-import {Renderer} from './renderer';
-import {PathfinderShaderProgram, ShaderMap} from './shader-loader';
-import {computeStemDarkeningAmount} from './text';
-import {assert, FLOAT32_SIZE, lerp, Range, UINT16_SIZE, UINT32_SIZE, unwrapNull} from './utils';
-import {unwrapUndef} from './utils';
-import {RenderContext} from './view';
-
-interface FastEdgeVAOs {
- upper: WebGLVertexArrayObject;
- lower: WebGLVertexArrayObject;
-}
-
-type Direction = 'upper' | 'lower';
-
-const DIRECTIONS: Direction[] = ['upper', 'lower'];
-
-const PATCH_VERTICES: Float32Array = new Float32Array([
- 0.0, 0.0,
- 0.5, 0.0,
- 1.0, 0.0,
- 0.0, 1.0,
- 0.5, 1.0,
- 1.0, 1.0,
-]);
-
-const MCAA_PATCH_INDICES: Uint8Array = new Uint8Array([0, 1, 2, 1, 3, 2]);
-
-export type TransformType = 'affine' | '3d';
-
-export abstract class XCAAStrategy extends AntialiasingStrategy {
- abstract readonly directRenderingMode: DirectRenderingMode;
-
- protected patchVertexBuffer: WebGLBuffer | null = null;
- protected patchIndexBuffer: WebGLBuffer | null = null;
-
- get passCount(): number {
- return 1;
- }
-
- protected abstract get transformType(): TransformType;
-
- protected abstract get patchIndices(): Uint8Array;
-
- protected pathBoundsBufferTextures: PathfinderBufferTexture[];
-
- protected supersampledFramebufferSize: glmatrix.vec2;
- protected destFramebufferSize: glmatrix.vec2;
-
- protected resolveVAO: WebGLVertexArrayObject | null;
-
- protected aaAlphaTexture: WebGLTexture | null = null;
- protected aaDepthTexture: WebGLTexture | null = null;
- protected aaFramebuffer: WebGLFramebuffer | null = null;
-
- protected abstract get mightUseAAFramebuffer(): boolean;
-
- constructor(level: number, subpixelAA: SubpixelAAType) {
- super(subpixelAA);
-
- this.supersampledFramebufferSize = glmatrix.vec2.create();
- this.destFramebufferSize = glmatrix.vec2.create();
-
- this.pathBoundsBufferTextures = [];
- }
-
- init(renderer: Renderer): void {
- super.init(renderer);
- }
-
- attachMeshes(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- this.createResolveVAO(renderer);
- this.pathBoundsBufferTextures = [];
-
- this.patchVertexBuffer = unwrapNull(gl.createBuffer());
- gl.bindBuffer(gl.ARRAY_BUFFER, this.patchVertexBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, PATCH_VERTICES.buffer as ArrayBuffer, gl.STATIC_DRAW);
- gl.bindBuffer(gl.ARRAY_BUFFER, null);
-
- this.patchIndexBuffer = unwrapNull(gl.createBuffer());
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.patchIndexBuffer);
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,
- this.patchIndices.buffer as ArrayBuffer,
- gl.STATIC_DRAW);
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
- }
-
- setFramebufferSize(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- this.destFramebufferSize = glmatrix.vec2.clone(renderer.destAllocatedSize);
- glmatrix.vec2.mul(this.supersampledFramebufferSize,
- this.destFramebufferSize,
- this.supersampleScale);
-
- this.initAAAlphaFramebuffer(renderer);
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
- }
-
- prepareForRendering(renderer: Renderer): void {}
-
- prepareForDirectRendering(renderer: Renderer): void {}
-
- finishAntialiasingObject(renderer: Renderer, objectIndex: number): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- this.initResolveFramebufferForObject(renderer, objectIndex);
-
- if (!this.usesAAFramebuffer(renderer))
- return;
-
- const usedSize = this.supersampledUsedSize(renderer);
- gl.scissor(0, 0, usedSize[0], usedSize[1]);
- gl.enable(gl.SCISSOR_TEST);
-
- // Clear out the color and depth textures.
- gl.clearColor(1.0, 1.0, 1.0, 1.0);
- gl.clearDepth(0.0);
- gl.depthMask(true);
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
- }
-
- prepareToRenderObject(renderer: Renderer, objectIndex: number): void {}
-
- finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void {
- // TODO(pcwalton)
- }
-
- antialiasObject(renderer: Renderer, objectIndex: number): void {
- // Perform early preparations.
- this.createPathBoundsBufferTextureForObjectIfNecessary(renderer, objectIndex);
-
- // Set up antialiasing.
- this.prepareAA(renderer);
-
- // Clear.
- this.clearForAA(renderer);
- }
-
- resolveAAForObject(renderer: Renderer, objectIndex: number): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- if (!this.usesAAFramebuffer(renderer))
- return;
-
- const resolveProgram = this.getResolveProgram(renderer);
- if (resolveProgram == null)
- return;
-
- // Set state for XCAA resolve.
- const usedSize = renderer.destUsedSize;
- gl.scissor(0, 0, usedSize[0], usedSize[1]);
- gl.enable(gl.SCISSOR_TEST);
- this.setDepthAndBlendModeForResolve(renderContext);
-
- // Clear out the resolve buffer, if necessary.
- this.clearForResolve(renderer);
-
- // Resolve.
- gl.useProgram(resolveProgram.program);
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.resolveVAO);
- gl.uniform2i(resolveProgram.uniforms.uFramebufferSize,
- this.destFramebufferSize[0],
- this.destFramebufferSize[1]);
- gl.activeTexture(renderContext.gl.TEXTURE0);
- gl.bindTexture(renderContext.gl.TEXTURE_2D, this.aaAlphaTexture);
- gl.uniform1i(resolveProgram.uniforms.uAAAlpha, 0);
- gl.uniform2i(resolveProgram.uniforms.uAAAlphaDimensions,
- this.supersampledFramebufferSize[0],
- this.supersampledFramebufferSize[1]);
- if (renderer.bgColor != null)
- gl.uniform4fv(resolveProgram.uniforms.uBGColor, renderer.bgColor);
- if (renderer.fgColor != null)
- gl.uniform4fv(resolveProgram.uniforms.uFGColor, renderer.fgColor);
- renderer.setTransformSTAndTexScaleUniformsForDest(resolveProgram.uniforms);
- this.setSubpixelAAKernelUniform(renderer, resolveProgram.uniforms);
- this.setAdditionalStateForResolveIfNecessary(renderer, resolveProgram, 1);
- gl.drawElements(renderContext.gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
- }
-
- resolve(pass: number, renderer: Renderer): void {}
-
- get transform(): glmatrix.mat4 {
- return glmatrix.mat4.create();
- }
-
- protected abstract usesAAFramebuffer(renderer: Renderer): boolean;
-
- protected supersampledUsedSize(renderer: Renderer): glmatrix.vec2 {
- const usedSize = glmatrix.vec2.create();
- glmatrix.vec2.mul(usedSize, renderer.destUsedSize, this.supersampleScale);
- return usedSize;
- }
-
- protected prepareAA(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- // Set state for antialiasing.
- const usedSize = this.supersampledUsedSize(renderer);
- if (this.usesAAFramebuffer(renderer))
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.aaFramebuffer);
- gl.viewport(0,
- 0,
- this.supersampledFramebufferSize[0],
- this.supersampledFramebufferSize[1]);
- gl.scissor(0, 0, usedSize[0], usedSize[1]);
- gl.enable(gl.SCISSOR_TEST);
- }
-
- protected setAAState(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- const usedSize = this.supersampledUsedSize(renderer);
- if (this.usesAAFramebuffer(renderer))
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.aaFramebuffer);
- gl.viewport(0,
- 0,
- this.supersampledFramebufferSize[0],
- this.supersampledFramebufferSize[1]);
- gl.scissor(0, 0, usedSize[0], usedSize[1]);
- gl.enable(gl.SCISSOR_TEST);
-
- this.setAADepthState(renderer);
- }
-
- protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- switch (this.transformType) {
- case 'affine':
- renderer.setTransformAffineUniforms(uniforms, 0);
- break;
- case '3d':
- renderer.setTransformUniform(uniforms, 0, 0);
- break;
- }
-
- gl.uniform2i(uniforms.uFramebufferSize,
- this.supersampledFramebufferSize[0],
- this.supersampledFramebufferSize[1]);
- renderer.pathTransformBufferTextures[0].ext.bind(gl, uniforms, 0);
- renderer.pathTransformBufferTextures[0].st.bind(gl, uniforms, 1);
- this.pathBoundsBufferTextures[objectIndex].bind(gl, uniforms, 2);
- renderer.setHintsUniform(uniforms);
- renderer.bindAreaLUT(4, uniforms);
- }
-
- protected setDepthAndBlendModeForResolve(renderContext: RenderContext): void {
- const gl = renderContext.gl;
- gl.disable(gl.DEPTH_TEST);
- gl.disable(gl.BLEND);
- }
-
- protected setAdditionalStateForResolveIfNecessary(renderer: Renderer,
- resolveProgram: PathfinderShaderProgram,
- firstFreeTextureUnit: number):
- void {}
-
- protected abstract clearForAA(renderer: Renderer): void;
- protected abstract getResolveProgram(renderer: Renderer): PathfinderShaderProgram | null;
- protected abstract setAADepthState(renderer: Renderer): void;
- protected abstract clearForResolve(renderer: Renderer): void;
-
- private initResolveFramebufferForObject(renderer: Renderer, objectIndex: number): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- gl.bindFramebuffer(gl.FRAMEBUFFER, renderer.destFramebuffer);
- renderer.setDrawViewport();
- gl.disable(gl.SCISSOR_TEST);
- }
-
- private initAAAlphaFramebuffer(renderer: Renderer): void {
- if (!this.mightUseAAFramebuffer) {
- this.aaAlphaTexture = null;
- this.aaDepthTexture = null;
- this.aaFramebuffer = null;
- return;
- }
-
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- this.aaAlphaTexture = unwrapNull(gl.createTexture());
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, this.aaAlphaTexture);
- gl.texImage2D(gl.TEXTURE_2D,
- 0,
- gl.RGB,
- this.supersampledFramebufferSize[0],
- this.supersampledFramebufferSize[1],
- 0,
- gl.RGB,
- renderContext.textureHalfFloatExt.HALF_FLOAT_OES,
- null);
- setTextureParameters(gl, gl.NEAREST);
-
- this.aaDepthTexture = createFramebufferDepthTexture(gl, this.supersampledFramebufferSize);
- this.aaFramebuffer = createFramebuffer(gl, this.aaAlphaTexture, this.aaDepthTexture);
- }
-
- private createPathBoundsBufferTextureForObjectIfNecessary(renderer: Renderer,
- objectIndex: number):
- void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- const pathBounds = renderer.pathBoundingRects(objectIndex);
-
- if (this.pathBoundsBufferTextures[objectIndex] == null) {
- this.pathBoundsBufferTextures[objectIndex] =
- new PathfinderBufferTexture(gl, 'uPathBounds');
- }
-
- this.pathBoundsBufferTextures[objectIndex].upload(gl, pathBounds);
- }
-
- private createResolveVAO(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- const resolveProgram = this.getResolveProgram(renderer);
- if (resolveProgram == null)
- return;
-
- this.resolveVAO = renderContext.vertexArrayObjectExt.createVertexArrayOES();
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.resolveVAO);
-
- gl.useProgram(resolveProgram.program);
- renderContext.initQuadVAO(resolveProgram.attributes);
-
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
- }
-
- protected get directDepthTexture(): WebGLTexture | null {
- return null;
- }
-
- protected get supersampleScale(): glmatrix.vec2 {
- return glmatrix.vec2.fromValues(this.subpixelAA !== 'none' ? 3.0 : 1.0, 1.0);
- }
-}
-
-export class MCAAStrategy extends XCAAStrategy {
- protected vao: WebGLVertexArrayObject | null;
-
- protected get patchIndices(): Uint8Array {
- return MCAA_PATCH_INDICES;
- }
-
- protected get transformType(): TransformType {
- return 'affine';
- }
-
- protected get mightUseAAFramebuffer(): boolean {
- return true;
- }
-
- attachMeshes(renderer: Renderer): void {
- super.attachMeshes(renderer);
-
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- this.vao = renderContext.vertexArrayObjectExt.createVertexArrayOES();
- }
-
- antialiasObject(renderer: Renderer, objectIndex: number): void {
- super.antialiasObject(renderer, objectIndex);
-
- const shaderProgram = this.edgeProgram(renderer);
- this.antialiasEdgesOfObjectWithProgram(renderer, objectIndex, shaderProgram);
- }
-
- protected usesAAFramebuffer(renderer: Renderer): boolean {
- return !renderer.isMulticolor;
- }
-
- protected getResolveProgram(renderer: Renderer): PathfinderShaderProgram | null {
- const renderContext = renderer.renderContext;
- if (renderer.isMulticolor)
- return null;
- if (this.subpixelAA !== 'none' && renderer.allowSubpixelAA)
- return renderContext.shaderPrograms.xcaaMonoSubpixelResolve;
- return renderContext.shaderPrograms.xcaaMonoResolve;
- }
-
- protected clearForAA(renderer: Renderer): void {
- if (!this.usesAAFramebuffer(renderer))
- return;
-
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- gl.clearColor(0.0, 0.0, 0.0, 0.0);
- gl.clearDepth(0.0);
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
- }
-
- protected setAADepthState(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- if (this.directRenderingMode !== 'conservative') {
- gl.disable(gl.DEPTH_TEST);
- return;
- }
-
- gl.depthFunc(gl.GREATER);
- gl.depthMask(false);
- gl.enable(gl.DEPTH_TEST);
- gl.disable(gl.CULL_FACE);
- }
-
- protected clearForResolve(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- if (!renderer.isMulticolor) {
- gl.clearColor(0.0, 0.0, 0.0, 1.0);
- gl.clear(gl.COLOR_BUFFER_BIT);
- }
- }
-
- protected setBlendModeForAA(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- if (renderer.isMulticolor)
- gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
- else
- gl.blendFunc(gl.ONE, gl.ONE);
-
- gl.blendEquation(gl.FUNC_ADD);
- gl.enable(gl.BLEND);
- }
-
- protected prepareAA(renderer: Renderer): void {
- super.prepareAA(renderer);
-
- this.setBlendModeForAA(renderer);
- }
-
- protected initVAOForObject(renderer: Renderer, objectIndex: number): void {
- if (renderer.meshBuffers == null || renderer.meshes == null)
- return;
-
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- const pathRange = renderer.pathRangeForObject(objectIndex);
- const meshIndex = renderer.meshIndexForObject(objectIndex);
-
- const shaderProgram = this.edgeProgram(renderer);
- const attributes = shaderProgram.attributes;
-
- // FIXME(pcwalton): Refactor.
- const vao = this.vao;
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao);
-
- const bBoxRanges = renderer.meshes[meshIndex].bBoxPathRanges;
- const offset = calculateStartFromIndexRanges(pathRange, bBoxRanges);
-
- gl.useProgram(shaderProgram.program);
- gl.bindBuffer(gl.ARRAY_BUFFER, renderer.renderContext.quadPositionsBuffer);
- gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, FLOAT32_SIZE * 2, 0);
- gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshBuffers[meshIndex].bBoxes);
- gl.vertexAttribPointer(attributes.aRect,
- 4,
- gl.FLOAT,
- false,
- FLOAT32_SIZE * 20,
- FLOAT32_SIZE * 0 + offset * FLOAT32_SIZE * 20);
- gl.vertexAttribPointer(attributes.aUV,
- 4,
- gl.FLOAT,
- false,
- FLOAT32_SIZE * 20,
- FLOAT32_SIZE * 4 + offset * FLOAT32_SIZE * 20);
- gl.vertexAttribPointer(attributes.aDUVDX,
- 4,
- gl.FLOAT,
- false,
- FLOAT32_SIZE * 20,
- FLOAT32_SIZE * 8 + offset * FLOAT32_SIZE * 20);
- gl.vertexAttribPointer(attributes.aDUVDY,
- 4,
- gl.FLOAT,
- false,
- FLOAT32_SIZE * 20,
- FLOAT32_SIZE * 12 + offset * FLOAT32_SIZE * 20);
- gl.vertexAttribPointer(attributes.aSignMode,
- 4,
- gl.FLOAT,
- false,
- FLOAT32_SIZE * 20,
- FLOAT32_SIZE * 16 + offset * FLOAT32_SIZE * 20);
-
- gl.enableVertexAttribArray(attributes.aTessCoord);
- gl.enableVertexAttribArray(attributes.aRect);
- gl.enableVertexAttribArray(attributes.aUV);
- gl.enableVertexAttribArray(attributes.aDUVDX);
- gl.enableVertexAttribArray(attributes.aDUVDY);
- gl.enableVertexAttribArray(attributes.aSignMode);
-
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aRect, 1);
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aUV, 1);
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aDUVDX, 1);
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aDUVDY, 1);
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aSignMode, 1);
-
- gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshBuffers[meshIndex].bBoxPathIDs);
- gl.vertexAttribPointer(attributes.aPathID,
- 1,
- gl.UNSIGNED_SHORT,
- false,
- UINT16_SIZE,
- offset * UINT16_SIZE);
- gl.enableVertexAttribArray(attributes.aPathID);
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1);
-
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderer.renderContext.quadElementsBuffer);
-
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
- }
-
- protected edgeProgram(renderer: Renderer): PathfinderShaderProgram {
- return renderer.renderContext.shaderPrograms.mcaa;
- }
-
- protected antialiasEdgesOfObjectWithProgram(renderer: Renderer,
- objectIndex: number,
- shaderProgram: PathfinderShaderProgram):
- void {
- if (renderer.meshBuffers == null || renderer.meshes == null)
- return;
-
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- const pathRange = renderer.pathRangeForObject(objectIndex);
- const meshIndex = renderer.meshIndexForObject(objectIndex);
-
- this.initVAOForObject(renderer, objectIndex);
-
- gl.useProgram(shaderProgram.program);
- const uniforms = shaderProgram.uniforms;
- this.setAAUniforms(renderer, uniforms, objectIndex);
-
- // FIXME(pcwalton): Refactor.
- const vao = this.vao;
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao);
-
- this.setBlendModeForAA(renderer);
- this.setAADepthState(renderer);
-
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer);
-
- const bBoxRanges = renderer.meshes[meshIndex].bBoxPathRanges;
- const count = calculateCountFromIndexRanges(pathRange, bBoxRanges);
-
- renderContext.instancedArraysExt
- .drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count);
-
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
- gl.disable(gl.DEPTH_TEST);
- gl.disable(gl.CULL_FACE);
- }
-
- get directRenderingMode(): DirectRenderingMode {
- // FIXME(pcwalton): Only in multicolor mode?
- return 'conservative';
- }
-
- protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): void {
- super.setAAUniforms(renderer, uniforms, objectIndex);
-
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- renderer.setPathColorsUniform(0, uniforms, 3);
-
- gl.uniform1i(uniforms.uMulticolor, renderer.isMulticolor ? 1 : 0);
- }
-}
-
-export class StencilAAAStrategy extends XCAAStrategy {
- directRenderingMode: DirectRenderingMode = 'none';
-
- protected transformType: TransformType = 'affine';
- protected patchIndices: Uint8Array = MCAA_PATCH_INDICES;
- protected mightUseAAFramebuffer: boolean = true;
-
- private vao: WebGLVertexArrayObject;
-
- attachMeshes(renderer: Renderer): void {
- super.attachMeshes(renderer);
- this.createVAO(renderer);
- }
-
- antialiasObject(renderer: Renderer, objectIndex: number): void {
- super.antialiasObject(renderer, objectIndex);
-
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- if (renderer.meshes == null)
- return;
-
- // Antialias.
- const shaderPrograms = renderer.renderContext.shaderPrograms;
- this.setAAState(renderer);
- this.setBlendModeForAA(renderer);
-
- const program = renderContext.shaderPrograms.stencilAAA;
- gl.useProgram(program.program);
- const uniforms = program.uniforms;
- this.setAAUniforms(renderer, uniforms, objectIndex);
-
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.vao);
-
- // FIXME(pcwalton): Only render the appropriate instances.
- const count = renderer.meshes[0].count('stencilSegments');
- for (let side = 0; side < 2; side++) {
- gl.uniform1i(uniforms.uSide, side);
- renderContext.instancedArraysExt
- .drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count);
- }
-
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
- }
-
- protected usesAAFramebuffer(renderer: Renderer): boolean {
- return true;
- }
-
- protected clearForAA(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- gl.clearColor(0.0, 0.0, 0.0, 0.0);
- gl.clearDepth(0.0);
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
- }
-
- protected getResolveProgram(renderer: Renderer): PathfinderShaderProgram | null {
- const renderContext = renderer.renderContext;
-
- if (this.subpixelAA !== 'none' && renderer.allowSubpixelAA)
- return renderContext.shaderPrograms.xcaaMonoSubpixelResolve;
- return renderContext.shaderPrograms.xcaaMonoResolve;
- }
-
- protected setAADepthState(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- gl.disable(gl.DEPTH_TEST);
- gl.disable(gl.CULL_FACE);
- }
-
- protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number):
- void {
- super.setAAUniforms(renderer, uniforms, objectIndex);
- renderer.setEmboldenAmountUniform(objectIndex, uniforms);
- }
-
- protected clearForResolve(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- gl.clearColor(0.0, 0.0, 0.0, 0.0);
- gl.clear(gl.COLOR_BUFFER_BIT);
- }
-
- private createVAO(renderer: Renderer): void {
- if (renderer.meshBuffers == null || renderer.meshes == null)
- return;
-
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- const program = renderContext.shaderPrograms.stencilAAA;
- const attributes = program.attributes;
-
- this.vao = renderContext.vertexArrayObjectExt.createVertexArrayOES();
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.vao);
-
- const vertexPositionsBuffer = renderer.meshBuffers[0].stencilSegments;
- const vertexNormalsBuffer = renderer.meshBuffers[0].stencilNormals;
- const pathIDsBuffer = renderer.meshBuffers[0].stencilSegmentPathIDs;
-
- gl.useProgram(program.program);
- gl.bindBuffer(gl.ARRAY_BUFFER, renderContext.quadPositionsBuffer);
- gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, 0, 0);
- gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionsBuffer);
- gl.vertexAttribPointer(attributes.aFromPosition, 2, gl.FLOAT, false, FLOAT32_SIZE * 6, 0);
- gl.vertexAttribPointer(attributes.aCtrlPosition,
- 2,
- gl.FLOAT,
- false,
- FLOAT32_SIZE * 6,
- FLOAT32_SIZE * 2);
- gl.vertexAttribPointer(attributes.aToPosition,
- 2,
- gl.FLOAT,
- false,
- FLOAT32_SIZE * 6,
- FLOAT32_SIZE * 4);
- gl.bindBuffer(gl.ARRAY_BUFFER, vertexNormalsBuffer);
- gl.vertexAttribPointer(attributes.aFromNormal, 2, gl.FLOAT, false, FLOAT32_SIZE * 6, 0);
- gl.vertexAttribPointer(attributes.aCtrlNormal,
- 2,
- gl.FLOAT,
- false,
- FLOAT32_SIZE * 6,
- FLOAT32_SIZE * 2);
- gl.vertexAttribPointer(attributes.aToNormal,
- 2,
- gl.FLOAT,
- false,
- FLOAT32_SIZE * 6,
- FLOAT32_SIZE * 4);
- gl.bindBuffer(gl.ARRAY_BUFFER, pathIDsBuffer);
- gl.vertexAttribPointer(attributes.aPathID, 1, gl.UNSIGNED_SHORT, false, 0, 0);
-
- gl.enableVertexAttribArray(attributes.aTessCoord);
- gl.enableVertexAttribArray(attributes.aFromPosition);
- gl.enableVertexAttribArray(attributes.aCtrlPosition);
- gl.enableVertexAttribArray(attributes.aToPosition);
- gl.enableVertexAttribArray(attributes.aFromNormal);
- gl.enableVertexAttribArray(attributes.aCtrlNormal);
- gl.enableVertexAttribArray(attributes.aToNormal);
- gl.enableVertexAttribArray(attributes.aPathID);
-
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aFromPosition, 1);
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aCtrlPosition, 1);
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aToPosition, 1);
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aFromNormal, 1);
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aCtrlNormal, 1);
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aToNormal, 1);
- renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1);
-
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer);
-
- renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
- }
-
- private setBlendModeForAA(renderer: Renderer): void {
- const renderContext = renderer.renderContext;
- const gl = renderContext.gl;
-
- gl.blendEquation(gl.FUNC_ADD);
- gl.blendFunc(gl.ONE, gl.ONE);
- gl.enable(gl.BLEND);
- }
-}
-
-/// Switches between mesh-based and stencil-based analytic antialiasing depending on whether stem
-/// darkening is enabled.
-///
-/// FIXME(pcwalton): Share textures and FBOs between the two strategies.
-export class AdaptiveStencilMeshAAAStrategy extends AntialiasingStrategy {
- private meshStrategy: MCAAStrategy;
- private stencilStrategy: StencilAAAStrategy;
-
- get directRenderingMode(): DirectRenderingMode {
- return 'none';
- }
-
- get passCount(): number {
- return 1;
- }
-
- constructor(level: number, subpixelAA: SubpixelAAType) {
- super(subpixelAA);
- this.meshStrategy = new MCAAStrategy(level, subpixelAA);
- this.stencilStrategy = new StencilAAAStrategy(level, subpixelAA);
- }
-
- init(renderer: Renderer): void {
- this.meshStrategy.init(renderer);
- this.stencilStrategy.init(renderer);
- }
-
- attachMeshes(renderer: Renderer): void {
- this.meshStrategy.attachMeshes(renderer);
- this.stencilStrategy.attachMeshes(renderer);
- }
-
- setFramebufferSize(renderer: Renderer): void {
- this.meshStrategy.setFramebufferSize(renderer);
- this.stencilStrategy.setFramebufferSize(renderer);
- }
-
- get transform(): glmatrix.mat4 {
- return this.meshStrategy.transform;
- }
-
- prepareForRendering(renderer: Renderer): void {
- this.getAppropriateStrategy(renderer).prepareForRendering(renderer);
- }
-
- prepareForDirectRendering(renderer: Renderer): void {
- this.getAppropriateStrategy(renderer).prepareForDirectRendering(renderer);
- }
-
- finishAntialiasingObject(renderer: Renderer, objectIndex: number): void {
- this.getAppropriateStrategy(renderer).finishAntialiasingObject(renderer, objectIndex);
- }
-
- prepareToRenderObject(renderer: Renderer, objectIndex: number): void {
- this.getAppropriateStrategy(renderer).prepareToRenderObject(renderer, objectIndex);
- }
-
- finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void {
- this.getAppropriateStrategy(renderer).finishDirectlyRenderingObject(renderer, objectIndex);
- }
-
- antialiasObject(renderer: Renderer, objectIndex: number): void {
- this.getAppropriateStrategy(renderer).antialiasObject(renderer, objectIndex);
- }
-
- resolveAAForObject(renderer: Renderer, objectIndex: number): void {
- this.getAppropriateStrategy(renderer).resolveAAForObject(renderer, objectIndex);
- }
-
- resolve(pass: number, renderer: Renderer): void {
- this.getAppropriateStrategy(renderer).resolve(pass, renderer);
- }
-
- worldTransformForPass(renderer: Renderer, pass: number): glmatrix.mat4 {
- return glmatrix.mat4.create();
- }
-
- private getAppropriateStrategy(renderer: Renderer): AntialiasingStrategy {
- return renderer.needsStencil ? this.stencilStrategy : this.meshStrategy;
- }
-}
-
-function calculateStartFromIndexRanges(pathRange: Range, indexRanges: Range[]): number {
- return indexRanges.length === 0 ? 0 : indexRanges[pathRange.start - 1].start;
-}
-
-function calculateCountFromIndexRanges(pathRange: Range, indexRanges: Range[]): number {
- if (indexRanges.length === 0)
- return 0;
-
- let lastIndex;
- if (pathRange.end - 1 >= indexRanges.length)
- lastIndex = unwrapUndef(_.last(indexRanges)).end;
- else
- lastIndex = indexRanges[pathRange.end - 1].start;
-
- const firstIndex = indexRanges[pathRange.start - 1].start;
-
- return lastIndex - firstIndex;
-}
diff --git a/demo/client/tsconfig.json b/demo/client/tsconfig.json
deleted file mode 100644
index 09871b14..00000000
--- a/demo/client/tsconfig.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "compilerOptions": {
- "target": "ES2017",
- "module": "commonjs",
- "types": [
- "base64-js",
- "gl-matrix",
- "lodash",
- "node",
- "opentype.js",
- "webgl-ext"
- ],
- "typeRoots": [
- "node_modules/@types"
- ],
- "sourceMap": true,
- "strict": true,
- "allowUnreachableCode": true
- },
- "include": [
- "src"
- ],
- "exclude": [
- "node_modules"
- ]
-}
\ No newline at end of file
diff --git a/demo/client/tslint.json b/demo/client/tslint.json
deleted file mode 100644
index 552e7c09..00000000
--- a/demo/client/tslint.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "defaultSeverity": "error",
- "extends": [
- "tslint:recommended"
- ],
- "jsRules": {
- "quotemark": false
- },
- "rules": {
- "quotemark": false,
- "interface-name": false,
- "curly": false,
- "member-access": false,
- "max-classes-per-file": false,
- "arrow-parens": false,
- "one-variable-per-declaration": false,
- "object-literal-shorthand": false,
- "no-empty": false,
- "variable-name": false,
- "no-bitwise": false,
- "new-parens": false,
- "no-conditional-assignment": false,
- "no-shadowed-variable": false,
- "no-var-requires": false,
- "max-line-length": [true, 100]
- },
- "rulesDirectory": []
-}
\ No newline at end of file
diff --git a/demo/client/webpack.config.js b/demo/client/webpack.config.js
deleted file mode 100644
index 5642207a..00000000
--- a/demo/client/webpack.config.js
+++ /dev/null
@@ -1,64 +0,0 @@
-const Handlebars = require('handlebars');
-const HandlebarsPlugin = require('handlebars-webpack-plugin');
-const RustdocPlugin = require('rustdoc-webpack-plugin');
-const fs = require('fs');
-const path = require('path');
-
-const cwd = fs.realpathSync(".");
-
-module.exports = {
- devtool: 'inline-source-map',
- entry: {
- '3d-demo': "./src/3d-demo.ts",
- 'svg-demo': "./src/svg-demo.ts",
- 'text-demo': "./src/text-demo.ts",
- 'reference-test': "./src/reference-test.ts",
- 'benchmark': "./src/benchmark.ts",
- 'mesh-debugger': "./src/mesh-debugger.ts",
- },
- module: {
- rules: [
- {
- test: /src(\/|\\)[a-zA-Z0-9_-]+\.tsx?$/,
- enforce: 'pre',
- loader: 'tslint-loader',
- exclude: /node_modules/,
- options: {
- configFile: "tslint.json",
- },
- },
- {
- test: /src(\/|\\)[a-zA-Z0-9_-]+\.tsx?$/,
- use: 'ts-loader',
- exclude: /node_modules/,
- },
- ]
- },
- resolve: {
- extensions: [".tsx", ".ts", ".html", ".js"],
- },
- output: {
- filename: "[name].js",
- path: __dirname,
- },
- plugins: [
- new HandlebarsPlugin({
- entry: "html/*.hbs",
- output: "./[name]",
- partials: ["html/partials/*.hbs"],
- helpers: {
- octicon: function(iconName) {
- const svg = fs.readFileSync(`node_modules/octicons/build/svg/${iconName}.svg`);
- return new Handlebars.SafeString(svg);
- }
- },
- }),
- new RustdocPlugin({
- directories: [fs.realpathSync("../..")],
- flags: {
- 'html-in-header': path.join(cwd, "doc-header.html"),
- 'html-before-content': path.join(cwd, "doc-before-content.html"),
- },
- }),
- ]
-}