molstar
Version:
A comprehensive macromolecular library.
120 lines • 5.95 kB
JavaScript
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { __assign } from "tslib";
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { edt } from '../../../mol-math/geometry/distance-transform';
import { createTextureImage } from '../../../mol-gl/renderable/util';
var TextAtlasCache = {};
export function getFontAtlas(props) {
var hash = JSON.stringify(props);
if (TextAtlasCache[hash] === undefined) {
TextAtlasCache[hash] = new FontAtlas(props);
}
return TextAtlasCache[hash];
}
export var FontAtlasParams = {
fontFamily: PD.Select('sans-serif', [['sans-serif', 'Sans Serif'], ['monospace', 'Monospace'], ['serif', 'Serif'], ['cursive', 'Cursive']]),
fontQuality: PD.Select(3, [[0, 'lower'], [1, 'low'], [2, 'medium'], [3, 'high'], [4, 'higher']]),
fontStyle: PD.Select('normal', [['normal', 'Normal'], ['italic', 'Italic'], ['oblique', 'Oblique']]),
fontVariant: PD.Select('normal', [['normal', 'Normal'], ['small-caps', 'Small Caps']]),
fontWeight: PD.Select('normal', [['normal', 'Normal'], ['bold', 'Bold']]),
};
var FontAtlas = /** @class */ (function () {
function FontAtlas(props) {
if (props === void 0) { props = {}; }
this.mapped = {};
this.scratchW = 0;
this.scratchH = 0;
this.currentX = 0;
this.currentY = 0;
this.cutoff = 0.5;
var p = __assign(__assign({}, PD.getDefaultValues(FontAtlasParams)), props);
this.props = p;
// create measurements
var fontSize = 32 * (p.fontQuality + 1);
this.buffer = fontSize / 8;
this.radius = fontSize / 3;
this.lineHeight = Math.round(fontSize + 2 * this.buffer + this.radius);
this.maxWidth = Math.round(this.lineHeight * 0.75);
// create texture (for ~350 characters)
this.texture = createTextureImage(350 * this.lineHeight * this.maxWidth, 1, Uint8Array);
// prepare scratch canvas
this.scratchCanvas = document.createElement('canvas');
this.scratchCanvas.width = this.maxWidth;
this.scratchCanvas.height = this.lineHeight;
this.scratchContext = this.scratchCanvas.getContext('2d');
this.scratchContext.font = p.fontStyle + " " + p.fontVariant + " " + p.fontWeight + " " + fontSize + "px " + p.fontFamily;
this.scratchContext.fillStyle = 'black';
this.scratchContext.textBaseline = 'middle';
// SDF scratch values
this.scratchData = new Uint8Array(this.lineHeight * this.maxWidth);
// temporary arrays for the distance transform
this.gridOuter = new Float64Array(this.lineHeight * this.maxWidth);
this.gridInner = new Float64Array(this.lineHeight * this.maxWidth);
this.f = new Float64Array(Math.max(this.lineHeight, this.maxWidth));
this.d = new Float64Array(Math.max(this.lineHeight, this.maxWidth));
this.z = new Float64Array(Math.max(this.lineHeight, this.maxWidth) + 1);
this.v = new Int16Array(Math.max(this.lineHeight, this.maxWidth));
this.middle = Math.ceil(this.lineHeight / 2);
// replacement Character
this.placeholder = this.get(String.fromCharCode(0xFFFD));
}
FontAtlas.prototype.get = function (char) {
if (this.mapped[char] === undefined) {
this.draw(char);
var _a = this.texture, array = _a.array, width = _a.width, height = _a.height;
var data = this.scratchData;
if (this.currentX + this.scratchW > width) {
this.currentX = 0;
this.currentY += this.scratchH;
}
if (this.currentY + this.scratchH > height) {
console.warn('canvas to small');
return this.placeholder;
}
this.mapped[char] = {
x: this.currentX, y: this.currentY,
w: this.scratchW, h: this.scratchH,
nw: this.scratchW / this.lineHeight, nh: this.scratchH / this.lineHeight
};
for (var y = 0; y < this.scratchH; ++y) {
for (var x = 0; x < this.scratchW; ++x) {
array[width * (this.currentY + y) + this.currentX + x] = data[y * this.scratchW + x];
}
}
this.currentX += this.scratchW;
}
return this.mapped[char];
};
FontAtlas.prototype.draw = function (char) {
var h = this.lineHeight;
var ctx = this.scratchContext;
var data = this.scratchData;
// measure text
var m = ctx.measureText(char);
var w = Math.min(this.maxWidth, Math.ceil(m.width + 2 * this.buffer));
var n = w * h;
ctx.clearRect(0, 0, w, h); // clear scratch area
ctx.fillText(char, this.buffer, this.middle); // draw text
var imageData = ctx.getImageData(0, 0, w, h);
for (var i = 0; i < n; i++) {
var a = imageData.data[i * 4 + 3] / 255; // alpha value
this.gridOuter[i] = a === 1 ? 0 : a === 0 ? Number.MAX_SAFE_INTEGER : Math.pow(Math.max(0, 0.5 - a), 2);
this.gridInner[i] = a === 1 ? Number.MAX_SAFE_INTEGER : a === 0 ? 0 : Math.pow(Math.max(0, a - 0.5), 2);
}
edt(this.gridOuter, w, h, this.f, this.d, this.v, this.z);
edt(this.gridInner, w, h, this.f, this.d, this.v, this.z);
for (var i = 0; i < n; i++) {
var d = this.gridOuter[i] - this.gridInner[i];
data[i] = Math.max(0, Math.min(255, Math.round(255 - 255 * (d / this.radius + this.cutoff))));
}
this.scratchW = w;
this.scratchH = h;
};
return FontAtlas;
}());
export { FontAtlas };
//# sourceMappingURL=font-atlas.js.map