pathfinder/demo/client/src/atlas.ts

156 lines
6.0 KiB
TypeScript
Raw Normal View History

// pathfinder/client/src/atlas.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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,
hint: Hint,
stemDarkeningAmount: 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, stemDarkeningAmount);
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 texture = unwrapNull(renderContext.gl.createTexture());
this._texture = texture;
renderContext.gl.bindTexture(renderContext.gl.TEXTURE_2D, texture);
renderContext.gl.texImage2D(renderContext.gl.TEXTURE_2D,
0,
renderContext.colorAlphaFormat,
ATLAS_SIZE[0],
ATLAS_SIZE[1],
0,
renderContext.colorAlphaFormat,
renderContext.gl.UNSIGNED_BYTE,
null);
setTextureParameters(renderContext.gl, renderContext.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);
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 {
return this.glyphStoreIndex * SUBPIXEL_GRANULARITY + this.glyphKey.subpixel + 1;
}
}
export class GlyphKey {
readonly id: number;
readonly subpixel: number;
constructor(id: number, subpixel: number) {
this.id = id;
this.subpixel = subpixel;
}
get sortKey(): number {
return this.id * SUBPIXEL_GRANULARITY + this.subpixel;
}
}