Snap baselines to the nearest pixel
This commit is contained in:
parent
24cfb03c66
commit
4ab917b79b
|
@ -104,7 +104,7 @@ class ThreeDView extends PathfinderDemoView {
|
||||||
pathColors[startOffset + 3] = 0xff; // alpha
|
pathColors[startOffset + 3] = 0xff; // alpha
|
||||||
|
|
||||||
const textGlyph = textGlyphs[pathIndex];
|
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);
|
pathTransforms.set([1, 1, glyphRect[0], glyphRect[1]], startOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,16 +164,6 @@ class ThreeDGlyph extends PathfinderGlyph {
|
||||||
constructor(glyph: opentype.Glyph) {
|
constructor(glyph: opentype.Glyph) {
|
||||||
super(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() {
|
function main() {
|
||||||
|
|
|
@ -260,7 +260,7 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
|
|
||||||
for (let glyphIndex = 0; glyphIndex < textGlyphs.length; glyphIndex++) {
|
for (let glyphIndex = 0; glyphIndex < textGlyphs.length; glyphIndex++) {
|
||||||
const textGlyph = textGlyphs[glyphIndex];
|
const textGlyph = textGlyphs[glyphIndex];
|
||||||
const rect = textGlyph.getRect(this.appController.pixelsPerUnit);
|
const rect = textGlyph.pixelRect(this.appController.pixelsPerUnit);
|
||||||
glyphPositions.set([
|
glyphPositions.set([
|
||||||
rect[0], rect[3],
|
rect[0], rect[3],
|
||||||
rect[2], rect[3],
|
rect[2], rect[3],
|
||||||
|
@ -291,7 +291,7 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
-translation[1] + this.canvas.height);
|
-translation[1] + this.canvas.height);
|
||||||
|
|
||||||
let atlasGlyphs =
|
let atlasGlyphs =
|
||||||
textGlyphs.filter(glyph => rectsIntersect(glyph.getRect(pixelsPerUnit), canvasRect))
|
textGlyphs.filter(glyph => rectsIntersect(glyph.pixelRect(pixelsPerUnit), canvasRect))
|
||||||
.map(textGlyph => new AtlasGlyph(textGlyph.opentypeGlyph));
|
.map(textGlyph => new AtlasGlyph(textGlyph.opentypeGlyph));
|
||||||
atlasGlyphs.sort((a, b) => a.index - b.index);
|
atlasGlyphs.sort((a, b) => a.index - b.index);
|
||||||
atlasGlyphs = _.sortedUniqBy(atlasGlyphs, glyph => glyph.index);
|
atlasGlyphs = _.sortedUniqBy(atlasGlyphs, glyph => glyph.index);
|
||||||
|
@ -313,15 +313,12 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
assert(pathID >= 0, "No path ID!");
|
assert(pathID >= 0, "No path ID!");
|
||||||
pathID++;
|
pathID++;
|
||||||
|
|
||||||
const atlasLocation = glyph.getRect(pixelsPerUnit);
|
const atlasOrigin = glyph.pixelOrigin(pixelsPerUnit);
|
||||||
const metrics = glyph.metrics;
|
const metrics = glyph.metrics;
|
||||||
const left = metrics.xMin * pixelsPerUnit;
|
|
||||||
const bottom = metrics.yMin * pixelsPerUnit;
|
|
||||||
|
|
||||||
transforms[pathID * 4 + 0] = pixelsPerUnit;
|
transforms[pathID * 4 + 0] = pixelsPerUnit;
|
||||||
transforms[pathID * 4 + 1] = pixelsPerUnit;
|
transforms[pathID * 4 + 1] = pixelsPerUnit;
|
||||||
transforms[pathID * 4 + 2] = atlasLocation[0] - left;
|
transforms[pathID * 4 + 2] = atlasOrigin[0];
|
||||||
transforms[pathID * 4 + 3] = atlasLocation[1] - bottom;
|
transforms[pathID * 4 + 3] = atlasOrigin[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pathTransformBufferTexture.upload(this.gl, transforms);
|
this.pathTransformBufferTexture.upload(this.gl, transforms);
|
||||||
|
@ -360,7 +357,7 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
|
|
||||||
// Set texture coordinates.
|
// Set texture coordinates.
|
||||||
const atlasGlyph = atlasGlyphs[atlasGlyphIndex];
|
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 atlasGlyphBL = atlasGlyphRect.slice(0, 2) as glmatrix.vec2;
|
||||||
const atlasGlyphTR = atlasGlyphRect.slice(2, 4) as glmatrix.vec2;
|
const atlasGlyphTR = atlasGlyphRect.slice(2, 4) as glmatrix.vec2;
|
||||||
glmatrix.vec2.div(atlasGlyphBL, atlasGlyphBL, ATLAS_SIZE);
|
glmatrix.vec2.div(atlasGlyphBL, atlasGlyphBL, ATLAS_SIZE);
|
||||||
|
@ -523,18 +520,18 @@ class Atlas {
|
||||||
|
|
||||||
for (const glyph of glyphs) {
|
for (const glyph of glyphs) {
|
||||||
// Place the glyph, and advance the origin.
|
// Place the glyph, and advance the origin.
|
||||||
glyph.setPixelPosition(nextOrigin, pixelsPerUnit);
|
glyph.setPixelLowerLeft(nextOrigin, pixelsPerUnit);
|
||||||
nextOrigin[0] = glyph.getRect(pixelsPerUnit)[2] + 1.0;
|
nextOrigin[0] = glyph.pixelRect(pixelsPerUnit)[2] + 1.0;
|
||||||
|
|
||||||
// If the glyph overflowed the shelf, make a new one and reposition the glyph.
|
// If the glyph overflowed the shelf, make a new one and reposition the glyph.
|
||||||
if (nextOrigin[0] > ATLAS_SIZE[0]) {
|
if (nextOrigin[0] > ATLAS_SIZE[0]) {
|
||||||
nextOrigin = glmatrix.vec2.fromValues(1.0, shelfBottom + 1.0);
|
nextOrigin = glmatrix.vec2.fromValues(1.0, shelfBottom + 1.0);
|
||||||
glyph.setPixelPosition(nextOrigin, pixelsPerUnit);
|
glyph.setPixelLowerLeft(nextOrigin, pixelsPerUnit);
|
||||||
nextOrigin[0] = glyph.getRect(pixelsPerUnit)[2] + 1.0;
|
nextOrigin[0] = glyph.pixelRect(pixelsPerUnit)[2] + 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grow the shelf as necessary.
|
// 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.
|
// 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) {
|
constructor(glyph: opentype.Glyph) {
|
||||||
super(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 {
|
class GlyphInstance extends PathfinderGlyph {
|
||||||
constructor(glyph: opentype.Glyph) {
|
constructor(glyph: opentype.Glyph) {
|
||||||
super(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 = {
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
||||||
|
|
|
@ -23,6 +23,13 @@ const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font";
|
||||||
|
|
||||||
type CreateGlyphFn<Glyph> = (glyph: opentype.Glyph) => Glyph;
|
type CreateGlyphFn<Glyph> = (glyph: opentype.Glyph) => Glyph;
|
||||||
|
|
||||||
|
export interface PixelMetrics {
|
||||||
|
left: number;
|
||||||
|
right: number;
|
||||||
|
ascent: number;
|
||||||
|
descent: number;
|
||||||
|
}
|
||||||
|
|
||||||
opentype.Font.prototype.isSupported = function() {
|
opentype.Font.prototype.isSupported = function() {
|
||||||
return (this as any).supported;
|
return (this as any).supported;
|
||||||
}
|
}
|
||||||
|
@ -110,7 +117,7 @@ export class TextLayout<Glyph extends PathfinderGlyph> {
|
||||||
for (const line of this.lineGlyphs) {
|
for (const line of this.lineGlyphs) {
|
||||||
for (let lineCharIndex = 0; lineCharIndex < line.length; lineCharIndex++) {
|
for (let lineCharIndex = 0; lineCharIndex < line.length; lineCharIndex++) {
|
||||||
const textGlyph = this.glyphStorage.textGlyphs[glyphIndex];
|
const textGlyph = this.glyphStorage.textGlyphs[glyphIndex];
|
||||||
textGlyph.position = glmatrix.vec2.clone(currentPosition);
|
textGlyph.origin = glmatrix.vec2.clone(currentPosition);
|
||||||
currentPosition[0] += textGlyph.advanceWidth;
|
currentPosition[0] += textGlyph.advanceWidth;
|
||||||
glyphIndex++;
|
glyphIndex++;
|
||||||
}
|
}
|
||||||
|
@ -128,7 +135,7 @@ export abstract class PathfinderGlyph {
|
||||||
constructor(glyph: opentype.Glyph) {
|
constructor(glyph: opentype.Glyph) {
|
||||||
this.opentypeGlyph = glyph;
|
this.opentypeGlyph = glyph;
|
||||||
this._metrics = null;
|
this._metrics = null;
|
||||||
this.position = glmatrix.vec2.create();
|
this.origin = glmatrix.vec2.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
get index(): number {
|
get index(): number {
|
||||||
|
@ -145,14 +152,50 @@ export abstract class PathfinderGlyph {
|
||||||
return this.opentypeGlyph.advanceWidth;
|
return this.opentypeGlyph.advanceWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPixelPosition(pixelPosition: glmatrix.vec2, pixelsPerUnit: number): void {
|
pixelOrigin(pixelsPerUnit: number): glmatrix.vec2 {
|
||||||
glmatrix.vec2.scale(this.position, pixelPosition, 1.0 / pixelsPerUnit);
|
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;
|
readonly opentypeGlyph: opentype.Glyph;
|
||||||
|
|
||||||
private _metrics: Metrics | null;
|
private _metrics: Metrics | null;
|
||||||
|
|
||||||
/// In font units.
|
/// In font units, relative to (0, 0).
|
||||||
position: glmatrix.vec2;
|
origin: glmatrix.vec2;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue