UNPKG

@awayjs/scene

Version:
964 lines (958 loc) 43.1 kB
import { __extends } from "tslib"; import { AssetBase, Point, Rectangle, ColorTransform, ColorUtils } from '@awayjs/core'; import { Float2Attributes, AttributesView } from '@awayjs/stage'; import { Style, TriangleElements } from '@awayjs/renderer'; import { GraphicsPath, Shape, GraphicsFactoryFills, GraphicsFactoryHelper, MaterialManager, GraphicsPathCommand, SolidFillStyle, TextureAtlas } from '@awayjs/graphics'; 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 solid = new SolidFillStyle(0xFFFFFF, 1); var material = MaterialManager.getMaterialForColor(solid); shape.material = material; if (material.getNumTextures()) { shape.style = new Style(); shape.style.image = TextureAtlas.getTextureForColor(solid); shape.style.uvMatrix = solid.getUVMatrix(); } 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.fntBitmap) { var argb = ColorUtils.float32ColorToARGB(format.color); textShape.fntBitmap = this._fnt_channels[channel]; textShape.fntColorTransform = new ColorTransform(argb[1] / 255, argb[2] / 255, argb[3] / 255); } 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 };