287 lines
12 KiB
JavaScript
287 lines
12 KiB
JavaScript
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
var Constants_1 = require("browser/renderer/atlas/Constants");
|
||
|
var Constants_2 = require("common/buffer/Constants");
|
||
|
var CharAtlasUtils_1 = require("./CharAtlasUtils");
|
||
|
var WebglUtils_1 = require("../WebglUtils");
|
||
|
var TEXTURE_WIDTH = 1024;
|
||
|
var TEXTURE_HEIGHT = 1024;
|
||
|
var TEXTURE_CAPACITY = Math.floor(TEXTURE_HEIGHT * 0.8);
|
||
|
var TRANSPARENT_COLOR = {
|
||
|
css: 'rgba(0, 0, 0, 0)',
|
||
|
rgba: 0
|
||
|
};
|
||
|
var NULL_RASTERIZED_GLYPH = {
|
||
|
offset: { x: 0, y: 0 },
|
||
|
texturePosition: { x: 0, y: 0 },
|
||
|
texturePositionClipSpace: { x: 0, y: 0 },
|
||
|
size: { x: 0, y: 0 },
|
||
|
sizeClipSpace: { x: 0, y: 0 }
|
||
|
};
|
||
|
var TMP_CANVAS_GLYPH_PADDING = 2;
|
||
|
var WebglCharAtlas = (function () {
|
||
|
function WebglCharAtlas(document, _config) {
|
||
|
this._config = _config;
|
||
|
this._didWarmUp = false;
|
||
|
this._cacheMap = {};
|
||
|
this._cacheMapCombined = {};
|
||
|
this._currentRowY = 0;
|
||
|
this._currentRowX = 0;
|
||
|
this._currentRowHeight = 0;
|
||
|
this.hasCanvasChanged = false;
|
||
|
this._workBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 };
|
||
|
this.cacheCanvas = document.createElement('canvas');
|
||
|
this.cacheCanvas.width = TEXTURE_WIDTH;
|
||
|
this.cacheCanvas.height = TEXTURE_HEIGHT;
|
||
|
this._cacheCtx = WebglUtils_1.throwIfFalsy(this.cacheCanvas.getContext('2d', { alpha: true }));
|
||
|
this._tmpCanvas = document.createElement('canvas');
|
||
|
this._tmpCanvas.width = this._config.scaledCharWidth * 2 + TMP_CANVAS_GLYPH_PADDING * 2;
|
||
|
this._tmpCanvas.height = this._config.scaledCharHeight + TMP_CANVAS_GLYPH_PADDING * 2;
|
||
|
this._tmpCtx = WebglUtils_1.throwIfFalsy(this._tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency }));
|
||
|
document.body.appendChild(this.cacheCanvas);
|
||
|
}
|
||
|
WebglCharAtlas.prototype.dispose = function () {
|
||
|
if (this.cacheCanvas.parentElement) {
|
||
|
this.cacheCanvas.parentElement.removeChild(this.cacheCanvas);
|
||
|
}
|
||
|
};
|
||
|
WebglCharAtlas.prototype.warmUp = function () {
|
||
|
if (!this._didWarmUp) {
|
||
|
this._doWarmUp();
|
||
|
this._didWarmUp = true;
|
||
|
}
|
||
|
};
|
||
|
WebglCharAtlas.prototype._doWarmUp = function () {
|
||
|
var _a;
|
||
|
for (var i = 33; i < 126; i++) {
|
||
|
var rasterizedGlyph = this._drawToCache(i, Constants_2.DEFAULT_ATTR, Constants_2.DEFAULT_COLOR, Constants_2.DEFAULT_COLOR);
|
||
|
this._cacheMap[i] = (_a = {},
|
||
|
_a[Constants_2.DEFAULT_ATTR] = rasterizedGlyph,
|
||
|
_a);
|
||
|
}
|
||
|
};
|
||
|
WebglCharAtlas.prototype.beginFrame = function () {
|
||
|
if (this._currentRowY > TEXTURE_CAPACITY) {
|
||
|
this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||
|
this._cacheMap = {};
|
||
|
this._currentRowHeight = 0;
|
||
|
this._currentRowX = 0;
|
||
|
this._currentRowY = 0;
|
||
|
this._doWarmUp();
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
WebglCharAtlas.prototype.getRasterizedGlyphCombinedChar = function (chars, attr, bg, fg) {
|
||
|
var rasterizedGlyphSet = this._cacheMapCombined[chars];
|
||
|
if (!rasterizedGlyphSet) {
|
||
|
rasterizedGlyphSet = {};
|
||
|
this._cacheMapCombined[chars] = rasterizedGlyphSet;
|
||
|
}
|
||
|
var rasterizedGlyph = rasterizedGlyphSet[attr];
|
||
|
if (!rasterizedGlyph) {
|
||
|
rasterizedGlyph = this._drawToCache(chars, attr, bg, fg);
|
||
|
rasterizedGlyphSet[attr] = rasterizedGlyph;
|
||
|
}
|
||
|
return rasterizedGlyph;
|
||
|
};
|
||
|
WebglCharAtlas.prototype.getRasterizedGlyph = function (code, attr, bg, fg) {
|
||
|
var rasterizedGlyphSet = this._cacheMap[code];
|
||
|
if (!rasterizedGlyphSet) {
|
||
|
rasterizedGlyphSet = {};
|
||
|
this._cacheMap[code] = rasterizedGlyphSet;
|
||
|
}
|
||
|
var rasterizedGlyph = rasterizedGlyphSet[attr];
|
||
|
if (!rasterizedGlyph) {
|
||
|
rasterizedGlyph = this._drawToCache(code, attr, bg, fg);
|
||
|
rasterizedGlyphSet[attr] = rasterizedGlyph;
|
||
|
}
|
||
|
return rasterizedGlyph;
|
||
|
};
|
||
|
WebglCharAtlas.prototype._getColorFromAnsiIndex = function (idx) {
|
||
|
if (idx >= this._config.colors.ansi.length) {
|
||
|
throw new Error('No color found for idx ' + idx);
|
||
|
}
|
||
|
return this._config.colors.ansi[idx];
|
||
|
};
|
||
|
WebglCharAtlas.prototype._getBackgroundColor = function (bg) {
|
||
|
if (this._config.allowTransparency) {
|
||
|
return TRANSPARENT_COLOR;
|
||
|
}
|
||
|
else if (bg === Constants_1.INVERTED_DEFAULT_COLOR) {
|
||
|
return this._config.colors.foreground;
|
||
|
}
|
||
|
else if (CharAtlasUtils_1.is256Color(bg)) {
|
||
|
return this._getColorFromAnsiIndex(bg);
|
||
|
}
|
||
|
return this._config.colors.background;
|
||
|
};
|
||
|
WebglCharAtlas.prototype._getForegroundColor = function (fg) {
|
||
|
if (fg === Constants_1.INVERTED_DEFAULT_COLOR) {
|
||
|
return this._config.colors.background;
|
||
|
}
|
||
|
else if (CharAtlasUtils_1.is256Color(fg)) {
|
||
|
return this._getColorFromAnsiIndex(fg);
|
||
|
}
|
||
|
return this._config.colors.foreground;
|
||
|
};
|
||
|
WebglCharAtlas.prototype._drawToCache = function (codeOrChars, attr, bg, fg) {
|
||
|
var chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;
|
||
|
this.hasCanvasChanged = true;
|
||
|
var flags = attr >> 18;
|
||
|
var bold = !!(flags & 1);
|
||
|
var dim = !!(flags & 32);
|
||
|
var italic = !!(flags & 64);
|
||
|
this._tmpCtx.save();
|
||
|
var backgroundColor = this._getBackgroundColor(bg);
|
||
|
this._tmpCtx.globalCompositeOperation = 'copy';
|
||
|
this._tmpCtx.fillStyle = backgroundColor.css;
|
||
|
this._tmpCtx.fillRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height);
|
||
|
this._tmpCtx.globalCompositeOperation = 'source-over';
|
||
|
var fontWeight = bold ? this._config.fontWeightBold : this._config.fontWeight;
|
||
|
var fontStyle = italic ? 'italic' : '';
|
||
|
this._tmpCtx.font =
|
||
|
fontStyle + " " + fontWeight + " " + this._config.fontSize * this._config.devicePixelRatio + "px " + this._config.fontFamily;
|
||
|
this._tmpCtx.textBaseline = 'top';
|
||
|
this._tmpCtx.fillStyle = this._getForegroundColor(fg).css;
|
||
|
if (dim) {
|
||
|
this._tmpCtx.globalAlpha = Constants_1.DIM_OPACITY;
|
||
|
}
|
||
|
this._tmpCtx.fillText(chars, TMP_CANVAS_GLYPH_PADDING, TMP_CANVAS_GLYPH_PADDING);
|
||
|
this._tmpCtx.restore();
|
||
|
var imageData = this._tmpCtx.getImageData(0, 0, this._tmpCanvas.width, this._tmpCanvas.height);
|
||
|
var isEmpty = clearColor(imageData, backgroundColor);
|
||
|
if (isEmpty) {
|
||
|
return NULL_RASTERIZED_GLYPH;
|
||
|
}
|
||
|
var rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox);
|
||
|
var clippedImageData = this._clipImageData(imageData, this._workBoundingBox);
|
||
|
if (this._currentRowX + this._config.scaledCharWidth > TEXTURE_WIDTH) {
|
||
|
this._currentRowX = 0;
|
||
|
this._currentRowY += this._currentRowHeight;
|
||
|
this._currentRowHeight = 0;
|
||
|
}
|
||
|
rasterizedGlyph.texturePosition.x = this._currentRowX;
|
||
|
rasterizedGlyph.texturePosition.y = this._currentRowY;
|
||
|
rasterizedGlyph.texturePositionClipSpace.x = this._currentRowX / TEXTURE_WIDTH;
|
||
|
rasterizedGlyph.texturePositionClipSpace.y = this._currentRowY / TEXTURE_HEIGHT;
|
||
|
this._currentRowHeight = Math.max(this._currentRowHeight, rasterizedGlyph.size.y);
|
||
|
this._currentRowX += rasterizedGlyph.size.x;
|
||
|
this._cacheCtx.putImageData(clippedImageData, rasterizedGlyph.texturePosition.x, rasterizedGlyph.texturePosition.y);
|
||
|
return rasterizedGlyph;
|
||
|
};
|
||
|
WebglCharAtlas.prototype._findGlyphBoundingBox = function (imageData, boundingBox) {
|
||
|
boundingBox.top = 0;
|
||
|
var found = false;
|
||
|
for (var y = 0; y < this._tmpCanvas.height; y++) {
|
||
|
for (var x = 0; x < this._tmpCanvas.width; x++) {
|
||
|
var alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
|
||
|
if (imageData.data[alphaOffset] !== 0) {
|
||
|
boundingBox.top = y;
|
||
|
found = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (found) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
boundingBox.left = 0;
|
||
|
found = false;
|
||
|
for (var x = 0; x < this._tmpCanvas.width; x++) {
|
||
|
for (var y = 0; y < this._tmpCanvas.height; y++) {
|
||
|
var alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
|
||
|
if (imageData.data[alphaOffset] !== 0) {
|
||
|
boundingBox.left = x;
|
||
|
found = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (found) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
boundingBox.right = this._tmpCanvas.width;
|
||
|
found = false;
|
||
|
for (var x = this._tmpCanvas.width - 1; x >= 0; x--) {
|
||
|
for (var y = 0; y < this._tmpCanvas.height; y++) {
|
||
|
var alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
|
||
|
if (imageData.data[alphaOffset] !== 0) {
|
||
|
boundingBox.right = x;
|
||
|
found = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (found) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
boundingBox.bottom = this._tmpCanvas.height;
|
||
|
found = false;
|
||
|
for (var y = this._tmpCanvas.height - 1; y >= 0; y--) {
|
||
|
for (var x = 0; x < this._tmpCanvas.width; x++) {
|
||
|
var alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
|
||
|
if (imageData.data[alphaOffset] !== 0) {
|
||
|
boundingBox.bottom = y;
|
||
|
found = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (found) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return {
|
||
|
texturePosition: { x: 0, y: 0 },
|
||
|
texturePositionClipSpace: { x: 0, y: 0 },
|
||
|
size: {
|
||
|
x: boundingBox.right - boundingBox.left + 1,
|
||
|
y: boundingBox.bottom - boundingBox.top + 1
|
||
|
},
|
||
|
sizeClipSpace: {
|
||
|
x: (boundingBox.right - boundingBox.left + 1) / TEXTURE_WIDTH,
|
||
|
y: (boundingBox.bottom - boundingBox.top + 1) / TEXTURE_HEIGHT
|
||
|
},
|
||
|
offset: {
|
||
|
x: -boundingBox.left + TMP_CANVAS_GLYPH_PADDING,
|
||
|
y: -boundingBox.top + TMP_CANVAS_GLYPH_PADDING
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
WebglCharAtlas.prototype._clipImageData = function (imageData, boundingBox) {
|
||
|
var width = boundingBox.right - boundingBox.left + 1;
|
||
|
var height = boundingBox.bottom - boundingBox.top + 1;
|
||
|
var clippedData = new Uint8ClampedArray(width * height * 4);
|
||
|
for (var y = boundingBox.top; y <= boundingBox.bottom; y++) {
|
||
|
for (var x = boundingBox.left; x <= boundingBox.right; x++) {
|
||
|
var oldOffset = y * this._tmpCanvas.width * 4 + x * 4;
|
||
|
var newOffset = (y - boundingBox.top) * width * 4 + (x - boundingBox.left) * 4;
|
||
|
clippedData[newOffset] = imageData.data[oldOffset];
|
||
|
clippedData[newOffset + 1] = imageData.data[oldOffset + 1];
|
||
|
clippedData[newOffset + 2] = imageData.data[oldOffset + 2];
|
||
|
clippedData[newOffset + 3] = imageData.data[oldOffset + 3];
|
||
|
}
|
||
|
}
|
||
|
return new ImageData(clippedData, width, height);
|
||
|
};
|
||
|
return WebglCharAtlas;
|
||
|
}());
|
||
|
exports.WebglCharAtlas = WebglCharAtlas;
|
||
|
function clearColor(imageData, color) {
|
||
|
var isEmpty = true;
|
||
|
var r = color.rgba >>> 24;
|
||
|
var g = color.rgba >>> 16 & 0xFF;
|
||
|
var b = color.rgba >>> 8 & 0xFF;
|
||
|
for (var offset = 0; offset < imageData.data.length; offset += 4) {
|
||
|
if (imageData.data[offset] === r &&
|
||
|
imageData.data[offset + 1] === g &&
|
||
|
imageData.data[offset + 2] === b) {
|
||
|
imageData.data[offset + 3] = 0;
|
||
|
}
|
||
|
else {
|
||
|
isEmpty = false;
|
||
|
}
|
||
|
}
|
||
|
return isEmpty;
|
||
|
}
|
||
|
//# sourceMappingURL=WebglCharAtlas.js.map
|