From 4ab917b79bae5f945292e7989bd80deaeb177ba1 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sun, 3 Sep 2017 16:35:10 -0700 Subject: [PATCH] Snap baselines to the nearest pixel --- demo/client/src/3d-demo.ts | 12 +------- demo/client/src/text-demo.ts | 58 +++++++----------------------------- demo/client/src/text.ts | 55 ++++++++++++++++++++++++++++++---- 3 files changed, 61 insertions(+), 64 deletions(-) diff --git a/demo/client/src/3d-demo.ts b/demo/client/src/3d-demo.ts index 777a789e..324f3d8c 100644 --- a/demo/client/src/3d-demo.ts +++ b/demo/client/src/3d-demo.ts @@ -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() { diff --git a/demo/client/src/text-demo.ts b/demo/client/src/text-demo.ts index 92eb4123..6f3fad64 100644 --- a/demo/client/src/text-demo.ts +++ b/demo/client/src/text-demo.ts @@ -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 = { diff --git a/demo/client/src/text.ts b/demo/client/src/text.ts index 32587ec7..70171fd0 100644 --- a/demo/client/src/text.ts +++ b/demo/client/src/text.ts @@ -23,6 +23,13 @@ const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font"; type CreateGlyphFn = (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 { 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; }