Get rotation working in the text demo.

There are some known artefacts when zoomed in heavily in XCAA mode.
This commit is contained in:
Patrick Walton 2017-11-30 14:02:57 -08:00
parent 95a1dd0195
commit 1de0378f1e
12 changed files with 178 additions and 70 deletions

View File

@ -253,6 +253,7 @@ class ThreeDController extends DemoAppController<ThreeDView> {
textFrameIndex < this.textFrames.length;
textFrameIndex++) {
const textFrame = this.textFrames[textFrameIndex];
const textBounds = textFrame.bounds;
let glyphDescriptors = [];
for (const run of textFrame.runs) {
@ -261,7 +262,9 @@ class ThreeDController extends DemoAppController<ThreeDView> {
glyphID: run.glyphIDs[glyphIndex],
position: run.calculatePixelOriginForGlyphAt(glyphIndex,
PIXELS_PER_UNIT,
hint),
0.0,
hint,
textBounds),
});
}
}
@ -361,6 +364,10 @@ class ThreeDRenderer extends Renderer {
camera: PerspectiveCamera;
get usesSTTransform(): boolean {
return this.camera.usesSTTransform;
}
get destFramebuffer(): WebGLFramebuffer | null {
return null;
}
@ -593,7 +600,7 @@ class ThreeDRenderer extends Renderer {
});
const glyph = this.renderContext.atlasGlyphs[glyphIndex];
const glyphMetrics = unwrapNull(font.metricsForGlyph(glyph.glyphKey.id));
const glyphUnitMetrics = new UnitMetrics(glyphMetrics, glmatrix.vec2.create());
const glyphUnitMetrics = new UnitMetrics(glyphMetrics, 0.0, glmatrix.vec2.create());
const firstPosition = this.glyphPositions.length / 2;
@ -843,6 +850,7 @@ class ThreeDRenderer extends Renderer {
this.renderContext.atlas.layoutGlyphs(atlasGlyphs,
this.renderContext.font,
this.renderContext.atlasPixelsPerUnit,
0.0,
hint,
glmatrix.vec2.create());
@ -937,7 +945,7 @@ class ThreeDAtlasRenderer extends TextRenderer {
if (glyphMetrics == null)
continue;
const glyphUnitMetrics = new UnitMetrics(glyphMetrics, glmatrix.vec2.create());
const glyphUnitMetrics = new UnitMetrics(glyphMetrics, 0.0, glmatrix.vec2.create());
const atlasGlyphRect = calculatePixelRectForGlyph(glyphUnitMetrics,
glyphPixelOrigin,
pixelsPerUnit,

View File

@ -33,6 +33,7 @@ export class Atlas {
layoutGlyphs(glyphs: AtlasGlyph[],
font: PathfinderFont,
pixelsPerUnit: number,
rotationAngle: number,
hint: Hint,
stemDarkeningAmount: glmatrix.vec2):
void {
@ -45,9 +46,9 @@ export class Atlas {
if (metrics == null)
continue;
const unitMetrics = new UnitMetrics(metrics, stemDarkeningAmount);
const unitMetrics = new UnitMetrics(metrics, rotationAngle, stemDarkeningAmount);
glyph.setPixelLowerLeft(nextOrigin, unitMetrics, pixelsPerUnit);
let pixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
nextOrigin[0] = calculatePixelRectForGlyph(unitMetrics,
pixelOrigin,
@ -128,7 +129,7 @@ export class AtlasGlyph {
const pixelXMin = calculatePixelXMin(metrics, pixelsPerUnit);
const pixelDescent = calculatePixelDescent(metrics, pixelsPerUnit);
const pixelOrigin = glmatrix.vec2.clone([pixelLowerLeft[0] - pixelXMin,
pixelLowerLeft[1] + pixelDescent]);
pixelLowerLeft[1] - pixelDescent]);
this.setPixelOrigin(pixelOrigin, pixelsPerUnit);
}

View File

@ -272,6 +272,10 @@ class BenchmarkRenderer extends Renderer {
renderingPromiseCallback: ((time: number) => void) | null;
get usesSTTransform(): boolean {
return this.camera.usesSTTransform;
}
get destFramebuffer(): WebGLFramebuffer | null {
return null;
}

View File

@ -11,6 +11,7 @@
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import {EPSILON} from "./utils";
import {PathfinderView} from "./view";
const PIXELS_PER_LINE: number = 16.0;
@ -79,6 +80,8 @@ export interface CameraView {
export abstract class Camera {
protected canvas: CameraView;
abstract get usesSTTransform(): boolean;
constructor(canvas: CameraView) {
this.canvas = canvas;
}
@ -105,6 +108,10 @@ export class OrthographicCamera extends Camera {
private readonly scaleBounds: boolean;
private readonly ignoreBounds: boolean;
get usesSTTransform(): boolean {
return Math.abs(this.rotationAngle) < EPSILON;
}
constructor(canvas: CameraView, options?: OrthographicCameraOptions) {
super(canvas);
@ -280,6 +287,10 @@ export class OrthographicCamera extends Camera {
export class PerspectiveCamera extends Camera {
canvas: HTMLCanvasElement;
get usesSTTransform(): boolean {
return false;
}
onChange: (() => void) | null;
translation: glmatrix.vec3;

View File

@ -469,6 +469,10 @@ class ReferenceTestRenderer extends Renderer {
renderContext: ReferenceTestView;
camera: OrthographicCamera;
get usesSTTransform(): boolean {
return this.camera.usesSTTransform;
}
get destFramebuffer(): WebGLFramebuffer | null {
return null;
}
@ -571,17 +575,8 @@ class ReferenceTestRenderer extends Renderer {
}
getPixelRectForGlyphAt(glyphIndex: number): glmatrix.vec4 {
const appController = this.renderContext.appController;
const font = unwrapNull(appController.font);
const hint = new Hint(font, this.pixelsPerUnit, true);
const textRun = unwrapNull(appController.textRun);
const glyphID = textRun.glyphIDs[glyphIndex];
return textRun.pixelRectForGlyphAt(glyphIndex,
this.pixelsPerUnit,
hint,
this.stemDarkeningAmount,
SUBPIXEL_GRANULARITY);
const textRun = unwrapNull(this.renderContext.appController.textRun);
return textRun.pixelRectForGlyphAt(glyphIndex);
}
protected createAAStrategy(aaType: AntialiasingStrategyName,
@ -609,11 +604,13 @@ class ReferenceTestRenderer extends Renderer {
const textRun = unwrapNull(appController.textRun);
const glyphID = textRun.glyphIDs[0];
const pixelRect = textRun.pixelRectForGlyphAt(0,
this.pixelsPerUnit,
hint,
glmatrix.vec2.create(),
SUBPIXEL_GRANULARITY);
textRun.recalculatePixelRects(this.pixelsPerUnit,
0.0,
hint,
glmatrix.vec2.create(),
SUBPIXEL_GRANULARITY,
glmatrix.vec4.create());
const pixelRect = textRun.pixelRectForGlyphAt(0);
const x = -pixelRect[0] / this.pixelsPerUnit;
const y = (canvas.height - (pixelRect[3] - pixelRect[1])) / this.pixelsPerUnit;

View File

@ -44,6 +44,8 @@ export abstract class Renderer {
meshes: PathfinderMeshBuffers[] | null;
meshData: PathfinderMeshData[] | null;
abstract get usesSTTransform(): boolean;
get emboldenAmount(): glmatrix.vec2 {
return glmatrix.vec2.create();
}

View File

@ -117,6 +117,10 @@ class SVGDemoRenderer extends Renderer {
camera: OrthographicCamera;
get usesSTTransform(): boolean {
return this.camera.usesSTTransform;
}
get destAllocatedSize(): glmatrix.vec2 {
const canvas = this.renderContext.canvas;
return glmatrix.vec2.clone([canvas.width, canvas.height]);

View File

@ -27,7 +27,7 @@ import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
import {Renderer} from './renderer';
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
import SSAAStrategy from './ssaa-strategy';
import {calculatePixelDescent, calculatePixelRectForGlyph, PathfinderFont} from "./text";
import {calculatePixelRectForGlyph, PathfinderFont} from "./text";
import {BUILTIN_FONT_URI, calculatePixelXMin, computeStemDarkeningAmount} from "./text";
import {GlyphStore, Hint, SimpleTextLayout, UnitMetrics} from "./text";
import {TextRenderContext, TextRenderer} from './text-renderer';
@ -506,17 +506,21 @@ class TextDemoRenderer extends TextRenderer {
const hint = this.createHint();
const pixelsPerUnit = this.pixelsPerUnit;
const rotationAngle = this.rotationAngle;
let globalGlyphIndex = 0;
for (const run of this.layout.textFrame.runs) {
run.recalculatePixelRects(pixelsPerUnit,
rotationAngle,
hint,
this.stemDarkeningAmount,
SUBPIXEL_GRANULARITY,
textBounds);
for (let glyphIndex = 0;
glyphIndex < run.glyphIDs.length;
glyphIndex++, globalGlyphIndex++) {
const rect = run.pixelRectForGlyphAt(glyphIndex,
pixelsPerUnit,
hint,
this.stemDarkeningAmount,
SUBPIXEL_GRANULARITY);
const rect = run.pixelRectForGlyphAt(glyphIndex);
glyphPositions.set([
rect[0], rect[3],
rect[2], rect[3],
@ -545,8 +549,10 @@ class TextDemoRenderer extends TextRenderer {
const font = this.renderContext.font;
const glyphStore = this.renderContext.glyphStore;
const pixelsPerUnit = this.pixelsPerUnit;
const rotationAngle = this.rotationAngle;
const textFrame = this.layout.textFrame;
const textBounds = textFrame.bounds;
const hint = this.createHint();
// Only build glyphs in view.
@ -561,11 +567,7 @@ class TextDemoRenderer extends TextRenderer {
const atlasGlyphs = [];
for (const run of textFrame.runs) {
for (let glyphIndex = 0; glyphIndex < run.glyphIDs.length; glyphIndex++) {
const pixelRect = run.pixelRectForGlyphAt(glyphIndex,
pixelsPerUnit,
hint,
this.stemDarkeningAmount,
SUBPIXEL_GRANULARITY);
const pixelRect = run.pixelRectForGlyphAt(glyphIndex);
if (!rectsIntersect(pixelRect, canvasRect))
continue;
@ -576,8 +578,10 @@ class TextDemoRenderer extends TextRenderer {
const subpixel = run.subpixelForGlyphAt(glyphIndex,
pixelsPerUnit,
rotationAngle,
hint,
SUBPIXEL_GRANULARITY);
SUBPIXEL_GRANULARITY,
textBounds);
const glyphKey = new GlyphKey(glyphID, subpixel);
atlasGlyphs.push(new AtlasGlyph(glyphStoreIndex, glyphKey));
}
@ -586,17 +590,21 @@ class TextDemoRenderer extends TextRenderer {
this.buildAtlasGlyphs(atlasGlyphs);
// TODO(pcwalton): Regenerate the IBOs to include only the glyphs we care about.
this.setGlyphTexCoords();
}
private setGlyphTexCoords(): void {
const gl = this.renderContext.gl;
const textFrame = this.layout.textFrame;
const textBounds = textFrame.bounds;
const font = this.renderContext.font;
const atlasGlyphs = this.renderContext.atlasGlyphs;
const hint = this.createHint();
const pixelsPerUnit = this.pixelsPerUnit;
const rotationAngle = this.rotationAngle;
const atlasGlyphKeys = atlasGlyphs.map(atlasGlyph => atlasGlyph.glyphKey.sortKey);
@ -611,8 +619,10 @@ class TextDemoRenderer extends TextRenderer {
const subpixel = run.subpixelForGlyphAt(glyphIndex,
pixelsPerUnit,
rotationAngle,
hint,
SUBPIXEL_GRANULARITY);
SUBPIXEL_GRANULARITY,
textBounds);
const glyphKey = new GlyphKey(textGlyphID, subpixel);
@ -627,6 +637,7 @@ class TextDemoRenderer extends TextRenderer {
continue;
const atlasGlyphUnitMetrics = new UnitMetrics(atlasGlyphMetrics,
rotationAngle,
this.stemDarkeningAmount);
const atlasGlyphPixelOrigin =
@ -649,12 +660,9 @@ class TextDemoRenderer extends TextRenderer {
}
}
this.glyphTexCoordsBuffer = unwrapNull(this.renderContext.gl.createBuffer());
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
this.glyphTexCoordsBuffer);
this.renderContext.gl.bufferData(this.renderContext.gl.ARRAY_BUFFER,
this.glyphBounds,
this.renderContext.gl.STATIC_DRAW);
this.glyphTexCoordsBuffer = unwrapNull(gl.createBuffer());
gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.glyphBounds, gl.STATIC_DRAW);
}
private setIdentityTexScaleUniform(uniforms: UniformMap): void {

View File

@ -64,6 +64,10 @@ export abstract class TextRenderer extends Renderer {
atlasFramebuffer: WebGLFramebuffer;
atlasDepthTexture: WebGLTexture;
get usesSTTransform(): boolean {
return this.camera.usesSTTransform;
}
get destFramebuffer(): WebGLFramebuffer {
return this.atlasFramebuffer;
}
@ -148,6 +152,7 @@ export abstract class TextRenderer extends Renderer {
const pathCount = this.pathCount;
const atlasGlyphs = this.renderContext.atlasGlyphs;
const pixelsPerUnit = this.pixelsPerUnit;
const rotationAngle = this.rotationAngle;
const font = this.renderContext.font;
const hint = this.createHint();
@ -157,7 +162,9 @@ export abstract class TextRenderer extends Renderer {
const atlasGlyphMetrics = font.metricsForGlyph(glyph.glyphKey.id);
if (atlasGlyphMetrics == null)
continue;
const atlasUnitMetrics = new UnitMetrics(atlasGlyphMetrics, this.stemDarkeningAmount);
const atlasUnitMetrics = new UnitMetrics(atlasGlyphMetrics,
0.0,
this.stemDarkeningAmount);
const pathID = glyph.pathID;
boundingRects[pathID * 4 + 0] = atlasUnitMetrics.left;
@ -196,6 +203,7 @@ export abstract class TextRenderer extends Renderer {
protected buildAtlasGlyphs(atlasGlyphs: AtlasGlyph[]): void {
const font = this.renderContext.font;
const pixelsPerUnit = this.pixelsPerUnit;
const rotationAngle = this.rotationAngle;
const hint = this.createHint();
atlasGlyphs.sort((a, b) => a.glyphKey.sortKey - b.glyphKey.sortKey);
@ -207,6 +215,7 @@ export abstract class TextRenderer extends Renderer {
this.renderContext.atlas.layoutGlyphs(atlasGlyphs,
font,
pixelsPerUnit,
rotationAngle,
hint,
this.stemDarkeningAmount);

View File

@ -94,6 +94,7 @@ export class TextRun {
readonly origin: number[];
private readonly font: PathfinderFont;
private pixelRects: glmatrix.vec4[];
constructor(text: number[] | string, origin: number[], font: PathfinderFont) {
if (typeof(text) === 'string') {
@ -107,6 +108,7 @@ export class TextRun {
this.origin = origin;
this.advances = [];
this.font = font;
this.pixelRects = [];
}
layout() {
@ -118,40 +120,79 @@ export class TextRun {
}
}
calculatePixelOriginForGlyphAt(index: number, pixelsPerUnit: number, hint: Hint):
calculatePixelOriginForGlyphAt(index: number,
pixelsPerUnit: number,
rotationAngle: number,
hint: Hint,
textFrameBounds: glmatrix.vec4):
glmatrix.vec2 {
const textGlyphOrigin = glmatrix.vec2.clone(this.origin);
textGlyphOrigin[0] += this.advances[index];
const textFrameCenter = glmatrix.vec2.clone([
0.5 * (textFrameBounds[0] + textFrameBounds[2]),
0.5 * (textFrameBounds[1] + textFrameBounds[3]),
]);
const transform = glmatrix.mat2d.create();
glmatrix.mat2d.fromTranslation(transform, textFrameCenter);
glmatrix.mat2d.rotate(transform, transform, -rotationAngle);
glmatrix.vec2.negate(textFrameCenter, textFrameCenter);
glmatrix.mat2d.translate(transform, transform, textFrameCenter);
const textGlyphOrigin = glmatrix.vec2.create();
glmatrix.vec2.add(textGlyphOrigin, [this.advances[index], 0.0], this.origin);
glmatrix.vec2.transformMat2d(textGlyphOrigin, textGlyphOrigin, transform);
glmatrix.vec2.scale(textGlyphOrigin, textGlyphOrigin, pixelsPerUnit);
return textGlyphOrigin;
}
pixelRectForGlyphAt(index: number,
pixelsPerUnit: number,
hint: Hint,
stemDarkeningAmount: glmatrix.vec2,
subpixelGranularity: number):
glmatrix.vec4 {
const metrics = unwrapNull(this.font.metricsForGlyph(this.glyphIDs[index]));
const unitMetrics = new UnitMetrics(metrics, stemDarkeningAmount);
const textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index, pixelsPerUnit, hint);
textGlyphOrigin[0] *= subpixelGranularity;
glmatrix.vec2.round(textGlyphOrigin, textGlyphOrigin);
textGlyphOrigin[0] /= subpixelGranularity;
return calculatePixelRectForGlyph(unitMetrics, textGlyphOrigin, pixelsPerUnit, hint);
pixelRectForGlyphAt(index: number): glmatrix.vec4 {
return this.pixelRects[index];
}
subpixelForGlyphAt(index: number,
pixelsPerUnit: number,
rotationAngle: number,
hint: Hint,
subpixelGranularity: number):
subpixelGranularity: number,
textFrameBounds: glmatrix.vec4):
number {
const textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index, pixelsPerUnit, hint)[0];
const textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index,
pixelsPerUnit,
rotationAngle,
hint,
textFrameBounds)[0];
return Math.abs(Math.round(textGlyphOrigin * subpixelGranularity) % subpixelGranularity);
}
recalculatePixelRects(pixelsPerUnit: number,
rotationAngle: number,
hint: Hint,
stemDarkeningAmount: 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 textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index,
pixelsPerUnit,
rotationAngle,
hint,
textFrameBounds);
textGlyphOrigin[0] *= subpixelGranularity;
glmatrix.vec2.round(textGlyphOrigin, textGlyphOrigin);
textGlyphOrigin[0] /= subpixelGranularity;
const pixelRect = calculatePixelRectForGlyph(unitMetrics,
textGlyphOrigin,
pixelsPerUnit,
hint);
this.pixelRects[index] = pixelRect;
}
}
get measure(): number {
const lastGlyphID = _.last(this.glyphIDs), lastAdvance = _.last(this.advances);
if (lastGlyphID == null || lastAdvance == null)
@ -340,11 +381,29 @@ export class UnitMetrics {
ascent: number;
descent: number;
constructor(metrics: Metrics, stemDarkeningAmount: glmatrix.vec2) {
this.left = metrics.xMin;
this.right = metrics.xMax + stemDarkeningAmount[0] * 2;
this.ascent = metrics.yMax + stemDarkeningAmount[1] * 2;
this.descent = metrics.yMin;
constructor(metrics: Metrics, rotationAngle: number, stemDarkeningAmount: 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 transform = glmatrix.mat2.create();
glmatrix.mat2.fromRotation(transform, -rotationAngle);
const lowerLeft = glmatrix.vec2.clone([Infinity, Infinity]);
const upperRight = glmatrix.vec2.clone([-Infinity, -Infinity]);
const points = [[left, bottom], [left, top], [right, top], [right, bottom]];
const transformedPoint = glmatrix.vec2.create();
for (const point of points) {
glmatrix.vec2.transformMat2(transformedPoint, point, transform);
glmatrix.vec2.min(lowerLeft, lowerLeft, transformedPoint);
glmatrix.vec2.max(upperRight, upperRight, transformedPoint);
}
this.left = lowerLeft[0];
this.right = upperRight[0];
this.ascent = upperRight[1];
this.descent = lowerLeft[1];
}
}
@ -353,7 +412,7 @@ export function calculatePixelXMin(metrics: UnitMetrics, pixelsPerUnit: number):
}
export function calculatePixelDescent(metrics: UnitMetrics, pixelsPerUnit: number): number {
return Math.ceil(-metrics.descent * pixelsPerUnit);
return Math.floor(metrics.descent * pixelsPerUnit);
}
function calculateSubpixelMetricsForGlyph(metrics: UnitMetrics, pixelsPerUnit: number, hint: Hint):

View File

@ -17,6 +17,8 @@ export const UINT16_SIZE: number = 2;
export const UINT32_MAX: number = 0xffffffff;
export const UINT32_SIZE: number = 4;
export const EPSILON: number = 0.001;
export function panic(message: string): never {
throw new PathfinderError(message);
}

View File

@ -1169,8 +1169,11 @@ export class AdaptiveMonochromeXCAAStrategy implements AntialiasingStrategy {
}
private getAppropriateStrategy(renderer: Renderer): AntialiasingStrategy {
if (glmatrix.vec2.equals(renderer.emboldenAmount, [0.0, 0.0]))
if (glmatrix.vec2.equals(renderer.emboldenAmount, [0.0, 0.0]) &&
renderer.usesSTTransform) {
return this.mcaaStrategy;
}
return this.ecaaStrategy;
}
}