diff --git a/demo/client/html/text-demo.html.hbs b/demo/client/html/text-demo.html.hbs index 073d1f39..eabb16c0 100644 --- a/demo/client/html/text-demo.html.hbs +++ b/demo/client/html/text-demo.html.hbs @@ -80,6 +80,12 @@ {{>partials/switch.html id="pf-stem-darkening" title="Stem Darkening"}} +
+ + +
diff --git a/demo/client/src/atlas.ts b/demo/client/src/atlas.ts index 60caf16f..94e23008 100644 --- a/demo/client/src/atlas.ts +++ b/demo/client/src/atlas.ts @@ -35,7 +35,7 @@ export class Atlas { pixelsPerUnit: number, rotationAngle: number, hint: Hint, - stemDarkeningAmount: glmatrix.vec2): + emboldenAmount: glmatrix.vec2): void { let nextOrigin = glmatrix.vec2.fromValues(1.0, 1.0); let shelfBottom = 2.0; @@ -46,7 +46,7 @@ export class Atlas { if (metrics == null) continue; - const unitMetrics = new UnitMetrics(metrics, rotationAngle, stemDarkeningAmount); + const unitMetrics = new UnitMetrics(metrics, rotationAngle, emboldenAmount); glyph.setPixelLowerLeft(nextOrigin, unitMetrics, pixelsPerUnit); let pixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit); diff --git a/demo/client/src/text-demo.ts b/demo/client/src/text-demo.ts index f9b04d78..f3317323 100644 --- a/demo/client/src/text-demo.ts +++ b/demo/client/src/text-demo.ts @@ -122,6 +122,7 @@ class TextDemoController extends DemoAppController { atlasGlyphs: AtlasGlyph[]; private hintingSelect: HTMLSelectElement; + private emboldenInput: HTMLInputElement; private editTextModal: HTMLElement; private editTextArea: HTMLTextAreaElement; @@ -132,6 +133,7 @@ class TextDemoController extends DemoAppController { private _fontSize: number; private _rotationAngle: number; + private _emboldenAmount: number; private text: string; @@ -146,11 +148,16 @@ class TextDemoController extends DemoAppController { this._fontSize = INITIAL_FONT_SIZE; this._rotationAngle = 0.0; + this._emboldenAmount = 0.0; this.hintingSelect = unwrapNull(document.getElementById('pf-hinting-select')) as HTMLSelectElement; this.hintingSelect.addEventListener('change', () => this.hintingChanged(), false); + this.emboldenInput = unwrapNull(document.getElementById('pf-embolden')) as + HTMLInputElement; + this.emboldenInput.addEventListener('input', () => this.emboldenAmountChanged(), false); + this.editTextModal = unwrapNull(document.getElementById('pf-edit-text-modal')); this.editTextArea = unwrapNull(document.getElementById('pf-edit-text-area')) as HTMLTextAreaElement; @@ -167,6 +174,10 @@ class TextDemoController extends DemoAppController { window.jQuery(this.editTextModal).modal(); } + get emboldenAmount(): number { + return this._emboldenAmount; + } + protected createView(gammaLUT: HTMLImageElement, commonShaderSource: string, shaderSources: ShaderMap): @@ -183,6 +194,11 @@ class TextDemoController extends DemoAppController { this.view.then(view => view.renderer.updateHinting()); } + private emboldenAmountChanged(): void { + this._emboldenAmount = parseFloat(this.emboldenInput.value); + this.view.then(view => view.renderer.updateEmboldenAmount()); + } + private updateText(): void { this.text = this.editTextArea.value; window.jQuery(this.editTextModal).modal('hide'); @@ -246,8 +262,12 @@ class TextDemoController extends DemoAppController { this.view.then(view => view.renderer.relayoutText()); } + get unitsPerEm(): number { + return this.font.opentypeFont.unitsPerEm; + } + get pixelsPerUnit(): number { - return this._fontSize / this.font.opentypeFont.unitsPerEm; + return this._fontSize / this.unitsPerEm; } get useHinting(): boolean { @@ -391,6 +411,12 @@ class TextDemoRenderer extends TextRenderer { return this.renderContext.appController.rotationAngle; } + protected get extraEmboldenAmount(): glmatrix.vec2 { + const appController = this.renderContext.appController; + const emboldenLength = appController.emboldenAmount * appController.unitsPerEm; + return glmatrix.vec2.clone([emboldenLength, emboldenLength]); + } + prepareToAttachText(): void { if (this.atlasFramebuffer == null) this.createAtlasFramebuffer(); @@ -428,6 +454,13 @@ class TextDemoRenderer extends TextRenderer { this.renderContext.setDirty(); } + updateEmboldenAmount(): void { + // Likewise, need to relayout the text. + this.layoutText(); + this.buildGlyphs(); + this.renderContext.setDirty(); + } + viewPanned(): void { this.buildGlyphs(); this.renderContext.setDirty(); @@ -513,7 +546,7 @@ class TextDemoRenderer extends TextRenderer { run.recalculatePixelRects(pixelsPerUnit, rotationAngle, hint, - this.stemDarkeningAmount, + this.emboldenAmount, SUBPIXEL_GRANULARITY, textBounds); @@ -638,7 +671,7 @@ class TextDemoRenderer extends TextRenderer { const atlasGlyphUnitMetrics = new UnitMetrics(atlasGlyphMetrics, rotationAngle, - this.stemDarkeningAmount); + this.emboldenAmount); const atlasGlyphPixelOrigin = atlasGlyph.calculateSubpixelOrigin(pixelsPerUnit); diff --git a/demo/client/src/text-renderer.ts b/demo/client/src/text-renderer.ts index cbe9c57f..fb432da9 100644 --- a/demo/client/src/text-renderer.ts +++ b/demo/client/src/text-renderer.ts @@ -81,7 +81,9 @@ export abstract class TextRenderer extends Renderer { } get emboldenAmount(): glmatrix.vec2 { - return this.stemDarkeningAmount; + const emboldenAmount = glmatrix.vec2.create(); + glmatrix.vec2.add(emboldenAmount, this.extraEmboldenAmount, this.stemDarkeningAmount); + return emboldenAmount; } get bgColor(): glmatrix.vec4 { @@ -127,6 +129,10 @@ export abstract class TextRenderer extends Renderer { return this.meshes == null ? 0 : this.meshes.length; } + protected get extraEmboldenAmount(): glmatrix.vec2 { + return glmatrix.vec2.create(); + } + private stemDarkening: StemDarkeningMode; private subpixelAA: SubpixelAAType; @@ -162,9 +168,7 @@ export abstract class TextRenderer extends Renderer { const atlasGlyphMetrics = font.metricsForGlyph(glyph.glyphKey.id); if (atlasGlyphMetrics == null) continue; - const atlasUnitMetrics = new UnitMetrics(atlasGlyphMetrics, - 0.0, - this.stemDarkeningAmount); + const atlasUnitMetrics = new UnitMetrics(atlasGlyphMetrics, 0.0, this.emboldenAmount); const pathID = glyph.pathID; boundingRects[pathID * 4 + 0] = atlasUnitMetrics.left; @@ -201,7 +205,8 @@ export abstract class TextRenderer extends Renderer { protected clearForDirectRendering(): void {} protected buildAtlasGlyphs(atlasGlyphs: AtlasGlyph[]): void { - const font = this.renderContext.font; + const renderContext = this.renderContext; + const font = renderContext.font; const pixelsPerUnit = this.pixelsPerUnit; const rotationAngle = this.rotationAngle; const hint = this.createHint(); @@ -211,13 +216,13 @@ export abstract class TextRenderer extends Renderer { if (atlasGlyphs.length === 0) return; - this.renderContext.atlasGlyphs = atlasGlyphs; - this.renderContext.atlas.layoutGlyphs(atlasGlyphs, - font, - pixelsPerUnit, - rotationAngle, - hint, - this.stemDarkeningAmount); + renderContext.atlasGlyphs = atlasGlyphs; + renderContext.atlas.layoutGlyphs(atlasGlyphs, + font, + pixelsPerUnit, + rotationAngle, + hint, + this.emboldenAmount); this.uploadPathTransforms(1); this.uploadPathColors(1); @@ -250,10 +255,10 @@ export abstract class TextRenderer extends Renderer { // the ascenders and x-heights of the glyphs are pixel snapped, while they aren't on macOS. // But we should really figure out what macOS does… const ascender = this.renderContext.font.opentypeFont.ascender; - const stemDarkeningAmount = this.stemDarkeningAmount; - const stemDarkeningYScale = (ascender + stemDarkeningAmount[1]) / ascender; + const emboldenAmount = this.emboldenAmount; + const stemDarkeningYScale = (ascender + emboldenAmount[1]) / ascender; - const stemDarkeningOffset = glmatrix.vec2.clone(stemDarkeningAmount); + const stemDarkeningOffset = glmatrix.vec2.clone(emboldenAmount); glmatrix.vec2.scale(stemDarkeningOffset, stemDarkeningOffset, pixelsPerUnit); glmatrix.vec2.scale(stemDarkeningOffset, stemDarkeningOffset, SQRT_1_2); glmatrix.vec2.mul(stemDarkeningOffset, stemDarkeningOffset, [1, stemDarkeningYScale]); diff --git a/demo/client/src/text.ts b/demo/client/src/text.ts index 88564cca..66ff6a30 100644 --- a/demo/client/src/text.ts +++ b/demo/client/src/text.ts @@ -167,13 +167,13 @@ export class TextRun { recalculatePixelRects(pixelsPerUnit: number, rotationAngle: number, hint: Hint, - stemDarkeningAmount: glmatrix.vec2, + emboldenAmount: glmatrix.vec2, subpixelGranularity: number, textFrameBounds: glmatrix.vec4): void { for (let index = 0; index < this.glyphIDs.length; index++) { const metrics = unwrapNull(this.font.metricsForGlyph(this.glyphIDs[index])); - const unitMetrics = new UnitMetrics(metrics, rotationAngle, stemDarkeningAmount); + const unitMetrics = new UnitMetrics(metrics, rotationAngle, emboldenAmount); const textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index, pixelsPerUnit, rotationAngle, @@ -381,11 +381,11 @@ export class UnitMetrics { ascent: number; descent: number; - constructor(metrics: Metrics, rotationAngle: number, stemDarkeningAmount: glmatrix.vec2) { + constructor(metrics: Metrics, rotationAngle: number, emboldenAmount: glmatrix.vec2) { const left = metrics.xMin; const bottom = metrics.yMin; - const right = metrics.xMax + stemDarkeningAmount[0] * 2; - const top = metrics.yMax + stemDarkeningAmount[1] * 2; + const right = metrics.xMax + emboldenAmount[0] * 2; + const top = metrics.yMax + emboldenAmount[1] * 2; const transform = glmatrix.mat2.create(); glmatrix.mat2.fromRotation(transform, -rotationAngle);