/* * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) * version 3 as published by the Free Software Foundation. In accordance with * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect * that Ascensio System SIA expressly excludes the warranty of non-infringement * of any third-party rights. * * This program is distributed WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under * Section 5 of the GNU AGPL version 3. * * Pursuant to Section 7(b) of the License you must retain the original Product * logo when distributing the program. Pursuant to Section 7(e) we decline to * grant you any rights under trademark law for use of our trademarks. * * All the Product's GUI elements, including illustrations and icon sets, as * well as technical writing content are licensed under the terms of the * Creative Commons Attribution-ShareAlike 4.0 International. See the License * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ "use strict"; (function (/** Window */window, undefined) { /* * Import * ----------------------------------------------------------------------------- */ var FontStyle = AscFonts.FontStyle; var asc = window["Asc"]; var asc_round = asc.round; var asc_floor = asc.floor; function colorObjToAscColor(color) { if (!color) { return AscCommon.CreateAscColorCustom(0, 0, 0, true); } var oRes = null; var r = color.getR(); var g = color.getG(); var b = color.getB(); var bTheme = false; if (color instanceof AscCommonExcel.ThemeColor && null != color.theme) { var array_colors_types = [6, 15, 7, 16, 0, 1, 2, 3, 4, 5]; var themePresentation = array_colors_types[color.theme]; var tintExcel = 0; if (null != color.tint) { tintExcel = color.tint; } var tintPresentation = 0; var basecolor = AscCommonExcel.g_oColorManager.getThemeColor(color.theme); var oThemeColorTint = AscCommonExcel.g_oThemeColorsDefaultModsSpreadsheet[AscCommon.GetDefaultColorModsIndex( basecolor.getR(), basecolor.getG(), basecolor.getB())]; if (null != oThemeColorTint) { for (var i = 0, length = oThemeColorTint.length; i < length; ++i) { var cur = oThemeColorTint[i]; //0.005 установлено экспериментально if (Math.abs(cur - tintExcel) < 0.005) { bTheme = true; tintPresentation = i; break; } } } if (bTheme) { oRes = new Asc.asc_CColor(); oRes.r = r; oRes.g = g; oRes.b = b; oRes.a = 255; oRes.type = Asc.c_oAscColor.COLOR_TYPE_SCHEME; oRes.value = themePresentation; } } if (!bTheme) { oRes = AscCommon.CreateAscColorCustom(r, g, b); } return oRes; } var oldPpi = undefined, cvt = undefined; /** * Gets ratio to convert units * @param {Number} fromUnits Units (0=px, 1=pt, 2=in, 3=mm) * @param {Number} toUnits Units (0=px, 1=pt, 2=in, 3=mm) * @param {Number} ppi Points per inch * @return {Number} Ratio */ function getCvtRatio(fromUnits, toUnits, ppi) { if (ppi !== oldPpi || oldPpi === undefined) { var _ppi = 1 / ppi, _72 = 1 / 72, _25_4 = 1 / 25.4; cvt = [/* px pt in mm */ /*px*/[1, 72 * _ppi, _ppi, 25.4 * _ppi], /*pt*/[ppi * _72, 1, _72, 25.4 * _72], /*in*/ [ppi, 72, 1, 25.4], /*mm*/[ppi * _25_4, 72 * _25_4, _25_4, 1]]; oldPpi = ppi; } return cvt[fromUnits][toUnits]; } /** @const */ var MATRIX_ORDER_PREPEND = AscCommon.MATRIX_ORDER_PREPEND; var MATRIX_ORDER_APPEND = AscCommon.MATRIX_ORDER_APPEND; /** * @constructor */ function Matrix() { if (!(this instanceof Matrix)) { return new Matrix(); } this.sx = 1.0; this.shx = 0.0; this.shy = 0.0; this.sy = 1.0; this.tx = 0.0; this.ty = 0.0; return this; } Matrix.prototype.reset = function () { this.sx = 1.0; this.shx = 0.0; this.shy = 0.0; this.sy = 1.0; this.tx = 0.0; this.ty = 0.0; }; Matrix.prototype.assign = function (sx, shx, shy, sy, tx, ty) { this.sx = sx; this.shx = shx; this.shy = shy; this.sy = sy; this.tx = tx; this.ty = ty; }; Matrix.prototype.copyFrom = function (matrix) { this.sx = matrix.sx; this.shx = matrix.shx; this.shy = matrix.shy; this.sy = matrix.sy; this.tx = matrix.tx; this.ty = matrix.ty; }; Matrix.prototype.clone = function () { var m = new Matrix(); m.copyFrom(this); return m; }; Matrix.prototype.multiply = function (matrix, order) { if (MATRIX_ORDER_PREPEND === order) { var m = matrix.clone(); m.multiply(this, MATRIX_ORDER_APPEND); this.copyFrom(m); } else { var t0 = this.sx * matrix.sx + this.shy * matrix.shx; var t2 = this.shx * matrix.sx + this.sy * matrix.shx; var t4 = this.tx * matrix.sx + this.ty * matrix.shx + matrix.tx; this.shy = this.sx * matrix.shy + this.shy * matrix.sy; this.sy = this.shx * matrix.shy + this.sy * matrix.sy; this.ty = this.tx * matrix.shy + this.ty * matrix.sy + matrix.ty; this.sx = t0; this.shx = t2; this.tx = t4; } }; Matrix.prototype.translate = function (x, y, order) { var m = new Matrix(); m.tx = x; m.ty = y; this.multiply(m, order); }; Matrix.prototype.scale = function (x, y, order) { var m = new Matrix(); m.sx = x; m.sy = y; this.multiply(m, order); }; Matrix.prototype.rotate = function (a, order) { var m = new Matrix(); var rad = AscCommon.deg2rad(a); m.sx = Math.cos(rad); m.shx = Math.sin(rad); m.shy = -Math.sin(rad); m.sy = Math.cos(rad); this.multiply(m, order); }; Matrix.prototype.rotateAt = function (a, x, y, order) { this.translate(-x, -y, order); this.rotate(a, order); this.translate(x, y, order); }; Matrix.prototype.determinant = function () { return this.sx * this.sy - this.shy * this.shx; }; Matrix.prototype.invert = function () { var det = this.determinant(); if (0.0001 > det) { return; } var d = 1 / det; var t0 = this.sy * d; this.sy = this.sx * d; this.shy = -this.shy * d; this.shx = -this.shx * d; var t4 = -this.tx * t0 - this.ty * this.shx; this.ty = -this.tx * this.shy - this.ty * this.sy; this.sx = t0; this.tx = t4; }; Matrix.prototype.transformPointX = function (x, y) { return x * this.sx + y * this.shx + this.tx; }; Matrix.prototype.transformPointY = function (x, y) { return x * this.shy + y * this.sy + this.ty; }; /** Calculates rotation angle */ Matrix.prototype.getRotation = function () { var x1 = 0.0; var y1 = 0.0; var x2 = 1.0; var y2 = 0.0; this.transformPoint(x1, y1); this.transformPoint(x2, y2); var a = Math.atan2(y2 - y1, x2 - x1); return AscCommon.rad2deg(a); }; /** * Creates text metrics * ----------------------------------------------------------------------------- * @constructor * @param {Number} width * @param {Number} height * @param {Number} lineHeight * @param {Number} baseline * @param {Number} descender * @param {Number} fontSize * @param {Number} widthBB * * @memberOf Asc */ function TextMetrics(width, height, lineHeight, baseline, descender, fontSize, widthBB) { this.width = width || 0; this.height = height || 0; this.lineHeight = lineHeight || 0; this.baseline = baseline || 0; this.descender = descender || 0; this.fontSize = fontSize || 0; this.widthBB = widthBB || 0; return this; } /** * Creates font metrics * ----------------------------------------------------------------------------- * @constructor * * @memberOf Asc */ function FontMetrics() { this.ascender = 0; this.descender = 0; this.lineGap = 0; this.nat_scale = 0; this.nat_y1 = 0; this.nat_y2 = 0; } FontMetrics.prototype.clone = function () { var res = new FontMetrics(); res.ascender = this.ascender; res.descender = this.descender; res.lineGap = this.lineGap; res.nat_scale = this.nat_scale; res.nat_y1 = this.nat_y1; res.nat_y2 = this.nat_y2; return res; }; function NativeContext() { this.ctx = window["native"]; } NativeContext.prototype.save = function () { this.ctx["PD_Save"](); }; NativeContext.prototype.restore = function () { this.ctx["PD_Restore"](); }; NativeContext.prototype.rect = function (x, y, w, h) { this.ctx["PD_rect"](x, y, w, h); }; NativeContext.prototype.clip = function () { this.ctx["PD_clip"](); }; NativeContext.prototype.fill = function () { this.ctx["PD_Fill"](); }; NativeContext.prototype.stroke = function () { this.ctx["PD_Stroke"](); }; NativeContext.prototype.fillRect = function (x, y, w, h) { this.rect(x, y, w, h); this.fill(); }; NativeContext.prototype.strokeRect = function (x, y, w, h) { this.rect(x, y, w, h); this.stroke(); }; NativeContext.prototype.beginPath = function () { this.ctx["PD_PathStart"](); }; NativeContext.prototype.closePath = function () { this.ctx["PD_PathClose"](); }; NativeContext.prototype.moveTo = function (x, y) { this.ctx["PD_PathMoveTo"](x, y); }; NativeContext.prototype.lineTo = function (x, y) { this.ctx["PD_PathLineTo"](x, y); }; NativeContext.prototype.lineHor = function (x1, y, x2) { this.ctx["PD_PathMoveTo"](x1, y); this.ctx["PD_PathLineTo"](x2, y); }; NativeContext.prototype.lineVer = function (x, y1, y2) { this.ctx["PD_PathMoveTo"](x, y1); this.ctx["PD_PathLineTo"](x, y2); }; NativeContext.prototype.bezierCurveTo = function (x1, y1, x2, y2, x3, y3) { this.ctx["PD_PathCurveTo"](x1, y1, x2, y2, x3, y3); }; NativeContext.prototype.setStrokeStyle = function (r, g, b, a) { this.ctx["PD_p_color"](r, g, b, a * 255); }; NativeContext.prototype.setFillStyle = function (r, g, b, a) { this.ctx["PD_b_color1"](r, g, b, a * 255); }; Object.defineProperty(NativeContext.prototype, "lineWidth", { set: function (value) { this.ctx["PD_p_width"](value); } }); NativeContext.prototype.clearRect = function (x, y, w, h) {}; NativeContext.prototype.getImageData = function (sx,sy,sw,sh) {}; NativeContext.prototype.putImageData = function (image_data,dx,dy,dirtyX,dirtyY,dirtyWidth,dirtyHeight) {}; // В текущей реализации используется несколько разных DrawingContext, но ссылающихся на одни и те же // FontManager, чтобы разрулить правильное выставление шрифта используем здесь локальные переменные let setupFontSize = -1; let setupFontName = ""; let setupFontStyle = -1; let setupRotated = false; let setupPpiX = -1; let setupPpiY = -1; function resetDrawingContextFonts() { setupFontSize = -1; setupFontName = ""; setupFontStyle = -1; setupRotated = false; setupPpiX = -1; setupPpiY = -1; } /** * Emulates scalable canvas context * ----------------------------------------------------------------------------- * @constructor * @param {Object} settings Settings : { * canvas : HTMLElement * units : units (0=px, 1=pt, 2=in, 3=mm) * font : AscCommonExcel.Font * } * * @memberOf Asc */ function DrawingContext(settings) { this.canvas = null; this.ctx = null; this.setCanvas(settings.canvas); this.textRotated = false; this.ppiX = 96; this.ppiY = 96; this.scaleFactor = 1; this._ppiInit(); this.LastFontOriginInfo = undefined; this._mct = new Matrix(); // units transform this._mt = new Matrix(); // user transform this._mbt = new Matrix(); // bound transform this._mft = new Matrix(); // full transform this._mift = new Matrix(); // inverted full transform this._im = new Matrix(); this.units = 3/*mm*/; this.changeUnits(undefined !== settings.units ? settings.units : this.units); this.fmgrGraphics = undefined !== settings.fmgrGraphics ? settings.fmgrGraphics : null; if (null === this.fmgrGraphics) { throw "Can not set graphics in DrawingContext"; } /** @type AscCommonExcel.Font */ this.font = undefined !== settings.font ? settings.font : null; // Font должен быть передан (он общий для всех DrawingContext, т.к. может возникнуть ситуация как в баге http://bugzilla.onlyoffice.com/show_bug.cgi?id=19784) if (null === this.font) { throw "Can not set font in DrawingContext"; } // AscCommon.CColor this.fillColor = new AscCommon.CColor(255, 255, 255); return this; } DrawingContext.prototype._ppiInit = function () { this.scaleFactor = 1; if (window["IS_NATIVE_EDITOR"]) { this.ppiX = this.ppiY = window["native"]["GetDeviceDPI"](); } else { this.ppiX = AscCommon.AscBrowser.convertToRetinaValue(96, true); this.ppiY = AscCommon.AscBrowser.convertToRetinaValue(96, true); } }; DrawingContext.prototype.toDataURL = function (type) { type = type || 'image/png'; var canvas = this.getCanvas(); return canvas.toDataURL(type); }; /** * Returns width of drawing context in current units * @param {Number} [units] Единицы измерения (0=px, 1=pt, 2=in, 3=mm) в которых будет возвращена ширина * @return {Number} */ DrawingContext.prototype.getWidth = function (units) { var i = units >= 0 && units <= 3 ? units : this.units; return this.canvas.width * getCvtRatio(0/*px*/, i, this.ppiX); }; /** * Returns height of drawing context in current units * @param {Number} [units] Единицы измерения (0=px, 1=pt, 2=in, 3=mm) в которых будет возвращена высота * @return {Number} */ DrawingContext.prototype.getHeight = function (units) { var i = units >= 0 && units <= 3 ? units : this.units; return this.canvas.height * getCvtRatio(0/*px*/, i, this.ppiY); }; /** * Returns canvas element * @type {Element} */ DrawingContext.prototype.getCanvas = function () { return this.canvas; }; /** * * @param canvas */ DrawingContext.prototype.setCanvas = function (canvas) { this.canvas = canvas || null; this.ctx = window["IS_NATIVE_EDITOR"] ? new NativeContext() : (this.canvas && AscCommon.AscBrowser.getContext2D(this.canvas)); }; /** * Returns pixels per inch ratio * @type {Number} */ DrawingContext.prototype.getPPIX = function () { return this.ppiX; }; /** * Returns pixels per inch ratio * @type {Number} */ DrawingContext.prototype.getPPIY = function () { return this.ppiY; }; /** * Returns currrent units (0=px, 1=pt, 2=in, 3=mm) * @type {Number} */ DrawingContext.prototype.getUnits = function () { return this.units; }; DrawingContext.prototype.moveImageDataSafari = function (sx, sy, w, h, x, y) { var sr = this._calcRect(sx, sy, w, h); var r = this._calcRect(x, y); var imgData = this.ctx.getImageData(sr.x, sr.y, sr.w, sr.h); var minX, maxX, minY, maxY; if (sx < x) { minX = sr.x; maxX = r.x; } else { minX = r.x; maxX = sr.x; } if (sy < y) { minY = sr.y; maxY = r.y; } else { minY = r.y; maxY = sr.y; } this.ctx.clearRect(minX, minY, maxX + sr.w, maxY + sr.h); this.ctx.putImageData(imgData, r.x, r.y); return this; }; DrawingContext.prototype.moveImageData = function (sx, sy, w, h, x, y) { var sr = this._calcRect(sx, sy, w, h); var r = this._calcRect(x, y); this.ctx.save(); this.ctx.globalCompositeOperation = 'copy'; this.ctx.beginPath(); this.ctx.rect(r.x, r.y, sr.w, sr.h); this.ctx.clip(); this.ctx.drawImage(this.getCanvas(), sr.x, sr.y, sr.w, sr.h, r.x, r.y, sr.w, sr.h); this.ctx.restore(); this.ctx.beginPath(); return this; }; /** * Changes units of drawing context * @param {Number} units New units of drawing context (0=px, 1=pt, 2=in, 3=mm) */ DrawingContext.prototype.changeUnits = function (units) { var i = units >= 0 && units <= 3 ? units : 0; this._mct.sx = getCvtRatio(i, 0/*px*/, this.ppiX); this._mct.sy = getCvtRatio(i, 0/*px*/, this.ppiY); this._calcMFT(); this.units = units; return this; }; /** * Returns currrent zoom ratio * @type {Number} */ DrawingContext.prototype.getZoom = function () { return this.scaleFactor; }; /** * Changes scale factor of drawing context by changing its PPI * @param {Number} factor */ DrawingContext.prototype.changeZoom = function (factor) { if (!factor) { factor = this.scaleFactor; this._ppiInit(); } factor = asc_round(factor * 1000) / 1000; this.ppiX = asc_round(this.ppiX / this.scaleFactor * factor * 1000) / 1000; this.ppiY = asc_round(this.ppiY / this.scaleFactor * factor * 1000) / 1000; this.scaleFactor = factor; // reinitialize this.changeUnits(this.units); this.setFont(this.font); return this; }; /** * Resets dimensions of drawing context (canvas 'width' and 'height' attributes) * @param {Number} width New width in current units * @param {Number} height New height in current units */ DrawingContext.prototype.resetSize = function (width, height) { var w = asc_round(width * getCvtRatio(this.units, 0/*px*/, this.ppiX)), h = asc_round( height * getCvtRatio(this.units, 0/*px*/, this.ppiY)); if (w !== this.canvas.width) { this.canvas.width = w; } if (h !== this.canvas.height) { this.canvas.height = h; } return this; }; /** * Expands dimensions of drawing context (canvas 'width' and 'height' attributes) * @param {Number} width New width in current units * @param {Number} height New height in current units */ DrawingContext.prototype.expand = function (width, height) { var w = asc_round(width * getCvtRatio(this.units, 0/*px*/, this.ppiX)), h = asc_round( height * getCvtRatio(this.units, 0/*px*/, this.ppiY)); if (w > this.canvas.width) { this.canvas.width = w; } if (h > this.canvas.height) { this.canvas.height = h; } return this; }; // Canvas methods DrawingContext.prototype.clear = function () { this.clearRect(0, 0, this.getWidth(), this.getHeight()); return this; }; DrawingContext.prototype.AddClipRect = function (x, y, w, h) { return this.save().beginPath().rect(x, y, w, h).clip(); }; DrawingContext.prototype.RemoveClipRect = function () { return this.restore(); }; DrawingContext.prototype.save = function () { this.ctx.save(); return this; }; DrawingContext.prototype.restore = function () { this.ctx.restore(); return this; }; DrawingContext.prototype.scale = function (kx, ky) { //TODO: implement scale() return this; }; DrawingContext.prototype.rotate = function (a) { //TODO: implement rotate() return this; }; DrawingContext.prototype.translate = function (dx, dy) { //TODO: implement translate() return this; }; DrawingContext.prototype.transform = function (sx, shy, shx, sy, tx, ty) { //TODO: implement transform() return this; }; DrawingContext.prototype.setTransform = function (sx, shy, shx, sy, tx, ty) { this._mbt.assign(sx, shx, shy, sy, tx, ty); return this; }; DrawingContext.prototype.setTextTransform = function (sx, shy, shx, sy, tx, ty) { this._mt.assign(sx, shx, shy, sy, tx, ty); return this; }; DrawingContext.prototype.updateTransforms = function () { this._calcMFT(); this.fmgrGraphics[1].SetTextMatrix(this._mt.sx, this._mt.shy, this._mt.shx, this._mt.sy, this._mt.tx, this._mt.ty); }; DrawingContext.prototype.resetTransforms = function () { this.setTransform(this._im.sx, this._im.shy, this._im.shx, this._im.sy, this._im.tx, this._im.ty); this.setTextTransform(this._im.sx, this._im.shy, this._im.shx, this._im.sy, this._im.tx, this._im.ty); this._calcMFT(); if (window["IS_NATIVE_EDITOR"]) { window["native"]["PD_transform"](this._im.sx, this._im.shy, this._im.shx, this._im.sy, this._im.tx, this._im.ty); } }; // Style methods DrawingContext.prototype.getFillStyle = function () { return this.ctx.fillStyle; }; DrawingContext.prototype.getStrokeStyle = function () { return this.ctx.strokeStyle; }; DrawingContext.prototype.getLineWidth = function () { return this.ctx.lineWidth; }; DrawingContext.prototype.getLineCap = function () { return this.ctx.lineCap; }; DrawingContext.prototype.getLineJoin = function () { return this.ctx.lineJoin; }; /** * @param {AscCommonExcel.RgbColor || AscCommonExcel.ThemeColor || AscCommon.CColor} val * @returns {DrawingContext} */ DrawingContext.prototype.setFillStyle = function (val) { var _r = val.getR(); var _g = val.getG(); var _b = val.getB(); var _a = val.getA(); this.fillColor = new AscCommon.CColor(_r, _g, _b, _a); if (this.ctx.fillStyle) { this.ctx.fillStyle = "rgba(" + _r + "," + _g + "," + _b + "," + _a + ")"; } else { this.ctx.setFillStyle(_r, _g, _b, _a); } return this; }; DrawingContext.prototype.setFillPattern = function (val) { this.ctx.fillStyle = val; return this; }; /** * @param {AscCommonExcel.RgbColor || AscCommonExcel.ThemeColor || AscCommon.CColor} val * @returns {DrawingContext} */ DrawingContext.prototype.setStrokeStyle = function (val) { var _r = val.getR(); var _g = val.getG(); var _b = val.getB(); var _a = val.getA(); if (this.ctx.strokeStyle) { this.ctx.strokeStyle = "rgba(" + _r + "," + _g + "," + _b + "," + _a + ")"; } else { this.ctx.setStrokeStyle(_r, _g, _b, _a); } return this; }; DrawingContext.prototype.setLineWidth = function (width) { this.ctx.lineWidth = width; return this; }; DrawingContext.prototype.setLineCap = function (cap) { this.ctx.lineCap = cap; return this; }; DrawingContext.prototype.setLineJoin = function (join) { this.ctx.lineJoin = join; return this; }; DrawingContext.prototype.setLineDash = function (segments) { if (this.ctx.setLineDash) { this.ctx.setLineDash(segments); } return this; }; DrawingContext.prototype.fillRect = function (x, y, w, h) { var r = this._calcRect(x, y, w, h); this.ctx.fillRect(r.x, r.y, r.w, r.h); return this; }; DrawingContext.prototype.strokeRect = function (x, y, w, h) { var isEven = 0 !== this.ctx.lineWidth % 2 ? 0.5 : 0; var r = this._calcRect(x, y, w, h); this.ctx.strokeRect(r.x + isEven, r.y + isEven, r.w, r.h); return this; }; DrawingContext.prototype.clearRect = function (x, y, w, h) { var r = this._calcRect(x, y, w, h); this.ctx.clearRect(r.x, r.y, r.w, r.h); return this; }; DrawingContext.prototype.clearRectByX = function (x, y, w, h) { var r = this._calcRect(x, y, w, h); this.ctx.clearRect(r.x - 1, r.y, r.w + 1, r.h); }; DrawingContext.prototype.clearRectByY = function (x, y, w, h) { var r = this._calcRect(x, y, w, h); this.ctx.clearRect(r.x, r.y - 1, r.w, r.h + 1); }; // Font and text methods DrawingContext.prototype.getFont = function () { return this.font.clone(); }; DrawingContext.prototype.getFontSize = function () { return this.font.getSize(); }; /** * @param {Number} [units] Units of result (0=px, 1=pt, 2=in, 3=mm) * @return {FontMetrics} */ DrawingContext.prototype.getFontMetrics = function (units) { var fm = this.fmgrGraphics[3]; var d = Math.abs(fm.m_lDescender); var ppiX = AscCommon.AscBrowser.convertToRetinaValue(96, true); var r = getCvtRatio(0/*px*/, units >= 0 && units <= 3 ? units : this.units, ppiX); var r2 = getCvtRatio(1/*pt*/, units >= 0 && units <= 3 ? units : this.units, ppiX); var factor = this.getFontSize() * r / fm.m_lUnits_Per_Em; var res = new FontMetrics(); res.ascender = factor * fm.m_lAscender; res.descender = factor * d; res.lineGap = factor * (fm.m_lLineHeight - fm.m_lAscender - d); var faceMetrics = fm.m_pFont.cellGetMetrics(); res.nat_scale = faceMetrics[0]; res.nat_y1 = faceMetrics[1]; res.nat_y2 = faceMetrics[2]; res.nat_y1 *= r2; res.nat_y2 *= r2; return res; }; DrawingContext.prototype.nativeTextDecorationTransform = function(isStart) { // текст рисуется с трансформом, а strikeout/underline - без (матрица применяется ДО отрисовщика) if (isStart) window["native"]["PD_transform"](this._im.sx, this._im.shy, this._im.shx, this._im.sy, this._im.tx, this._im.ty); else window["native"]["PD_transform"](this._mt.sx, this._mt.shy, this._mt.shx, this._mt.sy, this._mt.tx, this._mt.ty); }; /** * * @param font * @param [angle] * @returns {DrawingContext} */ DrawingContext.prototype.setFont = function (font, angle) { this.font.assign(font); var italic = this.font.getItalic(); var bold = this.font.getBold(); var fontStyle; if (italic && bold) { fontStyle = FontStyle.FontStyleBoldItalic; } else if (italic) { fontStyle = FontStyle.FontStyleItalic; } else if (bold) { fontStyle = FontStyle.FontStyleBold; } else { fontStyle = FontStyle.FontStyleRegular; } this.setTextRotated(!!angle); this._setFont(this.font.getName(), this.font.getSize(), fontStyle); return this; }; DrawingContext.prototype._setFont = function(fontName, fontSize, fontStyle) { let isRotated = this.textRotated; if (setupFontName === fontName && setupFontSize === fontSize && setupFontStyle === fontStyle && setupPpiX === this.ppiX && setupPpiY === this.ppiY && setupRotated === isRotated) { if (!window["IS_NATIVE_EDITOR"]) { // disable this optimization (see m_isPassCommands (core-ext)) return; } } setupFontSize = fontSize; setupFontName = fontName; setupFontStyle = fontStyle; setupRotated = isRotated; setupPpiX = this.ppiX; setupPpiY = this.ppiY; this.font.setName(fontName); this.font.setSize(fontSize); this.font.setBold(fontStyle & FontStyle.FontStyleBold); this.font.setItalic(fontStyle & FontStyle.FontStyleItalic); if (window["IS_NATIVE_EDITOR"]) { var fontInfo = AscFonts.g_fontApplication.GetFontInfo(fontName, fontStyle, this.LastFontOriginInfo); fontInfo = AscCommon.GetLoadInfoForMeasurer(fontInfo, fontStyle); var flag = 0; if (fontInfo.NeedBold) { flag |= 0x01; } if (fontInfo.NeedItalic) { flag |= 0x02; } if (fontInfo.SrcBold) { flag |= 0x04; } if (fontInfo.SrcItalic) { flag |= 0x08; } // выставляем шрифт и отрисовщику... var drawFontSize = fontSize * this.scaleFactor * 96.0 / 25.4; window["native"]["PD_LoadFont"](fontInfo.Path, fontInfo.FaceIndex, drawFontSize, flag); // на отрисовке ячейки трансформ выставляется/сбрасывается. так что тут - только если есть angle if (isRotated) window["native"]["PD_transform"](this._mt.sx, this._mt.shy, this._mt.shx, this._mt.sy, this._mt.tx, this._mt.ty); // ...и измерятелю AscFonts.g_fontApplication.LoadFont(fontName, AscCommon.g_font_loader, this.fmgrGraphics[3], fontSize, fontStyle, this.ppiX, this.ppiY); } else { let r; if (isRotated) { r = AscFonts.g_fontApplication.LoadFont(fontName, AscCommon.g_font_loader, this.fmgrGraphics[1], fontSize, fontStyle, this.ppiX, this.ppiY); } else { r = AscFonts.g_fontApplication.LoadFont(fontName, AscCommon.g_font_loader, this.fmgrGraphics[0], fontSize, fontStyle, this.ppiX, this.ppiY); AscFonts.g_fontApplication.LoadFont(fontName, AscCommon.g_font_loader, this.fmgrGraphics[3], fontSize, fontStyle, this.ppiX, this.ppiY); } if (isRotated) { this.fmgrGraphics[1].SetTextMatrix(this._mt.sx, this._mt.shy, this._mt.shx, this._mt.sy, this._mt.tx, this._mt.ty); } if (r === false) { throw "Can not use " + fontName + " font. (Check whether font file is loaded)"; } } }; DrawingContext.prototype.SetFontInternal = function(name, size, style) { this._setFont(name, size, style); }; DrawingContext.prototype.setTextRotated = function(isRotated) { this.textRotated = isRotated; }; /** * Returns dimensions of first char of string * @param {String} text Character to measure * @param {Number} units Units (0 = px, 1 = pt, 2 = in, 3 = mm) * @return {TextMetrics} Returns the char dimension */ DrawingContext.prototype.measureChar = function (text, units, code) { return this.measureText(text ? text.charAt(0) : null, units, [code]); }; /** * Returns dimensions of string * @param {String} text String to measure * @param {Number} units Units (0 = px, 1 = pt, 2 = in, 3 = mm) * @return {TextMetrics} Returns the dimension of string {width: w, height: h} */ DrawingContext.prototype.measureText = function (text, units, aCodes) { var code; var fm = this.fmgrGraphics[3]; var r = getCvtRatio(0/*px*/, units >= 0 && units <= 3 ? units : this.units, this.ppiX); var textLength = aCodes ? aCodes.length : text.length; for (var tmp, w = 0, w2 = 0, i = 0; i < textLength; ++i) { code = aCodes ? aCodes[i] : text.charCodeAt(i); // Replace Non-breaking space(0xA0) with White-space(0x20) tmp = fm.MeasureChar(0xA0 === code ? 0x20 : code); w += asc_round(tmp.fAdvanceX); // ToDo скачет при wrap в ячейке и zoom } w2 = w - tmp.fAdvanceX + tmp.oBBox.fMaxX - tmp.oBBox.fMinX + 1; return this._calcTextMetrics(w * r, w2 * r, fm, r); }; DrawingContext.prototype.fillGlyph = function (pGlyph, fmgr) { var nW = pGlyph.oBitmap.nWidth; var nH = pGlyph.oBitmap.nHeight; if (!(nW > 0 && nH > 0)) { return; } var nX = asc_floor(fmgr.m_oGlyphString.m_fX + pGlyph.fX + pGlyph.oBitmap.nX); var nY = asc_floor(fmgr.m_oGlyphString.m_fY + pGlyph.fY - pGlyph.oBitmap.nY); var _r = this.fillColor.r; var _g = this.fillColor.g; var _b = this.fillColor.b; pGlyph.oBitmap.oGlyphData.checkColor(_r, _g, _b, nW, nH); pGlyph.oBitmap.draw(this.ctx, nX, nY); }; DrawingContext.prototype.fillText = function (text, x, y, maxWidth, charWidths, angle) { var code, pGlyph; var manager = angle ? this.fmgrGraphics[1] : this.fmgrGraphics[0]; var _x = this._mift.transformPointX(x, y); var _y = this._mift.transformPointY(x, y); var length = text.length; for (var i = 0; i < length; ++i) { code = text.charCodeAt ? text.charCodeAt(i) : text[i]; // Replace Non-breaking space(0xA0) with White-space(0x20) code = 0xA0 === code ? 0x20 : code; if (window["IS_NATIVE_EDITOR"]) { window["native"]["PD_FillText"](_x, _y, code); // убрать это!!! все сдвиги ДОЛЖНЫ быть вщять из измерятеля/шейпера _x += asc_round((this.measureChar(undefined, 3, code).width) * this.scaleFactor * 96.0 / 25.4); } else { _x = asc_round(manager.LoadString4C(code, _x, _y)); pGlyph = manager.m_oGlyphString.m_pGlyphsBuffer[0]; if (null === pGlyph || null === pGlyph.oBitmap) { continue; } this.fillGlyph(pGlyph, manager); } } return this; }; DrawingContext.prototype.fillTextCode = function (text, x, y, maxWidth, charWidths, angle) { return this.fillText(text, x, y, maxWidth, charWidths, angle); }; DrawingContext.prototype.tg = function(gid, x, y, codePoints) { var _x = this._mift.transformPointX(x, y); var _y = this._mift.transformPointY(x, y); if (window["IS_NATIVE_EDITOR"]) { window["native"]["PD_FillTextG"](_x, _y, gid); } else { let fontManager = this.textRotated ? this.fmgrGraphics[1] : this.fmgrGraphics[0]; try { fontManager.LoadString3C(gid, _x, _y, codePoints); } catch(err){} let glyph = fontManager.m_oGlyphString.m_pGlyphsBuffer[0]; if (!glyph || !glyph.oBitmap) return this; this.fillGlyph(glyph, fontManager); } return this; }; // Path methods DrawingContext.prototype.beginPath = function () { this.ctx.beginPath(); return this; }; DrawingContext.prototype.closePath = function () { this.ctx.closePath(); return this; }; DrawingContext.prototype.moveTo = function (x, y) { var r = this._calcRect(x, y); this.ctx.moveTo(r.x, r.y); return this; }; DrawingContext.prototype.lineTo = function (x, y) { var r = this._calcRect(x, y); this.ctx.lineTo(r.x, r.y); return this; }; DrawingContext.prototype.lineDiag = function (x1, y1, x2, y2) { var isEven = 0 !== this.ctx.lineWidth % 2 ? 0.5 : 0; var r1 = this._calcRect(x1, y1); var r2 = this._calcRect(x2, y2); this.ctx.moveTo(r1.x + isEven, r1.y + isEven); this.ctx.lineTo(r2.x + isEven, r2.y + isEven); return this; }; DrawingContext.prototype.lineHor = function (x1, y, x2) { var isEven = 0 !== this.ctx.lineWidth % 2 ? 0.5 : 0; var r1 = this._calcRect(x1, y); var r2 = this._calcRect(x2, y); this.ctx.moveTo(r1.x, r1.y + isEven); this.ctx.lineTo(r2.x, r2.y + isEven); return this; }; DrawingContext.prototype.lineVer = function (x, y1, y2) { var isEven = 0 !== this.ctx.lineWidth % 2 ? 0.5 : 0; var r1 = this._calcRect(x, y1); var r2 = this._calcRect(x, y2); this.ctx.moveTo(r1.x + isEven, r1.y); this.ctx.lineTo(r2.x + isEven, r2.y); return this; }; // Отрисовка на 1px меньше DrawingContext.prototype.lineHorPrevPx = function (x1, y, x2) { var isEven = (0 !== this.ctx.lineWidth % 2 ? 0.5 : 0) - 1; var r1 = this._calcRect(x1, y); var r2 = this._calcRect(x2, y); this.ctx.moveTo(r1.x, r1.y + isEven); this.ctx.lineTo(r2.x, r2.y + isEven); return this; }; DrawingContext.prototype.lineVerPrevPx = function (x, y1, y2) { var isEven = (0 !== this.ctx.lineWidth % 2 ? 0.5 : 0) - 1; var r1 = this._calcRect(x, y1); var r2 = this._calcRect(x, y2); this.ctx.moveTo(r1.x + isEven, r1.y); this.ctx.lineTo(r2.x + isEven, r2.y); return this; }; DrawingContext.prototype.dashLineCleverHor = function (x1, y, x2) { var isEven = 0 !== this.ctx.lineWidth % 2 ? 0.5 : 0; var w_dot = AscCommonExcel.c_oAscCoAuthoringDottedWidth, w_dist = AscCommonExcel.c_oAscCoAuthoringDottedDistance; var _x1 = this._mct.transformPointX(x1, y); var _y = this._mct.transformPointY(x1, y) - 1; var _x2 = this._mct.transformPointX(x2, y); var ctx = this.ctx; _x1 = (_x1 >> 0); _y = (_y >> 0) + isEven; _x2 = (_x2 >> 0); for (; _x1 < _x2; _x1 += w_dist) { ctx.moveTo(_x1, _y); _x1 += w_dot; if (_x1 > _x2) { _x1 = _x2; } ctx.lineTo(_x1, _y); } }; DrawingContext.prototype.dashLineCleverVer = function (x, y1, y2) { var isEven = 0 !== this.ctx.lineWidth % 2 ? 0.5 : 0; var w_dot = AscCommonExcel.c_oAscCoAuthoringDottedWidth, w_dist = AscCommonExcel.c_oAscCoAuthoringDottedDistance; var _y1 = this._mct.transformPointY(x, y1); var _x = this._mct.transformPointX(x, y1) - 1; var _y2 = this._mct.transformPointY(x, y2); var ctx = this.ctx; _y1 = (_y1 >> 0); _x = (_x >> 0) + isEven; _y2 = (_y2 >> 0); for (; _y1 < _y2; _y1 += w_dist) { ctx.moveTo(_x, _y1); _y1 += w_dot; if (_y1 > _y2) { _y1 = _y2; } ctx.lineTo(_x, _y1); } }; DrawingContext.prototype.dashLine = function (x1, y1, x2, y2, w_dot, w_dist) { var len = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); if (len < 1) { len = 1; } var len_x1 = Math.abs(w_dot * (x2 - x1) / len); var len_y1 = Math.abs(w_dot * (y2 - y1) / len); var len_x2 = Math.abs(w_dist * (x2 - x1) / len); var len_y2 = Math.abs(w_dist * (y2 - y1) / len); var i, j; if (x1 <= x2 && y1 <= y2) { for (i = x1, j = y1; i < x2 || j < y2; i += len_x2, j += len_y2) { this.moveTo(i, j); i += len_x1; j += len_y1; if (i > x2) { i = x2; } if (j > y2) { j = y2; } this.lineTo(i, j); } } else if (x1 <= x2 && y1 > y2) { for (i = x1, j = y1; i < x2 || j > y2; i += len_x2, j -= len_y2) { this.moveTo(i, j); i += len_x1; j -= len_y1; if (i > x2) { i = x2; } if (j < y2) { j = y2; } this.lineTo(i, j); } } else if (x1 > x2 && y1 <= y2) { for (i = x1, j = y1; i > x2 || j < y2; i -= len_x2, j += len_y2) { this.moveTo(i, j); i -= len_x1; j += len_y1; if (i < x2) { i = x2; } if (j > y2) { j = y2; } this.lineTo(i, j); } } else { for (i = x1, j = y1; i > x2 || j > y2; i -= len_x2, j -= len_y2) { this.moveTo(i, j); i -= len_x1; j -= len_y1; if (i < x2) { i = x2; } if (j < y2) { j = y2; } this.lineTo(i, j); } } }; DrawingContext.prototype.dashRect = function (x1, y1, x2, y2, x3, y3, x4, y4, w_dot, w_dist) { this.dashLine(x1, y1, x2, y2, w_dot, w_dist); this.dashLine(x2, y2, x4, y4, w_dot, w_dist); this.dashLine(x4, y4, x3, y3, w_dot, w_dist); this.dashLine(x3, y3, x1, y1, w_dot, w_dist); }; DrawingContext.prototype.rect = function (x, y, w, h) { var r = this._calcRect(x, y, w, h); this.ctx.rect(r.x, r.y, r.w, r.h); return this; }; DrawingContext.prototype.arc = function (x, y, radius, startAngle, endAngle, antiClockwise, dx, dy) { var r = this._calcRect(x, y); dx = typeof dx !== "undefined" ? dx : 0; dy = typeof dy !== "undefined" ? dy : 0; this.ctx.arc(r.x + dx, r.y + dy, radius, startAngle, endAngle, antiClockwise); return this; }; DrawingContext.prototype.bezierCurveTo = function (x1, y1, x2, y2, x3, y3) { var p1 = this._calcRect(x1, y1), p2 = this._calcRect(x2, y2), p3 = this._calcRect(x3, y3); this.ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); return this; }; DrawingContext.prototype.fill = function () { this.ctx.fill(); return this; }; DrawingContext.prototype.stroke = function () { this.ctx.stroke(); return this; }; DrawingContext.prototype.clip = function () { this.ctx.clip(); return this; }; // Image methods DrawingContext.prototype.drawImage = function (img, sx, sy, sw, sh, dx, dy, dw, dh) { var sr = this._calcRect(sx, sy, sw, sh), dr = this._calcRect(dx, dy, dw, dh); this.ctx.drawImage(img, sr.x, sr.y, sr.w, sr.h, dr.x, dr.y, dr.w, dr.h); return this; }; // Private methods DrawingContext.prototype._calcRect = function (x, y, w, h) { var wh = w !== undefined && h !== undefined, x2 = x + w, y2 = y + h, _x = this._mft.transformPointX(x, y), _y = this._mft.transformPointY(x, y); return { x: asc_round(_x), y: asc_round(_y), w: wh ? asc_round(this._mft.transformPointX(x2, y2) - _x) : undefined, h: wh ? asc_round(this._mft.transformPointY(x2, y2) - _y) : undefined }; }; DrawingContext.prototype._calcMFT = function () { this._mft = this._mct.clone(); this._mft.multiply(this._mbt, MATRIX_ORDER_PREPEND); this._mft.multiply(this._mt, MATRIX_ORDER_PREPEND); this._mift = this._mt.clone(); this._mift.invert(); this._mift.multiply(this._mft, MATRIX_ORDER_PREPEND); }; /** * @param {Number} w Ширина текста * @param {Number} wBB Ширина Bound Box текста * @param {AscFonts.CFontManager} fm Объект AscFonts.CFontManager для получения метрик шрифта * @param {Number} r Коэффициент перевода pt -> в текущие единицы измерения (this.units) * @return {TextMetrics} */ DrawingContext.prototype._calcTextMetrics = function (w, wBB, fm, r) { var factor = this.getFontSize() * r / fm.m_lUnits_Per_Em, l = fm.m_lLineHeight * factor, b = fm.m_lAscender * factor, d = Math.abs(fm.m_lDescender * factor); return new TextMetrics(w, b + d, l, b, d, this.font.getSize(), wBB); }; /* * Export * ----------------------------------------------------------------------------- */ window['Asc'] = window['Asc'] || {}; window["Asc"].getCvtRatio = getCvtRatio; window["Asc"].colorObjToAscColor = colorObjToAscColor; window["Asc"].TextMetrics = TextMetrics; window["Asc"].FontMetrics = FontMetrics; window["Asc"].DrawingContext = DrawingContext; window["Asc"].Matrix = Matrix; window['AscCommonExcel'] = window['AscCommonExcel'] || {}; window["AscCommonExcel"].resetDrawingContextFonts = resetDrawingContextFonts; })(window);