Implement some rudimentary text layout for the 3D demo
This commit is contained in:
parent
2cce20db10
commit
7de664e4a9
|
@ -9,6 +9,7 @@
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
|
import * as opentype from "opentype.js";
|
||||||
|
|
||||||
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
||||||
import {DemoAppController} from "./app-controller";
|
import {DemoAppController} from "./app-controller";
|
||||||
|
@ -16,12 +17,18 @@ import {PerspectiveCamera} from "./camera";
|
||||||
import {mat4, vec2} from "gl-matrix";
|
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, TextLayout, PathfinderGlyph} from "./text";
|
import {BUILTIN_FONT_URI, PathfinderGlyph, TextRun, TextLayout, GlyphStorage} from "./text";
|
||||||
import {PathfinderError, 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";
|
||||||
|
import * as _ from "lodash";
|
||||||
|
|
||||||
const TEXT: string = "Lorem ipsum dolor sit amet";
|
const WIDTH: number = 40000;
|
||||||
|
|
||||||
|
const TEXT: string[][] = [
|
||||||
|
[ "Lorem ipsum", "dolor sit amet" ],
|
||||||
|
[ "consectetur adipiscing elit." ],
|
||||||
|
];
|
||||||
|
|
||||||
const FONT: string = 'open-sans';
|
const FONT: string = 'open-sans';
|
||||||
|
|
||||||
|
@ -51,13 +58,39 @@ class ThreeDController extends DemoAppController<ThreeDView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fileLoaded(): void {
|
protected fileLoaded(): void {
|
||||||
this.layout = new TextLayout(this.fileData, TEXT, glyph => new ThreeDGlyph(glyph));
|
const font = opentype.parse(this.fileData);
|
||||||
this.layout.layoutText();
|
assert(font.isSupported(), "The font type is unsupported!");
|
||||||
this.layout.glyphStorage.partition().then((baseMeshes: PathfinderMeshData) => {
|
|
||||||
|
const createGlyph = (glyph: opentype.Glyph) => new ThreeDGlyph(glyph);
|
||||||
|
let textRuns = [];
|
||||||
|
for (let lineNumber = 0; lineNumber < TEXT.length; lineNumber++) {
|
||||||
|
const line = TEXT[lineNumber];
|
||||||
|
|
||||||
|
const lineY = -lineNumber * font.lineHeight();
|
||||||
|
const lineGlyphs = line.map(string => {
|
||||||
|
const glyphs = font.stringToGlyphs(string).map(createGlyph);
|
||||||
|
return { glyphs: glyphs, width: _.sumBy(glyphs, glyph => glyph.advanceWidth) };
|
||||||
|
});
|
||||||
|
|
||||||
|
const usedSpace = _.sumBy(lineGlyphs, 'width');
|
||||||
|
const emptySpace = Math.max(WIDTH - usedSpace, 0.0);
|
||||||
|
const spacing = emptySpace / Math.max(lineGlyphs.length - 1, 1);
|
||||||
|
|
||||||
|
let currentX = 0.0;
|
||||||
|
for (const glyphInfo of lineGlyphs) {
|
||||||
|
textRuns.push(new TextRun(glyphInfo.glyphs, [currentX, lineY], font, createGlyph));
|
||||||
|
currentX += glyphInfo.width + spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.glyphStorage = new GlyphStorage(this.fileData, textRuns, createGlyph, font);
|
||||||
|
this.glyphStorage.layoutRuns();
|
||||||
|
|
||||||
|
this.glyphStorage.partition().then((baseMeshes: PathfinderMeshData) => {
|
||||||
this.baseMeshes = baseMeshes;
|
this.baseMeshes = baseMeshes;
|
||||||
this.expandedMeshes = this.layout.glyphStorage.expandMeshes(baseMeshes).meshes;
|
this.expandedMeshes = this.glyphStorage.expandMeshes(baseMeshes).meshes;
|
||||||
this.view.then(view => {
|
this.view.then(view => {
|
||||||
view.uploadPathMetadata(this.layout.glyphStorage.textGlyphs.length);
|
view.uploadPathMetadata();
|
||||||
view.attachMeshes(this.expandedMeshes);
|
view.attachMeshes(this.expandedMeshes);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -77,7 +110,7 @@ class ThreeDController extends DemoAppController<ThreeDView> {
|
||||||
return FONT;
|
return FONT;
|
||||||
}
|
}
|
||||||
|
|
||||||
layout: TextLayout<ThreeDGlyph>;
|
glyphStorage: GlyphStorage<ThreeDGlyph>;
|
||||||
|
|
||||||
private baseMeshes: PathfinderMeshData;
|
private baseMeshes: PathfinderMeshData;
|
||||||
private expandedMeshes: PathfinderMeshData;
|
private expandedMeshes: PathfinderMeshData;
|
||||||
|
@ -95,8 +128,9 @@ class ThreeDView extends PathfinderDemoView {
|
||||||
this.camera.onChange = () => this.setDirty();
|
this.camera.onChange = () => this.setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadPathMetadata(pathCount: number) {
|
uploadPathMetadata() {
|
||||||
const textGlyphs = this.appController.layout.glyphStorage.textGlyphs;
|
const textGlyphs = this.appController.glyphStorage.allGlyphs;
|
||||||
|
const pathCount = textGlyphs.length;
|
||||||
|
|
||||||
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));
|
||||||
|
@ -121,7 +155,7 @@ class ThreeDView extends PathfinderDemoView {
|
||||||
aaLevel: number,
|
aaLevel: number,
|
||||||
subpixelAA: boolean):
|
subpixelAA: boolean):
|
||||||
AntialiasingStrategy {
|
AntialiasingStrategy {
|
||||||
if (aaType != 'ecaa')
|
if (aaType !== 'ecaa')
|
||||||
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
|
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
|
||||||
throw new PathfinderError("Unsupported antialiasing type!");
|
throw new PathfinderError("Unsupported antialiasing type!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,10 @@ import {B_QUAD_UPPER_RIGHT_VERTEX_OFFSET} from "./meshes";
|
||||||
import {B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET, B_QUAD_LOWER_LEFT_VERTEX_OFFSET} from "./meshes";
|
import {B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET, B_QUAD_LOWER_LEFT_VERTEX_OFFSET} from "./meshes";
|
||||||
import {B_QUAD_LOWER_RIGHT_VERTEX_OFFSET} from "./meshes";
|
import {B_QUAD_LOWER_RIGHT_VERTEX_OFFSET} from "./meshes";
|
||||||
import {B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET, PathfinderMeshData} from "./meshes";
|
import {B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET, PathfinderMeshData} from "./meshes";
|
||||||
import {BUILTIN_FONT_URI, GlyphStorage, PathfinderGlyph} from "./text";
|
import { BUILTIN_FONT_URI, GlyphStorage, PathfinderGlyph, TextRun } from "./text";
|
||||||
import {unwrapNull, UINT32_SIZE, UINT32_MAX} from "./utils";
|
import { unwrapNull, UINT32_SIZE, UINT32_MAX, assert } from "./utils";
|
||||||
import {PathfinderView} from "./view";
|
import {PathfinderView} from "./view";
|
||||||
|
import * as opentype from "opentype.js";
|
||||||
|
|
||||||
const CHARACTER: string = 'r';
|
const CHARACTER: string = 'r';
|
||||||
|
|
||||||
|
@ -39,9 +40,12 @@ class MeshDebuggerAppController extends AppController {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fileLoaded(): void {
|
protected fileLoaded(): void {
|
||||||
this.glyphStorage = new GlyphStorage(this.fileData,
|
const font = opentype.parse(this.fileData);
|
||||||
CHARACTER,
|
assert(font.isSupported(), "The font type is unsupported!");
|
||||||
glyph => new MeshDebuggerGlyph(glyph));
|
|
||||||
|
const createGlyph = (glyph: opentype.Glyph) => new MeshDebuggerGlyph(glyph);
|
||||||
|
const textRun = new TextRun<MeshDebuggerGlyph>(CHARACTER, [0, 0], font, createGlyph);
|
||||||
|
this.glyphStorage = new GlyphStorage(this.fileData, [textRun], createGlyph, font);
|
||||||
|
|
||||||
this.glyphStorage.partition().then(meshes => {
|
this.glyphStorage.partition().then(meshes => {
|
||||||
this.meshes = meshes;
|
this.meshes = meshes;
|
||||||
|
|
|
@ -103,6 +103,7 @@ type ShaderType = number;
|
||||||
declare module 'opentype.js' {
|
declare module 'opentype.js' {
|
||||||
interface Font {
|
interface Font {
|
||||||
isSupported(): boolean;
|
isSupported(): boolean;
|
||||||
|
lineHeight(): number;
|
||||||
}
|
}
|
||||||
interface Glyph {
|
interface Glyph {
|
||||||
getIndex(): number;
|
getIndex(): number;
|
||||||
|
@ -252,9 +253,9 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
|
|
||||||
/// Lays out glyphs on the canvas.
|
/// Lays out glyphs on the canvas.
|
||||||
private layoutGlyphs() {
|
private layoutGlyphs() {
|
||||||
this.appController.layout.layoutText();
|
this.appController.layout.layoutRuns();
|
||||||
|
|
||||||
const textGlyphs = this.appController.layout.glyphStorage.textGlyphs;
|
const textGlyphs = this.appController.layout.glyphStorage.allGlyphs;
|
||||||
const glyphPositions = new Float32Array(textGlyphs.length * 8);
|
const glyphPositions = new Float32Array(textGlyphs.length * 8);
|
||||||
const glyphIndices = new Uint32Array(textGlyphs.length * 6);
|
const glyphIndices = new Uint32Array(textGlyphs.length * 6);
|
||||||
|
|
||||||
|
@ -280,7 +281,7 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildAtlasGlyphs() {
|
private buildAtlasGlyphs() {
|
||||||
const textGlyphs = this.appController.layout.glyphStorage.textGlyphs;
|
const textGlyphs = this.appController.layout.glyphStorage.allGlyphs;
|
||||||
const pixelsPerUnit = this.appController.pixelsPerUnit;
|
const pixelsPerUnit = this.appController.pixelsPerUnit;
|
||||||
|
|
||||||
// Only build glyphs in view.
|
// Only build glyphs in view.
|
||||||
|
@ -338,7 +339,7 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private setGlyphTexCoords() {
|
private setGlyphTexCoords() {
|
||||||
const textGlyphs = this.appController.layout.glyphStorage.textGlyphs;
|
const textGlyphs = this.appController.layout.glyphStorage.allGlyphs;
|
||||||
const atlasGlyphs = this.appController.atlasGlyphs;
|
const atlasGlyphs = this.appController.atlasGlyphs;
|
||||||
|
|
||||||
const atlasGlyphIndices = atlasGlyphs.map(atlasGlyph => atlasGlyph.index);
|
const atlasGlyphIndices = atlasGlyphs.map(atlasGlyph => atlasGlyph.index);
|
||||||
|
@ -452,7 +453,7 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
this.gl.uniform1i(blitProgram.uniforms.uSource, 0);
|
this.gl.uniform1i(blitProgram.uniforms.uSource, 0);
|
||||||
this.setIdentityTexScaleUniform(blitProgram.uniforms);
|
this.setIdentityTexScaleUniform(blitProgram.uniforms);
|
||||||
this.gl.drawElements(this.gl.TRIANGLES,
|
this.gl.drawElements(this.gl.TRIANGLES,
|
||||||
this.appController.layout.glyphStorage.textGlyphs.length * 6,
|
this.appController.layout.glyphStorage.allGlyphs.length * 6,
|
||||||
this.gl.UNSIGNED_INT,
|
this.gl.UNSIGNED_INT,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,30 @@ export const BUILTIN_FONT_URI: string = "/otf/demo";
|
||||||
|
|
||||||
const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font";
|
const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font";
|
||||||
|
|
||||||
|
export class TextRun<Glyph extends PathfinderGlyph> {
|
||||||
|
constructor(text: string | Glyph[],
|
||||||
|
origin: number[],
|
||||||
|
font: Font,
|
||||||
|
createGlyph: CreateGlyphFn<Glyph>) {
|
||||||
|
if (typeof(text) === 'string')
|
||||||
|
text = font.stringToGlyphs(text).map(createGlyph);
|
||||||
|
|
||||||
|
this.glyphs = text;
|
||||||
|
this.origin = origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
layout() {
|
||||||
|
let currentX = this.origin[0];
|
||||||
|
for (const glyph of this.glyphs) {
|
||||||
|
glyph.origin = glmatrix.vec2.fromValues(currentX, this.origin[1]);
|
||||||
|
currentX += glyph.advanceWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly glyphs: Glyph[];
|
||||||
|
readonly origin: number[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface ExpandedMeshData {
|
export interface ExpandedMeshData {
|
||||||
meshes: PathfinderMeshData;
|
meshes: PathfinderMeshData;
|
||||||
}
|
}
|
||||||
|
@ -38,9 +62,14 @@ opentype.Font.prototype.isSupported = function() {
|
||||||
return (this as any).supported;
|
return (this as any).supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opentype.Font.prototype.lineHeight = function() {
|
||||||
|
const os2Table = this.tables.os2;
|
||||||
|
return os2Table.sTypoAscender - os2Table.sTypoDescender + os2Table.sTypoLineGap;
|
||||||
|
};
|
||||||
|
|
||||||
export class GlyphStorage<Glyph extends PathfinderGlyph> {
|
export class GlyphStorage<Glyph extends PathfinderGlyph> {
|
||||||
constructor(fontData: ArrayBuffer,
|
constructor(fontData: ArrayBuffer,
|
||||||
textGlyphs: Glyph[] | string,
|
textRuns: TextRun<Glyph>[],
|
||||||
createGlyph: CreateGlyphFn<Glyph>,
|
createGlyph: CreateGlyphFn<Glyph>,
|
||||||
font?: Font) {
|
font?: Font) {
|
||||||
if (font == null) {
|
if (font == null) {
|
||||||
|
@ -48,15 +77,12 @@ export class GlyphStorage<Glyph extends PathfinderGlyph> {
|
||||||
assert(font.isSupported(), "The font type is unsupported!");
|
assert(font.isSupported(), "The font type is unsupported!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof(textGlyphs) === 'string')
|
|
||||||
textGlyphs = font.stringToGlyphs(textGlyphs).map(createGlyph);
|
|
||||||
|
|
||||||
this.fontData = fontData;
|
this.fontData = fontData;
|
||||||
this.textGlyphs = textGlyphs;
|
this.textRuns = textRuns;
|
||||||
this.font = font;
|
this.font = font;
|
||||||
|
|
||||||
// Determine all glyphs potentially needed.
|
// Determine all glyphs potentially needed.
|
||||||
this.uniqueGlyphs = this.textGlyphs.map(textGlyph => textGlyph);
|
this.uniqueGlyphs = _.flatMap(this.textRuns, textRun => textRun.glyphs);
|
||||||
this.uniqueGlyphs.sort((a, b) => a.index - b.index);
|
this.uniqueGlyphs.sort((a, b) => a.index - b.index);
|
||||||
this.uniqueGlyphs = _.sortedUniqBy(this.uniqueGlyphs, glyph => glyph.index);
|
this.uniqueGlyphs = _.sortedUniqBy(this.uniqueGlyphs, glyph => glyph.index);
|
||||||
}
|
}
|
||||||
|
@ -106,56 +132,60 @@ export class GlyphStorage<Glyph extends PathfinderGlyph> {
|
||||||
const expandedCoverInteriorIndices: number[] = [];
|
const expandedCoverInteriorIndices: number[] = [];
|
||||||
const expandedCoverCurveIndices: number[] = [];
|
const expandedCoverCurveIndices: number[] = [];
|
||||||
|
|
||||||
for (let textGlyphIndex = 0; textGlyphIndex < this.textGlyphs.length; textGlyphIndex++) {
|
let textGlyphIndex = 0;
|
||||||
const textGlyph = this.textGlyphs[textGlyphIndex];
|
for (const textRun of this.textRuns) {
|
||||||
const uniqueGlyphIndex = _.sortedIndexBy(this.uniqueGlyphs, textGlyph, 'index');
|
for (const textGlyph of textRun.glyphs) {
|
||||||
if (uniqueGlyphIndex < 0)
|
const uniqueGlyphIndex = _.sortedIndexBy(this.uniqueGlyphs, textGlyph, 'index');
|
||||||
continue;
|
if (uniqueGlyphIndex < 0)
|
||||||
const firstBVertexIndex = _.sortedIndex(bVertexPathIDs, uniqueGlyphIndex + 1);
|
continue;
|
||||||
if (firstBVertexIndex < 0)
|
const firstBVertexIndex = _.sortedIndex(bVertexPathIDs, uniqueGlyphIndex + 1);
|
||||||
continue;
|
if (firstBVertexIndex < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
// Copy over vertices.
|
// Copy over vertices.
|
||||||
let bVertexIndex = firstBVertexIndex;
|
let bVertexIndex = firstBVertexIndex;
|
||||||
const firstExpandedBVertexIndex = expandedBVertexPathIDs.length;
|
const firstExpandedBVertexIndex = expandedBVertexPathIDs.length;
|
||||||
while (bVertexIndex < bVertexPathIDs.length &&
|
while (bVertexIndex < bVertexPathIDs.length &&
|
||||||
bVertexPathIDs[bVertexIndex] === uniqueGlyphIndex + 1) {
|
bVertexPathIDs[bVertexIndex] === uniqueGlyphIndex + 1) {
|
||||||
expandedBVertexPositions.push(bVertexPositions[bVertexIndex * 2 + 0],
|
expandedBVertexPositions.push(bVertexPositions[bVertexIndex * 2 + 0],
|
||||||
bVertexPositions[bVertexIndex * 2 + 1]);
|
bVertexPositions[bVertexIndex * 2 + 1]);
|
||||||
expandedBVertexPathIDs.push(textGlyphIndex + 1);
|
expandedBVertexPathIDs.push(textGlyphIndex + 1);
|
||||||
expandedBVertexLoopBlinnData.push(bVertexLoopBlinnData[bVertexIndex]);
|
expandedBVertexLoopBlinnData.push(bVertexLoopBlinnData[bVertexIndex]);
|
||||||
bVertexIndex++;
|
bVertexIndex++;
|
||||||
}
|
|
||||||
|
|
||||||
// Copy over indices.
|
|
||||||
copyIndices(expandedCoverInteriorIndices,
|
|
||||||
new Uint32Array(meshes.coverInteriorIndices),
|
|
||||||
firstExpandedBVertexIndex,
|
|
||||||
firstBVertexIndex,
|
|
||||||
bVertexIndex);
|
|
||||||
copyIndices(expandedCoverCurveIndices,
|
|
||||||
new Uint32Array(meshes.coverCurveIndices),
|
|
||||||
firstExpandedBVertexIndex,
|
|
||||||
firstBVertexIndex,
|
|
||||||
bVertexIndex);
|
|
||||||
|
|
||||||
// Copy over B-quads.
|
|
||||||
let firstBQuadIndex =
|
|
||||||
_.findIndex(bQuads, bQuad => bVertexPathIDs[bQuad[0]] == uniqueGlyphIndex + 1);
|
|
||||||
if (firstBQuadIndex < 0)
|
|
||||||
firstBQuadIndex = bQuads.length;
|
|
||||||
const indexDelta = firstExpandedBVertexIndex - firstBVertexIndex;
|
|
||||||
for (let bQuadIndex = firstBQuadIndex; bQuadIndex < bQuads.length; bQuadIndex++) {
|
|
||||||
const bQuad = bQuads[bQuadIndex];
|
|
||||||
if (bVertexPathIDs[bQuad[0]] !== uniqueGlyphIndex + 1)
|
|
||||||
break;
|
|
||||||
for (let indexIndex = 0; indexIndex < B_QUAD_SIZE / UINT32_SIZE; indexIndex++) {
|
|
||||||
const srcIndex = bQuad[indexIndex];
|
|
||||||
if (srcIndex === UINT32_MAX)
|
|
||||||
expandedBQuads.push(srcIndex);
|
|
||||||
else
|
|
||||||
expandedBQuads.push(srcIndex + indexDelta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy over indices.
|
||||||
|
copyIndices(expandedCoverInteriorIndices,
|
||||||
|
new Uint32Array(meshes.coverInteriorIndices),
|
||||||
|
firstExpandedBVertexIndex,
|
||||||
|
firstBVertexIndex,
|
||||||
|
bVertexIndex);
|
||||||
|
copyIndices(expandedCoverCurveIndices,
|
||||||
|
new Uint32Array(meshes.coverCurveIndices),
|
||||||
|
firstExpandedBVertexIndex,
|
||||||
|
firstBVertexIndex,
|
||||||
|
bVertexIndex);
|
||||||
|
|
||||||
|
// Copy over B-quads.
|
||||||
|
let firstBQuadIndex =
|
||||||
|
_.findIndex(bQuads, bQuad => bVertexPathIDs[bQuad[0]] == uniqueGlyphIndex + 1);
|
||||||
|
if (firstBQuadIndex < 0)
|
||||||
|
firstBQuadIndex = bQuads.length;
|
||||||
|
const indexDelta = firstExpandedBVertexIndex - firstBVertexIndex;
|
||||||
|
for (let bQuadIndex = firstBQuadIndex; bQuadIndex < bQuads.length; bQuadIndex++) {
|
||||||
|
const bQuad = bQuads[bQuadIndex];
|
||||||
|
if (bVertexPathIDs[bQuad[0]] !== uniqueGlyphIndex + 1)
|
||||||
|
break;
|
||||||
|
for (let indexIndex = 0; indexIndex < B_QUAD_SIZE / UINT32_SIZE; indexIndex++) {
|
||||||
|
const srcIndex = bQuad[indexIndex];
|
||||||
|
if (srcIndex === UINT32_MAX)
|
||||||
|
expandedBQuads.push(srcIndex);
|
||||||
|
else
|
||||||
|
expandedBQuads.push(srcIndex + indexDelta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textGlyphIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,9 +208,17 @@ export class GlyphStorage<Glyph extends PathfinderGlyph> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layoutRuns() {
|
||||||
|
this.textRuns.forEach(textRun => textRun.layout());
|
||||||
|
}
|
||||||
|
|
||||||
|
get allGlyphs(): Glyph[] {
|
||||||
|
return _.flatMap(this.textRuns, textRun => textRun.glyphs);
|
||||||
|
}
|
||||||
|
|
||||||
readonly fontData: ArrayBuffer;
|
readonly fontData: ArrayBuffer;
|
||||||
readonly font: Font;
|
readonly font: Font;
|
||||||
readonly textGlyphs: Glyph[];
|
readonly textRuns: TextRun<Glyph>[];
|
||||||
readonly uniqueGlyphs: Glyph[];
|
readonly uniqueGlyphs: Glyph[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,34 +227,25 @@ export class TextLayout<Glyph extends PathfinderGlyph> {
|
||||||
const font = opentype.parse(fontData);
|
const font = opentype.parse(fontData);
|
||||||
assert(font.isSupported(), "The font type is unsupported!");
|
assert(font.isSupported(), "The font type is unsupported!");
|
||||||
|
|
||||||
this.lineGlyphs = text.split("\n").map(line => font.stringToGlyphs(line).map(createGlyph));
|
const os2Table = font.tables.os2;
|
||||||
|
|
||||||
const textGlyphs = _.flatten(this.lineGlyphs);
|
|
||||||
this.glyphStorage = new GlyphStorage(fontData, textGlyphs, createGlyph, font);
|
|
||||||
}
|
|
||||||
|
|
||||||
layoutText() {
|
|
||||||
const os2Table = this.glyphStorage.font.tables.os2;
|
|
||||||
const lineHeight = os2Table.sTypoAscender - os2Table.sTypoDescender +
|
const lineHeight = os2Table.sTypoAscender - os2Table.sTypoDescender +
|
||||||
os2Table.sTypoLineGap;
|
os2Table.sTypoLineGap;
|
||||||
|
this.textRuns = text.split("\n").map((line, lineNumber) => {
|
||||||
|
return new TextRun<Glyph>(line, [0.0, -lineHeight * lineNumber], font, createGlyph);
|
||||||
|
});
|
||||||
|
|
||||||
const currentPosition = glmatrix.vec2.create();
|
this.glyphStorage = new GlyphStorage(fontData, this.textRuns, createGlyph, font);
|
||||||
|
|
||||||
let glyphIndex = 0;
|
|
||||||
for (const line of this.lineGlyphs) {
|
|
||||||
for (let lineCharIndex = 0; lineCharIndex < line.length; lineCharIndex++) {
|
|
||||||
const textGlyph = this.glyphStorage.textGlyphs[glyphIndex];
|
|
||||||
textGlyph.origin = glmatrix.vec2.clone(currentPosition);
|
|
||||||
currentPosition[0] += textGlyph.advanceWidth;
|
|
||||||
glyphIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPosition[0] = 0;
|
|
||||||
currentPosition[1] -= lineHeight;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly lineGlyphs: Glyph[][];
|
layoutRuns() {
|
||||||
|
this.textRuns.forEach(textRun => textRun.layout());
|
||||||
|
}
|
||||||
|
|
||||||
|
get allGlyphs(): Glyph[] {
|
||||||
|
return _.flatMap(this.textRuns, textRun => textRun.glyphs);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly textRuns: TextRun<Glyph>[];
|
||||||
readonly glyphStorage: GlyphStorage<Glyph>;
|
readonly glyphStorage: GlyphStorage<Glyph>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue