scichart
Version:
Fast WebGL JavaScript Charting Library and Framework
377 lines (376 loc) • 19.9 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.measureTextWidth = exports.measureTextHeight = exports.TextureManager = void 0;
var app_1 = require("../../../constants/app");
var DeletableEntity_1 = require("../../../Core/DeletableEntity");
var AxisAlignment_1 = require("../../../types/AxisAlignment");
var LabelAlignment_1 = require("../../../types/LabelAlignment");
var font_1 = require("../../../utils/font");
var parseColor_1 = require("../../../utils/parseColor");
var NativeObject_1 = require("../Helpers/NativeObject");
var DpiHelper_1 = require("./DpiHelper");
/** @ignore */
var DEFAULT_HEIGHT = 1080;
/** @ignore */
var DEFAULT_WIDTH = 1920;
/** @ignore */
var PT_TO_PX = 96 / 72;
/** @ignore */
var PX_TO_PT = 72 / 96;
var TextureManager = /** @class */ (function (_super) {
__extends(TextureManager, _super);
function TextureManager(webAssemblyContext) {
var _this = _super.call(this) || this;
_this.webAssemblyContext = webAssemblyContext;
if (!app_1.IS_TEST_ENV) {
_this.canvas = document.createElement("canvas");
_this.canvas.width = DEFAULT_WIDTH;
_this.canvas.height = DEFAULT_HEIGHT;
_this.ctx = _this.canvas.getContext("2d", { willReadFrequently: true });
}
return _this;
}
TextureManager.prototype.createSimpleTextTexture = function (text, textStyle, backgroundColor, displayVertically, displayMirrored, opacity) {
var rotation = 0;
if (displayVertically) {
if (displayMirrored) {
rotation = 90;
}
else {
rotation = -90;
}
}
return this.createTextTexture([text], textStyle, rotation, undefined, backgroundColor, opacity);
};
/**
* Create a text texture supporting multiline and arbitrary rotation
* @param text
* @param textStyle
* @param rotation
* @param lineSpacing Expressed as a fraction of the font size. Default 1.1
* @param backgroundColor
* @param opacity
* @returns
*/
TextureManager.prototype.createTextTextureNative = function (text, textStyle, rotation, lineSpacing, backgroundColor, opacity) {
var fontStyle = textStyle.fontStyle, fontWeight = textStyle.fontWeight, fontFamily = textStyle.fontFamily, fontSizePx = textStyle.fontSize, padding = textStyle.padding, alignment = textStyle.alignment, color = textStyle.color;
var nativeContext = this.webAssemblyContext.SCRTGetMainRenderContext2D();
// TODO support rotation, spacing and alignment
var fontKey = (0, NativeObject_1.getFontKey)(this.webAssemblyContext, { fontFamily: fontFamily, fontSize: fontSizePx !== null && fontSizePx !== void 0 ? fontSizePx : 12 });
var bgColor = backgroundColor ? (0, parseColor_1.parseColorToUIntArgb)(backgroundColor) : 0;
var bitmapTexture = nativeContext.CreateTextTextureColored(fontKey, text.join("\n"), (0, parseColor_1.parseColorToUIntArgb)(color), bgColor);
this.webAssemblyContext.SCRTSetTextureLinearSamplerEnabled(bitmapTexture, false);
var textureWidth = bitmapTexture.GetWidth();
var textureHeight = bitmapTexture.GetHeight();
return {
bitmapTexture: bitmapTexture,
textureWidth: textureWidth,
textureHeight: textureHeight
};
};
/**
* Create a text texture supporting multiline and arbitrary rotation
* @param text
* @param textStyle
* @param rotation
* @param lineSpacing Expressed as a fraction of the font size. Default 1.1
* @param backgroundColor
* @param opacity
* @returns
*/
TextureManager.prototype.createTextTexture = function (text, textStyle, rotation, lineSpacing, backgroundColor, opacity) {
var _a;
if (app_1.IS_TEST_ENV) {
return { bitmapTexture: undefined, textureWidth: 1, textureHeight: 1 };
}
var fontStyle = textStyle.fontStyle, fontWeight = textStyle.fontWeight, fontFamily = textStyle.fontFamily, fontSizePx = textStyle.fontSize, padding = textStyle.padding, alignment = textStyle.alignment, color = textStyle.color;
// Save state
this.ctx.save();
this.ctx.globalAlpha = opacity !== null && opacity !== void 0 ? opacity : 1;
this.ctx.textBaseline = "alphabetic";
// Switched this back to alphabetic because...reasons
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-textbaseline-alphabetic
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
if (backgroundColor) {
this.ctx.fillStyle = backgroundColor;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
this.ctx.fillStyle = color;
this.ctx.font = (0, font_1.getFontString)(fontStyle, fontWeight, fontSizePx, fontFamily);
var textureWidth = 0;
// const fontSizePt = Math.ceil(fontSizePx * PX_TO_PT);
var textureHeight = padding.top + padding.bottom;
var maxLineHeight = 0;
var firstLineOffset = 0;
var lineHeights = [];
for (var index = 0; index < text.length; index++) {
var line = text[index];
var textMeasure = this.ctx.measureText(line);
var lineHeight = Math.round(textMeasure.actualBoundingBoxAscent + textMeasure.actualBoundingBoxDescent);
lineHeights.push(lineHeight);
if (lineHeight > maxLineHeight) {
maxLineHeight = lineHeight;
}
if (index === 0) {
firstLineOffset = textMeasure.actualBoundingBoxAscent;
}
var lineWidth = Math.round(textMeasure.width + padding.left + padding.right);
if (lineWidth > textureWidth) {
textureWidth = lineWidth;
}
textureHeight += lineHeight;
}
var lineSpacingPixels = Math.round(maxLineHeight * ((lineSpacing !== null && lineSpacing !== void 0 ? lineSpacing : 1.1) - 1));
textureHeight += (text.length - 1) * lineSpacingPixels;
var y = padding.top + firstLineOffset;
var x = padding.left;
if (!textureWidth || !textureHeight) {
return { bitmapTexture: undefined, textureWidth: textureWidth, textureHeight: textureHeight };
}
var newTextureWidth = textureWidth;
var newTextureHeight = textureHeight;
if (rotation) {
// convert to radians
var rotationRad = (rotation * Math.PI) / 180;
// We need to Math.ceil to fix rotation for WebGL1
newTextureWidth = Math.round(textureWidth * Math.abs(Math.cos(rotationRad)) + textureHeight * Math.abs(Math.sin(rotationRad)));
newTextureHeight = Math.round(textureWidth * Math.abs(Math.sin(rotationRad)) + textureHeight * Math.abs(Math.cos(rotationRad)));
// translate to the center of rectangle
this.ctx.translate(newTextureWidth / 2, newTextureHeight / 2);
this.ctx.rotate(rotationRad);
// translate to the new origin that is located in the left top center of the texture
this.ctx.translate(-textureWidth / 2, -textureHeight / 2);
}
var multilineAlignment = (_a = textStyle.multilineAlignment) !== null && _a !== void 0 ? _a : alignment;
var maxLineTextWidth = textureWidth - padding.right - padding.left;
for (var index = 0; index < text.length; index++) {
var line = text[index];
if (multilineAlignment === LabelAlignment_1.ELabelAlignment.Center || multilineAlignment === LabelAlignment_1.ELabelAlignment.Right) {
var lineWidth = (0, exports.measureTextWidth)(this.ctx, line);
x =
multilineAlignment === LabelAlignment_1.ELabelAlignment.Right
? textureWidth - lineWidth - padding.right
: (maxLineTextWidth - lineWidth) / 2 + padding.left;
}
this.ctx.fillText(line, Math.round(x), Math.round(y));
y += lineHeights[index] + lineSpacingPixels;
}
this.ctx.strokeStyle = color;
this.ctx.restore();
// Useful for debugging rotated textures
// this.ctx.beginPath();
// this.ctx.rect(0, 0, newTextureWidth, newTextureHeight);
// this.ctx.stroke();
return this.createTextureFromCtxBuffer(newTextureWidth, newTextureHeight);
};
TextureManager.prototype.createAxisMarkerTexture = function (axisAlignment, text, fontStyle, fontWeight, fontSizePx, fontFamily, color, padding, backgroundColor, opacity) {
if (padding === void 0) { padding = 0; }
this.ctx.globalAlpha = opacity !== null && opacity !== void 0 ? opacity : 1;
this.ctx.textBaseline = "top";
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.font = (0, font_1.getFontString)(fontStyle, fontWeight, fontSizePx, fontFamily);
var textureWidth = (0, exports.measureTextWidth)(this.ctx, text) + 4 * padding;
var textureHeight = (0, exports.measureTextHeight)(fontSizePx) + 2 * padding;
var _a = calcAxisMarkerTextureParams(textureHeight, textureWidth, text, axisAlignment), angle = _a.angle, alignmentLeft = _a.alignmentLeft, alignmentTop = _a.alignmentTop, finalTextureWidth = _a.finalTextureWidth, finalTextureHeight = _a.finalTextureHeight, halfHeight = _a.halfHeight;
this.ctx.save();
this.ctx.translate((textureWidth + halfHeight) / 2, textureHeight / 2);
this.ctx.rotate(angle);
this.ctx.translate(alignmentLeft, alignmentTop);
if (backgroundColor) {
var region = new Path2D();
var height = textureHeight;
var width = textureWidth;
region.moveTo(0, 0);
region.lineTo(width, 0);
region.lineTo(halfHeight + width, halfHeight);
region.lineTo(width, height);
region.lineTo(0, height);
region.closePath();
this.ctx.fillStyle = backgroundColor;
this.ctx.fill(region);
}
// const fontSizePt = Math.ceil(fontSizePx * PX_TO_PT);
if (axisAlignment !== AxisAlignment_1.EAxisAlignment.Right) {
this.ctx.fillStyle = color;
this.ctx.fillText(text, 2 * padding, padding);
}
this.ctx.restore();
// display label in default orientation if marker is placed on the right side
if (axisAlignment === AxisAlignment_1.EAxisAlignment.Right) {
this.ctx.fillStyle = color;
this.ctx.fillText(text, 2 * padding + halfHeight, padding);
}
// uncomment this line to get texture width and height to use in unit tests
// console.log("createAxisMarkerTexture width, height", finalTextureWidth, finalTextureHeight);
return this.createTextureFromCtxBuffer(finalTextureWidth, finalTextureHeight);
};
/**
* Creates {@link TSRTexture} from image
* @param image The image
* @param imageWidth The image width, not premultiplied value
* @param imageHeight The image height, not premultiplied value
*/
TextureManager.prototype.createTextureFromImage = function (image, imageWidth, imageHeight) {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
var width = (imageWidth !== null && imageWidth !== void 0 ? imageWidth : image.width) * DpiHelper_1.DpiHelper.PIXEL_RATIO;
var height = (imageHeight !== null && imageHeight !== void 0 ? imageHeight : image.height) * DpiHelper_1.DpiHelper.PIXEL_RATIO;
this.ctx.drawImage(image, 0, 0, width, height);
return this.createTextureFromCtxBuffer(width, height);
};
TextureManager.prototype.getTextureContext = function (width, height) {
if (app_1.IS_TEST_ENV)
return undefined;
if (width > this.canvas.width) {
this.canvas.width = width;
}
if (height > this.canvas.height) {
this.canvas.height = height;
}
// TODO it's not clear if this is actually required, and it's slow.
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
return this.ctx;
};
TextureManager.prototype.createTextureFromCtxBuffer = function (textureWidth, textureHeight) {
var imageData = this.ctx.getImageData(0, 0, textureWidth, textureHeight);
return this.createTextureFromImageData(imageData, textureWidth, textureHeight);
};
TextureManager.prototype.createTextureFromImageData = function (imageData, textureWidth, textureHeight) {
var textureFormat = this.webAssemblyContext.eTSRTextureFormat.TSR_TEXTUREFORMAT_A8B8G8R8;
var bitmapTexture = this.webAssemblyContext.SCRTCreateBitmapTexture(textureWidth, textureHeight, textureFormat);
var buffer;
try {
// const imageData = this.ctx.getImageData(0, 0, textureWidth, textureHeight);
var clampedCharArray = imageData.data;
/// because plain c functions were receiving the object pointer as null, this is the way around that...
this.webAssemblyContext.SCRTSetActiveTexture(bitmapTexture);
// Allocate some space in the heap for the data (making sure to use the appropriate memory size)
// @ts-ignore
buffer = this.webAssemblyContext._malloc(clampedCharArray.length);
// Assign the data to the heap - Keep in mind bytes per element
// @ts-ignore
this.webAssemblyContext.HEAP8.set(clampedCharArray, buffer);
// Call the function with "number" parameter type for the array (the pointer), and an extra length parameter
// @ts-ignore
var result = this.webAssemblyContext.ccall("SCRTFillActiveTextureCharArray", null, ["number", "number", "number"], [textureWidth, textureHeight, buffer]);
}
catch (e) {
console.error(e);
}
finally {
// This needs to happen in finally block, otherwise thrown errors will stop code execution before
// @ts-ignore
this.webAssemblyContext._free(buffer);
// @shady : Textures with text should use point sampling, not linear, to prevent black edges issue
this.webAssemblyContext.SCRTSetTextureLinearSamplerEnabled(bitmapTexture, false);
}
return {
bitmapTexture: bitmapTexture,
textureWidth: textureWidth,
textureHeight: textureHeight
};
};
TextureManager.prototype.delete = function () {
this.canvas = undefined;
this.ctx = undefined;
this.webAssemblyContext = undefined;
};
TextureManager.prototype.createTextureFromCtx = function (textureWidth, textureHeight) {
// TODO: Michael cache the canvas, UIntVector, bitmapTexture unless size changed
// Logger.log(`New Texture: Size is ${textureWidth}, ${textureHeight}`);
var imageData = this.ctx.getImageData(0, 0, textureWidth, textureHeight);
// Array of Uint8
var imageArr = imageData.data;
// Array of Uint32
var size = textureWidth * textureHeight;
var aPixels = new this.webAssemblyContext.UIntVector();
// Set initial capacity and fill with zeros
aPixels.resize(size, 0);
for (var i = 0; i < size; i++) {
var el = i * 4;
var a = imageArr[el + 3];
// Only set pixels that are not alpha=0
if (a !== 0) {
var r = imageArr[el];
var g = imageArr[el + 1];
var b = imageArr[el + 2];
// tslint:disable-next-line:no-bitwise
var pixel = (a << 24) | (b << 16) | (g << 8) | r;
// tslint:disable-next-line:no-bitwise
aPixels.set(i, pixel >>> 0);
}
}
// TODO: Consider faster ways of transferring memory to WebAssembly
// e.g. can we do this? https://github.com/WebAssembly/design/issues/1231
var textureFormat = this.webAssemblyContext.eTSRTextureFormat.TSR_TEXTUREFORMAT_A8B8G8R8;
var bitmapTexture = this.webAssemblyContext.SCRTCreateBitmapTexture(textureWidth, textureHeight, textureFormat);
this.webAssemblyContext.SCRTFillTextureAbgr(bitmapTexture, textureWidth, textureHeight, aPixels);
aPixels.delete();
return {
bitmapTexture: bitmapTexture,
textureWidth: textureWidth,
textureHeight: textureHeight
};
};
return TextureManager;
}(DeletableEntity_1.DeletableEntity));
exports.TextureManager = TextureManager;
var measureTextHeight = function (fontSizePx) { return fontSizePx; };
exports.measureTextHeight = measureTextHeight;
var measureTextWidth = function (ctx, text) {
if (app_1.IS_TEST_ENV)
return 1;
var textMeasure = ctx.measureText(text);
return Math.ceil(textMeasure.width);
};
exports.measureTextWidth = measureTextWidth;
/** @ignore */
var calcAxisMarkerTextureParams = function (textureHeight, textureWidth, text, axisAlignment) {
var halfHeight = Math.ceil(textureHeight / 2);
var angle = 0;
var alignmentLeft = 0;
var alignmentTop = 0;
var finalTextureWidth = textureWidth + halfHeight;
var finalTextureHeight = textureHeight;
switch (axisAlignment) {
case AxisAlignment_1.EAxisAlignment.Left:
angle = 0;
alignmentLeft = -(textureWidth + halfHeight) / 2;
alignmentTop = -textureHeight / 2;
break;
case AxisAlignment_1.EAxisAlignment.Top:
angle = Math.PI * 0.5;
alignmentLeft = -textureHeight / 2;
alignmentTop = (textureWidth + halfHeight) / 2 - textureHeight;
finalTextureWidth = textureHeight;
finalTextureHeight = textureWidth + halfHeight;
break;
case AxisAlignment_1.EAxisAlignment.Right:
angle = Math.PI;
alignmentLeft = -(textureWidth + halfHeight) / 2;
alignmentTop = -textureHeight / 2;
break;
case AxisAlignment_1.EAxisAlignment.Bottom:
angle = Math.PI * 1.5;
alignmentLeft = -textureWidth;
alignmentTop = -(textureWidth + halfHeight) / 2;
finalTextureWidth = textureHeight;
finalTextureHeight = textureWidth + halfHeight;
break;
}
return { angle: angle, alignmentLeft: alignmentLeft, alignmentTop: alignmentTop, finalTextureWidth: finalTextureWidth, finalTextureHeight: finalTextureHeight, halfHeight: halfHeight };
};