Implement subpixel glyph positioning in the text demo
This commit is contained in:
parent
7e4308d52e
commit
08b9afdca9
|
@ -11,7 +11,8 @@
|
|||
import * as base64js from 'base64-js';
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import { expectNotNull, panic, PathfinderError, UINT32_MAX, UINT32_SIZE } from './utils';
|
||||
import {expectNotNull, FLOAT32_SIZE, panic, PathfinderError, UINT16_SIZE} from './utils';
|
||||
import {UINT32_MAX, UINT32_SIZE} from './utils';
|
||||
|
||||
const BUFFER_TYPES: Meshes<BufferType> = {
|
||||
bQuads: 'ARRAY_BUFFER',
|
||||
|
@ -172,11 +173,13 @@ export class PathfinderMeshData implements Meshes<ArrayBuffer> {
|
|||
indexIndex => indexIndex % 4 < 3);
|
||||
|
||||
// Copy over B-quads.
|
||||
let firstBQuadIndex = findFirstBQuadIndex(bQuads, pathID);
|
||||
let firstBQuadIndex = findFirstBQuadIndex(bQuads, bVertexPathIDs, pathID);
|
||||
if (firstBQuadIndex == null)
|
||||
firstBQuadIndex = bQuads.length;
|
||||
const indexDelta = firstExpandedBVertexIndex - firstBVertexIndex;
|
||||
for (let bQuadIndex = firstBQuadIndex; bQuadIndex < bQuads.length; bQuadIndex++) {
|
||||
for (let bQuadIndex = firstBQuadIndex;
|
||||
bQuadIndex < bQuads.length / B_QUAD_FIELD_COUNT;
|
||||
bQuadIndex++) {
|
||||
const bQuad = bQuads[bQuadIndex];
|
||||
if (bVertexPathIDs[bQuads[bQuadIndex * B_QUAD_FIELD_COUNT]] !== pathID)
|
||||
break;
|
||||
|
@ -192,23 +195,48 @@ export class PathfinderMeshData implements Meshes<ArrayBuffer> {
|
|||
textGlyphIndex++;
|
||||
}
|
||||
|
||||
const expandedBQuadsBuffer = new ArrayBuffer(expandedBQuads.length * UINT32_SIZE);
|
||||
const expandedBVertexLoopBlinnDataBuffer =
|
||||
new ArrayBuffer(expandedBVertexLoopBlinnData.length * UINT32_SIZE);
|
||||
const expandedBVertexPathIDsBuffer =
|
||||
new ArrayBuffer(expandedBVertexPathIDs.length * UINT16_SIZE);
|
||||
const expandedBVertexPositionsBuffer =
|
||||
new ArrayBuffer(expandedBVertexPositions.length * FLOAT32_SIZE);
|
||||
const expandedCoverCurveIndicesBuffer =
|
||||
new ArrayBuffer(expandedCoverCurveIndices.length * UINT32_SIZE);
|
||||
const expandedCoverInteriorIndicesBuffer =
|
||||
new ArrayBuffer(expandedCoverInteriorIndices.length * UINT32_SIZE);
|
||||
const expandedEdgeLowerCurveIndicesBuffer =
|
||||
new ArrayBuffer(expandedEdgeLowerCurveIndices.length * UINT32_SIZE);
|
||||
const expandedEdgeLowerLineIndicesBuffer =
|
||||
new ArrayBuffer(expandedEdgeLowerLineIndices.length * UINT32_SIZE);
|
||||
const expandedEdgeUpperCurveIndicesBuffer =
|
||||
new ArrayBuffer(expandedEdgeUpperCurveIndices.length * UINT32_SIZE);
|
||||
const expandedEdgeUpperLineIndicesBuffer =
|
||||
new ArrayBuffer(expandedEdgeUpperLineIndices.length * UINT32_SIZE);
|
||||
|
||||
(new Uint32Array(expandedBQuadsBuffer)).set(expandedBQuads);
|
||||
(new Uint32Array(expandedBVertexLoopBlinnDataBuffer)).set(expandedBVertexLoopBlinnData);
|
||||
(new Uint16Array(expandedBVertexPathIDsBuffer)).set(expandedBVertexPathIDs);
|
||||
(new Float32Array(expandedBVertexPositionsBuffer)).set(expandedBVertexPositions);
|
||||
(new Uint32Array(expandedCoverCurveIndicesBuffer)).set(expandedCoverCurveIndices);
|
||||
(new Uint32Array(expandedCoverInteriorIndicesBuffer)).set(expandedCoverInteriorIndices);
|
||||
(new Uint32Array(expandedEdgeLowerCurveIndicesBuffer)).set(expandedEdgeLowerCurveIndices);
|
||||
(new Uint32Array(expandedEdgeLowerLineIndicesBuffer)).set(expandedEdgeLowerLineIndices);
|
||||
(new Uint32Array(expandedEdgeUpperCurveIndicesBuffer)).set(expandedEdgeUpperCurveIndices);
|
||||
(new Uint32Array(expandedEdgeUpperLineIndicesBuffer)).set(expandedEdgeUpperLineIndices);
|
||||
|
||||
return new PathfinderMeshData({
|
||||
bQuads: new Uint32Array(expandedBQuads).buffer as ArrayBuffer,
|
||||
bVertexLoopBlinnData: new Uint32Array(expandedBVertexLoopBlinnData).buffer as
|
||||
ArrayBuffer,
|
||||
bVertexPathIDs: new Uint16Array(expandedBVertexPathIDs).buffer as ArrayBuffer,
|
||||
bVertexPositions: new Float32Array(expandedBVertexPositions).buffer as ArrayBuffer,
|
||||
coverCurveIndices: new Uint32Array(expandedCoverCurveIndices).buffer as ArrayBuffer,
|
||||
coverInteriorIndices: new Uint32Array(expandedCoverInteriorIndices).buffer as
|
||||
ArrayBuffer,
|
||||
edgeLowerCurveIndices: new Uint32Array(expandedEdgeLowerCurveIndices).buffer as
|
||||
ArrayBuffer,
|
||||
edgeLowerLineIndices: new Uint32Array(expandedEdgeLowerLineIndices).buffer as
|
||||
ArrayBuffer,
|
||||
edgeUpperCurveIndices: new Uint32Array(expandedEdgeUpperCurveIndices).buffer as
|
||||
ArrayBuffer,
|
||||
edgeUpperLineIndices: new Uint32Array(expandedEdgeUpperLineIndices).buffer as
|
||||
ArrayBuffer,
|
||||
bQuads: expandedBQuadsBuffer,
|
||||
bVertexLoopBlinnData: expandedBVertexLoopBlinnDataBuffer,
|
||||
bVertexPathIDs: expandedBVertexPathIDsBuffer,
|
||||
bVertexPositions: expandedBVertexPositionsBuffer,
|
||||
coverCurveIndices: expandedCoverCurveIndicesBuffer,
|
||||
coverInteriorIndices: expandedCoverInteriorIndicesBuffer,
|
||||
edgeLowerCurveIndices: expandedEdgeLowerCurveIndicesBuffer,
|
||||
edgeLowerLineIndices: expandedEdgeLowerLineIndicesBuffer,
|
||||
edgeUpperCurveIndices: expandedEdgeUpperCurveIndicesBuffer,
|
||||
edgeUpperLineIndices: expandedEdgeUpperLineIndicesBuffer,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -264,15 +292,14 @@ function copyIndices(destIndices: number[],
|
|||
}
|
||||
}
|
||||
|
||||
function findFirstBQuadIndex(bQuads: Uint32Array, queryPathID: number): number | null {
|
||||
let low = 0, high = bQuads.length / B_QUAD_FIELD_COUNT;
|
||||
while (low < high) {
|
||||
const mid = low + (high - low) / 2;
|
||||
const thisPathID = bQuads[mid * B_QUAD_FIELD_COUNT];
|
||||
if (queryPathID <= thisPathID)
|
||||
high = mid;
|
||||
else
|
||||
low = mid + 1;
|
||||
function findFirstBQuadIndex(bQuads: Uint32Array,
|
||||
bVertexPathIDs: Uint16Array,
|
||||
queryPathID: number):
|
||||
number | null {
|
||||
for (let bQuadIndex = 0; bQuadIndex < bQuads.length / B_QUAD_FIELD_COUNT; bQuadIndex++) {
|
||||
const thisPathID = bVertexPathIDs[bQuads[bQuadIndex * B_QUAD_FIELD_COUNT]];
|
||||
if (thisPathID === queryPathID)
|
||||
return bQuadIndex;
|
||||
}
|
||||
return bQuads[low * B_QUAD_FIELD_COUNT] === queryPathID ? low : null;
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import {calculatePixelDescent, calculatePixelRectForGlyph, PathfinderFont} from
|
|||
import {BUILTIN_FONT_URI, calculatePixelXMin, GlyphStore, Hint, SimpleTextLayout} from "./text";
|
||||
import {assert, expectNotNull, panic, PathfinderError, scaleRect, UINT32_SIZE} from './utils';
|
||||
import {unwrapNull} from './utils';
|
||||
import { MonochromePathfinderView, Timings, TIMINGS } from './view';
|
||||
import {MonochromePathfinderView, Timings, TIMINGS} from './view';
|
||||
|
||||
const DEFAULT_TEXT: string =
|
||||
`’Twas brillig, and the slithy toves
|
||||
|
@ -206,11 +206,13 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
|||
|
||||
const glyphStore = new GlyphStore(font, uniqueGlyphIDs);
|
||||
glyphStore.partition().then(result => {
|
||||
const meshes = this.expandMeshes(result.meshes, uniqueGlyphIDs.length);
|
||||
|
||||
this.view.then(view => {
|
||||
this.font = font;
|
||||
this.layout = newLayout;
|
||||
this.glyphStore = glyphStore;
|
||||
this.meshes = result.meshes;
|
||||
this.meshes = meshes;
|
||||
|
||||
view.attachText();
|
||||
view.uploadPathColors(1);
|
||||
|
@ -219,6 +221,15 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
|||
});
|
||||
}
|
||||
|
||||
private expandMeshes(meshes: PathfinderMeshData, glyphCount: number): PathfinderMeshData {
|
||||
const pathIDs = [];
|
||||
for (let glyphIndex = 0; glyphIndex < glyphCount; glyphIndex++) {
|
||||
for (let subpixel = 0; subpixel < SUBPIXEL_GRANULARITY; subpixel++)
|
||||
pathIDs.push(glyphIndex + 1);
|
||||
}
|
||||
return meshes.expand(pathIDs);
|
||||
}
|
||||
|
||||
get atlas(): Atlas {
|
||||
return this._atlas;
|
||||
}
|
||||
|
@ -242,6 +253,10 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
|||
return this.hintingSelect.selectedIndex !== 0;
|
||||
}
|
||||
|
||||
get pathCount(): number {
|
||||
return this.glyphStore.glyphIDs.length * SUBPIXEL_GRANULARITY;
|
||||
}
|
||||
|
||||
protected get builtinFileURI(): string {
|
||||
return BUILTIN_FONT_URI;
|
||||
}
|
||||
|
@ -249,7 +264,6 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
|||
protected get defaultFile(): string {
|
||||
return DEFAULT_FONT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TextDemoView extends MonochromePathfinderView {
|
||||
|
@ -316,8 +330,7 @@ class TextDemoView extends MonochromePathfinderView {
|
|||
}
|
||||
|
||||
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
||||
const atlasGlyphs = this.appController.atlasGlyphs;
|
||||
const pathCount = atlasGlyphs.length;
|
||||
const pathCount = this.appController.pathCount;
|
||||
|
||||
const pathColors = new Uint8Array(4 * (pathCount + 1));
|
||||
|
||||
|
@ -331,15 +344,15 @@ class TextDemoView extends MonochromePathfinderView {
|
|||
}
|
||||
|
||||
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
||||
const glyphCount = this.appController.glyphStore.glyphIDs.length;
|
||||
const pathCount = this.appController.pathCount;
|
||||
const atlasGlyphs = this.appController.atlasGlyphs;
|
||||
const pixelsPerUnit = this.appController.pixelsPerUnit;
|
||||
|
||||
const transforms = new Float32Array((glyphCount + 1) * 4);
|
||||
const transforms = new Float32Array((pathCount + 1) * 4);
|
||||
|
||||
for (const glyph of atlasGlyphs) {
|
||||
const pathID = glyph.glyphStoreIndex + 1;
|
||||
const atlasOrigin = glyph.calculatePixelOrigin(pixelsPerUnit);
|
||||
const pathID = glyph.pathID;
|
||||
const atlasOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
|
||||
|
||||
transforms[pathID * 4 + 0] = pixelsPerUnit;
|
||||
transforms[pathID * 4 + 1] = pixelsPerUnit;
|
||||
|
@ -451,7 +464,10 @@ class TextDemoView extends MonochromePathfinderView {
|
|||
for (let glyphIndex = 0;
|
||||
glyphIndex < run.glyphIDs.length;
|
||||
glyphIndex++, globalGlyphIndex++) {
|
||||
const rect = run.pixelRectForGlyphAt(glyphIndex, pixelsPerUnit, hint);
|
||||
const rect = run.pixelRectForGlyphAt(glyphIndex,
|
||||
pixelsPerUnit,
|
||||
hint,
|
||||
SUBPIXEL_GRANULARITY);
|
||||
glyphPositions.set([
|
||||
rect[0], rect[3],
|
||||
rect[2], rect[3],
|
||||
|
@ -494,7 +510,10 @@ class TextDemoView extends MonochromePathfinderView {
|
|||
let atlasGlyphs = [];
|
||||
for (const run of textFrame.runs) {
|
||||
for (let glyphIndex = 0; glyphIndex < run.glyphIDs.length; glyphIndex++) {
|
||||
const pixelRect = run.pixelRectForGlyphAt(glyphIndex, pixelsPerUnit, hint);
|
||||
const pixelRect = run.pixelRectForGlyphAt(glyphIndex,
|
||||
pixelsPerUnit,
|
||||
hint,
|
||||
SUBPIXEL_GRANULARITY);
|
||||
if (!rectsIntersect(pixelRect, canvasRect))
|
||||
continue;
|
||||
|
||||
|
@ -503,7 +522,11 @@ class TextDemoView extends MonochromePathfinderView {
|
|||
if (glyphStoreIndex == null)
|
||||
continue;
|
||||
|
||||
const glyphKey = new GlyphKey(glyphID);
|
||||
const subpixel = run.subpixelForGlyphAt(glyphIndex,
|
||||
pixelsPerUnit,
|
||||
hint,
|
||||
SUBPIXEL_GRANULARITY);
|
||||
const glyphKey = new GlyphKey(glyphID, subpixel);
|
||||
atlasGlyphs.push(new AtlasGlyph(glyphStoreIndex, glyphKey));
|
||||
}
|
||||
}
|
||||
|
@ -519,12 +542,12 @@ class TextDemoView extends MonochromePathfinderView {
|
|||
this.uploadPathTransforms(1);
|
||||
|
||||
// TODO(pcwalton): Regenerate the IBOs to include only the glyphs we care about.
|
||||
const glyphCount = this.appController.glyphStore.glyphIDs.length;
|
||||
const pathHints = new Float32Array((glyphCount + 1) * 4);
|
||||
const pathCount = this.appController.pathCount;
|
||||
const pathHints = new Float32Array((pathCount + 1) * 4);
|
||||
|
||||
for (let glyphID = 0; glyphID < glyphCount; glyphID++) {
|
||||
pathHints[glyphID * 4 + 0] = hint.xHeight;
|
||||
pathHints[glyphID * 4 + 1] = hint.hintedXHeight;
|
||||
for (let pathID = 0; pathID < pathCount; pathID++) {
|
||||
pathHints[pathID * 4 + 0] = hint.xHeight;
|
||||
pathHints[pathID * 4 + 1] = hint.hintedXHeight;
|
||||
}
|
||||
|
||||
const pathHintsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathHints');
|
||||
|
@ -555,7 +578,7 @@ class TextDemoView extends MonochromePathfinderView {
|
|||
const hint = this.appController.createHint();
|
||||
const pixelsPerUnit = this.appController.pixelsPerUnit;
|
||||
|
||||
const atlasGlyphIDs = atlasGlyphs.map(atlasGlyph => atlasGlyph.glyphKey.id);
|
||||
const atlasGlyphKeys = atlasGlyphs.map(atlasGlyph => atlasGlyph.glyphKey.sortKey);
|
||||
|
||||
const glyphTexCoords = new Float32Array(textFrame.totalGlyphCount * 8);
|
||||
|
||||
|
@ -566,7 +589,14 @@ class TextDemoView extends MonochromePathfinderView {
|
|||
glyphIndex++, globalGlyphIndex++) {
|
||||
const textGlyphID = run.glyphIDs[glyphIndex];
|
||||
|
||||
const atlasGlyphIndex = _.sortedIndexOf(atlasGlyphIDs, textGlyphID);
|
||||
const subpixel = run.subpixelForGlyphAt(glyphIndex,
|
||||
pixelsPerUnit,
|
||||
hint,
|
||||
SUBPIXEL_GRANULARITY);
|
||||
|
||||
const glyphKey = new GlyphKey(textGlyphID, subpixel);
|
||||
|
||||
const atlasGlyphIndex = _.sortedIndexOf(atlasGlyphKeys, glyphKey.sortKey);
|
||||
if (atlasGlyphIndex < 0)
|
||||
continue;
|
||||
|
||||
|
@ -576,7 +606,7 @@ class TextDemoView extends MonochromePathfinderView {
|
|||
if (atlasGlyphMetrics == null)
|
||||
continue;
|
||||
|
||||
const atlasGlyphPixelOrigin = atlasGlyph.calculatePixelOrigin(pixelsPerUnit);
|
||||
const atlasGlyphPixelOrigin = atlasGlyph.calculateSubpixelOrigin(pixelsPerUnit);
|
||||
const atlasGlyphRect = calculatePixelRectForGlyph(atlasGlyphMetrics,
|
||||
atlasGlyphPixelOrigin,
|
||||
pixelsPerUnit,
|
||||
|
@ -674,7 +704,7 @@ class Atlas {
|
|||
continue;
|
||||
|
||||
glyph.setPixelLowerLeft(nextOrigin, metrics, pixelsPerUnit);
|
||||
let pixelOrigin = glyph.calculatePixelOrigin(pixelsPerUnit);
|
||||
let pixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
|
||||
nextOrigin[0] = calculatePixelRectForGlyph(metrics,
|
||||
pixelOrigin,
|
||||
pixelsPerUnit,
|
||||
|
@ -684,7 +714,7 @@ class Atlas {
|
|||
if (nextOrigin[0] > ATLAS_SIZE[0]) {
|
||||
nextOrigin = glmatrix.vec2.clone([1.0, shelfBottom + 1.0]);
|
||||
glyph.setPixelLowerLeft(nextOrigin, metrics, pixelsPerUnit);
|
||||
pixelOrigin = glyph.calculatePixelOrigin(pixelsPerUnit);
|
||||
pixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit);
|
||||
nextOrigin[0] = calculatePixelRectForGlyph(metrics,
|
||||
pixelOrigin,
|
||||
pixelsPerUnit,
|
||||
|
@ -700,7 +730,7 @@ class Atlas {
|
|||
}
|
||||
|
||||
// FIXME(pcwalton): Could be more precise if we don't have a full row.
|
||||
this._usedSize = glmatrix.vec2.fromValues(ATLAS_SIZE[0], shelfBottom);
|
||||
this._usedSize = glmatrix.vec2.clone([ATLAS_SIZE[0], shelfBottom]);
|
||||
}
|
||||
|
||||
ensureTexture(gl: WebGLRenderingContext): WebGLTexture {
|
||||
|
@ -740,10 +770,11 @@ class AtlasGlyph {
|
|||
this.origin = glmatrix.vec2.create();
|
||||
}
|
||||
|
||||
calculatePixelOrigin(pixelsPerUnit: number): glmatrix.vec2 {
|
||||
calculateSubpixelOrigin(pixelsPerUnit: number): glmatrix.vec2 {
|
||||
const pixelOrigin = glmatrix.vec2.create();
|
||||
glmatrix.vec2.scale(pixelOrigin, this.origin, pixelsPerUnit);
|
||||
glmatrix.vec2.round(pixelOrigin, pixelOrigin);
|
||||
pixelOrigin[0] += this.glyphKey.subpixel / SUBPIXEL_GRANULARITY;
|
||||
return pixelOrigin;
|
||||
}
|
||||
|
||||
|
@ -759,17 +790,23 @@ class AtlasGlyph {
|
|||
private setPixelOrigin(pixelOrigin: glmatrix.vec2, pixelsPerUnit: number): void {
|
||||
glmatrix.vec2.scale(this.origin, pixelOrigin, 1.0 / pixelsPerUnit);
|
||||
}
|
||||
|
||||
get pathID(): number {
|
||||
return this.glyphStoreIndex * SUBPIXEL_GRANULARITY + this.glyphKey.subpixel + 1;
|
||||
}
|
||||
}
|
||||
|
||||
class GlyphKey {
|
||||
readonly id: number;
|
||||
readonly subpixel: number;
|
||||
|
||||
constructor(id: number) {
|
||||
constructor(id: number, subpixel: number) {
|
||||
this.id = id;
|
||||
this.subpixel = subpixel;
|
||||
}
|
||||
|
||||
get sortKey(): number {
|
||||
return this.id;
|
||||
return this.id * SUBPIXEL_GRANULARITY + this.subpixel;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -107,25 +107,36 @@ export class TextRun {
|
|||
return textGlyphOrigin;
|
||||
}
|
||||
|
||||
pixelRectForGlyphAt(index: number, pixelsPerUnit: number, hint: Hint): glmatrix.vec4 {
|
||||
pixelRectForGlyphAt(index: number,
|
||||
pixelsPerUnit: number,
|
||||
hint: Hint,
|
||||
subpixelGranularity: number):
|
||||
glmatrix.vec4 {
|
||||
const metrics = unwrapNull(this.font.metricsForGlyph(this.glyphIDs[index]));
|
||||
const textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index, pixelsPerUnit, hint);
|
||||
|
||||
textGlyphOrigin[0] *= subpixelGranularity;
|
||||
glmatrix.vec2.round(textGlyphOrigin, textGlyphOrigin);
|
||||
textGlyphOrigin[0] /= subpixelGranularity;
|
||||
|
||||
return calculatePixelRectForGlyph(metrics, textGlyphOrigin, pixelsPerUnit, hint);
|
||||
}
|
||||
|
||||
subpixelForGlyphAt(index: number,
|
||||
pixelsPerUnit: number,
|
||||
hint: Hint,
|
||||
subpixelGranularity: number):
|
||||
number {
|
||||
const textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index, pixelsPerUnit, hint)[0];
|
||||
return Math.abs(Math.round(textGlyphOrigin * subpixelGranularity) % subpixelGranularity);
|
||||
}
|
||||
|
||||
get measure(): number {
|
||||
const lastGlyphID = _.last(this.glyphIDs), lastAdvance = _.last(this.advances);
|
||||
if (lastGlyphID == null || lastAdvance == null)
|
||||
return 0.0;
|
||||
return lastAdvance + this.font.opentypeFont.glyphs.get(lastGlyphID).advanceWidth;
|
||||
}
|
||||
|
||||
private pixelMetricsForGlyphAt(index: number, pixelsPerUnit: number, hint: Hint):
|
||||
PixelMetrics {
|
||||
const metrics = unwrapNull(this.font.metricsForGlyph(index));
|
||||
return calculatePixelMetricsForGlyph(metrics, pixelsPerUnit, hint);
|
||||
}
|
||||
}
|
||||
|
||||
export class TextFrame {
|
||||
|
@ -293,25 +304,25 @@ export function calculatePixelDescent(metrics: Metrics, pixelsPerUnit: number):
|
|||
return Math.ceil(-metrics.yMin * pixelsPerUnit);
|
||||
}
|
||||
|
||||
function calculatePixelMetricsForGlyph(metrics: Metrics, pixelsPerUnit: number, hint: Hint):
|
||||
PixelMetrics {
|
||||
function calculateSubpixelMetricsForGlyph(metrics: Metrics, pixelsPerUnit: number, hint: Hint):
|
||||
PixelMetrics {
|
||||
const top = hint.hintPosition(glmatrix.vec2.fromValues(0, metrics.yMax))[1];
|
||||
return {
|
||||
ascent: Math.ceil(top * pixelsPerUnit),
|
||||
descent: calculatePixelDescent(metrics, pixelsPerUnit),
|
||||
left: calculatePixelXMin(metrics, pixelsPerUnit),
|
||||
right: Math.ceil(metrics.xMax * pixelsPerUnit),
|
||||
ascent: top * pixelsPerUnit,
|
||||
descent: metrics.yMin * pixelsPerUnit,
|
||||
left: metrics.xMin * pixelsPerUnit,
|
||||
right: metrics.xMax * pixelsPerUnit,
|
||||
};
|
||||
}
|
||||
|
||||
export function calculatePixelRectForGlyph(metrics: Metrics,
|
||||
pixelOrigin: glmatrix.vec2,
|
||||
subpixelOrigin: glmatrix.vec2,
|
||||
pixelsPerUnit: number,
|
||||
hint: Hint):
|
||||
glmatrix.vec4 {
|
||||
const pixelMetrics = calculatePixelMetricsForGlyph(metrics, pixelsPerUnit, hint);
|
||||
return glmatrix.vec4.clone([pixelOrigin[0] + pixelMetrics.left,
|
||||
pixelOrigin[1] - pixelMetrics.descent,
|
||||
pixelOrigin[0] + pixelMetrics.right,
|
||||
pixelOrigin[1] + pixelMetrics.ascent]);
|
||||
}
|
||||
const pixelMetrics = calculateSubpixelMetricsForGlyph(metrics, pixelsPerUnit, hint);
|
||||
return glmatrix.vec4.clone([Math.floor(subpixelOrigin[0] + pixelMetrics.left),
|
||||
Math.floor(subpixelOrigin[1] + pixelMetrics.descent),
|
||||
Math.ceil(subpixelOrigin[0] + pixelMetrics.right),
|
||||
Math.ceil(subpixelOrigin[1] + pixelMetrics.ascent)]);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
|
||||
import * as glmatrix from 'gl-matrix';
|
||||
|
||||
export const FLOAT32_SIZE: number = 4;
|
||||
|
||||
export const UINT16_SIZE: number = 2;
|
||||
|
||||
export const UINT32_MAX: number = 0xffffffff;
|
||||
export const UINT32_SIZE: number = 4;
|
||||
|
||||
|
|
Loading…
Reference in New Issue