Snap stems up to the nearest pixel when hinting

This commit is contained in:
Patrick Walton 2017-09-29 18:02:33 -07:00
parent 190c9fb35f
commit 99f6f2a104
4 changed files with 48 additions and 16 deletions

View File

@ -321,6 +321,8 @@ class TextDemoView extends MonochromePathfinderView {
}
updateHinting(): void {
// Need to relayout the text because the pixel bounds of the glyphs can change from this...
this.layoutText();
this.buildAtlasGlyphs();
this.setDirty();
}
@ -371,8 +373,6 @@ class TextDemoView extends MonochromePathfinderView {
protected onZoom() {
this.appController.fontSize = this.camera.scale *
this.appController.font.opentypeFont.unitsPerEm;
this.buildAtlasGlyphs();
this.setDirty();
}
protected compositeIfNecessary() {
@ -548,6 +548,8 @@ class TextDemoView extends MonochromePathfinderView {
for (let pathID = 0; pathID < pathCount; pathID++) {
pathHints[pathID * 4 + 0] = hint.xHeight;
pathHints[pathID * 4 + 1] = hint.hintedXHeight;
pathHints[pathID * 4 + 2] = hint.stemHeight;
pathHints[pathID * 4 + 3] = hint.hintedStemHeight;
}
const pathHintsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathHints');

View File

@ -15,7 +15,7 @@ import * as opentype from "opentype.js";
import {Metrics} from 'opentype.js';
import {B_QUAD_SIZE, PathfinderMeshData} from "./meshes";
import {assert, panic, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils";
import {assert, lerp, panic, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils";
export const BUILTIN_FONT_URI: string = "/otf/demo";
@ -262,6 +262,8 @@ export class SimpleTextLayout {
export class Hint {
readonly xHeight: number;
readonly hintedXHeight: number;
readonly stemHeight: number;
readonly hintedStemHeight: number;
private useHinting: boolean;
@ -270,29 +272,41 @@ export class Hint {
const os2Table = font.opentypeFont.tables.os2;
this.xHeight = os2Table.sxHeight != null ? os2Table.sxHeight : 0;
this.stemHeight = os2Table.sCapHeight != null ? os2Table.sCapHeight : 0;
if (!useHinting) {
this.hintedXHeight = this.xHeight;
this.hintedStemHeight = this.stemHeight;
} else {
this.hintedXHeight = Math.ceil(Math.ceil(this.xHeight * pixelsPerUnit) /
pixelsPerUnit);
this.hintedStemHeight = Math.ceil(Math.ceil(this.stemHeight * pixelsPerUnit) /
pixelsPerUnit);
}
}
/// NB: This must match `hintPosition()` in `common.inc.glsl`.
hintPosition(position: glmatrix.vec2): glmatrix.vec2 {
if (!this.useHinting)
return position;
if (position[1] < 0.0)
return position;
if (position[1] >= this.hintedXHeight) {
return glmatrix.vec2.fromValues(position[0],
position[1] - this.xHeight + this.hintedXHeight);
if (position[1] >= this.stemHeight) {
const y = position[1] - this.stemHeight + this.hintedStemHeight;
return glmatrix.vec2.clone([position[0], y]);
}
return glmatrix.vec2.fromValues(position[0],
position[1] / this.xHeight * this.hintedXHeight);
if (position[1] >= this.xHeight) {
const y = lerp(this.hintedXHeight, this.hintedStemHeight,
(position[1] - this.xHeight) / (this.stemHeight - this.xHeight));
return glmatrix.vec2.clone([position[0], y]);
}
if (position[1] >= 0.0) {
const y = lerp(0.0, this.hintedXHeight, position[1] / this.xHeight);
return glmatrix.vec2.clone([position[0], y]);
}
return position;
}
}

View File

@ -54,6 +54,10 @@ export function scaleRect(rect: glmatrix.vec4, scale: number): glmatrix.vec4 {
return glmatrix.vec4.clone([upperLeft[0], upperLeft[1], lowerRight[0], lowerRight[1]]);
}
export function lerp(a: number, b: number, t: number): number {
return a + (b - a) * t;
}
export class PathfinderError extends Error {
constructor(message?: string | undefined) {
super(message);

View File

@ -40,12 +40,24 @@ vec2 transformVertexPositionST(vec2 position, vec4 stTransform) {
return position * stTransform.xy + stTransform.zw;
}
/// pathHints.x: xHeight
/// pathHints.y: hintedXHeight
/// pathHints.z: stemHeight
/// pathHints.w: hintedStemHeight
vec2 hintPosition(vec2 position, vec4 pathHints) {
if (position.y <= 0.0)
return position;
if (position.y >= pathHints.x)
return vec2(position.x, position.y - pathHints.x + pathHints.y);
return vec2(position.x, position.y / pathHints.x * pathHints.y);
float y;
if (position.y >= pathHints.z) {
y = position.y - pathHints.z + pathHints.w;
} else if (position.y >= pathHints.x) {
float t = (position.y - pathHints.x) / (pathHints.z - pathHints.x);
y = mix(pathHints.y, pathHints.w, t);
} else if (position.y >= 0.0) {
y = mix(0.0, pathHints.y, position.y / pathHints.x);
} else {
y = position.y;
}
return vec2(position.x, y);
}
vec2 convertClipToScreenSpace(vec2 position, ivec2 framebufferSize) {