p5
Version:
[](https://www.npmjs.com/package/p5)
882 lines (793 loc) • 28.7 kB
JavaScript
import { a7 as TEXTURE, c as TRIANGLES } from '../constants-DwbuOBz3.js';
import { R as Renderer3D } from '../rendering-C5SM-3b6.js';
import { Vector } from '../math/p5.Vector.js';
import { Geometry } from './p5.Geometry.js';
import { Font, arrayCommandsToObjects } from '../type/p5.Font.js';
import '../creating_reading-Be7_6X4p.js';
import 'colorjs.io/fn';
import '../color/color_spaces/hsb.js';
import '../strands/ir_types.js';
import '../dom/p5.Element.js';
import '../dom/p5.File.js';
import '../io/p5.XML.js';
import '../p5.Renderer-N-APumjv.js';
import '../image/filters.js';
import '../shape/custom_shapes.js';
import '../core/States.js';
import '../io/utilities.js';
import '../dom/p5.MediaElement.js';
import '../shape/2d_primitives.js';
import '../core/helpers.js';
import '../shape/attributes.js';
import '../shape/curves.js';
import '../shape/vertex.js';
import '../color/setting.js';
import 'omggif';
import '../io/csv.js';
import '../image/pixels.js';
import '../core/transform.js';
import './GeometryBuilder.js';
import '../math/p5.Matrix.js';
import '../math/Matrices/Matrix.js';
import '../math/Matrices/MatrixInterface.js';
import './p5.Quat.js';
import './ShapeBuilder.js';
import 'libtess';
import './p5.RenderBuffer.js';
import './GeometryBufferCache.js';
import '../image/const.js';
import './p5.DataArray.js';
import '../type/textCore.js';
import '../core/filterShaders.js';
import './enums.js';
import './p5.Shader.js';
import '../math/trigonometry.js';
import '@japont/unicode-range';
import '../type/unicodeRanges.js';
import '../type/lib/Typr.js';
import 'pako';
import '@davepagurek/bezier-path';
function text(p5, fn) {
Renderer3D.prototype.maxCachedGlyphs = function() {
// TODO: use more than vibes to find a good value for this
return 200;
};
Font.prototype._getFontInfo = function(axs) {
// For WebGL, a cache of font data to use on the GPU.
this._fontInfos = this._fontInfos || {};
const key = JSON.stringify(axs);
if (this._fontInfos[key]) {
const val = this._fontInfos[key];
return val;
} else {
const val = new FontInfo(this, { axs });
this._fontInfos[key] = val;
return val;
}
};
// rendering constants
// the number of rows/columns dividing each glyph
const charGridWidth = 9;
const charGridHeight = charGridWidth;
// size of the image holding the bezier stroke info
const strokeImageWidth = 64;
const strokeImageHeight = 64;
// size of the image holding the stroke indices for each row/col
const gridImageWidth = 64;
const gridImageHeight = 64;
// size of the image holding the offset/length of each row/col stripe
const cellImageWidth = 64;
const cellImageHeight = 64;
/**
* @private
* @class ImageInfos
* @param {Integer} width
* @param {Integer} height
*
* the ImageInfos class holds a list of ImageDatas of a given size.
*/
class ImageInfos {
constructor(width, height) {
this.width = width;
this.height = height;
this.infos = []; // the list of images
}
/**
*
* @param {Integer} space
* @return {Object} contains the ImageData, and pixel index into that
* ImageData where the free space was allocated.
*
* finds free space of a given size in the ImageData list
*/
findImage(space) {
const imageSize = this.width * this.height;
if (space > imageSize)
throw new Error('font is too complex to render in 3D');
// search through the list of images, looking for one with
// anough unused space.
let imageInfo, imageData;
for (let ii = this.infos.length - 1; ii >= 0; --ii) {
const imageInfoTest = this.infos[ii];
if (imageInfoTest.index + space < imageSize) {
// found one
imageInfo = imageInfoTest;
imageData = imageInfoTest.imageData;
break;
}
}
if (!imageInfo) {
try {
// create a new image
imageData = new ImageData(this.width, this.height);
} catch (err) {
// for browsers that don't support ImageData constructors (ie IE11)
// create an ImageData using the old method
let canvas = document.getElementsByTagName('canvas')[0];
const created = !canvas;
if (!canvas) {
// create a temporary canvas
canvas = document.createElement('canvas');
canvas.style.display = 'none';
document.body.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
if (ctx) {
imageData = ctx.createImageData(this.width, this.height);
}
if (created) {
// distroy the temporary canvas, if necessary
document.body.removeChild(canvas);
}
}
// construct & dd the new image info
imageInfo = { index: 0, imageData };
this.infos.push(imageInfo);
}
const index = imageInfo.index;
imageInfo.index += space; // move to the start of the next image
imageData._dirty = true;
return { imageData, index };
}
}
/**
* @function setPixel
* @private
* @param {Object} imageInfo
* @param {Number} r
* @param {Number} g
* @param {Number} b
* @param {Number} a
*
* writes the next pixel into an indexed ImageData
*/
function setPixel(imageInfo, r, g, b, a) {
const imageData = imageInfo.imageData;
const pixels = imageData.data;
let index = imageInfo.index++ * 4;
pixels[index++] = r;
pixels[index++] = g;
pixels[index++] = b;
pixels[index++] = a;
}
const SQRT3 = Math.sqrt(3);
/**
* @private
* @class FontInfo
* @param {Object} font an opentype.js font object
*
* contains cached images and glyph information for an opentype font
*/
class FontInfo {
constructor(font, { axs } = {}) {
this.font = font;
this.axs = axs;
// the bezier curve coordinates
this.strokeImageInfos = new ImageInfos(
strokeImageWidth,
strokeImageHeight
);
// lists of curve indices for each row/column slice
this.colDimImageInfos = new ImageInfos(gridImageWidth, gridImageHeight);
this.rowDimImageInfos = new ImageInfos(gridImageWidth, gridImageHeight);
// the offset & length of each row/col slice in the glyph
this.colCellImageInfos = new ImageInfos(cellImageWidth, cellImageHeight);
this.rowCellImageInfos = new ImageInfos(cellImageWidth, cellImageHeight);
// the cached information for each glyph
this.glyphInfos = {};
}
/**
* @param {Glyph} glyph the x positions of points in the curve
* @returns {Object} the glyphInfo for that glyph
*
* calculates rendering info for a glyph, including the curve information,
* row & column stripes compiled into textures.
*/
getGlyphInfo(glyph) {
// check the cache
let gi = this.glyphInfos[glyph.index];
if (gi) return gi;
const axs = this.axs;
const {
glyph: {
path: { commands }
}
} = this.font._singleShapeToPath(glyph.shape, { axs });
let xMin = Infinity;
let xMax = -Infinity;
let yMin = Infinity;
let yMax = -Infinity;
for (const cmd of commands) {
for (let i = 1; i < cmd.length; i += 2) {
xMin = Math.min(xMin, cmd[i]);
xMax = Math.max(xMax, cmd[i]);
yMin = Math.min(yMin, cmd[i + 1]);
yMax = Math.max(yMax, cmd[i + 1]);
}
}
// don't bother rendering invisible glyphs
if (xMin >= xMax || yMin >= yMax || !commands.length) {
return (this.glyphInfos[glyph.index] = {});
}
const gWidth = xMax - xMin;
const gHeight = yMax - yMin;
// Convert arrays to named objects
const cmds = arrayCommandsToObjects(commands);
let i;
const strokes = []; // the strokes in this glyph
const rows = []; // the indices of strokes in each row
const cols = []; // the indices of strokes in each column
for (i = charGridWidth - 1; i >= 0; --i) cols.push([]);
for (i = charGridHeight - 1; i >= 0; --i) rows.push([]);
/**
* @function push
* @private
* @param {Number[]} xs the x positions of points in the curve
* @param {Number[]} ys the y positions of points in the curve
* @param {Object} v the curve information
*
* adds a curve to the rows & columns that it intersects with
*/
function push(xs, ys, v) {
const index = strokes.length; // the index of this stroke
strokes.push(v); // add this stroke to the list
/**
* @function minMax
* @private
* @param {Number[]} rg the list of values to compare
* @param {Number} min the initial minimum value
* @param {Number} max the initial maximum value
*
* find the minimum & maximum value in a list of values
*/
function minMax(rg, min, max) {
for (let i = rg.length; i-- > 0; ) {
const v = rg[i];
if (min > v) min = v;
if (max < v) max = v;
}
return { min, max };
}
// Expand the bounding box of the glyph by the number of cells below
// before rounding. Curves only partially through a cell won't be added
// to adjacent cells, but ones that are close will be. This helps fix
// small visual glitches that occur when curves are close to grid cell
// boundaries.
const cellOffset = 0.5;
// loop through the rows & columns that the curve intersects
// adding the curve to those slices
const mmX = minMax(xs, 1, 0);
const ixMin = Math.max(
Math.floor(mmX.min * charGridWidth - cellOffset),
0
);
const ixMax = Math.min(
Math.ceil(mmX.max * charGridWidth + cellOffset),
charGridWidth
);
for (let iCol = ixMin; iCol < ixMax; ++iCol) cols[iCol].push(index);
const mmY = minMax(ys, 1, 0);
const iyMin = Math.max(
Math.floor(mmY.min * charGridHeight - cellOffset),
0
);
const iyMax = Math.min(
Math.ceil(mmY.max * charGridHeight + cellOffset),
charGridHeight
);
for (let iRow = iyMin; iRow < iyMax; ++iRow) rows[iRow].push(index);
}
/**
* @function clamp
* @private
* @param {Number} v the value to clamp
* @param {Number} min the minimum value
* @param {Number} max the maxmimum value
*
* clamps a value between a minimum & maximum value
*/
function clamp(v, min, max) {
if (v < min) return min;
if (v > max) return max;
return v;
}
/**
* @function byte
* @private
* @param {Number} v the value to scale
*
* converts a floating-point number in the range 0-1 to a byte 0-255
*/
function byte(v) {
return clamp(255 * v, 0, 255);
}
/**
* @private
* @class Cubic
* @param {Number} p0 the start point of the curve
* @param {Number} c0 the first control point
* @param {Number} c1 the second control point
* @param {Number} p1 the end point
*
* a cubic curve
*/
class Cubic {
constructor(p0, c0, c1, p1) {
this.p0 = p0;
this.c0 = c0;
this.c1 = c1;
this.p1 = p1;
}
/**
* @return {Object} the quadratic approximation
*
* converts the cubic to a quadtratic approximation by
* picking an appropriate quadratic control point
*/
toQuadratic() {
return {
x: this.p0.x,
y: this.p0.y,
x1: this.p1.x,
y1: this.p1.y,
cx: ((this.c0.x + this.c1.x) * 3 - (this.p0.x + this.p1.x)) / 4,
cy: ((this.c0.y + this.c1.y) * 3 - (this.p0.y + this.p1.y)) / 4
};
}
/**
* @return {Number} the error
*
* calculates the magnitude of error of this curve's
* quadratic approximation.
*/
quadError() {
return (
Vector.sub(
Vector.sub(this.p1, this.p0),
Vector.mult(Vector.sub(this.c1, this.c0), 3)
).mag() / 2
);
}
/**
* @param {Number} t the value (0-1) at which to split
* @return {Cubic} the second part of the curve
*
* splits the cubic into two parts at a point 't' along the curve.
* this cubic keeps its start point and its end point becomes the
* point at 't'. the 'end half is returned.
*/
split(t) {
const m1 = Vector.lerp(this.p0, this.c0, t);
const m2 = Vector.lerp(this.c0, this.c1, t);
const mm1 = Vector.lerp(m1, m2, t);
this.c1 = Vector.lerp(this.c1, this.p1, t);
this.c0 = Vector.lerp(m2, this.c1, t);
const pt = Vector.lerp(mm1, this.c0, t);
const part1 = new Cubic(this.p0, m1, mm1, pt);
this.p0 = pt;
return part1;
}
/**
* @return {Cubic[]} the non-inflecting pieces of this cubic
*
* returns an array containing 0, 1 or 2 cubics split resulting
* from splitting this cubic at its inflection points.
* this cubic is (potentially) altered and returned in the list.
*/
splitInflections() {
const a = Vector.sub(this.c0, this.p0);
const b = Vector.sub(Vector.sub(this.c1, this.c0), a);
const c = Vector.sub(
Vector.sub(Vector.sub(this.p1, this.c1), a),
Vector.mult(b, 2)
);
const cubics = [];
// find the derivative coefficients
let A = b.x * c.y - b.y * c.x;
if (A !== 0) {
let B = a.x * c.y - a.y * c.x;
let C = a.x * b.y - a.y * b.x;
const disc = B * B - 4 * A * C;
if (disc >= 0) {
if (A < 0) {
A = -A;
B = -B;
C = -C;
}
const Q = Math.sqrt(disc);
const t0 = (-B - Q) / (2 * A); // the first inflection point
let t1 = (-B + Q) / (2 * A); // the second inflection point
// test if the first inflection point lies on the curve
if (t0 > 0 && t0 < 1) {
// split at the first inflection point
cubics.push(this.split(t0));
// scale t2 into the second part
t1 = 1 - (1 - t1) / (1 - t0);
}
// test if the second inflection point lies on the curve
if (t1 > 0 && t1 < 1) {
// split at the second inflection point
cubics.push(this.split(t1));
}
}
}
cubics.push(this);
return cubics;
}
}
/**
* @function cubicToQuadratics
* @private
* @param {Number} x0
* @param {Number} y0
* @param {Number} cx0
* @param {Number} cy0
* @param {Number} cx1
* @param {Number} cy1
* @param {Number} x1
* @param {Number} y1
* @returns {Cubic[]} an array of cubics whose quadratic approximations
* closely match the civen cubic.
*
* converts a cubic curve to a list of quadratics.
*/
function cubicToQuadratics(x0, y0, cx0, cy0, cx1, cy1, x1, y1) {
// create the Cubic object and split it at its inflections
const cubics = new Cubic(
new Vector(x0, y0),
new Vector(cx0, cy0),
new Vector(cx1, cy1),
new Vector(x1, y1)
).splitInflections();
const qs = []; // the final list of quadratics
const precision = 30 / SQRT3;
// for each of the non-inflected pieces of the original cubic
for (let cubic of cubics) {
// the cubic is iteratively split in 3 pieces:
// the first piece is accumulated in 'qs', the result.
// the last piece is accumulated in 'tail', temporarily.
// the middle piece is repeatedly split again, while necessary.
const tail = [];
let t3;
for (;;) {
// calculate this cubic's precision
t3 = precision / cubic.quadError();
if (t3 >= 0.5 * 0.5 * 0.5) {
break; // not too bad, we're done
}
// find a split point based on the error
const t = Math.pow(t3, 1.0 / 3.0);
// split the cubic in 3
const start = cubic.split(t);
const middle = cubic.split(1 - t / (1 - t));
qs.push(start); // the first part
tail.push(cubic); // the last part
cubic = middle; // iterate on the middle piece
}
if (t3 < 1) {
// a little excess error, split the middle in two
qs.push(cubic.split(0.5));
}
// add the middle piece to the result
qs.push(cubic);
// finally add the tail, reversed, onto the result
Array.prototype.push.apply(qs, tail.reverse());
}
return qs;
}
/**
* @function pushLine
* @private
* @param {Number} x0
* @param {Number} y0
* @param {Number} x1
* @param {Number} y1
*
* add a straight line to the row/col grid of a glyph
*/
function pushLine(x0, y0, x1, y1) {
const mx = (x0 + x1) / 2;
const my = (y0 + y1) / 2;
push([x0, x1], [y0, y1], { x: x0, y: y0, cx: mx, cy: my });
}
/**
* @function samePoint
* @private
* @param {Number} x0
* @param {Number} y0
* @param {Number} x1
* @param {Number} y1
* @return {Boolean} true if the two points are sufficiently close
*
* tests if two points are close enough to be considered the same
*/
function samePoint(x0, y0, x1, y1) {
return Math.abs(x1 - x0) < 0.00001 && Math.abs(y1 - y0) < 0.00001;
}
let x0, y0, xs, ys;
for (const cmd of cmds) {
// scale the coordinates to the range 0-1
const x1 = (cmd.x - xMin) / gWidth;
const y1 = (cmd.y - yMin) / gHeight;
// don't bother if this point is the same as the last
if (samePoint(x0, y0, x1, y1)) continue;
switch (cmd.type) {
case 'M': {
// move
xs = x1;
ys = y1;
break;
}
case 'L': {
// line
pushLine(x0, y0, x1, y1);
break;
}
case 'Q': {
// quadratic
const cx = (cmd.x1 - xMin) / gWidth;
const cy = (cmd.y1 - yMin) / gHeight;
push([x0, x1, cx], [y0, y1, cy], { x: x0, y: y0, cx, cy });
break;
}
case 'Z': {
// end
if (!samePoint(x0, y0, xs, ys)) {
// add an extra line closing the loop, if necessary
pushLine(x0, y0, xs, ys);
strokes.push({ x: xs, y: ys });
} else {
strokes.push({ x: x0, y: y0 });
}
break;
}
case 'C': {
// cubic
const cx1 = (cmd.x1 - xMin) / gWidth;
const cy1 = (cmd.y1 - yMin) / gHeight;
const cx2 = (cmd.x2 - xMin) / gWidth;
const cy2 = (cmd.y2 - yMin) / gHeight;
const qs = cubicToQuadratics(x0, y0, cx1, cy1, cx2, cy2, x1, y1);
for (let iq = 0; iq < qs.length; iq++) {
const q = qs[iq].toQuadratic();
push([q.x, q.x1, q.cx], [q.y, q.y1, q.cy], q);
}
break;
}
default:
throw new Error(`unknown command type: ${cmd.type}`);
}
x0 = x1;
y0 = y1;
}
// allocate space for the strokes
const strokeCount = strokes.length;
const strokeImageInfo = this.strokeImageInfos.findImage(strokeCount);
const strokeOffset = strokeImageInfo.index;
// fill the stroke image
for (let il = 0; il < strokeCount; ++il) {
const s = strokes[il];
setPixel(strokeImageInfo, byte(s.x), byte(s.y), byte(s.cx), byte(s.cy));
}
/**
* @function layout
* @private
* @param {Number[][]} dim
* @param {ImageInfos} dimImageInfos
* @param {ImageInfos} cellImageInfos
* @return {Object}
*
* lays out the curves in a dimension (row or col) into two
* images, one for the indices of the curves themselves, and
* one containing the offset and length of those index spans.
*/
function layout(dim, dimImageInfos, cellImageInfos) {
const dimLength = dim.length; // the number of slices in this dimension
const dimImageInfo = dimImageInfos.findImage(dimLength);
const dimOffset = dimImageInfo.index;
// calculate the total number of stroke indices in this dimension
let totalStrokes = 0;
for (let id = 0; id < dimLength; ++id) {
totalStrokes += dim[id].length;
}
// allocate space for the stroke indices
const cellImageInfo = cellImageInfos.findImage(totalStrokes);
// for each slice in the glyph
for (let i = 0; i < dimLength; ++i) {
const strokeIndices = dim[i];
const strokeCount = strokeIndices.length;
const cellLineIndex = cellImageInfo.index;
// write the offset and count into the glyph slice image
setPixel(
dimImageInfo,
cellLineIndex >> 7,
cellLineIndex & 0x7f,
strokeCount >> 7,
strokeCount & 0x7f
);
// for each stroke index in that slice
for (let iil = 0; iil < strokeCount; ++iil) {
// write the stroke index into the slice's image
const strokeIndex = strokeIndices[iil] + strokeOffset;
setPixel(cellImageInfo, strokeIndex >> 7, strokeIndex & 0x7f, 0, 0);
}
}
return {
cellImageInfo,
dimOffset,
dimImageInfo
};
}
// initialize the info for this glyph
gi = this.glyphInfos[glyph.index] = {
glyph,
uGlyphRect: [xMin, yMin, xMax, yMax],
strokeImageInfo,
strokes,
colInfo: layout(cols, this.colDimImageInfos, this.colCellImageInfos),
rowInfo: layout(rows, this.rowDimImageInfos, this.rowCellImageInfos)
};
gi.uGridOffset = [gi.colInfo.dimOffset, gi.rowInfo.dimOffset];
return gi;
}
}
Renderer3D.prototype._renderText = function (line, x, y, maxY, minY) {
if (!this.states.textFont || typeof this.states.textFont === 'string') {
console.log(
'WEBGL: you must load and set a font before drawing text. See `loadFont` and `textFont` for more details.'
);
return;
}
if (y >= maxY || !this.states.fillColor) {
return; // don't render lines beyond our maxY position
}
if (!p5.Font.hasGlyphData(this.states.textFont)) {
console.log(
'WEBGL: only Opentype (.otf) and Truetype (.ttf) fonts with glyph data are supported. Make sure to set the font using textFont() before drawing text.'
);
return;
}
this.push(); // fix to #803
// remember this state, so it can be restored later
const doStroke = this.states.strokeColor;
const drawMode = this.states.drawMode;
this.states.setValue('strokeColor', null);
this.states.setValue('drawMode', TEXTURE);
// get the cached FontInfo object
const { font } = this.states.textFont;
if (!font) {
throw new Error(
'In WebGL mode, textFont() needs to be given the result of loadFont() instead of a font family name.'
);
}
const axs = font._currentAxes(this);
let fontInfo = font._getFontInfo(axs);
// calculate the alignment and move/scale the view accordingly
// TODO: check this
const pos = { x, y }; // this.states.textFont._handleAlignment(this, line, x, y);
const fontSize = this.states.textSize;
const scale = fontSize / (font.data?.head?.unitsPerEm || 1000);
this.translate(pos.x, pos.y, 0);
this.scale(scale, scale, 1);
// initialize the font shader
const initializeShader = !this._defaultFontShader;
const sh = this._getFontShader();
sh.init();
sh.bindShader('text'); // first time around, bind the shader fully
if (initializeShader) {
// these are constants, really. just initialize them one-time.
sh.setUniform('uGridImageSize', [gridImageWidth, gridImageHeight]);
sh.setUniform('uCellsImageSize', [cellImageWidth, cellImageHeight]);
sh.setUniform('uStrokeImageSize', [strokeImageWidth, strokeImageHeight]);
sh.setUniform('uGridSize', [charGridWidth, charGridHeight]);
}
const curFillColor = this.states.fillSet
? this.states.curFillColor
: [0, 0, 0, 1];
this._setGlobalUniforms(sh);
this._applyColorBlend(curFillColor);
let g = this.geometryBufferCache.getGeometryByID('glyph');
if (!g) {
// create the geometry for rendering a quad
g = this._textGeom = new Geometry(
1,
1,
function () {
for (let i = 0; i <= 1; i++) {
for (let j = 0; j <= 1; j++) {
this.vertices.push(new Vector(j, i, 0));
this.uvs.push(j, i);
}
}
},
this
);
g.gid = 'glyph';
g.computeFaces().computeNormals();
this.geometryBufferCache.ensureCached(g);
}
// bind the shader buffers
for (const buff of this.buffers.text) {
buff._prepareBuffer(g, sh);
}
// this will have to do for now...
sh.setUniform('uMaterialColor', curFillColor);
this._disableRemainingAttributes(sh);
this._beforeDrawText();
this.glyphDataCache = this.glyphDataCache || new Set();
try {
// fetch the glyphs in the line of text
const glyphs = font._positionGlyphs(line);
for (const glyph of glyphs) {
const gi = fontInfo.getGlyphInfo(glyph);
if (gi.uGlyphRect) {
const rowInfo = gi.rowInfo;
const colInfo = gi.colInfo;
// Bump the resources for this glyph to the end of the cache list by deleting and re-adding
const glyphResources = [
gi.strokeImageInfo.imageData,
rowInfo.cellImageInfo.imageData,
rowInfo.dimImageInfo.imageData,
colInfo.cellImageInfo.imageData,
colInfo.dimImageInfo.imageData
];
for (const resource of glyphResources) {
this.glyphDataCache.delete(resource);
this.glyphDataCache.add(resource);
}
// If we have too many glyph textures, remove the least recently used
// ones from GPU memory. The data still exists on the CPU and will be
// re-uploaded if it gets actively used again.
while (this.glyphDataCache.size > this.maxCachedGlyphs()) {
const data = this.glyphDataCache.values().next().value;
this.glyphDataCache.delete(data);
const tex = this.textures.get(data);
if (tex) {
tex.remove();
this.textures.delete(data);
}
}
sh.setUniform('uSamplerStrokes', gi.strokeImageInfo.imageData);
sh.setUniform('uSamplerRowStrokes', rowInfo.cellImageInfo.imageData);
sh.setUniform('uSamplerRows', rowInfo.dimImageInfo.imageData);
sh.setUniform('uSamplerColStrokes', colInfo.cellImageInfo.imageData);
sh.setUniform('uSamplerCols', colInfo.dimImageInfo.imageData);
sh.setUniform('uGridOffset', gi.uGridOffset);
sh.setUniform('uGlyphRect', gi.uGlyphRect);
sh.setUniform('uGlyphOffset', glyph.x);
sh.bindTextures(); // afterwards, only textures need updating
// draw it
this._drawBuffers(g, { mode: TRIANGLES, count: 1 });
}
}
} finally {
// clean up
sh.unbindShader();
this.states.setValue('strokeColor', doStroke);
this.states.setValue('drawMode', drawMode);
this._afterDrawText();
this.pop();
}
};
}
export { text as default };