@awayjs/scene
Version:
AwayJS scene classes
973 lines (967 loc) • 43.5 kB
JavaScript
import { __extends } from "tslib";
import { Matrix, AssetBase, Point, Rectangle, ColorTransform, ColorUtils } from '@awayjs/core';
import { ImageSampler, Float2Attributes, AttributesView } from '@awayjs/stage';
import { Style, TriangleElements } from '@awayjs/renderer';
import { GraphicsPath, Shape, GraphicsFactoryFills, GraphicsFactoryHelper, MaterialManager, GraphicsPathCommand } from '@awayjs/graphics';
import { MethodMaterial } from '@awayjs/materials';
import { TesselatedFontChar } from './TesselatedFontChar';
import { Settings } from '../Settings';
import { FNTGeneratorCanvas } from './FNTGeneratorCanvas';
var ONCE_EMIT_TABLE = Object.create(null);
function once(obj, key) {
if (key === void 0) { key = ''; }
var has = ONCE_EMIT_TABLE[obj._id + key];
ONCE_EMIT_TABLE[obj._id + key] = true;
return !has;
}
/**
* GraphicBase wraps a TriangleElements as a scene graph instantiation. A GraphicBase is owned by a Sprite object.
*
*
* @see away.base.TriangleElements
* @see away.entities.Sprite
*
* @class away.base.GraphicBase
*/
var TesselatedFontTable = /** @class */ (function (_super) {
__extends(TesselatedFontTable, _super);
/**
* Creates a new TesselatedFont object
* If a opentype_font object is passed, the chars will get tessellated whenever requested.
* If no opentype font object is passed, it is expected that tesselated chars
*/
function TesselatedFontTable(opentype_font) {
if (opentype_font === void 0) { opentype_font = null; }
var _this = _super.call(this) || this;
/*internal*/ _this._font_chars_dic = {};
/*internal*/ _this._current_size = 0;
/*internal*/ _this._size_multiply = 0;
_this._font_chars = [];
// default size
_this._font_em_size = 32;
_this._ascent = 0;
_this._descent = 0;
_this._usesCurves = false;
_this._glyphIdxToChar = {};
_this._fntSizeLimit = -1;
_this._fnt_channels = [];
if (opentype_font) {
_this._opentype_font = opentype_font;
/*
console.log("head.yMax ",opentype_font);
console.log("head.yMax "+opentype_font.tables.head.yMax);
console.log("head.yMin "+opentype_font.tables.head.yMin);
console.log("font.numGlyphs "+opentype_font.numGlyphs);
console.log('Ascender', opentype_font.tables.hhea.ascender);
console.log('Descender', opentype_font.tables.hhea.descender);
console.log('Typo Ascender', opentype_font.tables.os2.sTypoAscender);
console.log('Typo Descender', opentype_font.tables.os2.sTypoDescender);
*/
_this._font_em_size = 72; //2048;
_this._ascent = opentype_font.tables.hhea.ascender / (_this._opentype_font.unitsPerEm / 72);
_this._descent = -2 + opentype_font.tables.hhea.descender / (_this._opentype_font.unitsPerEm / 72);
_this._current_size = 0;
_this._size_multiply = 0;
var thisGlyph = _this._opentype_font.charToGlyph(String.fromCharCode(32));
if (thisGlyph) {
//this._whitespace_width = thisGlyph.advanceWidth / (this._opentype_font.unitsPerEm / 72);
}
return _this;
}
return _this;
}
TesselatedFontTable.prototype.addFNTChannel = function (mat) {
this._fnt_channels.push(mat);
};
TesselatedFontTable.prototype.getGlyphCount = function () {
return this._font_chars.length;
};
TesselatedFontTable.prototype.generateFNTData = function (stage) {
// already exist
if (this._fntSizeLimit > 0) {
return;
}
var generator = new FNTGeneratorCanvas(stage, false);
var bitmaps = generator.generate(this.font, this._font_em_size, this._current_size, 4);
if (bitmaps && bitmaps.length > 0) {
/**
* Random value, anyway we render real font dimension ;)
*/
this._fntSizeLimit = this._current_size * 4;
}
};
TesselatedFontTable.prototype.generateFNTTextures = function (padding, fontSize, texSize) {
console.log('generateFNTTextures');
if (fontSize) {
this._fntSizeLimit = fontSize * 0.5; //why half?
this.initFontSize(fontSize);
}
if (this._opentype_font) {
// if this was loaded from opentype,
// we make sure all glyphs are tesselated first
for (var g = 0; g < this._opentype_font.glyphs.length; g++) {
var thisGlyph = this._opentype_font.charToGlyph(String.fromCharCode(this._opentype_font.glyphs.glyphs[g].unicode));
if (thisGlyph) {
var thisPath = thisGlyph.getPath();
var awayPath = new GraphicsPath();
var i = 0;
var len = thisPath.commands.length;
var startx = 0;
var starty = 0;
var y_offset = this._ascent;
for (i = 0; i < len; i++) {
var cmd = thisPath.commands[i];
//console.log("cmd", cmd.type, cmd.x, cmd.y, cmd.x1, cmd.y1, cmd.x2, cmd.y2);
if (cmd.type === 'M') {
awayPath.moveTo(cmd.x, cmd.y + y_offset);
startx = cmd.x;
starty = cmd.y + y_offset;
}
else if (cmd.type === 'L') {
awayPath.lineTo(cmd.x, cmd.y + y_offset);
}
else if (cmd.type === 'Q') {
awayPath.curveTo(cmd.x1, cmd.y1 + y_offset, cmd.x, cmd.y + y_offset);
}
else if (cmd.type === 'C') {
awayPath.cubicCurveTo(cmd.x1, cmd.y1 + y_offset, cmd.x2, cmd.y2 + y_offset, cmd.x, cmd.y + y_offset);
}
else if (cmd.type === 'Z') {
awayPath.lineTo(startx, starty);
}
}
var t_font_char = new TesselatedFontChar(null, null, awayPath);
t_font_char.char_width = (thisGlyph.advanceWidth * (1 / thisGlyph.path.unitsPerEm * 72));
t_font_char.fill_data = GraphicsFactoryFills.pathToAttributesBuffer(awayPath, true);
if (!t_font_char.fill_data) {
console.log('error tesselating opentype glyph', this._opentype_font.glyphs.glyphs[g]);
//return null;
}
else {
this._font_chars.push(t_font_char);
this._font_chars_dic[this._opentype_font.glyphs.glyphs[g].unicode] = t_font_char;
}
//console.log("unicode:",this._opentype_font.glyphs.glyphs[g].unicode);
//console.log("name:",this._opentype_font.glyphs.glyphs[g].name);
}
else {
console.log('no char found for', this._opentype_font.glyphs.glyphs[g]);
}
}
}
var char_vertices;
var x = 0;
var y = 0;
var buffer;
var v;
var glyph_verts = [[]];
var channel_idx = 0;
var maxSize = 0;
var allChars = [];
var maxy = Number.MIN_VALUE;
var maxx = Number.MIN_VALUE;
var miny = Number.MAX_VALUE;
var minx = Number.MAX_VALUE;
var tmpx = 0;
var tmpy = 0;
// step1: loop over all chars, get their bounds and sort them by there height
// done without applying any font-scale to the glyphdata
var allAreas = 0;
for (var i = 0; i < this._font_chars.length; i++) {
char_vertices = this._font_chars[i].fill_data;
//console.log("got the glyph from opentype", this._opentype_font.glyphs.glyphs[g]);
buffer = new Float32Array(char_vertices.buffer);
maxy = Number.MIN_VALUE;
maxx = Number.MIN_VALUE;
miny = Number.MAX_VALUE;
minx = Number.MAX_VALUE;
tmpx = 0;
tmpy = 0;
for (v = 0; v < char_vertices.count; v++) {
tmpx = (buffer[v * 2]);
tmpy = (buffer[v * 2 + 1]);
if (tmpx < minx)
minx = tmpx;
if (tmpx > maxx)
maxx = tmpx;
if (tmpy < miny)
miny = tmpy;
if (tmpy > maxy)
maxy = tmpy;
}
this._font_chars[i].fnt_rect = new Rectangle((minx) / (this._font_chars[i].char_width), (miny) / (this._font_em_size), (maxx - minx) / (this._font_chars[i].char_width), (maxy - miny) / (this._font_em_size));
allAreas += (maxx - minx + padding + padding) * (maxy - miny + padding + padding);
allChars[allChars.length] = {
idx: i,
minx: minx,
miny: miny,
width: maxx - minx,
height: maxy - miny
};
}
allChars.sort(function (a, b) {
return a.height > b.height ? -1 : 1;
});
// now allChars contains all glyphs sorted by height
var curHeight = 0;
maxSize = texSize;
var size_multiply = 1;
if (fontSize) {
this.initFontSize(fontSize);
size_multiply = this._size_multiply;
}
else {
// figure out the optiomal fontSize so that the glyphs fill the texture
// imagine the spaces needed by all glyphs would be a perfect quat
// compare the width of that quat with the available width to have a first guess at fontSize
// make the size slightly bigger as the estimate and check if all glyphs will fit
// if they do not fit, slightly reduce the scale.
// repeat until all glyphs fit
size_multiply = maxSize / (Math.sqrt(allAreas)) * 1.2;
var invalid = true;
var curCharHeight = 0;
while (invalid) {
invalid = false;
x = 0;
y = padding;
for (var i = 0; i < allChars.length; i++) {
curCharHeight = allChars[i].height * size_multiply;
tmpx = 0;
tmpy = 0;
x += padding;
if (x + allChars[i].width * size_multiply + padding >= maxSize) {
x = padding;
y += curHeight + padding * 2;
curHeight = 0;
}
if (curCharHeight > curHeight) {
curHeight = curCharHeight;
}
if (y + curCharHeight + padding * 2 >= maxSize) {
invalid = true;
size_multiply = size_multiply * 0.995;
break;
}
x += (allChars[i].width) * size_multiply + padding;
}
}
}
this._fntSizeLimit = size_multiply * this._font_em_size * 0.5; //why half?
// collect the glyph-data:
var font_char;
x = 0;
y = padding;
for (var i = 0; i < allChars.length; i++) {
font_char = this._font_chars[allChars[i].idx];
char_vertices = font_char.fill_data;
buffer = new Float32Array(char_vertices.buffer);
minx = allChars[i].minx;
maxx = Number.MIN_VALUE;
miny = allChars[i].miny;
maxy = Number.MIN_VALUE;
tmpx = 0;
tmpy = 0;
x += padding;
if (x + allChars[i].width * size_multiply + padding >= maxSize) {
x = padding;
y += curHeight + padding * 2;
curHeight = 0;
}
allChars[i].height *= size_multiply;
if (allChars[i].height > curHeight) {
curHeight = allChars[i].height;
}
if (y + curHeight + padding * 2 >= maxSize) {
x = padding;
y = padding;
curHeight = 0;
channel_idx++;
glyph_verts[channel_idx] = [];
}
font_char.fnt_channel = channel_idx;
for (v = 0; v < char_vertices.count; v++) {
tmpx = ((buffer[v * 2] - minx) * size_multiply) + x;
tmpy = ((buffer[v * 2 + 1] - miny) * size_multiply) + y;
glyph_verts[channel_idx][glyph_verts[channel_idx].length] = tmpx;
glyph_verts[channel_idx][glyph_verts[channel_idx].length] = tmpy;
if (tmpx > maxx) {
maxx = tmpx;
}
if (tmpy > maxy) {
maxy = tmpy;
}
}
font_char.fnt_uv = new Rectangle(x / maxSize, 1 - (y / maxSize), (maxx - x) / maxSize, ((maxy - y) / maxSize));
//this._drawDebugRect(glyph_verts[channel_idx], x, maxx, y, maxy);
x += (allChars[i].width) * size_multiply + padding;
}
var shapes = [];
for (var i = 0; i < glyph_verts.length; i++) {
var attr_length = 2; //(tess_fontTable.usesCurves)?3:2;
var attributesView = new AttributesView(Float32Array, attr_length);
attributesView.set(glyph_verts[i]);
var vertexBuffer = attributesView.attributesBuffer.cloneBufferView();
attributesView.dispose();
var elements = new TriangleElements(vertexBuffer);
elements.setPositions(new Float2Attributes(vertexBuffer));
var shape = Shape.getShape(elements);
var sampler = new ImageSampler();
shape.style = new Style();
var color = 0xFFFFFF;
var alpha = 1;
var obj = MaterialManager.getMaterialForColor(color, alpha);
shape.material = obj.material;
if (obj.colorPos) {
shape.style.addSamplerAt(sampler, shape.material.getTextureAt(0));
shape.material.animateUVs = true;
shape.style.uvMatrix = new Matrix(0, 0, 0, 0, obj.colorPos.x, obj.colorPos.y);
}
shapes[i] = shape;
}
return shapes;
};
TesselatedFontTable.prototype.getRatio = function (size) {
return this._size_multiply;
};
TesselatedFontTable.prototype.hasChar = function (char_code) {
var has = !!this._font_chars_dic[char_code];
if (!has && this._opentype_font) {
has = !!this._opentype_font.charToGlyph(String.fromCharCode(parseInt(char_code)));
}
return has;
};
TesselatedFontTable.prototype.changeOpenTypeFont = function (newOpenTypeFont, tesselateAllOld) {
if (tesselateAllOld === void 0) { tesselateAllOld = true; }
if ((tesselateAllOld) && (this._opentype_font)) {
//todo: make sure all available chars are tesselated
}
// todo: when updating a font we must take care that they are compatible in terms of em_size
this._opentype_font = newOpenTypeFont;
this._ascent = newOpenTypeFont.ascender; // * (1024 / newOpenTypeFont.unitsPerEm);
this._descent = newOpenTypeFont.descender; //* (1024 / newOpenTypeFont.unitsPerEm);
this._font_em_size = newOpenTypeFont.unitsPerEm;
var space = this.getChar('32');
if (space) {
this._whitespace_width = space.char_width;
}
//console.log('changeOpenTypeFont', this._ascent, this._descent, this._font_em_size);
};
TesselatedFontTable.prototype.initFontSize = function (font_size) {
if (this._current_size === font_size) {
return;
}
this._current_size = font_size;
this._size_multiply = font_size / this._font_em_size;
if (!this._size_multiply) {
debugger;
}
};
TesselatedFontTable.prototype.getCharVertCnt = function (char_code) {
var t_font_char = this._font_chars_dic[char_code];
if (t_font_char) {
return t_font_char.fill_data.length;
}
return 0;
};
TesselatedFontTable.prototype.getCharWidth = function (char_code) {
var space = this._whitespace_width * this._size_multiply || TesselatedFontTable.DEFAULT_SPACE;
// SPACE
if (char_code == '32') {
return (space * 20 | 0) / 20;
}
// TAB
if (char_code == '9') {
// todo: temporary change this to 2.
// need to implement textFormat tabstop
return (space * 2 * 20) / 20;
}
var t_font_char = this.getChar(char_code, false);
if (t_font_char) {
return ((t_font_char.char_width * this._size_multiply * 20) | 0) / 20;
}
else if (char_code == '9679') {
t_font_char = this.createPointGlyph_9679();
return ((t_font_char.char_width * this._size_multiply * 20) | 0) / 20;
}
return 0;
};
Object.defineProperty(TesselatedFontTable.prototype, "usesCurves", {
get: function () {
return this._usesCurves;
},
enumerable: false,
configurable: true
});
TesselatedFontTable.prototype.getLineHeight = function () {
// @todo: root evil for wrong linespace :(
// i am still not sure if we use the correct version of this 3:
return this._size_multiply * (this._ascent - this.descent); // used for sf
//return this._size_multiply * this._font_em_size; // used a long time for poki etc
//return (this._ascent+this._descent)*this._size_multiply; // used long ago for icycle
};
TesselatedFontTable.prototype.getUnderLineHeight = function () {
return this._size_multiply * (this._ascent - this.descent / 2);
};
Object.defineProperty(TesselatedFontTable.prototype, "assetType", {
get: function () {
return TesselatedFontTable.assetType;
},
enumerable: false,
configurable: true
});
TesselatedFontTable.prototype.dispose = function () {
for (var i = 0; i < this._font_chars.length; i++) {
this._font_chars[i].dispose();
}
this._font_chars.length = 0;
this._font_chars_dic = null;
};
Object.defineProperty(TesselatedFontTable.prototype, "fntSizeLimit", {
get: function () {
return this._fntSizeLimit;
},
set: function (value) {
this._fntSizeLimit = value;
},
enumerable: false,
configurable: true
});
Object.defineProperty(TesselatedFontTable.prototype, "ascent", {
get: function () {
return this._ascent;
},
set: function (value) {
this._ascent = value;
},
enumerable: false,
configurable: true
});
Object.defineProperty(TesselatedFontTable.prototype, "descent", {
get: function () {
return this._descent;
},
set: function (value) {
this._descent = value;
},
enumerable: false,
configurable: true
});
TesselatedFontTable.prototype.get_font_chars = function () {
return this._font_chars;
};
TesselatedFontTable.prototype.get_font_em_size = function () {
return this._font_em_size;
};
TesselatedFontTable.prototype.set_whitespace_width = function (value) {
this._whitespace_width = value;
};
TesselatedFontTable.prototype.get_whitespace_width = function () {
return this._whitespace_width;
};
TesselatedFontTable.prototype.set_font_em_size = function (font_em_size) {
this._font_em_size = font_em_size;
};
TesselatedFontTable.prototype.buildTextLineFromIndices = function (field, format, x, y, indices, advance) {
var textShape = field.getTextShapeForIdentifierAndFormat(format.color.toString(), format);
var origin_x = x;
var indicesCount = indices.length;
var charEntries = [];
var textBuffSize = 0;
y -= this._ascent * this._size_multiply; //this.getLineHeight();
// loop over all the words and create the text data for it
// each word provides its own start-x and start-y values, so we can just ignore whitespace-here
for (var i = 0; i < indicesCount; i++) {
var idx = indices[i];
var charGlyph = this._glyphIdxToChar[idx];
if (!charGlyph) {
x += advance[i];
//console.log("no glyph found at idx", idx, "todo: support fallback fonts");
continue;
}
if (charGlyph.fill_data === null) {
if (charGlyph.fill_data_path.commands[0] == GraphicsPathCommand.MOVE_TO
&& charGlyph.fill_data_path.data[0] == 0
&& charGlyph.fill_data_path.data[1] == 0) {
charGlyph.fill_data_path.data.shift();
charGlyph.fill_data_path.data.shift();
charGlyph.fill_data_path.commands.shift();
charGlyph.fill_data_path.commands[0] = GraphicsPathCommand.LINE_TO;
}
charGlyph.fill_data = GraphicsFactoryFills.pathToAttributesBuffer(charGlyph.fill_data_path, true);
}
var charVertices = charGlyph.fill_data;
if (charVertices) {
charEntries.push({
char: charGlyph,
x: x,
y: y,
selected: false
});
textBuffSize += charVertices.buffer.byteLength / 4;
}
x += advance[i]; // * size_multiply;
}
var buff = new Float32Array(textBuffSize);
this._fillBuffer(buff, charEntries, false);
textShape.addChunk(buff);
return new Point(x - origin_x, this.getLineHeight());
};
TesselatedFontTable.prototype.fillTextRun = function (tf, format, startWord, wordCnt) {
var useFNT = this._fntSizeLimit >= 0 && this._fntSizeLimit >= format.size;
if (useFNT) {
return this.fillTextRunFNT(tf, format, startWord, wordCnt);
}
var textShape = this._queryShape(tf, format);
var textShapeSelected;
var newFormat;
var wordsCount = startWord + wordCnt;
var size_multiply = this._size_multiply;
var select_start = tf.selectionBeginIndex;
var select_end = tf.selectionEndIndex;
var start_x = 0;
if (tf.selectable) {
newFormat = format.clone();
newFormat.color = 0xffffff;
textShapeSelected = this._queryShape(tf, newFormat);
if (newFormat.underline && (startWord + 1) < tf.words.length) {
start_x = tf.words.get(startWord).x;
}
if (tf.selectionEndIndex < tf.selectionBeginIndex) {
select_start = tf.selectionEndIndex;
select_end = tf.selectionBeginIndex;
}
}
var selectedBuffSize = 0;
var textBuffSize = 0;
var charEntries = [];
var selectedCharEntries = [];
var underlines = [];
// loop over all the words and create the text data for it
// each word provides its own start-x and start-y values, so we can just ignore whitespace-here
for (var w = startWord; w < wordsCount; w += 1) {
var word = tf.words.get(w);
var x = word.x;
var y = word.y;
var startIdx = word.start;
var charsCount = startIdx + word.len;
for (var c = startIdx; c < charsCount; c++) {
var curTShape = (tf.isInFocus && c >= select_start && c < select_end) ?
textShapeSelected : textShape;
var code = tf.chars_codes[c];
if (code === 32 || code === 9) {
continue;
}
var charGlyph = this.getChar(code, true);
if (!charGlyph) {
if (once(this, 'miss' + tf.chars_codes[c])) {
console.debug('[TesselatedFontTable] Error: char not found in fontTable', tf.chars_codes[c], String.fromCharCode(tf.chars_codes[c]));
}
continue;
}
if (curTShape === textShapeSelected) {
selectedCharEntries.push({
char: charGlyph,
x: x,
y: y,
selected: true
});
selectedBuffSize += charGlyph.fill_data.buffer.byteLength / 4;
}
else {
charEntries.push({
char: charGlyph,
x: x,
y: y,
selected: false
});
textBuffSize += charGlyph.fill_data.buffer.byteLength / 4;
}
x += charGlyph.char_width * size_multiply;
}
var half_thickness = 0.25 * tf.internalScale.y;
var topY = y + this.getUnderLineHeight() + half_thickness;
var bottomY = y + this.getUnderLineHeight() - half_thickness;
if (tf.selectable && newFormat.underline && (startWord + 1) < tf.words.length) {
var underBuff = new Float32Array(12);
underBuff[0] = start_x;
underBuff[1] = bottomY;
underBuff[2] = start_x;
underBuff[3] = topY;
underBuff[4] = x;
underBuff[5] = topY;
underBuff[6] = x;
underBuff[7] = topY;
underBuff[8] = x;
underBuff[9] = bottomY;
underBuff[10] = start_x;
underBuff[11] = bottomY;
underlines.push(underBuff);
}
} // for word
// reallock buffers and fill
if (textBuffSize > 0) {
textBuffSize += underlines.length * 12;
var buff = new Float32Array(textBuffSize);
var offset = this._fillBuffer(buff, charEntries, false);
for (var _i = 0, underlines_1 = underlines; _i < underlines_1.length; _i++) {
var under = underlines_1[_i];
buff.set(under, offset);
offset += under.length;
}
textShape.addChunk(buff);
}
if (selectedBuffSize > 0) {
var buff = new Float32Array(selectedBuffSize);
this._fillBuffer(buff, selectedCharEntries, true);
textShapeSelected.addChunk(buff);
}
};
TesselatedFontTable.prototype._cacheKey = function (color, channel) {
if (color === void 0) { color = 0xffffff; }
if (channel === void 0) { channel = -1; }
var fnt = channel >= 0;
return "".concat(color).concat(fnt).concat(fnt ? channel : 0);
};
/**
* Query shape from TextField cache
* @param tf
* @param format
* @param channel - FNT channel, -1 for regular font
* @private
*/
TesselatedFontTable.prototype._queryShape = function (tf, format, channel) {
if (channel === void 0) { channel = -1; }
var textShape = tf.getTextShapeForIdentifierAndFormat(this._cacheKey(format.color, channel), format);
if (channel >= 0 && !textShape.fntMaterial) {
var argb = ColorUtils.float32ColorToARGB(format.color);
var mat = new MethodMaterial(this._fnt_channels[channel]);
mat.colorTransform = new ColorTransform(argb[1] / 255, argb[2] / 255, argb[3] / 255);
mat.bothSides = true;
mat.alphaBlending = true;
mat.useColorTransform = true;
mat.style.sampler = new ImageSampler(false, true, true);
textShape.fntMaterial = mat;
}
return textShape;
};
/**
* Fill text run but use FNT Texture.
*/
TesselatedFontTable.prototype.fillTextRunFNT = function (tf, format, startWord, wordCnt) {
var textShape = this._queryShape(tf, format, 0);
var charShapesCache = {};
var charShapesSelectedCache = {};
var wordsCount = startWord + wordCnt;
var size_multiply = this._size_multiply;
var textShapeSelected;
var select_start = tf.selectionBeginIndex;
var select_end = tf.selectionEndIndex;
var start_x = 0;
if (tf.selectable) {
var newFormat = format.clone();
newFormat.color = 0xffffff;
textShapeSelected = this._queryShape(tf, newFormat, 0);
charShapesSelectedCache[textShapeSelected.cacheId] = textShapeSelected;
if (newFormat.underline && (startWord + 1) < tf.words.length) {
start_x = tf.words.get(startWord).x;
}
if (tf.selectionEndIndex < tf.selectionBeginIndex) {
select_start = tf.selectionEndIndex;
select_end = tf.selectionBeginIndex;
}
}
charShapesCache[textShape.cacheId] = textShape;
var shapeArray = new Map();
var underlines = [];
// loop over all the words and create the text data for it
// each word provides its own start-x and start-y values, so we can just ignore whitespace-here
for (var w = startWord; w < wordsCount; w += 1) {
var word = tf.words.get(w);
var x = word.x;
var y = word.y;
var startIdx = word.start;
var charsCount = startIdx + word.len;
var preFilledShape = null;
var target = null;
var color = 0;
var fntID = -1;
for (var c = startIdx; c < charsCount; c++) {
// space codes
if (tf.chars_codes[c] === 32 || tf.chars_codes[c] === 9) {
continue;
}
var charGlyph = this.getChar(tf.chars_codes[c], false);
if (!charGlyph) {
if (once(this, 'miss' + tf.chars_codes[c])) {
console.debug('[TesselatedFontTable] Error: char not found in fontTable', tf.chars_codes[c], String.fromCharCode(tf.chars_codes[c]));
}
continue;
}
var selected = (tf.isInFocus && c >= select_start && c < select_end);
var baseShape = textShape;
var cacheStore = charShapesCache;
if (selected) {
baseShape = textShapeSelected;
cacheStore = charShapesSelectedCache;
}
var curFormat = baseShape.format;
var cacheId = preFilledShape ? preFilledShape.cacheId : '';
if (!cacheId || color !== curFormat.color || charGlyph.fnt_channel !== fntID) {
cacheId = this._cacheKey(curFormat.color, charGlyph.fnt_channel);
color = curFormat.color;
fntID = charGlyph.fnt_channel;
}
var fntFilledShape = preFilledShape && preFilledShape.cacheId !== cacheId
? cacheStore[cacheId]
: preFilledShape;
if (!fntFilledShape) {
fntFilledShape = this._queryShape(tf, curFormat, charGlyph.fnt_channel);
cacheStore[fntFilledShape.cacheId] = fntFilledShape;
}
if (preFilledShape !== fntFilledShape || !target) {
// FNT is very little, has 6 vertices per shape (instead of 300 ++)
// fill it directly
target = shapeArray.get(fntFilledShape) || {
uv: [],
pos: []
};
shapeArray.set(fntFilledShape, target);
preFilledShape = fntFilledShape;
}
this._emitCharFNT(target, charGlyph, x, y);
x += charGlyph.char_width * size_multiply;
}
/*
todo Generate underline for regular shape
const half_thickness: number = 0.25 * tf.internalScale.y;
const topY: number = y + this.getUnderLineHeight() + half_thickness;
const bottomY: number = y + this.getUnderLineHeight() - half_thickness;
if (tf.selectable && newFormat.underline && (startWord + 1) < tf.words.length) {
const underBuff = new Float32Array(12);
underBuff[0] = start_x;
underBuff[1] = bottomY;
underBuff[2] = start_x;
underBuff[3] = topY;
underBuff[4] = x;
underBuff[5] = topY;
underBuff[6] = x;
underBuff[7] = topY;
underBuff[8] = x;
underBuff[9] = bottomY;
underBuff[10] = start_x;
underBuff[11] = bottomY;
underlines.push(underBuff);
}
*/
} // for word
// oh, es5... iterating over map won't working
var shapes = Array.from(shapeArray.keys());
for (var _i = 0, shapes_1 = shapes; _i < shapes_1.length; _i++) {
var shape = shapes_1[_i];
var values = shapeArray.get(shape);
shape.addChunk(new Float32Array(values.pos), new Float32Array(values.uv));
}
};
TesselatedFontTable.prototype._emitCharFNT = function (target, charGlyph, x, y) {
var scale = this._size_multiply;
var size = this._font_em_size * scale;
var rect = charGlyph.fnt_rect;
var uv = charGlyph.fnt_uv;
var width = charGlyph.char_width * scale;
if (!rect) {
// empty chars not have data
return;
}
var x1 = x + width * rect.x * 20;
var x2 = x1 + width * rect.width * 20;
var y1 = y + size * rect.y * 20;
var y2 = y1 + size * rect.height * 20;
target.pos.push(x1, y1, x1, y2, x2, y1, x2, y1, x1, y2, x2, y2);
target.uv.push(uv.x, uv.y, uv.x, uv.bottom, uv.right, uv.y, uv.right, uv.y, uv.x, uv.bottom, uv.right, uv.bottom);
};
TesselatedFontTable.prototype.createPointGlyph_9679 = function () {
var verts = [];
GraphicsFactoryHelper.drawElipse(this._font_em_size / 2, this._font_em_size / 2, this._font_em_size / 8, this._font_em_size / 8, verts, 0, 360, 5, false);
var attributesView = new AttributesView(Float32Array, 2);
attributesView.set(verts);
var attributesBuffer = attributesView.attributesBuffer.cloneBufferView();
attributesView.dispose();
var t_font_char = new TesselatedFontChar(attributesBuffer, null, null);
t_font_char.char_width = this._font_em_size;
this._font_chars.push(t_font_char);
this._font_chars_dic['9679'] = t_font_char;
return t_font_char;
};
TesselatedFontTable.prototype.getCharOpenType = function (name) {
if (!this._opentype_font) {
return null;
}
var thisGlyph = this._opentype_font.charToGlyph(String.fromCharCode(parseInt(name)));
if (!thisGlyph) {
return null;
}
//console.log("got the glyph from opentype");
var thisPath = thisGlyph.getPath();
var awayPath = new GraphicsPath();
var i = 0;
var len = thisPath.commands.length;
//awayPath.lineTo(0, 0);
//awayPath.moveTo(0,0);//-100);
//awayPath.curveTo(100, 250, 200,0);
//awayPath.lineTo(150, 100);
//awayPath.moveTo(0,20);
//awayPath.curveTo(100, 270, 200,20);
//awayPath.moveTo(0,-20);
//awayPath.moveTo(0,-10);
//awayPath.curveTo(100, -110, 200,-10);
var startx = 0;
var starty = 0;
var y_offset = this._ascent;
//40 = 66
var scale = (this._opentype_font.unitsPerEm / 72);
for (i = 0; i < len; i++) {
var cmd = thisPath.commands[i];
//console.log("cmd", cmd.type, cmd.x, cmd.y, cmd.x1, cmd.y1, cmd.x2, cmd.y2);
if (cmd.type === 'M') {
awayPath.moveTo(scale * cmd.x, scale * cmd.y + y_offset);
startx = scale * cmd.x;
starty = scale * cmd.y + y_offset;
}
else if (cmd.type === 'L') {
awayPath.lineTo(scale * cmd.x, scale * cmd.y + y_offset);
}
else if (cmd.type === 'Q') {
awayPath.curveTo(scale * cmd.x1, scale * cmd.y1 + y_offset, scale * cmd.x, scale * cmd.y + y_offset);
}
else if (cmd.type === 'C') {
var mergedX = cmd.x1 + (cmd.x2 - cmd.x1) / 2;
var mergedY = cmd.y1 + (cmd.y2 - cmd.y1) / 2;
awayPath.curveTo(scale * cmd.x1, scale * cmd.y1 + y_offset, scale * mergedX, scale * mergedY + y_offset);
awayPath.curveTo(scale * cmd.x2, scale * cmd.y2 + y_offset, scale * cmd.x, scale * cmd.y + y_offset);
}
else if (cmd.type === 'Z') {
awayPath.lineTo(startx, starty);
}
}
var t_font_char = new TesselatedFontChar(null, null, awayPath);
t_font_char.char_width = thisGlyph.advanceWidth; //(1 / thisGlyph.path.unitsPerEm * 72);
t_font_char.fill_data = GraphicsFactoryFills.pathToAttributesBuffer(awayPath, true, null, scale * Settings.FONT_TESSELATION_QUALITY);
t_font_char.lastTesselatedScale = scale;
if (!t_font_char.fill_data) {
if (once(this, 'tess' + name)) {
console.debug('[TesselatedFontTable] Error:tesselating opentype glyph:', name.charCodeAt(0));
}
return null;
}
this._font_chars.push(t_font_char);
this._font_chars_dic[name] = t_font_char;
};
TesselatedFontTable.prototype.getChar = function (name, tesselation) {
if (tesselation === void 0) { tesselation = true; }
name = '' + name;
var fontChar = this._font_chars_dic[name];
if (!tesselation && fontChar) {
return fontChar;
}
if (!fontChar) {
if (this._opentype_font) {
return this.getCharOpenType(name);
}
if (name == '9679') {
return this.createPointGlyph_9679();
}
return null;
}
// tesselation pass
var scale = this._size_multiply;
var qualityStepScale = Math.max(0.01, Math.round(scale * 100) / 100);
if (fontChar.fill_data_path === null) {
return null;
}
// we already has buffer with tesselation scale ratio
if (!(fontChar.fill_data == null && fontChar.stroke_data == null)
&& fontChar.lastTesselatedScale >= qualityStepScale) {
return fontChar;
}
var path = fontChar.fill_data_path;
// hack for messed up "X": remove the first command if it is moveTo that points to 0,0
// change the new first command to moveTo
if (path.commands[0] == GraphicsPathCommand.MOVE_TO && path.data[0] == 0 && path.data[1] == 0) {
path.data.shift();
path.data.shift();
path.commands.shift();
path.commands[0] = GraphicsPathCommand.LINE_TO;
}
if (fontChar.fill_data) {
// it not have dispose, but clear will drop abstraction if it exist
fontChar.fill_data.clear();
}
fontChar.fill_data = GraphicsFactoryFills.pathToAttributesBuffer(fontChar.fill_data_path, true, null, qualityStepScale * Settings.FONT_TESSELATION_QUALITY);
if (fontChar.lastTesselatedScale > 0) {
console.debug('[TesselatedFontTable] Retesselate char from scale:', fontChar.lastTesselatedScale, qualityStepScale, String.fromCharCode(+name));
}
fontChar.lastTesselatedScale = qualityStepScale;
if (!fontChar.fill_data) {
if (once(this, 'tess' + name)) {
console.debug('[TesselatedFontTable] Error:tesselating glyph:', name.charCodeAt(0));
}
return null;
}
return fontChar;
};
TesselatedFontTable.prototype.setChar = function (name, char_width, fills_data, stroke_data, uses_curves, glyph_idx, fill_data_path) {
if (fills_data === void 0) { fills_data = null; }
if (stroke_data === void 0) { stroke_data = null; }
if (uses_curves === void 0) { uses_curves = false; }
if (glyph_idx === void 0) { glyph_idx = 0; }
if (fill_data_path === void 0) { fill_data_path = null; }
char_width = Math.floor(char_width * 20) / 20;
//console.log("adding char", name, String.fromCharCode(parseInt(name)));
if ((fills_data == null) && (stroke_data == null) && (fill_data_path == null))
throw ("TesselatedFontTable: trying to create a TesselatedFontChar with no data \n\t\t\t\t(fills_data, stroke_data and fill_data_path is null)");
if (this._font_chars.length > 0) {
if (uses_curves != this._usesCurves) {
throw ("TesselatedFontTable: Can not set different types of graphic-glyphs\n\t\t\t\t\t(curves vs non-cuves) on the same FontTable!");
}
}
else {
this._usesCurves = uses_curves;
}
var t_font_char = new TesselatedFontChar(fills_data, stroke_data, fill_data_path);
t_font_char.char_width = char_width;
t_font_char.glyph_idx = glyph_idx;
t_font_char.name = name;
this._glyphIdxToChar[glyph_idx] = t_font_char;
this._font_chars.push(t_font_char);
this._font_chars_dic[name] = t_font_char;
};
TesselatedFontTable.prototype.roundTo = function (val) {
var p = Settings.TEXT_SHAPE_ROUND_PRECISION;
var invP = 1 / (p || 1);
return Math.floor(val * invP) * p;
};
TesselatedFontTable.prototype._fillBuffer = function (buffer, chars, selection) {
if (selection === void 0) { selection = false; }
var scale = this._size_multiply;
var offset = 0;
for (var _i = 0, chars_1 = chars; _i < chars_1.length; _i++) {
var entry = chars_1[_i];
if (entry.selected !== selection) {
continue;
}
var x = entry.x, y = entry.y, char = entry.char;
var view = new Float32Array(char.fill_data.buffer);
var count = view.length;
for (var v = 0; v < count; v += 2) {
buffer[offset + v + 0] = this.roundTo(view[v + 0] * scale + x);
buffer[offset + v + 1] = this.roundTo(view[v + 1] * scale + y);
}
offset += view.length;
}
return offset;
};
TesselatedFontTable.assetType = '[asset TesselatedFontTable]';
TesselatedFontTable.DEFAULT_SPACE = 14;
return TesselatedFontTable;
}(AssetBase));
export { TesselatedFontTable };