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 { updateHinting(): void {
// Need to relayout the text because the pixel bounds of the glyphs can change from this...
this.layoutText();
this.buildAtlasGlyphs(); this.buildAtlasGlyphs();
this.setDirty(); this.setDirty();
} }
@ -371,8 +373,6 @@ class TextDemoView extends MonochromePathfinderView {
protected onZoom() { protected onZoom() {
this.appController.fontSize = this.camera.scale * this.appController.fontSize = this.camera.scale *
this.appController.font.opentypeFont.unitsPerEm; this.appController.font.opentypeFont.unitsPerEm;
this.buildAtlasGlyphs();
this.setDirty();
} }
protected compositeIfNecessary() { protected compositeIfNecessary() {
@ -548,6 +548,8 @@ class TextDemoView extends MonochromePathfinderView {
for (let pathID = 0; pathID < pathCount; pathID++) { for (let pathID = 0; pathID < pathCount; pathID++) {
pathHints[pathID * 4 + 0] = hint.xHeight; pathHints[pathID * 4 + 0] = hint.xHeight;
pathHints[pathID * 4 + 1] = hint.hintedXHeight; 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'); 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 {Metrics} from 'opentype.js';
import {B_QUAD_SIZE, PathfinderMeshData} from "./meshes"; 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"; export const BUILTIN_FONT_URI: string = "/otf/demo";
@ -262,6 +262,8 @@ export class SimpleTextLayout {
export class Hint { export class Hint {
readonly xHeight: number; readonly xHeight: number;
readonly hintedXHeight: number; readonly hintedXHeight: number;
readonly stemHeight: number;
readonly hintedStemHeight: number;
private useHinting: boolean; private useHinting: boolean;
@ -270,29 +272,41 @@ export class Hint {
const os2Table = font.opentypeFont.tables.os2; const os2Table = font.opentypeFont.tables.os2;
this.xHeight = os2Table.sxHeight != null ? os2Table.sxHeight : 0; this.xHeight = os2Table.sxHeight != null ? os2Table.sxHeight : 0;
this.stemHeight = os2Table.sCapHeight != null ? os2Table.sCapHeight : 0;
if (!useHinting) { if (!useHinting) {
this.hintedXHeight = this.xHeight; this.hintedXHeight = this.xHeight;
this.hintedStemHeight = this.stemHeight;
} else { } else {
this.hintedXHeight = Math.ceil(Math.ceil(this.xHeight * pixelsPerUnit) / this.hintedXHeight = Math.ceil(Math.ceil(this.xHeight * pixelsPerUnit) /
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 { hintPosition(position: glmatrix.vec2): glmatrix.vec2 {
if (!this.useHinting) if (!this.useHinting)
return position; return position;
if (position[1] < 0.0) if (position[1] >= this.stemHeight) {
return position; const y = position[1] - this.stemHeight + this.hintedStemHeight;
return glmatrix.vec2.clone([position[0], y]);
if (position[1] >= this.hintedXHeight) {
return glmatrix.vec2.fromValues(position[0],
position[1] - this.xHeight + this.hintedXHeight);
} }
return glmatrix.vec2.fromValues(position[0], if (position[1] >= this.xHeight) {
position[1] / this.xHeight * this.hintedXHeight); 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]]); 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 { export class PathfinderError extends Error {
constructor(message?: string | undefined) { constructor(message?: string | undefined) {
super(message); super(message);

View File

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