Implement "slight" hinting by rounding x-heights up to the nearest pixel
This commit is contained in:
parent
43513da957
commit
eb9c0ceb4d
|
@ -50,6 +50,14 @@
|
||||||
Subpixel AA
|
Subpixel AA
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="pf-hinting-select">Hinting</label>
|
||||||
|
<select id="pf-hinting-select"
|
||||||
|
class="form-control custom-select">
|
||||||
|
<option value="none">None</option>
|
||||||
|
<option value="slight">Slight</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-arrow-box"></div>
|
<div class="pf-arrow-box"></div>
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {mat4, vec2} from "gl-matrix";
|
||||||
import {PathfinderMeshData} from "./meshes";
|
import {PathfinderMeshData} from "./meshes";
|
||||||
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
||||||
import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStorage, PathfinderGlyph} from "./text";
|
import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStorage, PathfinderGlyph} from "./text";
|
||||||
import {SimpleTextLayout, TextFrame, TextRun} from "./text";
|
import {Hint, SimpleTextLayout, TextFrame, TextRun} from "./text";
|
||||||
import {PathfinderError, assert, panic, unwrapNull} from "./utils";
|
import {PathfinderError, assert, panic, unwrapNull} from "./utils";
|
||||||
import {PathfinderDemoView, Timings} from "./view";
|
import {PathfinderDemoView, Timings} from "./view";
|
||||||
import SSAAStrategy from "./ssaa-strategy";
|
import SSAAStrategy from "./ssaa-strategy";
|
||||||
|
@ -225,6 +225,8 @@ class ThreeDView extends PathfinderDemoView {
|
||||||
const textFrame = this.appController.glyphStorage.textFrames[textFrameIndex];
|
const textFrame = this.appController.glyphStorage.textFrames[textFrameIndex];
|
||||||
const textGlyphs = textFrame.allGlyphs;
|
const textGlyphs = textFrame.allGlyphs;
|
||||||
const pathCount = textGlyphs.length;
|
const pathCount = textGlyphs.length;
|
||||||
|
|
||||||
|
const hint = new Hint(this.appController.glyphStorage.font, PIXELS_PER_UNIT, false);
|
||||||
|
|
||||||
const pathColors = new Uint8Array(4 * (pathCount + 1));
|
const pathColors = new Uint8Array(4 * (pathCount + 1));
|
||||||
const pathTransforms = new Float32Array(4 * (pathCount + 1));
|
const pathTransforms = new Float32Array(4 * (pathCount + 1));
|
||||||
|
@ -235,7 +237,7 @@ class ThreeDView extends PathfinderDemoView {
|
||||||
pathColors.set(TEXT_COLOR, startOffset);
|
pathColors.set(TEXT_COLOR, startOffset);
|
||||||
|
|
||||||
const textGlyph = textGlyphs[pathIndex];
|
const textGlyph = textGlyphs[pathIndex];
|
||||||
const glyphRect = textGlyph.pixelRect(PIXELS_PER_UNIT);
|
const glyphRect = textGlyph.pixelRect(hint, PIXELS_PER_UNIT);
|
||||||
pathTransforms.set([1, 1, glyphRect[0], glyphRect[1]], startOffset);
|
pathTransforms.set([1, 1, glyphRect[0], glyphRect[1]], startOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -305,6 +305,8 @@ export abstract class ECAAStrategy extends AntialiasingStrategy {
|
||||||
this.bVertexPositionBufferTexture.bind(view.gl, uniforms, 0);
|
this.bVertexPositionBufferTexture.bind(view.gl, uniforms, 0);
|
||||||
this.bVertexPathIDBufferTexture.bind(view.gl, uniforms, 1);
|
this.bVertexPathIDBufferTexture.bind(view.gl, uniforms, 1);
|
||||||
view.pathTransformBufferTextures[0].bind(view.gl, uniforms, 2);
|
view.pathTransformBufferTextures[0].bind(view.gl, uniforms, 2);
|
||||||
|
if (view.pathHintsBufferTexture !== null)
|
||||||
|
view.pathHintsBufferTexture.bind(view.gl, uniforms, 3);
|
||||||
view.gl.uniform1f(uniforms.uScaleX, this.supersampleScale[0]);
|
view.gl.uniform1f(uniforms.uScaleX, this.supersampleScale[0]);
|
||||||
view.instancedArraysExt.drawElementsInstancedANGLE(view.gl.TRIANGLES,
|
view.instancedArraysExt.drawElementsInstancedANGLE(view.gl.TRIANGLES,
|
||||||
6,
|
6,
|
||||||
|
@ -336,6 +338,8 @@ export abstract class ECAAStrategy extends AntialiasingStrategy {
|
||||||
this.bVertexPositionBufferTexture.bind(view.gl, uniforms, 0);
|
this.bVertexPositionBufferTexture.bind(view.gl, uniforms, 0);
|
||||||
this.bVertexPathIDBufferTexture.bind(view.gl, uniforms, 1);
|
this.bVertexPathIDBufferTexture.bind(view.gl, uniforms, 1);
|
||||||
view.pathTransformBufferTextures[0].bind(view.gl, uniforms, 2);
|
view.pathTransformBufferTextures[0].bind(view.gl, uniforms, 2);
|
||||||
|
if (view.pathHintsBufferTexture !== null)
|
||||||
|
view.pathHintsBufferTexture.bind(view.gl, uniforms, 3);
|
||||||
view.gl.uniform1f(uniforms.uScaleX, this.supersampleScale[0]);
|
view.gl.uniform1f(uniforms.uScaleX, this.supersampleScale[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from
|
||||||
import {UniformMap} from './gl-utils';
|
import {UniformMap} from './gl-utils';
|
||||||
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
||||||
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
|
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
|
||||||
import {BUILTIN_FONT_URI, PathfinderGlyph, SimpleTextLayout} from "./text";
|
import {BUILTIN_FONT_URI, Hint, PathfinderGlyph, SimpleTextLayout} from "./text";
|
||||||
import {PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic} from './utils';
|
import {PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic} from './utils';
|
||||||
import {MonochromePathfinderView, Timings} from './view';
|
import {MonochromePathfinderView, Timings} from './view';
|
||||||
import PathfinderBufferTexture from './buffer-texture';
|
import PathfinderBufferTexture from './buffer-texture';
|
||||||
|
@ -127,7 +127,12 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
|
|
||||||
this._fontSize = INITIAL_FONT_SIZE;
|
this._fontSize = INITIAL_FONT_SIZE;
|
||||||
|
|
||||||
|
this.hintingSelect = unwrapNull(document.getElementById('pf-hinting-select')) as
|
||||||
|
HTMLSelectElement;
|
||||||
|
this.hintingSelect.addEventListener('change', () => this.hintingChanged(), false);
|
||||||
|
|
||||||
this.fpsLabel = unwrapNull(document.getElementById('pf-fps-label'));
|
this.fpsLabel = unwrapNull(document.getElementById('pf-fps-label'));
|
||||||
|
|
||||||
this.editTextModal = unwrapNull(document.getElementById('pf-edit-text-modal'));
|
this.editTextModal = unwrapNull(document.getElementById('pf-edit-text-modal'));
|
||||||
this.editTextArea = unwrapNull(document.getElementById('pf-edit-text-area')) as
|
this.editTextArea = unwrapNull(document.getElementById('pf-edit-text-area')) as
|
||||||
HTMLTextAreaElement;
|
HTMLTextAreaElement;
|
||||||
|
@ -144,7 +149,11 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
window.jQuery(this.editTextModal).modal();
|
window.jQuery(this.editTextModal).modal();
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateText() {
|
private hintingChanged(): void {
|
||||||
|
this.view.then(view => view.updateHinting());
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateText(): void {
|
||||||
this.text = this.editTextArea.value;
|
this.text = this.editTextArea.value;
|
||||||
this.recreateLayout();
|
this.recreateLayout();
|
||||||
|
|
||||||
|
@ -162,7 +171,9 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private recreateLayout() {
|
private recreateLayout() {
|
||||||
this.layout = new SimpleTextLayout(this.fileData, this.text, glyph => new GlyphInstance(glyph));
|
this.layout = new SimpleTextLayout(this.fileData,
|
||||||
|
this.text,
|
||||||
|
glyph => new GlyphInstance(glyph));
|
||||||
this.layout.glyphStorage.partition().then((meshes: PathfinderMeshData) => {
|
this.layout.glyphStorage.partition().then((meshes: PathfinderMeshData) => {
|
||||||
this.meshes = meshes;
|
this.meshes = meshes;
|
||||||
this.view.then(view => {
|
this.view.then(view => {
|
||||||
|
@ -197,6 +208,14 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
return this._fontSize / this.layout.glyphStorage.font.unitsPerEm;
|
return this._fontSize / this.layout.glyphStorage.font.unitsPerEm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get useHinting(): boolean {
|
||||||
|
return this.hintingSelect.selectedIndex !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
createHint(): Hint {
|
||||||
|
return new Hint(this.layout.glyphStorage.font, this.pixelsPerUnit, this.useHinting);
|
||||||
|
}
|
||||||
|
|
||||||
protected get builtinFileURI(): string {
|
protected get builtinFileURI(): string {
|
||||||
return BUILTIN_FONT_URI;
|
return BUILTIN_FONT_URI;
|
||||||
}
|
}
|
||||||
|
@ -206,6 +225,9 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fpsLabel: HTMLElement;
|
private fpsLabel: HTMLElement;
|
||||||
|
|
||||||
|
private hintingSelect: HTMLSelectElement;
|
||||||
|
|
||||||
private editTextModal: HTMLElement;
|
private editTextModal: HTMLElement;
|
||||||
private editTextArea: HTMLTextAreaElement;
|
private editTextArea: HTMLTextAreaElement;
|
||||||
|
|
||||||
|
@ -263,7 +285,8 @@ 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.pixelRect(this.appController.pixelsPerUnit);
|
const rect = textGlyph.pixelRect(this.appController.createHint(),
|
||||||
|
this.appController.pixelsPerUnit);
|
||||||
glyphPositions.set([
|
glyphPositions.set([
|
||||||
rect[0], rect[3],
|
rect[0], rect[3],
|
||||||
rect[2], rect[3],
|
rect[2], rect[3],
|
||||||
|
@ -286,6 +309,8 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
const textGlyphs = this.appController.layout.glyphStorage.allGlyphs;
|
const textGlyphs = this.appController.layout.glyphStorage.allGlyphs;
|
||||||
const pixelsPerUnit = this.appController.pixelsPerUnit;
|
const pixelsPerUnit = this.appController.pixelsPerUnit;
|
||||||
|
|
||||||
|
const hint = this.appController.createHint();
|
||||||
|
|
||||||
// Only build glyphs in view.
|
// Only build glyphs in view.
|
||||||
const translation = this.camera.translation;
|
const translation = this.camera.translation;
|
||||||
const canvasRect = glmatrix.vec4.fromValues(-translation[0],
|
const canvasRect = glmatrix.vec4.fromValues(-translation[0],
|
||||||
|
@ -294,13 +319,14 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
-translation[1] + this.canvas.height);
|
-translation[1] + this.canvas.height);
|
||||||
|
|
||||||
let atlasGlyphs =
|
let atlasGlyphs =
|
||||||
textGlyphs.filter(glyph => rectsIntersect(glyph.pixelRect(pixelsPerUnit), canvasRect))
|
textGlyphs.filter(glyph => {
|
||||||
.map(textGlyph => new AtlasGlyph(textGlyph.opentypeGlyph));
|
return rectsIntersect(glyph.pixelRect(hint, pixelsPerUnit), canvasRect);
|
||||||
|
}).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);
|
||||||
this.appController.atlasGlyphs = atlasGlyphs;
|
this.appController.atlasGlyphs = atlasGlyphs;
|
||||||
|
|
||||||
this.appController.atlas.layoutGlyphs(atlasGlyphs, pixelsPerUnit);
|
this.appController.atlas.layoutGlyphs(atlasGlyphs, hint, pixelsPerUnit);
|
||||||
|
|
||||||
const uniqueGlyphs = this.appController.layout.glyphStorage.uniqueGlyphs;
|
const uniqueGlyphs = this.appController.layout.glyphStorage.uniqueGlyphs;
|
||||||
const uniqueGlyphIndices = uniqueGlyphs.map(glyph => glyph.index);
|
const uniqueGlyphIndices = uniqueGlyphs.map(glyph => glyph.index);
|
||||||
|
@ -308,6 +334,7 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
|
|
||||||
// TODO(pcwalton): Regenerate the IBOs to include only the glyphs we care about.
|
// TODO(pcwalton): Regenerate the IBOs to include only the glyphs we care about.
|
||||||
const transforms = new Float32Array((uniqueGlyphs.length + 1) * 4);
|
const transforms = new Float32Array((uniqueGlyphs.length + 1) * 4);
|
||||||
|
const pathHints = new Float32Array((uniqueGlyphs.length + 1) * 4);
|
||||||
|
|
||||||
for (let glyphIndex = 0; glyphIndex < atlasGlyphs.length; glyphIndex++) {
|
for (let glyphIndex = 0; glyphIndex < atlasGlyphs.length; glyphIndex++) {
|
||||||
const glyph = atlasGlyphs[glyphIndex];
|
const glyph = atlasGlyphs[glyphIndex];
|
||||||
|
@ -322,11 +349,18 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
transforms[pathID * 4 + 1] = pixelsPerUnit;
|
transforms[pathID * 4 + 1] = pixelsPerUnit;
|
||||||
transforms[pathID * 4 + 2] = atlasOrigin[0];
|
transforms[pathID * 4 + 2] = atlasOrigin[0];
|
||||||
transforms[pathID * 4 + 3] = atlasOrigin[1];
|
transforms[pathID * 4 + 3] = atlasOrigin[1];
|
||||||
|
|
||||||
|
pathHints[pathID * 4 + 0] = hint.xHeight;
|
||||||
|
pathHints[pathID * 4 + 1] = hint.hintedXHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathTransformBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathTransform');
|
const pathTransformBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathTransform');
|
||||||
pathTransformBufferTexture.upload(this.gl, transforms);
|
pathTransformBufferTexture.upload(this.gl, transforms);
|
||||||
this.pathTransformBufferTextures = [pathTransformBufferTexture];
|
this.pathTransformBufferTextures = [pathTransformBufferTexture];
|
||||||
|
|
||||||
|
const pathHintsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathHints');
|
||||||
|
pathHintsBufferTexture.upload(this.gl, pathHints);
|
||||||
|
this.pathHintsBufferTexture = pathHintsBufferTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createAtlasFramebuffer() {
|
private createAtlasFramebuffer() {
|
||||||
|
@ -346,6 +380,8 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
const textGlyphs = this.appController.layout.glyphStorage.allGlyphs;
|
const textGlyphs = this.appController.layout.glyphStorage.allGlyphs;
|
||||||
const atlasGlyphs = this.appController.atlasGlyphs;
|
const atlasGlyphs = this.appController.atlasGlyphs;
|
||||||
|
|
||||||
|
const hint = this.appController.createHint();
|
||||||
|
|
||||||
const atlasGlyphIndices = atlasGlyphs.map(atlasGlyph => atlasGlyph.index);
|
const atlasGlyphIndices = atlasGlyphs.map(atlasGlyph => atlasGlyph.index);
|
||||||
|
|
||||||
const glyphTexCoords = new Float32Array(textGlyphs.length * 8);
|
const glyphTexCoords = new Float32Array(textGlyphs.length * 8);
|
||||||
|
@ -362,7 +398,7 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
|
|
||||||
// Set texture coordinates.
|
// Set texture coordinates.
|
||||||
const atlasGlyph = atlasGlyphs[atlasGlyphIndex];
|
const atlasGlyph = atlasGlyphs[atlasGlyphIndex];
|
||||||
const atlasGlyphRect = atlasGlyph.pixelRect(this.appController.pixelsPerUnit);
|
const atlasGlyphRect = atlasGlyph.pixelRect(hint, 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);
|
||||||
|
@ -406,6 +442,11 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
this.rebuildAtlasIfNecessary();
|
this.rebuildAtlasIfNecessary();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateHinting(): void {
|
||||||
|
this.setDirty();
|
||||||
|
this.rebuildAtlasIfNecessary();
|
||||||
|
}
|
||||||
|
|
||||||
private setIdentityTexScaleUniform(uniforms: UniformMap) {
|
private setIdentityTexScaleUniform(uniforms: UniformMap) {
|
||||||
this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
|
this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
|
||||||
}
|
}
|
||||||
|
@ -531,24 +572,24 @@ class Atlas {
|
||||||
this._usedSize = glmatrix.vec2.create();
|
this._usedSize = glmatrix.vec2.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutGlyphs(glyphs: AtlasGlyph[], pixelsPerUnit: number) {
|
layoutGlyphs(glyphs: AtlasGlyph[], hint: Hint, pixelsPerUnit: number) {
|
||||||
let nextOrigin = glmatrix.vec2.fromValues(1.0, 1.0);
|
let nextOrigin = glmatrix.vec2.fromValues(1.0, 1.0);
|
||||||
let shelfBottom = 2.0;
|
let shelfBottom = 2.0;
|
||||||
|
|
||||||
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.setPixelLowerLeft(nextOrigin, pixelsPerUnit);
|
glyph.setPixelLowerLeft(nextOrigin, pixelsPerUnit);
|
||||||
nextOrigin[0] = glyph.pixelRect(pixelsPerUnit)[2] + 1.0;
|
nextOrigin[0] = glyph.pixelRect(hint, 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.setPixelLowerLeft(nextOrigin, pixelsPerUnit);
|
glyph.setPixelLowerLeft(nextOrigin, pixelsPerUnit);
|
||||||
nextOrigin[0] = glyph.pixelRect(pixelsPerUnit)[2] + 1.0;
|
nextOrigin[0] = glyph.pixelRect(hint, pixelsPerUnit)[2] + 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grow the shelf as necessary.
|
// Grow the shelf as necessary.
|
||||||
shelfBottom = Math.max(shelfBottom, glyph.pixelRect(pixelsPerUnit)[3] + 1.0);
|
shelfBottom = Math.max(shelfBottom, glyph.pixelRect(hint, 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.
|
||||||
|
|
|
@ -301,24 +301,29 @@ export abstract class PathfinderGlyph {
|
||||||
}
|
}
|
||||||
|
|
||||||
setPixelLowerLeft(pixelLowerLeft: glmatrix.vec2, pixelsPerUnit: number): void {
|
setPixelLowerLeft(pixelLowerLeft: glmatrix.vec2, pixelsPerUnit: number): void {
|
||||||
const pixelMetrics = this.pixelMetrics(pixelsPerUnit);
|
const pixelDescent = this.calculatePixelDescent(pixelsPerUnit);
|
||||||
const pixelOrigin = glmatrix.vec2.fromValues(pixelLowerLeft[0],
|
const pixelOrigin = glmatrix.vec2.fromValues(pixelLowerLeft[0],
|
||||||
pixelLowerLeft[1] + pixelMetrics.descent);
|
pixelLowerLeft[1] + pixelDescent);
|
||||||
this.setPixelOrigin(pixelOrigin, pixelsPerUnit);
|
this.setPixelOrigin(pixelOrigin, pixelsPerUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected pixelMetrics(pixelsPerUnit: number): PixelMetrics {
|
private calculatePixelDescent(pixelsPerUnit: number): number {
|
||||||
|
return Math.ceil(-this.metrics.yMin * pixelsPerUnit);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected pixelMetrics(hint: Hint, pixelsPerUnit: number): PixelMetrics {
|
||||||
const metrics = this.metrics;
|
const metrics = this.metrics;
|
||||||
|
const top = hint.hintPosition(glmatrix.vec2.fromValues(0, metrics.yMax))[1];
|
||||||
return {
|
return {
|
||||||
left: Math.floor(metrics.xMin * pixelsPerUnit),
|
left: Math.floor(metrics.xMin * pixelsPerUnit),
|
||||||
right: Math.ceil(metrics.xMax * pixelsPerUnit),
|
right: Math.ceil(metrics.xMax * pixelsPerUnit),
|
||||||
ascent: Math.ceil(metrics.yMax * pixelsPerUnit),
|
ascent: Math.ceil(top * pixelsPerUnit),
|
||||||
descent: Math.ceil(-metrics.yMin * pixelsPerUnit),
|
descent: this.calculatePixelDescent(pixelsPerUnit),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pixelRect(pixelsPerUnit: number): glmatrix.vec4 {
|
pixelRect(hint: Hint, pixelsPerUnit: number): glmatrix.vec4 {
|
||||||
const pixelMetrics = this.pixelMetrics(pixelsPerUnit);
|
const pixelMetrics = this.pixelMetrics(hint, pixelsPerUnit);
|
||||||
const textGlyphOrigin = glmatrix.vec2.clone(this.origin);
|
const textGlyphOrigin = glmatrix.vec2.clone(this.origin);
|
||||||
glmatrix.vec2.scale(textGlyphOrigin, textGlyphOrigin, pixelsPerUnit);
|
glmatrix.vec2.scale(textGlyphOrigin, textGlyphOrigin, pixelsPerUnit);
|
||||||
glmatrix.vec2.round(textGlyphOrigin, textGlyphOrigin);
|
glmatrix.vec2.round(textGlyphOrigin, textGlyphOrigin);
|
||||||
|
@ -338,6 +343,42 @@ export abstract class PathfinderGlyph {
|
||||||
origin: glmatrix.vec2;
|
origin: glmatrix.vec2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Hint {
|
||||||
|
constructor(font: Font, pixelsPerUnit: number, useHinting: boolean) {
|
||||||
|
this.useHinting = useHinting;
|
||||||
|
|
||||||
|
const os2Table = font.tables.os2;
|
||||||
|
this.xHeight = os2Table.sxHeight != null ? os2Table.sxHeight : 0;
|
||||||
|
|
||||||
|
if (!useHinting) {
|
||||||
|
this.hintedXHeight = this.xHeight;
|
||||||
|
} else {
|
||||||
|
this.hintedXHeight = Math.ceil(Math.ceil(this.xHeight * pixelsPerUnit) /
|
||||||
|
pixelsPerUnit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return glmatrix.vec2.fromValues(position[0],
|
||||||
|
position[1] / this.xHeight * this.hintedXHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly xHeight: number;
|
||||||
|
readonly hintedXHeight: number;
|
||||||
|
private useHinting: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
function copyIndices(destIndices: number[],
|
function copyIndices(destIndices: number[],
|
||||||
srcIndices: Uint32Array,
|
srcIndices: Uint32Array,
|
||||||
firstExpandedIndex: number,
|
firstExpandedIndex: number,
|
||||||
|
|
|
@ -328,6 +328,8 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
this.pathTransformBufferTextures[objectIndex].bind(this.gl,
|
this.pathTransformBufferTextures[objectIndex].bind(this.gl,
|
||||||
directInteriorProgram.uniforms,
|
directInteriorProgram.uniforms,
|
||||||
1);
|
1);
|
||||||
|
if (this.pathHintsBufferTexture != null)
|
||||||
|
this.pathHintsBufferTexture.bind(this.gl, directInteriorProgram.uniforms, 2);
|
||||||
let indexCount = this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER,
|
let indexCount = this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER,
|
||||||
this.gl.BUFFER_SIZE) / UINT32_SIZE;
|
this.gl.BUFFER_SIZE) / UINT32_SIZE;
|
||||||
this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0);
|
this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0);
|
||||||
|
@ -383,6 +385,8 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
this.pathTransformBufferTextures[objectIndex].bind(this.gl,
|
this.pathTransformBufferTextures[objectIndex].bind(this.gl,
|
||||||
directCurveProgram.uniforms,
|
directCurveProgram.uniforms,
|
||||||
1);
|
1);
|
||||||
|
if (this.pathHintsBufferTexture != null)
|
||||||
|
this.pathHintsBufferTexture.bind(this.gl, directCurveProgram.uniforms, 2);
|
||||||
indexCount = this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER,
|
indexCount = this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER,
|
||||||
this.gl.BUFFER_SIZE) / UINT32_SIZE;
|
this.gl.BUFFER_SIZE) / UINT32_SIZE;
|
||||||
this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0);
|
this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0);
|
||||||
|
@ -517,6 +521,7 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
meshData: PathfinderMeshData[];
|
meshData: PathfinderMeshData[];
|
||||||
|
|
||||||
pathTransformBufferTextures: PathfinderBufferTexture[];
|
pathTransformBufferTextures: PathfinderBufferTexture[];
|
||||||
|
pathHintsBufferTexture: PathfinderBufferTexture | null;
|
||||||
protected pathColorsBufferTextures: PathfinderBufferTexture[];
|
protected pathColorsBufferTextures: PathfinderBufferTexture[];
|
||||||
|
|
||||||
private atlasRenderingTimerQuery: WebGLQuery;
|
private atlasRenderingTimerQuery: WebGLQuery;
|
||||||
|
|
|
@ -40,6 +40,14 @@ vec2 transformVertexPositionST(vec2 position, vec4 stTransform) {
|
||||||
return position * stTransform.xy + stTransform.zw;
|
return position * stTransform.xy + stTransform.zw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
vec2 convertScreenToClipSpace(vec2 position, ivec2 framebufferSize) {
|
vec2 convertScreenToClipSpace(vec2 position, ivec2 framebufferSize) {
|
||||||
return position / vec2(framebufferSize) * 2.0 - 1.0;
|
return position / vec2(framebufferSize) * 2.0 - 1.0;
|
||||||
}
|
}
|
||||||
|
@ -57,7 +65,11 @@ bool computeQuadPosition(out vec2 outPosition,
|
||||||
inout vec2 rightPosition,
|
inout vec2 rightPosition,
|
||||||
vec2 quadPosition,
|
vec2 quadPosition,
|
||||||
ivec2 framebufferSize,
|
ivec2 framebufferSize,
|
||||||
vec4 transform) {
|
vec4 transform,
|
||||||
|
vec4 hints) {
|
||||||
|
leftPosition = hintPosition(leftPosition, hints);
|
||||||
|
rightPosition = hintPosition(rightPosition, hints);
|
||||||
|
|
||||||
leftPosition = transformVertexPositionST(leftPosition, transform);
|
leftPosition = transformVertexPositionST(leftPosition, transform);
|
||||||
rightPosition = transformVertexPositionST(rightPosition, transform);
|
rightPosition = transformVertexPositionST(rightPosition, transform);
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,10 @@ uniform mat4 uTransform;
|
||||||
uniform ivec2 uFramebufferSize;
|
uniform ivec2 uFramebufferSize;
|
||||||
uniform ivec2 uPathColorsDimensions;
|
uniform ivec2 uPathColorsDimensions;
|
||||||
uniform ivec2 uPathTransformDimensions;
|
uniform ivec2 uPathTransformDimensions;
|
||||||
|
uniform ivec2 uPathHintsDimensions;
|
||||||
uniform sampler2D uPathColors;
|
uniform sampler2D uPathColors;
|
||||||
uniform sampler2D uPathTransform;
|
uniform sampler2D uPathTransform;
|
||||||
|
uniform sampler2D uPathHints;
|
||||||
|
|
||||||
attribute vec2 aPosition;
|
attribute vec2 aPosition;
|
||||||
attribute vec2 aTexCoord;
|
attribute vec2 aTexCoord;
|
||||||
|
@ -24,9 +26,11 @@ varying float vSign;
|
||||||
void main() {
|
void main() {
|
||||||
int pathID = int(aPathID);
|
int pathID = int(aPathID);
|
||||||
|
|
||||||
|
vec4 pathHints = fetchFloat4Data(uPathHints, pathID, uPathHintsDimensions);
|
||||||
vec4 pathTransform = fetchFloat4Data(uPathTransform, pathID, uPathTransformDimensions);
|
vec4 pathTransform = fetchFloat4Data(uPathTransform, pathID, uPathTransformDimensions);
|
||||||
|
|
||||||
vec2 position = transformVertexPositionST(aPosition, pathTransform);
|
vec2 position = hintPosition(aPosition, pathHints);
|
||||||
|
position = transformVertexPositionST(position, pathTransform);
|
||||||
position = transformVertexPosition(position, uTransform);
|
position = transformVertexPosition(position, uTransform);
|
||||||
position = convertScreenToClipSpace(position, uFramebufferSize);
|
position = convertScreenToClipSpace(position, uFramebufferSize);
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,10 @@ uniform mat4 uTransform;
|
||||||
uniform ivec2 uFramebufferSize;
|
uniform ivec2 uFramebufferSize;
|
||||||
uniform ivec2 uPathColorsDimensions;
|
uniform ivec2 uPathColorsDimensions;
|
||||||
uniform ivec2 uPathTransformDimensions;
|
uniform ivec2 uPathTransformDimensions;
|
||||||
|
uniform ivec2 uPathHintsDimensions;
|
||||||
uniform sampler2D uPathColors;
|
uniform sampler2D uPathColors;
|
||||||
uniform sampler2D uPathTransform;
|
uniform sampler2D uPathTransform;
|
||||||
|
uniform sampler2D uPathHints;
|
||||||
|
|
||||||
attribute vec2 aPosition;
|
attribute vec2 aPosition;
|
||||||
attribute float aPathID;
|
attribute float aPathID;
|
||||||
|
@ -20,9 +22,11 @@ varying vec2 vPathID;
|
||||||
void main() {
|
void main() {
|
||||||
int pathID = int(aPathID);
|
int pathID = int(aPathID);
|
||||||
|
|
||||||
|
vec4 pathHints = fetchFloat4Data(uPathHints, pathID, uPathHintsDimensions);
|
||||||
vec4 pathTransform = fetchFloat4Data(uPathTransform, pathID, uPathTransformDimensions);
|
vec4 pathTransform = fetchFloat4Data(uPathTransform, pathID, uPathTransformDimensions);
|
||||||
|
|
||||||
vec2 position = transformVertexPositionST(aPosition, pathTransform);
|
vec2 position = hintPosition(aPosition, pathHints);
|
||||||
|
position = transformVertexPositionST(position, pathTransform);
|
||||||
position = transformVertexPosition(position, uTransform);
|
position = transformVertexPosition(position, uTransform);
|
||||||
position = convertScreenToClipSpace(position, uFramebufferSize);
|
position = convertScreenToClipSpace(position, uFramebufferSize);
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,11 @@ uniform float uScaleX;
|
||||||
uniform ivec2 uBVertexPositionDimensions;
|
uniform ivec2 uBVertexPositionDimensions;
|
||||||
uniform ivec2 uBVertexPathIDDimensions;
|
uniform ivec2 uBVertexPathIDDimensions;
|
||||||
uniform ivec2 uPathTransformDimensions;
|
uniform ivec2 uPathTransformDimensions;
|
||||||
|
uniform ivec2 uPathHintsDimensions;
|
||||||
uniform sampler2D uBVertexPosition;
|
uniform sampler2D uBVertexPosition;
|
||||||
uniform sampler2D uBVertexPathID;
|
uniform sampler2D uBVertexPathID;
|
||||||
uniform sampler2D uPathTransform;
|
uniform sampler2D uPathTransform;
|
||||||
|
uniform sampler2D uPathHints;
|
||||||
|
|
||||||
attribute vec2 aQuadPosition;
|
attribute vec2 aQuadPosition;
|
||||||
attribute vec4 aUpperPointIndices;
|
attribute vec4 aUpperPointIndices;
|
||||||
|
@ -41,9 +43,15 @@ void main() {
|
||||||
|
|
||||||
int pathID = fetchUInt16Data(uBVertexPathID, pointIndices.x, uBVertexPathIDDimensions);
|
int pathID = fetchUInt16Data(uBVertexPathID, pointIndices.x, uBVertexPathIDDimensions);
|
||||||
|
|
||||||
|
vec4 hints = fetchFloat4Data(uPathHints, pathID, uPathHintsDimensions);
|
||||||
vec4 transform = fetchFloat4Data(uPathTransform, pathID, uPathTransformDimensions);
|
vec4 transform = fetchFloat4Data(uPathTransform, pathID, uPathTransformDimensions);
|
||||||
transform.xz *= uScaleX;
|
transform.xz *= uScaleX;
|
||||||
|
|
||||||
|
upperLeftPosition = hintPosition(upperLeftPosition, hints);
|
||||||
|
upperRightPosition = hintPosition(upperRightPosition, hints);
|
||||||
|
lowerLeftPosition = hintPosition(lowerLeftPosition, hints);
|
||||||
|
lowerRightPosition = hintPosition(lowerRightPosition, hints);
|
||||||
|
|
||||||
upperLeftPosition = transformVertexPositionST(upperLeftPosition, transform);
|
upperLeftPosition = transformVertexPositionST(upperLeftPosition, transform);
|
||||||
upperRightPosition = transformVertexPositionST(upperRightPosition, transform);
|
upperRightPosition = transformVertexPositionST(upperRightPosition, transform);
|
||||||
lowerLeftPosition = transformVertexPositionST(lowerLeftPosition, transform);
|
lowerLeftPosition = transformVertexPositionST(lowerLeftPosition, transform);
|
||||||
|
|
|
@ -9,9 +9,11 @@ uniform float uScaleX;
|
||||||
uniform ivec2 uBVertexPositionDimensions;
|
uniform ivec2 uBVertexPositionDimensions;
|
||||||
uniform ivec2 uBVertexPathIDDimensions;
|
uniform ivec2 uBVertexPathIDDimensions;
|
||||||
uniform ivec2 uPathTransformDimensions;
|
uniform ivec2 uPathTransformDimensions;
|
||||||
|
uniform ivec2 uPathHintsDimensions;
|
||||||
uniform sampler2D uBVertexPosition;
|
uniform sampler2D uBVertexPosition;
|
||||||
uniform sampler2D uBVertexPathID;
|
uniform sampler2D uBVertexPathID;
|
||||||
uniform sampler2D uPathTransform;
|
uniform sampler2D uPathTransform;
|
||||||
|
uniform sampler2D uPathHints;
|
||||||
uniform bool uLowerPart;
|
uniform bool uLowerPart;
|
||||||
|
|
||||||
attribute vec2 aQuadPosition;
|
attribute vec2 aQuadPosition;
|
||||||
|
@ -38,6 +40,7 @@ void main() {
|
||||||
|
|
||||||
int pathID = fetchUInt16Data(uBVertexPathID, pointIndices.x, uBVertexPathIDDimensions);
|
int pathID = fetchUInt16Data(uBVertexPathID, pointIndices.x, uBVertexPathIDDimensions);
|
||||||
|
|
||||||
|
vec4 hints = fetchFloat4Data(uPathHints, pathID, uPathHintsDimensions);
|
||||||
vec4 transform = fetchFloat4Data(uPathTransform, pathID, uPathTransformDimensions);
|
vec4 transform = fetchFloat4Data(uPathTransform, pathID, uPathTransformDimensions);
|
||||||
transform.xz *= uScaleX;
|
transform.xz *= uScaleX;
|
||||||
|
|
||||||
|
@ -48,7 +51,9 @@ void main() {
|
||||||
rightPosition,
|
rightPosition,
|
||||||
aQuadPosition,
|
aQuadPosition,
|
||||||
uFramebufferSize,
|
uFramebufferSize,
|
||||||
transform)) {
|
transform,
|
||||||
|
hints)) {
|
||||||
|
controlPointPosition = hintPosition(controlPointPosition, hints);
|
||||||
controlPointPosition = transformVertexPositionST(controlPointPosition, transform);
|
controlPointPosition = transformVertexPositionST(controlPointPosition, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,11 @@ uniform ivec2 uFramebufferSize;
|
||||||
uniform ivec2 uBVertexPositionDimensions;
|
uniform ivec2 uBVertexPositionDimensions;
|
||||||
uniform ivec2 uBVertexPathIDDimensions;
|
uniform ivec2 uBVertexPathIDDimensions;
|
||||||
uniform ivec2 uPathTransformDimensions;
|
uniform ivec2 uPathTransformDimensions;
|
||||||
|
uniform ivec2 uPathHintsDimensions;
|
||||||
uniform sampler2D uBVertexPosition;
|
uniform sampler2D uBVertexPosition;
|
||||||
uniform sampler2D uBVertexPathID;
|
uniform sampler2D uBVertexPathID;
|
||||||
uniform sampler2D uPathTransform;
|
uniform sampler2D uPathTransform;
|
||||||
|
uniform sampler2D uPathHints;
|
||||||
uniform bool uLowerPart;
|
uniform bool uLowerPart;
|
||||||
|
|
||||||
attribute vec2 aQuadPosition;
|
attribute vec2 aQuadPosition;
|
||||||
|
@ -33,6 +35,7 @@ void main() {
|
||||||
|
|
||||||
int pathID = fetchUInt16Data(uBVertexPathID, pointIndices.x, uBVertexPathIDDimensions);
|
int pathID = fetchUInt16Data(uBVertexPathID, pointIndices.x, uBVertexPathIDDimensions);
|
||||||
|
|
||||||
|
vec4 hints = fetchFloat4Data(uPathHints, pathID, uPathHintsDimensions);
|
||||||
vec4 transform = fetchFloat4Data(uPathTransform, pathID, uPathTransformDimensions);
|
vec4 transform = fetchFloat4Data(uPathTransform, pathID, uPathTransformDimensions);
|
||||||
transform.xz *= uScaleX;
|
transform.xz *= uScaleX;
|
||||||
|
|
||||||
|
@ -43,7 +46,8 @@ void main() {
|
||||||
rightPosition,
|
rightPosition,
|
||||||
aQuadPosition,
|
aQuadPosition,
|
||||||
uFramebufferSize,
|
uFramebufferSize,
|
||||||
transform);
|
transform,
|
||||||
|
hints);
|
||||||
|
|
||||||
float depth = convertPathIndexToViewportDepthValue(pathID);
|
float depth = convertPathIndexToViewportDepthValue(pathID);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue