Snap baselines to the nearest pixel

This commit is contained in:
Patrick Walton 2017-09-03 16:35:10 -07:00
parent 24cfb03c66
commit 4ab917b79b
3 changed files with 61 additions and 64 deletions

View File

@ -104,7 +104,7 @@ class ThreeDView extends PathfinderDemoView {
pathColors[startOffset + 3] = 0xff; // alpha
const textGlyph = textGlyphs[pathIndex];
const glyphRect = textGlyph.getRect(PIXELS_PER_UNIT);
const glyphRect = textGlyph.pixelRect(PIXELS_PER_UNIT);
pathTransforms.set([1, 1, glyphRect[0], glyphRect[1]], startOffset);
}
@ -164,16 +164,6 @@ class ThreeDGlyph extends PathfinderGlyph {
constructor(glyph: opentype.Glyph) {
super(glyph);
}
getRect(pixelsPerUnit: number): glmatrix.vec4 {
const rect =
glmatrix.vec4.fromValues(this.position[0],
this.position[1],
this.position[0] + this.metrics.xMax - this.metrics.xMin,
this.position[1] + this.metrics.yMax - this.metrics.yMin);
glmatrix.vec4.scale(rect, rect, pixelsPerUnit);
return rect;
}
}
function main() {

View File

@ -260,7 +260,7 @@ class TextDemoView extends MonochromePathfinderView {
for (let glyphIndex = 0; glyphIndex < textGlyphs.length; glyphIndex++) {
const textGlyph = textGlyphs[glyphIndex];
const rect = textGlyph.getRect(this.appController.pixelsPerUnit);
const rect = textGlyph.pixelRect(this.appController.pixelsPerUnit);
glyphPositions.set([
rect[0], rect[3],
rect[2], rect[3],
@ -291,7 +291,7 @@ class TextDemoView extends MonochromePathfinderView {
-translation[1] + this.canvas.height);
let atlasGlyphs =
textGlyphs.filter(glyph => rectsIntersect(glyph.getRect(pixelsPerUnit), canvasRect))
textGlyphs.filter(glyph => rectsIntersect(glyph.pixelRect(pixelsPerUnit), canvasRect))
.map(textGlyph => new AtlasGlyph(textGlyph.opentypeGlyph));
atlasGlyphs.sort((a, b) => a.index - b.index);
atlasGlyphs = _.sortedUniqBy(atlasGlyphs, glyph => glyph.index);
@ -313,15 +313,12 @@ class TextDemoView extends MonochromePathfinderView {
assert(pathID >= 0, "No path ID!");
pathID++;
const atlasLocation = glyph.getRect(pixelsPerUnit);
const atlasOrigin = glyph.pixelOrigin(pixelsPerUnit);
const metrics = glyph.metrics;
const left = metrics.xMin * pixelsPerUnit;
const bottom = metrics.yMin * pixelsPerUnit;
transforms[pathID * 4 + 0] = pixelsPerUnit;
transforms[pathID * 4 + 1] = pixelsPerUnit;
transforms[pathID * 4 + 2] = atlasLocation[0] - left;
transforms[pathID * 4 + 3] = atlasLocation[1] - bottom;
transforms[pathID * 4 + 2] = atlasOrigin[0];
transforms[pathID * 4 + 3] = atlasOrigin[1];
}
this.pathTransformBufferTexture.upload(this.gl, transforms);
@ -360,7 +357,7 @@ class TextDemoView extends MonochromePathfinderView {
// Set texture coordinates.
const atlasGlyph = atlasGlyphs[atlasGlyphIndex];
const atlasGlyphRect = atlasGlyph.getRect(this.appController.pixelsPerUnit);
const atlasGlyphRect = atlasGlyph.pixelRect(this.appController.pixelsPerUnit);
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);
@ -523,18 +520,18 @@ class Atlas {
for (const glyph of glyphs) {
// Place the glyph, and advance the origin.
glyph.setPixelPosition(nextOrigin, pixelsPerUnit);
nextOrigin[0] = glyph.getRect(pixelsPerUnit)[2] + 1.0;
glyph.setPixelLowerLeft(nextOrigin, pixelsPerUnit);
nextOrigin[0] = glyph.pixelRect(pixelsPerUnit)[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.fromValues(1.0, shelfBottom + 1.0);
glyph.setPixelPosition(nextOrigin, pixelsPerUnit);
nextOrigin[0] = glyph.getRect(pixelsPerUnit)[2] + 1.0;
glyph.setPixelLowerLeft(nextOrigin, pixelsPerUnit);
nextOrigin[0] = glyph.pixelRect(pixelsPerUnit)[2] + 1.0;
}
// Grow the shelf as necessary.
shelfBottom = Math.max(shelfBottom, glyph.getRect(pixelsPerUnit)[3] + 1.0);
shelfBottom = Math.max(shelfBottom, glyph.pixelRect(pixelsPerUnit)[3] + 1.0);
}
// FIXME(pcwalton): Could be more precise if we don't have a full row.
@ -574,45 +571,12 @@ class AtlasGlyph extends PathfinderGlyph {
constructor(glyph: opentype.Glyph) {
super(glyph);
}
getRect(pixelsPerUnit: number): glmatrix.vec4 {
const glyphSize = glmatrix.vec2.fromValues(this.metrics.xMax - this.metrics.xMin,
this.metrics.yMax - this.metrics.yMin);
glmatrix.vec2.scale(glyphSize, glyphSize, pixelsPerUnit);
glmatrix.vec2.ceil(glyphSize, glyphSize);
const glyphBL = glmatrix.vec2.create(), glyphTR = glmatrix.vec2.create();
glmatrix.vec2.scale(glyphBL, this.position, pixelsPerUnit);
glmatrix.vec2.add(glyphTR, glyphBL, glyphSize);
return glmatrix.vec4.fromValues(glyphBL[0], glyphBL[1], glyphTR[0], glyphTR[1]);
}
}
class GlyphInstance extends PathfinderGlyph {
constructor(glyph: opentype.Glyph) {
super(glyph);
}
getRect(pixelsPerUnit: number): glmatrix.vec4 {
// Determine the atlas size.
const atlasSize = glmatrix.vec2.fromValues(this.metrics.xMax - this.metrics.xMin,
this.metrics.yMax - this.metrics.yMin);
glmatrix.vec2.scale(atlasSize, atlasSize, pixelsPerUnit);
glmatrix.vec2.ceil(atlasSize, atlasSize);
// Set positions.
const textGlyphBL = glmatrix.vec2.create(), textGlyphTR = glmatrix.vec2.create();
const offset = glmatrix.vec2.fromValues(this.metrics.leftSideBearing,
this.metrics.yMin);
glmatrix.vec2.add(textGlyphBL, this.position, offset);
glmatrix.vec2.scale(textGlyphBL, textGlyphBL, pixelsPerUnit);
glmatrix.vec2.round(textGlyphBL, textGlyphBL);
glmatrix.vec2.add(textGlyphTR, textGlyphBL, atlasSize);
return glmatrix.vec4.fromValues(textGlyphBL[0], textGlyphBL[1],
textGlyphTR[0], textGlyphTR[1]);
}
}
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {

View File

@ -23,6 +23,13 @@ const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font";
type CreateGlyphFn<Glyph> = (glyph: opentype.Glyph) => Glyph;
export interface PixelMetrics {
left: number;
right: number;
ascent: number;
descent: number;
}
opentype.Font.prototype.isSupported = function() {
return (this as any).supported;
}
@ -110,7 +117,7 @@ export class TextLayout<Glyph extends PathfinderGlyph> {
for (const line of this.lineGlyphs) {
for (let lineCharIndex = 0; lineCharIndex < line.length; lineCharIndex++) {
const textGlyph = this.glyphStorage.textGlyphs[glyphIndex];
textGlyph.position = glmatrix.vec2.clone(currentPosition);
textGlyph.origin = glmatrix.vec2.clone(currentPosition);
currentPosition[0] += textGlyph.advanceWidth;
glyphIndex++;
}
@ -128,7 +135,7 @@ export abstract class PathfinderGlyph {
constructor(glyph: opentype.Glyph) {
this.opentypeGlyph = glyph;
this._metrics = null;
this.position = glmatrix.vec2.create();
this.origin = glmatrix.vec2.create();
}
get index(): number {
@ -145,14 +152,50 @@ export abstract class PathfinderGlyph {
return this.opentypeGlyph.advanceWidth;
}
setPixelPosition(pixelPosition: glmatrix.vec2, pixelsPerUnit: number): void {
glmatrix.vec2.scale(this.position, pixelPosition, 1.0 / pixelsPerUnit);
pixelOrigin(pixelsPerUnit: number): glmatrix.vec2 {
const origin = glmatrix.vec2.create();
glmatrix.vec2.scale(origin, this.origin, pixelsPerUnit);
return origin;
}
setPixelOrigin(pixelOrigin: glmatrix.vec2, pixelsPerUnit: number): void {
glmatrix.vec2.scale(this.origin, pixelOrigin, 1.0 / pixelsPerUnit);
}
setPixelLowerLeft(pixelLowerLeft: glmatrix.vec2, pixelsPerUnit: number): void {
const pixelMetrics = this.pixelMetrics(pixelsPerUnit);
const pixelOrigin = glmatrix.vec2.fromValues(pixelLowerLeft[0],
pixelLowerLeft[1] + pixelMetrics.descent);
this.setPixelOrigin(pixelOrigin, pixelsPerUnit);
}
protected pixelMetrics(pixelsPerUnit: number): PixelMetrics {
const metrics = this.metrics;
return {
left: Math.floor(metrics.xMin * pixelsPerUnit),
right: Math.ceil(metrics.xMax * pixelsPerUnit),
ascent: Math.ceil(metrics.yMax * pixelsPerUnit),
descent: Math.ceil(-metrics.yMin * pixelsPerUnit),
};
}
pixelRect(pixelsPerUnit: number): glmatrix.vec4 {
const pixelMetrics = this.pixelMetrics(pixelsPerUnit);
const textGlyphOrigin = glmatrix.vec2.clone(this.origin);
glmatrix.vec2.scale(textGlyphOrigin, textGlyphOrigin, pixelsPerUnit);
glmatrix.vec2.round(textGlyphOrigin, textGlyphOrigin);
return glmatrix.vec4.fromValues(textGlyphOrigin[0],
textGlyphOrigin[1] - pixelMetrics.descent,
textGlyphOrigin[0] + pixelMetrics.right,
textGlyphOrigin[1] + pixelMetrics.ascent);
}
readonly opentypeGlyph: opentype.Glyph;
private _metrics: Metrics | null;
/// In font units.
position: glmatrix.vec2;
/// In font units, relative to (0, 0).
origin: glmatrix.vec2;
}