/* * (c) Copyright Ascensio System SIA 2010-2023 * * 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, document) { // Import let Shape_Type = window['AscVisio'].Shape_Type; let isInvertCoords = true; function convertVsdxTextToPptxText(text){ // Replace LineSeparator return text.replaceAll("\u2028", "\n"); } /** * get full flip using group flips * @return {{flipV: (boolean|*), flipH: (boolean|*)}} */ AscFormat.CGraphicObjectBase.prototype.getFullFlipVSpPr = function () { let group = this.group; let flipV = this.spPr.xfrm.flipV; while (group) { flipV = group.spPr.xfrm.flipV ? !flipV : flipV; group = group.group; } return flipV; }; /** * calculateShapeParamsAndConvertToCShape or CGroupShape which combines shape and text if Shape has text * @memberof Shape_Type * @param {CVisioDocument} visioDocument * @param {Page_Type} pageInfo * @param {Number} drawingPageScale * @param {CGroupShape?} currentGroupHandling * @return {(CShape | CGroupShape)} cShape or cGroupShape (if shape and text) */ Shape_Type.prototype.convertShape = function (visioDocument, pageInfo, drawingPageScale, currentGroupHandling) { // Method start // Refact: // 1) I guess any cell can be = THEMEVAL() so better to always // use Cell_Type.calculateValue method // consider sometimes = THEMEVAL() can be replaced not to Themed but // to concrete value on save for Cell_Type.v // 2) May be create methods on rows sections and shape - // this.calculateCellValue("FillBkgnd",this, pageInfo, // visioDocument.themes, themeValWasUsedFor, true); // 3) May be bind arguments to calculateValue function // 4) May be move getTextCShape to other file let maxHeightScaledIn; if (currentGroupHandling) { let heightMM = currentGroupHandling.spPr.xfrm.extY; maxHeightScaledIn = heightMM / g_dKoef_in_to_mm; } else { let pageIndex = visioDocument.pages.page.indexOf(pageInfo); maxHeightScaledIn = visioDocument.GetHeightScaledMM(pageIndex) / g_dKoef_in_to_mm; } // there was case with shape type group with no PinX and PinY // https://disk.yandex.ru/d/tl877cuzcRcZYg let pinX_inch = this.getCellNumberValueWithScale("PinX", drawingPageScale); let pinY_inch = this.getCellNumberValueWithScale("PinY", drawingPageScale); /** @type {{ [key: string]: Cell_Type }} */ let layerProperties = this.getLayerProperties(pageInfo); // only if all shape layers are invisible shape is invisible let areShapeLayersInvisible = layerProperties["Visible"] !== undefined && layerProperties["Visible"].v === "0"; /** * @type {CUniFill | undefined} * if layerColor is applied fill is always white */ let layerColor; /** * @type {CUniFill} * if layerColor is applied fill is always white */ let layerFill = AscFormat.CreateUnfilFromRGB(255, 255, 255); if (layerProperties["Color"] !== undefined && layerProperties["Color"].v !== "255") { let layerColorUniColor = layerProperties["Color"].calculateValue(this, pageInfo, visioDocument.themes); layerColor = AscFormat.CreateUnfilFromRGB(layerColorUniColor.color.RGBA.R, layerColorUniColor.color.RGBA.G, layerColorUniColor.color.RGBA.B); } let isShapeDeleted = this.del === "1" || this.del === true; if (isShapeDeleted) { return createEmptyShape(); } // also check for {}, undefined, NaN, null if (isNaN(pinX_inch) || pinX_inch === null || isNaN(pinY_inch) || pinY_inch === null || areShapeLayersInvisible) { // AscCommon.consoleLog('pinX_inch or pinY_inch is NaN for Shape or areShapeLayersInvisible. Its ok sometimes. ' + // 'Empty CShape is returned. See original shape: ', this); // let's use empty shape return createEmptyShape(); } let shapeAngle = this.getCellNumberValue("Angle"); let locPinX_inch = this.getCellNumberValueWithScale("LocPinX", drawingPageScale); let locPinY_inch = this.getCellNumberValueWithScale("LocPinY", drawingPageScale); let shapeWidth_inch = this.getCellNumberValueWithScale("Width", drawingPageScale); let shapeHeight_inch = this.getCellNumberValueWithScale("Height", drawingPageScale); if (isInvertCoords) { pinY_inch = maxHeightScaledIn - pinY_inch; shapeAngle *= -1; locPinY_inch = shapeHeight_inch - locPinY_inch; } // to rotate around point we: 1) move shape to new cords 2) rotate shape around center using shape angle let newCords = getCordsRotatedAroundPoint(pinX_inch, pinY_inch, locPinX_inch, locPinY_inch, shapeWidth_inch, shapeHeight_inch, shapeAngle); let x_inch = newCords[0]; let y_inch = newCords[1]; /** * Fill without gradient used for handleQuickStyleVariation function and for handleTextQuickStyleVariation. * We need fill without pattern and gradient applied. Pattern applied can set NoSolidFill object without color, * so we will not be able to calculate handleVariationColor function result * @type CUniFill */ let uniFillForegndNoGradient = null; /** * Used for handleQuickStyleVariation function and for handleTextQuickStyleVariation. * @type CUniFill */ let lineUniFillNoGradient = null; /** * Final uniFill with gradient and pattern applied. * Used for result shape fill. * @type CUniFill */ let uniFillForegndWithPattern = null; /** * lineUniFill after gradient applied. * Used for result shape stroke. * @type {CUniFill} */ let lineUniFill = null; if (layerColor) { uniFillForegndWithPattern = layerFill; uniFillForegndNoGradient = layerFill; lineUniFillNoGradient = layerColor; lineUniFill = layerColor; } else { /** * @type boolean */ let fillGradientEnabled; /** * Fill without pattern applied. But with gradient applied. * @type CUniFill */ let uniFillForegnd = null; /** @type CUniFill */ let uniFillBkgnd = null; /** * Let's memorize what color properties used themeVal because quickStyleVariation can change only those * color props that used themeVal function. * @type {{lineUniFill: boolean, uniFillForegnd: boolean}} */ let themeValWasUsedFor = { lineUniFill : false, uniFillForegnd: false } uniFillForegndNoGradient = getForegroundNoGradient(this, pageInfo, visioDocument, themeValWasUsedFor); uniFillBkgnd = getFillBackground(this, pageInfo, visioDocument, themeValWasUsedFor); lineUniFillNoGradient = getLineColorNoGradient(this, pageInfo, visioDocument, themeValWasUsedFor); // calculate variation before pattern bcs pattern can make NoFillUniFill object without color // use quickStyleVariation only if themes exist in file. // Default theme which come to visioDocument.themes[0] should not be considered. // See bug https://bugzilla.onlyoffice.com/show_bug.cgi?id=76044 if (this.calculateColorThemeIndex(pageInfo) !== 0) { let newFills = handleQuickStyleVariation(lineUniFillNoGradient, uniFillForegndNoGradient, this, themeValWasUsedFor, pageInfo, visioDocument.themes); uniFillForegndNoGradient = newFills[0]; lineUniFillNoGradient = newFills[1]; } // FillGradientDir and FillPattern can tell about gradient type // if FillGradient Enabled // FillGradientDir defines gradient type. If gradient is linear gradient type is complemented with angle. // 13 FillGradientDir is path. path cant be set in interface. also like some radial gradient types witch cant be set in interface. // FillGradientDir > 13 is linear like FillGradientDir = 0 // // if FillGradientEnabled Disabled // FillPattern defines gradient type and colors define. There linear types with different predefined angles, rectandulat and radial gradient types. // Rectangular and radial gradient types differs. There are two colors when i set three colors for gradient. Also FillPattern gradients are not listed in // interface. Only true patterns. // // Its better to convert linear FillPattern gradients there. But FillPattern radial gradients seems to be // not like FillGradientDir radial gradients but with different colors let fillGradientEnabledCell = this.getCell("FillGradientEnabled"); if (fillGradientEnabledCell !== undefined) { fillGradientEnabled = fillGradientEnabledCell.calculateValue(this, pageInfo, visioDocument.themes, undefined, true); } else { fillGradientEnabled = false; } if (fillGradientEnabled) { uniFillForegnd = calculateGradient(this, pageInfo, visioDocument.themes, themeValWasUsedFor, true); } else { uniFillForegnd = uniFillForegndNoGradient; } let lineGradientEnabled; let lineGradientEnabledCell = this.getCell("LineGradientEnabled"); if (lineGradientEnabledCell !== undefined) { lineGradientEnabled = lineGradientEnabledCell.calculateValue(this, pageInfo, visioDocument.themes); } else { lineGradientEnabled = false; } if (lineGradientEnabled) { // Line gradient uniFill is not supported for now so let's take middle color from gradient let lineGradientFill = calculateGradient(this, pageInfo, visioDocument.themes, themeValWasUsedFor, false); let colorIndex = Math.floor(lineGradientFill.fill.colors.length / 2); let cUniColor = lineGradientFill.fill.colors[colorIndex].color; // lineUniFill = lineGradientFill; lineUniFill = new AscFormat.CUniFill(); lineUniFill.setFill(new AscFormat.CSolidFill()); lineUniFill.fill.setColor(cUniColor); } else { lineUniFill = lineUniFillNoGradient; } uniFillForegndWithPattern = getUnifillForegroundWithPattern(this, pageInfo,visioDocument, themeValWasUsedFor, uniFillBkgnd, uniFillForegnd, fillGradientEnabled); // Block end // Used functions: /** * Calculate FillForegnd without gradient for handleQuickStyleVariation * @param {Shape_Type} shape * @param {Page_Type} pageInfo * @param {CVisioDocument} visioDocument * @param {{lineUniFill: boolean, uniFillForegnd: boolean}} themeValWasUsedFor * @return {CUniFill} uniFillForegndNoGradient */ function getForegroundNoGradient(shape, pageInfo, visioDocument, themeValWasUsedFor) { const fillForegndCell = shape.getCell("FillForegnd"); let res; if (fillForegndCell) { // AscCommon.consoleLog("FillForegnd was found:", fillForegndCell); res = fillForegndCell.calculateValue(shape, pageInfo, visioDocument.themes, themeValWasUsedFor, false); const fillForegndTransValue = shape.getCellNumberValue("FillForegndTrans"); if (!isNaN(fillForegndTransValue)) { /** @type {CSolidFill} */ const fillObj = res.fill; fillObj.color.color.RGBA.A = fillObj.color.color.RGBA.A * (1 - fillForegndTransValue); } else { // AscCommon.consoleLog("fillForegndTrans value is themed or something. Not calculated for", this); } } else { AscCommon.consoleLog("fillForegnd cell not found for", shape); // try to get from theme // uniFillForegnd = AscVisio.themeval(null, this, pageInfo, visioDocument.themes, "FillColor", // undefined, fillGradientEnabled); // just use white res = AscFormat.CreateUnfilFromRGB(255, 255, 255); } return res; } /** * Calculate FillBkgnd with gradient * @param {Shape_Type} shape * @param {Page_Type} pageInfo * @param {CVisioDocument} visioDocument * @param {{lineUniFill: boolean, uniFillForegnd: boolean}} themeValWasUsedFor * @return {CUniFill} uniFillBkgnd */ function getFillBackground(shape, pageInfo, visioDocument, themeValWasUsedFor) { const fillBkgndCell = shape.getCell("FillBkgnd"); let res; if (fillBkgndCell) { // AscCommon.consoleLog("FillBkgnd was found:", fillBkgndCell); res = fillBkgndCell.calculateValue(shape, pageInfo, visioDocument.themes, themeValWasUsedFor); if (!(res.fill.type === Asc.c_oAscFill.FILL_TYPE_GRAD)) { const fillBkgndTransValue = shape.getCellNumberValue("FillBkgndTrans"); if (!isNaN(fillBkgndTransValue)) { /** @type {CSolidFill} */ const fillObj = res.fill; fillObj.color.color.RGBA.A = fillObj.color.color.RGBA.A * (1 - fillBkgndTransValue); } else { // AscCommon.consoleLog("fillBkgndTrans value is themed or something. Not calculated for", this); } } } return res; } /** * Calculate LineColor no gradient * @param {Shape_Type} shape * @param {Page_Type} pageInfo * @param {CVisioDocument} visioDocument * @param {{lineUniFill: boolean, uniFillForegnd: boolean}} themeValWasUsedFor * @return {CUniFill} lineUniFillNoGradient */ function getLineColorNoGradient(shape, pageInfo, visioDocument, themeValWasUsedFor) { const lineColorCell = shape.getCell("LineColor"); let res; if (lineColorCell) { // AscCommon.consoleLog("LineColor was found for shape", lineColorCell); res = lineColorCell.calculateValue(shape, pageInfo, visioDocument.themes, themeValWasUsedFor, false); const lineTransValue = shape.getCellNumberValue("LineColorTrans"); if (!isNaN(lineTransValue)) { // lineUniFillNoGradient.transparent is opacity in fact // setting RGBA.A doesn't work res.transparent = 255 - lineTransValue * 255; } } else { AscCommon.consoleLog("LineColor cell for line stroke (border) was not found painting dark"); res = AscFormat.CreateUnfilFromRGB(0, 0, 0); } return res; } /** * Using shape data and params for calculations * @param {Shape_Type} shape * @param {Page_Type} pageInfo * @param {CVisioDocument} visioDocument * @param {{lineUniFill: boolean, uniFillForegnd: boolean}} themeValWasUsedFor * @param {CUniFill} uniFillBkgnd * @param {CUniFill} uniFillForegnd * @param {boolean} fillGradientEnabled * @return {CUniFill} fill foreground with pattern applied */ function getUnifillForegroundWithPattern(shape, pageInfo, visioDocument, themeValWasUsedFor, uniFillBkgnd, uniFillForegnd, fillGradientEnabled) { const fillPatternTypeCell = shape.getCell("FillPattern"); const fillPatternType = fillPatternTypeCell ? fillPatternTypeCell.calculateValue(shape, pageInfo, visioDocument.themes) : 1; let res; if (!isNaN(fillPatternType) && uniFillBkgnd && uniFillForegnd) { // https://learn.microsoft.com/ru-ru/office/client-developer/visio/fillpattern-cell-fill-format-section let isfillPatternTypeGradient = fillPatternType >= 25 && fillPatternType <= 40; if (fillGradientEnabled) { res = uniFillForegnd; } else if (fillPatternType === 0) { res = AscFormat.CreateNoFillUniFill(); } else if (fillPatternType === 1) { // convert fill to solid // // FILL_TYPE_NONE never comes // FILL_TYPE_BLIP - images are handled separate from fill // FILL_TYPE_NOFILL never comes // FILL_TYPE_SOLID is handled // FILL_TYPE_GRAD if gradient is not enabled uniFillNoGradient is in uniFillForegnd // which is solid otherwise fillGradientEnabled is true and above code run // FILL_TYPE_PATT is handled // FILL_TYPE_GRP empty fill if (uniFillForegnd.fill.type === Asc.c_oAscFill.FILL_TYPE_PATT) { res = new AscFormat.CUniFill(); res.fill = new AscFormat.CSolidFill(); res.fill.color = uniFillForegnd.fill.fgClr; } else if (uniFillForegnd.fill.type === Asc.c_oAscFill.FILL_TYPE_SOLID) { res = uniFillForegnd; } else { res = uniFillForegnd; AscCommon.consoleLog("Unknown fill type. Need to convert to solid"); } } else if (isfillPatternTypeGradient) { if (fillPatternType === 25) { let fillGradientStops = []; // has color (CUniColor) and pos from 0 to 100000 let colorStop1 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color1 = uniFillForegnd.fill.color; let pos1 = 0; colorStop1.setColor(color1); colorStop1.setPos(pos1); fillGradientStops.push({Gs : colorStop1}); let colorStop2 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color2 = uniFillBkgnd.fill.color; let pos2 = 100000; colorStop2.setColor(color2); colorStop2.setPos(pos2); fillGradientStops.push({Gs : colorStop2}); res = AscFormat.builder_CreateLinearGradient(fillGradientStops, 0); } else if (fillPatternType === 26) { let fillGradientStops = []; // has color (CUniColor) and pos from 0 to 100000 let colorStop1 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color1 = uniFillBkgnd.fill.color; let pos1 = 0; colorStop1.setColor(color1); colorStop1.setPos(pos1); fillGradientStops.push({Gs : colorStop1}); let colorStop2 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color2 = uniFillForegnd.fill.color; let pos2 = 50000; colorStop2.setColor(color2); colorStop2.setPos(pos2); fillGradientStops.push({Gs : colorStop2}); let colorStop3 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color3 = uniFillBkgnd.fill.color; let pos3 = 100000; colorStop3.setColor(color3); colorStop3.setPos(pos3); fillGradientStops.push({Gs : colorStop3}); res = AscFormat.builder_CreateLinearGradient(fillGradientStops, 0); } else if (fillPatternType === 27) { let fillGradientStops = []; // has color (CUniColor) and pos from 0 to 100000 let colorStop1 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color1 = uniFillBkgnd.fill.color; let pos1 = 0; colorStop1.setColor(color1); colorStop1.setPos(pos1); fillGradientStops.push({Gs : colorStop1}); let colorStop2 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color2 = uniFillForegnd.fill.color; let pos2 = 100000; colorStop2.setColor(color2); colorStop2.setPos(pos2); fillGradientStops.push({Gs : colorStop2}); res = AscFormat.builder_CreateLinearGradient(fillGradientStops, 0); } else if (fillPatternType === 28) { let fillGradientStops = []; // has color (CUniColor) and pos from 0 to 100000 let colorStop1 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color1 = uniFillForegnd.fill.color; let pos1 = 0; colorStop1.setColor(color1); colorStop1.setPos(pos1); fillGradientStops.push({Gs : colorStop1}); let colorStop2 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color2 = uniFillBkgnd.fill.color; let pos2 = 100000; colorStop2.setColor(color2); colorStop2.setPos(pos2); fillGradientStops.push({Gs : colorStop2}); res = AscFormat.builder_CreateLinearGradient(fillGradientStops, 90 * AscFormat.degToC); } else if (fillPatternType === 29) { let fillGradientStops = []; // has color (CUniColor) and pos from 0 to 100000 let colorStop1 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color1 = uniFillBkgnd.fill.color; let pos1 = 0; colorStop1.setColor(color1); colorStop1.setPos(pos1); fillGradientStops.push({Gs : colorStop1}); let colorStop2 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color2 = uniFillForegnd.fill.color; let pos2 = 50000; colorStop2.setColor(color2); colorStop2.setPos(pos2); fillGradientStops.push({Gs : colorStop2}); let colorStop3 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color3 = uniFillBkgnd.fill.color; let pos3 = 100000; colorStop3.setColor(color3); colorStop3.setPos(pos3); fillGradientStops.push({Gs : colorStop3}); res = AscFormat.builder_CreateLinearGradient(fillGradientStops, 90 * AscFormat.degToC); } else if (fillPatternType === 30) { let fillGradientStops = []; // has color (CUniColor) and pos from 0 to 100000 let colorStop1 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color1 = uniFillForegnd.fill.color; let pos1 = 0; colorStop1.setColor(color1); colorStop1.setPos(pos1); fillGradientStops.push({Gs : colorStop1}); let colorStop2 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color2 = uniFillBkgnd.fill.color; let pos2 = 100000; colorStop2.setColor(color2); colorStop2.setPos(pos2); fillGradientStops.push({Gs : colorStop2}); res = AscFormat.builder_CreateLinearGradient(fillGradientStops, -90 * AscFormat.degToC); } else { let fillGradientStops = []; // has color (CUniColor) and pos from 0 to 100000 let colorStop1 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color1 = uniFillForegnd.fill.color; let pos1 = 0; colorStop1.setColor(color1); colorStop1.setPos(pos1); fillGradientStops.push({Gs : colorStop1}); let colorStop2 = new AscFormat.CGs(); // calculate color (AscFormat.CUniColor) let color2 = uniFillBkgnd.fill.color; let pos2 = 100000; colorStop2.setColor(color2); colorStop2.setPos(pos2); fillGradientStops.push({Gs : colorStop2}); res = AscFormat.builder_CreateRadialGradient(fillGradientStops); } } else if (fillPatternType > 1) { // create patt fill using foregnd and bkgnd colors const ooxmlFillPatternType = mapVisioFillPatternToOOXML(fillPatternType); res = AscFormat.CreatePatternFillUniFill(ooxmlFillPatternType, uniFillBkgnd.fill.color, uniFillForegnd.fill.color); function mapVisioFillPatternToOOXML(fillPatternType) { // change down to up and up to down bcs of Global matrix inverted let upSideDownPatterns = false; switch (fillPatternType) { case 2: return upSideDownPatterns ? AscCommon.global_hatch_offsets["dnDiag"] : AscCommon.global_hatch_offsets["upDiag"]; case 3: return AscCommon.global_hatch_offsets["cross"]; case 4: return AscCommon.global_hatch_offsets["diagCross"]; case 5: return upSideDownPatterns ? AscCommon.global_hatch_offsets["upDiag"] : AscCommon.global_hatch_offsets["dnDiag"]; case 6: return AscCommon.global_hatch_offsets["horz"]; case 7: return AscCommon.global_hatch_offsets["vert"]; case 8: return AscCommon.global_hatch_offsets["pct60"]; case 9: return AscCommon.global_hatch_offsets["pct40"]; case 10: return AscCommon.global_hatch_offsets["pct25"]; case 11: return AscCommon.global_hatch_offsets["pct20"]; case 12: return AscCommon.global_hatch_offsets["pct10"]; case 13: return AscCommon.global_hatch_offsets["dkHorz"]; case 14: return AscCommon.global_hatch_offsets["dkVert"]; case 15: return upSideDownPatterns ? AscCommon.global_hatch_offsets["dkUpDiag"] : AscCommon.global_hatch_offsets["dkDnDiag"]; case 16: return upSideDownPatterns ? AscCommon.global_hatch_offsets["dkDnDiag"] : AscCommon.global_hatch_offsets["dkUpDiag"]; case 17: return AscCommon.global_hatch_offsets["smCheck"]; case 18: return AscCommon.global_hatch_offsets["trellis"]; case 19: return AscCommon.global_hatch_offsets["ltHorz"]; case 20: return AscCommon.global_hatch_offsets["ltVert"]; case 21: return upSideDownPatterns ? AscCommon.global_hatch_offsets["ltUpDiag"] : AscCommon.global_hatch_offsets["ltDnDiag"]; case 22: return upSideDownPatterns ? AscCommon.global_hatch_offsets["ltDnDiag"] : AscCommon.global_hatch_offsets["ltUpDiag"]; case 23: return AscCommon.global_hatch_offsets["smGrid"]; case 24: return AscCommon.global_hatch_offsets["pct50"]; default: AscCommon.consoleLog("patten fill unhandled"); return AscCommon.global_hatch_offsets["cross"]; } } } } else if (uniFillForegnd) { res = uniFillForegnd; } else { AscCommon.consoleLog("FillForegnd not found for shape", shape); res = AscFormat.CreateNoFillUniFill(); } return res; } /** * Get proper shape data and calculate fill or line gradient * @param {Shape_Type} shape * @param {Page_Type} pageInfo * @param {CTheme[]} visioDocumentThemes * @param {{lineUniFill: boolean, uniFillForegnd: boolean}} themeValWasUsedFor * @param {boolean} isFillGradient - if not isFillGradient calculates line gradient * @return {CUniFill} gradient fill */ function calculateGradient(shape, pageInfo, visioDocumentThemes, themeValWasUsedFor, isFillGradient) { /** * @type {CUniFill | undefined} */ let returnValue; let gradientDirCellName = isFillGradient ? "FillGradientDir" : "LineGradientDir"; let gradientDir = shape.getCellNumberValue(gradientDirCellName); let invertGradient = false; // global matrix transform: invert Y axis causes 0 is bottom of gradient and 100000 is top // let invertGradient = !isInvertCoords; // if (gradientDir === 3) { // // radial gradient seems to be handled in another way // invertGradient = isInvertCoords; // } // now let's come through gradient stops let gradientStopsSectionName = isFillGradient ? "FillGradient" : "LineGradient"; let fillGradientStopsSection = shape.getSection(gradientStopsSectionName); let rows = fillGradientStopsSection.getElements(); let fillGradientStops = []; let prevPos = invertGradient ? 100000 : 0; for (const rowKey in rows) { let row = rows[rowKey]; if (row.del) { continue; } // has color (CUniColor) and pos from 0 to 100000 let colorStop = new AscFormat.CGs(); // calculate color (CUniColor) let color = new AscFormat.CUniColor(); let gradientStopColorCell = row.getCell("GradientStopColor"); color = gradientStopColorCell.calculateValue(shape, pageInfo, visioDocumentThemes, themeValWasUsedFor, true, rowKey); let gradientStopColorTransCell = row.getCell("GradientStopColorTrans"); let gradientStopColorTransValue = gradientStopColorTransCell.calculateValue(shape, pageInfo, visioDocumentThemes, themeValWasUsedFor, true, rowKey); color.RGBA.A = color.RGBA.A * (1 - gradientStopColorTransValue); // now let's get pos let gradientStopPositionCell = row.getCell("GradientStopPosition"); let pos = gradientStopPositionCell.calculateValue(shape, pageInfo, visioDocumentThemes, undefined, true, rowKey); pos = invertGradient ? 100000 - pos : pos; // if new pos < prevPos break if (!invertGradient && pos < prevPos || invertGradient && pos > prevPos) { break; } prevPos = pos; colorStop.setColor(color); colorStop.setPos(pos); fillGradientStops.push({Gs : colorStop}); if ((pos === 100000 && !invertGradient) || (invertGradient && pos === 0)) { break; } } // gradientDir 0 is linear. FillGradientDir from 1 to 13 including are // unhandled so paint them as radial. FillGradientDir from 14 including as same as FillGradientDir 0 if (gradientDir && gradientDir !== 0 && gradientDir < 14) { // radial returnValue = AscFormat.builder_CreateRadialGradient(fillGradientStops); } else { let gradientAngleCellName = isFillGradient ? "FillGradientAngle" : "LineGradientAngle"; let gradientAngleCell = shape.getCell(gradientAngleCellName); // TODO handle multiple gradient types let gradientAngle = gradientAngleCell.calculateValue(shape, pageInfo, visioDocumentThemes); returnValue = AscFormat.builder_CreateLinearGradient(fillGradientStops, gradientAngle); } return returnValue; } /** * handle QuickStyleVariation cell which can change color (but only if color is a result of ThemeVal) * cant be separated for unifill and stroke * @param {CUniFill} lineUniFill stroke * @param {CUniFill} fillUniFill * @param {Shape_Type} shape * @param {{lineUniFill: boolean, uniFillForegnd: boolean}} themeValWasUsedFor * @param {Page_Type} pageInfo * @param {CTheme[]} themes * @return {[]} [newFillUnifill, newLineUniFill] */ function handleQuickStyleVariation(lineUniFill, fillUniFill, shape, themeValWasUsedFor, pageInfo, themes) { // https://learn.microsoft.com/en-us/openspecs/sharepoint_protocols/ms-vsdx/68bb0221-d8a1-476e-a132-8c60a49cea63?redirectedfrom=MSDN // consider "QuickStyleVariation" cell // https://visualsignals.typepad.co.uk/vislog/2013/05/visio-2013-themes-in-the-shapesheet-part-2.html let backgroundColorHSL = {H: undefined, S: undefined, L: undefined}; let lineColorHSL = {H: undefined, S: undefined, L: undefined}; let fillColorHSL = {H: undefined, S: undefined, L: undefined}; // in quick style variation we need to consider fill.color not fill.color.color because // fill.color consider mods applied. And we need to store new color to fill.color.color because // fill.color is calculated in recalculate function from fill.color.color // Calculate fill.color if it is not calculated let theme = shape.getTheme(pageInfo, themes); fillUniFill.fill.color.Calculate(theme); lineUniFill.fill.color.Calculate(theme); let lineColorRGBA = lineUniFill.fill && lineUniFill.fill.color && lineUniFill.fill.color.RGBA; let fillColorRGBA = fillUniFill.fill && fillUniFill.fill.color && fillUniFill.fill.color.RGBA; // let lineColorNoMods = lineUniFill.fill && lineUniFill.fill.color && lineUniFill.fill.color.color // && lineUniFill.fill.color.color.RGBA; // let fillColorNoMods = fillUniFill.fill && fillUniFill.fill.color && fillUniFill.fill.color.color // && fillUniFill.fill.color.color.RGBA; let newLineUniFill = new AscFormat.CUniFill(); newLineUniFill.fill = new AscFormat.CSolidFill(); newLineUniFill.fill.color = new AscFormat.CUniColor(); newLineUniFill.fill.color.color = new AscFormat.CRGBColor(); let newLineColorNoMods = newLineUniFill.fill.color.color; // set defaults for new color newLineColorNoMods.setColor(lineColorRGBA.R, lineColorRGBA.G, lineColorRGBA.B); newLineColorNoMods.RGBA.A = lineColorRGBA.A; newLineUniFill.transparent = lineUniFill.transparent; let newFillUniFill = new AscFormat.CUniFill(); newFillUniFill.fill = new AscFormat.CSolidFill(); newFillUniFill.fill.color = new AscFormat.CUniColor(); newFillUniFill.fill.color.color = new AscFormat.CRGBColor(); let newFillColorNoMods = newFillUniFill.fill.color.color; // set defaults for new color newFillColorNoMods.setColor(fillColorRGBA.R, fillColorRGBA.G, fillColorRGBA.B); newFillColorNoMods.RGBA.A = fillColorRGBA.A; newFillUniFill.transparent = fillUniFill.transparent; if (lineColorRGBA !== undefined && fillColorRGBA !== undefined) { AscFormat.CColorModifiers.prototype.RGB2HSL(255, 255, 255, backgroundColorHSL); AscFormat.CColorModifiers.prototype.RGB2HSL(lineColorRGBA.R, lineColorRGBA.G, lineColorRGBA.B, lineColorHSL); AscFormat.CColorModifiers.prototype.RGB2HSL(fillColorRGBA.R, fillColorRGBA.G, fillColorRGBA.B, fillColorHSL); // covert L to percents backgroundColorHSL.L = backgroundColorHSL.L / 255 * 100; lineColorHSL.L = lineColorHSL.L / 255 * 100; fillColorHSL.L = fillColorHSL.L / 255 * 100; let quickStyleVariationCell = shape.getCell("QuickStyleVariation"); if (quickStyleVariationCell) { let quickStyleVariationCellValue = Number(quickStyleVariationCell.v); if ((quickStyleVariationCellValue & 4) === 4 && themeValWasUsedFor.lineUniFill) { // line color variation enabled (bit mask used) if (Math.abs(backgroundColorHSL.L - lineColorHSL.L) < 16.66) { if (backgroundColorHSL.L <= 72.92) { // if background is dark set stroke to white newLineColorNoMods.setColor(255, 255, 255); newLineColorNoMods.RGBA.A = 255; newLineUniFill.transparent = 255; // transparent is opacity in fact } else { if (Math.abs(backgroundColorHSL.L - fillColorHSL.L) > Math.abs(backgroundColorHSL.L - lineColorHSL.L)) { // evaluation = THEMEVAL("FillColor") newLineColorNoMods.setColor(fillColorRGBA.R, fillColorRGBA.G, fillColorRGBA.B); newLineColorNoMods.RGBA.A = fillColorRGBA.A; // transparency should not be considered // newLineUniFill.transparent = fillUniFill.transparent; } else { // evaluation = THEMEVAL("LineColor") or not affected I guess // get theme line color despite cell // lineUniFillNoGradient = AscVisio.themeval(this.theme, shape, null, "LineColor"); } } } } if ((quickStyleVariationCellValue & 8) === 8 && themeValWasUsedFor.uniFillForegnd) { // fill color variation enabled (bit mask used) if (Math.abs(backgroundColorHSL.L - fillColorHSL.L) < 16.66) { if (backgroundColorHSL.L <= 72.92) { // if background is dark set stroke to white newFillColorNoMods.setColor(255, 255, 255); newFillColorNoMods.RGBA.A = 255; newFillUniFill.transparent = 255; // transparent is opacity in fact } else { if (Math.abs(backgroundColorHSL.L - lineColorHSL.L) > Math.abs(backgroundColorHSL.L - fillColorHSL.L)) { // evaluation = THEMEVAL("LineColor") newFillColorNoMods.setColor(lineColorRGBA.R, lineColorRGBA.G, lineColorRGBA.B); newFillColorNoMods.RGBA.A = lineColorRGBA.A; // transparency should not be considered // newFillUniFill.transparent = lineUniFill.transparent; } } } } // if ((quickStyleVariationCellValue & 2) === 2) { // // text color variation enabled (bit mask used) // // Text color variation is realized in getTextCShape function handleTextQuickStyleVariation // } } } return [newFillUniFill, newLineUniFill]; } } let lineWidthEmu = getLineWidth(this, pageInfo, visioDocument); // not scaling lineWidth /** * @type {CLn} */ let oStroke = AscFormat.builder_CreateLine(lineWidthEmu, {UniFill: lineUniFill}); // seems to be unsupported for now // see [MS-VSDX]-220215 (1) - 2.4.4.170 LineCap let lineCap = getLineCap(this, pageInfo, visioDocument); oStroke.setCap(lineCap); let linePattern = getPresetDash(this, pageInfo, visioDocument, lineCap); if (linePattern === 11 && oStroke.Fill) { // 11 type is vsdx transparent //todo реализовать прозрачный тип через отдельную настройку или разделить fill для линий и наконечников //в vsdx может быть прозрачная линия с видимыми наконечниками oStroke.Fill.fill = new AscFormat.CNoFill(); } else { oStroke.setPrstDash(linePattern); } // Each geometry section may have arrows // Arrow are displayed only if that geometry section has NoFill cell equal to 1 // For now as we set arrow for all the shape let's check first geometry only let firstGeometrySection = this.getSection("Geometry_0"); if (firstGeometrySection && firstGeometrySection.getCellNumberValue("NoFill") === 1) { let endArrowTypeCell = this.getCell("EndArrow"); let endArrowSizeCell = this.getCell("EndArrowSize"); let endArrowType = endArrowTypeCell ? endArrowTypeCell.calculateValue(this, pageInfo, visioDocument.themes) : 0; let endArrowSize = endArrowSizeCell ? endArrowSizeCell.calculateValue(this, pageInfo, visioDocument.themes) : 1; let endArrow = getEndArrow(endArrowType, endArrowSize); oStroke.setTailEnd(endArrow); let beginArrowTypeCell = this.getCell("BeginArrow"); let beginArrowSizeCell = this.getCell("BeginArrowSize"); let beginArrowType = beginArrowTypeCell ? beginArrowTypeCell.calculateValue(this, pageInfo, visioDocument.themes) : 0; let beginArrowSize = beginArrowSizeCell ? beginArrowSizeCell.calculateValue(this, pageInfo, visioDocument.themes) : 1; let beginArrow = getEndArrow(beginArrowType, beginArrowSize); oStroke.setHeadEnd(beginArrow); } // apply flip props consider flip point: locPinX_inch or locPinY_inch let flipHorizontally = this.getCellNumberValue("FlipX") === 1; if (flipHorizontally) { x_inch = x_inch + 2 * (locPinX_inch - shapeWidth_inch / 2); } let flipVertically = this.getCellNumberValue("FlipY") === 1; if (flipVertically) { y_inch = y_inch + 2 * (locPinY_inch - shapeHeight_inch / 2); } let x_mm = x_inch * g_dKoef_in_to_mm; let y_mm = y_inch * g_dKoef_in_to_mm; let shapeWidth_mm = shapeWidth_inch * g_dKoef_in_to_mm; let shapeHeight_mm = shapeHeight_inch * g_dKoef_in_to_mm; let cShape = this.convertToCShapeUsingParamsObj({ x_mm: x_mm, y_mm: y_mm, w_mm: shapeWidth_mm, h_mm: shapeHeight_mm, rot: shapeAngle, oFill: uniFillForegndWithPattern, oStroke: oStroke, flipHorizontally: flipHorizontally, flipVertically: flipVertically, pageInfo: pageInfo, cVisioDocument: visioDocument, drawingPageScale : drawingPageScale, isInvertCoords: isInvertCoords, isShapeDeleted: isShapeDeleted, id: this.id }); // set shadow // check shadow pattern let shadowPatternCell = this.getCell("ShdwPattern"); let shadowPattern = shadowPatternCell ? shadowPatternCell.calculateValue(this, pageInfo, visioDocument.themes) : 0; let isShadowVisible = shadowPattern === 1; if (isShadowVisible) { let shadows = getShadows(this, pageInfo, visioDocument); let outerShadow = shadows[0]; let innerShadow = shadows[1]; // cShape.spPr.changeShadow(shadow); cShape.spPr.effectProps = new AscFormat.CEffectProperties(); // EffectDag (effect container) doesn't work for now // cShape.spPr.effectProps.EffectDag = new AscFormat.CEffectContainer(); // cShape.spPr.effectProps.EffectDag.name = "1container"; // // cShape.spPr.effectProps.EffectDag.type = AscFormat.effectcontainertypeTree; // // cShape.spPr.effectProps.EffectDag.type = AscFormat.effectcontainertypeSib; // cShape.spPr.effectProps.EffectDag.effectList.push(shadow); cShape.spPr.effectProps.EffectLst = new AscFormat.CEffectLst(); cShape.spPr.effectProps.EffectLst.outerShdw = outerShadow; cShape.spPr.effectProps.EffectLst.innerShdw = innerShadow; /** * @param {Shape_Type} shape * @param {Page_Type} pageInfo * @param {CVisioDocument} visioDocument * @return {[COuterShdw,CInnerShdw]} outer shadow and inner */ function getShadows(shape, pageInfo, visioDocument) { /** * @type {COuterShdw} */ let shadow = new AscFormat.COuterShdw(); let shadowColor = getShadowColor(shape, pageInfo, visioDocument); shadow.color = shadowColor; let shadowTypeCell = shape.getCell("ShapeShdwType"); // TODO check themed type. shadowTypeCell.calculateValue return undefined on THEMEVAL // because there is an issue with visio THEMEVAL it sometimes return 0 sometimes 1 on empty effectStyleLst // where shadow data should be // see files: offsets shadow properties themeval type 1.vsdx and offsets shadow properties themeval type 0.vsdx // in https://bugzilla.onlyoffice.com/show_bug.cgi?id=75884 let shadowType = shadowTypeCell && shadowTypeCell.calculateValue(shape, pageInfo,visioDocument.themes); let shadowOffsetX_inch; let shadowOffsetY_inch; let shadowScaleX; let shadowScaleY; if (shadowType !== undefined && shadowType === 0) { shadowOffsetX_inch = 0.0625; shadowOffsetY_inch = -0.0625; shadowScaleX = 1; shadowScaleY = 1; } else { let shapeShdwScaleFactorCell = shape.getCell("ShapeShdwScaleFactor"); let shapeShdwScaleFactor = shapeShdwScaleFactorCell && shapeShdwScaleFactorCell.calculateValue(shape, pageInfo, visioDocument.themes); shadowScaleX = shapeShdwScaleFactor; shadowScaleY = shapeShdwScaleFactor; // set offsets for shadow let shadowOffsetXcell = shape.getCell("ShapeShdwOffsetX"); if (shadowOffsetXcell) { shadowOffsetX_inch = shadowOffsetXcell.calculateValue(shape, pageInfo, visioDocument.themes); } else { shadowOffsetX_inch = 0.125; } let shadowOffsetYcell = shape.getCell("ShapeShdwOffsetY"); if (shadowOffsetYcell) { shadowOffsetY_inch = shadowOffsetYcell.calculateValue(shape, pageInfo, visioDocument.themes); } else { shadowOffsetY_inch = -0.125; } } let shadowSx = shadowScaleX * 100000; let shadowSy = shadowScaleY * 100000; shadow.sx = shadowSx; shadow.sy = shadowSy; let shadowOffsetX = shadowOffsetX_inch === undefined ? 0 : shadowOffsetX_inch * g_dKoef_in_to_mm; let shadowOffsetY = shadowOffsetY_inch === undefined ? 0 : shadowOffsetY_inch * g_dKoef_in_to_mm; let atan = Math.atan2(shadowOffsetY, shadowOffsetX); let emuDist = Math.hypot(shadowOffsetX, shadowOffsetY) * g_dKoef_mm_to_emu; shadow.dist = emuDist; // if true move to cord system where y goes down if (isInvertCoords) { atan = -atan; } let dirC = atan * AscFormat.radToDeg * AscFormat.degToC; shadow.dir = dirC; shadow.rotWithShape = true; /** * @type {CInnerShdw} */ let shadowInner = new AscFormat.CInnerShdw(); if (shadowType && shadowType === 3) { shadowInner = new AscFormat.CInnerShdw(); shadowInner.color = shadowColor; shadowInner.sx = shadowSx; shadowInner.sy = shadowSy; shadowInner.dist = emuDist; shadowInner.dir = dirC; shadowInner.rotWithShape = true; } return [shadow, shadowInner]; /** * @param {Shape_Type} shape * @param {Page_Type} pageInfo * @param {CVisioDocument} visioDocument * @return {CUniColor} shadow color */ function getShadowColor(shape, pageInfo, visioDocument) { let shadowForegndCell = shape.getCell("ShdwForegnd"); let shadowColor; if (shadowForegndCell) { // AscCommon.consoleLog("FillForegnd was found:", fillForegndCell); shadowColor = shadowForegndCell.calculateValue(shape, pageInfo, visioDocument.themes); let mainFillAlphaCoef = 1; let fillForegndTransValue = shape.getCellNumberValue("FillForegndTrans"); if (!isNaN(fillForegndTransValue)) { mainFillAlphaCoef = 1 - fillForegndTransValue; } let shadowTransValue = 0; let shadowTransCell = shape.getCell("ShdwForegndTrans"); // if themed alpha is included in shadowColor already if (shadowTransCell.getStringValue() !== "Themed") { shadowTransValue = shadowTransCell.getNumberValue("ShdwForegndTrans"); let shadowAlpha = (1 - shadowTransValue) * mainFillAlphaCoef; if (shadowAlpha !== 1) { let oMod = new AscFormat.CColorMod("alpha", shadowAlpha * 100 * 1000 + 0.5 >> 0); shadowColor.addColorMod(oMod); } } else { // check if alpha is set already let alphaMod = shadowColor.Mods && shadowColor.Mods.Mods.find(function (mod) { return mod.name === "alpha"; }); if (alphaMod) { alphaMod.val = alphaMod.val * mainFillAlphaCoef; } else { let oMod = new AscFormat.CColorMod("alpha", mainFillAlphaCoef * 100 * 1000 + 0.5 >> 0); shadowColor.addColorMod(oMod); } } } else { // AscCommon.consoleLog("shadow foreground cell not found for", this); shadowColor = AscFormat.CreateUniColorRGB(0,0,0); } return shadowColor; } } } // not scaling fontSize let textCShape = getTextCShape(visioDocument.themes[0], this, cShape, lineUniFillNoGradient, uniFillForegndNoGradient, drawingPageScale, maxHeightScaledIn, visioDocument.pageIndex, visioDocument.pages.page.length, pageInfo, layerColor); if (textCShape !== null) { if (isShapeDeleted) { textCShape.setBDeleted(true); } } if (this.type === AscVisio.SHAPE_TYPES_FOREIGN) { // AscCommon.consoleLog("Shape has type Foreign and may not be displayed. " + // "Check shape.elements --> ForeignData_Type obj. See shape:", this); let foreignDataObject = this.getForeignDataObject(); if (foreignDataObject) { if (this.cImageShape !== null) { setCImageShapeParams(this.cImageShape, cShape, this, shapeWidth_mm, shapeHeight_mm); this.cImageShape.setParent2(visioDocument); cShape = this.cImageShape; /** * changing cImageShape to set its size, position and other props from cShape * @param cImageShape * @param cShape * @param shapeType * @param shapeWidth_mm * @param shapeHeight_mm */ function setCImageShapeParams(cImageShape, cShape, shapeType, shapeWidth_mm, shapeHeight_mm) { // move some properties from shape to image. Then we will return cImageShape instead of cShape cImageShape.setLocks(0); cImageShape.setBDeleted(false); cImageShape.setSpPr(cShape.spPr.createDuplicate()); cImageShape.spPr.setParent(cImageShape); let imgWidth_inch = shapeType.getCellNumberValueWithScale("ImgWidth", drawingPageScale); let imgHeight_inch = shapeType.getCellNumberValueWithScale("ImgHeight", drawingPageScale); let imgOffsetX_inch = shapeType.getCellNumberValueWithScale("ImgOffsetX", drawingPageScale); let imgOffsetY_inch = shapeType.getCellNumberValueWithScale("ImgOffsetY", drawingPageScale); let imgWidth_mm = imgWidth_inch * g_dKoef_in_to_mm; let imgHeight_mm = imgHeight_inch * g_dKoef_in_to_mm; cImageShape.blipFill.srcRect = new AscFormat.CSrcRect(); let rect = cImageShape.blipFill.srcRect; // add scale if (imgWidth_inch !== undefined && imgHeight_inch !== undefined) { let widthScale = imgWidth_mm / shapeWidth_mm; let heightScale = imgHeight_mm / shapeHeight_mm; // coords in our class CSrcRect is srcRect relative i.e. relative to original image size // isInvertCoords check? rect.setLTRB(0, 100 - 1/heightScale * 100, 1/widthScale * 100, 100); } // add horizontal shift if (imgOffsetX_inch !== undefined) { let imgOffsetX_mm = imgOffsetX_inch * g_dKoef_in_to_mm; let offsetX = imgOffsetX_mm / imgWidth_mm; rect.setLTRB(rect.l - offsetX * 100, rect.t, rect.r - offsetX * 100, rect.b); } // add vertical shift if (imgOffsetY_inch !== undefined) { let imgOffsetY_mm = imgOffsetY_inch * g_dKoef_in_to_mm; let offsetY = imgOffsetY_mm / imgHeight_mm; rect.setLTRB(rect.l, rect.t + offsetY * 100, rect.r, rect.b + offsetY * 100); } cImageShape.rot = cShape.rot; // cImageShape.brush = cShape.brush; cImageShape.bounds = cShape.bounds; cImageShape.flipH = cShape.flipH; cImageShape.flipV = cShape.flipV; cImageShape.localTransform = cShape.localTransform; // cImageShape.pen = cShape.pen; cImageShape.Id = cShape.Id; } } else { AscCommon.consoleLog("Unknown error: cImageShape was not initialized on ooxml parse"); } } } // combine textCShape and geometryCShape to group if (textCShape !== null) { let groupShape = new AscFormat.CGroupShape(); // this.graphicObjectsController = new AscFormat.DrawingObjectsController(); // let groupShape = AscFormat.builder_CreateGroup(); groupShape.setLocks(0); groupShape.setBDeleted(false); // Create CGroupShape with SpPr from cShape but with no fill and line let noLineFillSpPr = cShape.spPr.createDuplicate(); noLineFillSpPr.setFill(AscFormat.CreateNoFillUniFill()); noLineFillSpPr.setLn(AscFormat.CreateNoFillLine()); groupShape.setSpPr(noLineFillSpPr); groupShape.spPr.setParent(groupShape); // these props came to group cShape.spPr.xfrm.rot = 0; cShape.spPr.xfrm.flipV = false; cShape.spPr.xfrm.flipH = false; groupShape.brush = cShape.brush; groupShape.bounds = cShape.bounds; groupShape.localTransform = cShape.localTransform; groupShape.pen = cShape.pen; groupShape.Id = cShape.Id + "ShapeAndText"; groupShape.addToSpTree(groupShape.spTree.length, cShape); groupShape.spTree[groupShape.spTree.length - 1].setGroup(groupShape); cShape.spPr.xfrm.setOffX(0); cShape.spPr.xfrm.setOffY(0); groupShape.addToSpTree(groupShape.spTree.length, textCShape); groupShape.spTree[groupShape.spTree.length - 1].setGroup(groupShape); textCShape.spPr.xfrm.setOffX(textCShape.spPr.xfrm.offX - groupShape.spPr.xfrm.offX); textCShape.spPr.xfrm.setOffY(textCShape.spPr.xfrm.offY - groupShape.spPr.xfrm.offY); textCShape.spPr.xfrm.flipH = false; textCShape.spPr.xfrm.flipV = false; // In power point presentations on flipV text position is flipped + text // is mirrored horizontally and vertically (https://disk.yandex.ru/d/Hi8OCMITgb730Q) // below we remove text mirror. In visio text is never mirrored. (https://disk.yandex.ru/d/JjbNzzZLDIAEuQ) // (on flipH in power point presentation text is not mirrored) let currentFlip = groupShape.spPr.xfrm.flipV; let groupFlip = currentGroupHandling && currentGroupHandling.getFullFlipVSpPr(); let flip = groupFlip ? !currentFlip : currentFlip; if (flip) { textCShape.spPr.xfrm.setRot(Math.PI + textCShape.spPr.xfrm.rot); } groupShape.setParent2(visioDocument); return groupShape; } else { return cShape; } // Method end // Used functions: // TODO import /** * Calculates coordinates after rotation using rotation point * @param {number} rotatePointX_global * @param {number} rotatePointY_global * @param {number} rotatePointX_local * @param {number} rotatePointY_local * @param {number} shapeWidth * @param {number} shapeHeight * @param {number} angle - radians rotate clockwise. E.g. 30 degrees goes down. * @return {[x: number, y: number]} returns left bottom corner coordinates. */ function getCordsRotatedAroundPoint(rotatePointX_global, rotatePointY_global, rotatePointX_local, rotatePointY_local, shapeWidth, shapeHeight, angle) { // to rotate around point we 1) add one more offset 2) rotate around center // could be refactored maybe // https://www.figma.com/design/SJSKMY5dGoAvRg75YnHpdX/newRotateScheme?node-id=0-1&node-type=canvas&t=UTtoZyLRItzaQvS9-0 let redVector = {x: -(rotatePointX_local - shapeWidth/2), y: -(rotatePointY_local - shapeHeight/2)}; // rotate antiClockWise by shapeAngle let purpleVector = rotatePointAroundCordsStartClockWise(redVector.x, redVector.y, -angle); let rotatedCenter = {x: rotatePointX_global + purpleVector.x, y: rotatePointY_global + purpleVector.y}; let turquoiseVector = {x: -shapeWidth/2, y: -shapeHeight/2}; let x = rotatedCenter.x + turquoiseVector.x; let y = rotatedCenter.y + turquoiseVector.y; return [x, y]; } //TODO import /** * afin rotate clockwise * @param {number} x * @param {number} y * @param {number} radiansRotateAngle radians Rotate clockwise Angle. E.g. 30 degrees rotates does DOWN. * @returns {{x: number, y: number}} point */ function rotatePointAroundCordsStartClockWise(x, y, radiansRotateAngle) { let newX = x * Math.cos(radiansRotateAngle) + y * Math.sin(radiansRotateAngle); let newY = x * (-1) * Math.sin(radiansRotateAngle) + y * Math.cos(radiansRotateAngle); return {x : newX, y: newY}; } /** * @param {CTheme} theme * @param {Shape_Type} shape * @param {CShape} cShape * @param {CUniFill} lineUniFill - without gradient to handle text quickStyleVariation * @param {CUniFill} fillUniFill - without gradient to handle text quickStyleVariation * @param {number} drawingPageScale * @param {number} maxHeightScaledIn * @param {number} currentPageIndex * @param {number} pagesCount * @param {Page_Type} pageInfo * @param {CUniFill?} layerColor * @return {CShape | null} return textCShape or null if no text */ function getTextCShape(theme, shape, cShape, lineUniFill, fillUniFill, drawingPageScale, maxHeightScaledIn, currentPageIndex, pagesCount, pageInfo, layerColor) { // see 2.2.8 Text [MS-VSDX]-220215 /** * handle QuickStyleVariation cell which can change color (but only if color is a result of ThemeVal) * @param textUniColor * @param lineUniFill * @param fillUniFill * @param {{fontColor:boolean}} themeValWasUsedFor - sets during calculateCellValue */ function handleTextQuickStyleVariation(textUniColor, lineUniFill, fillUniFill, themeValWasUsedFor) { // https://learn.microsoft.com/en-us/openspecs/sharepoint_protocols/ms-vsdx/68bb0221-d8a1-476e-a132-8c60a49cea63?redirectedfrom=MSDN // consider "QuickStyleVariation" cell // https://visualsignals.typepad.co.uk/vislog/2013/05/visio-2013-themes-in-the-shapesheet-part-2.html // line and fill QuickStyleVariation are handled in handleQuickStyleVariation if (!themeValWasUsedFor.fontColor) { return; } let backgroundColorHSL = {H: undefined, S: undefined, L: undefined}; let textColorHSL = {H: undefined, S: undefined, L: undefined}; let lineColorHSL = {H: undefined, S: undefined, L: undefined}; let fillColorHSL = {H: undefined, S: undefined, L: undefined}; let textColorRGBA = textUniColor.color && textUniColor.color.RGBA; let lineColorRGBA = lineUniFill.fill && lineUniFill.fill.color && lineUniFill.fill.color.color.RGBA; let fillColorRGBA = fillUniFill.fill && fillUniFill.fill.color && fillUniFill.fill.color.color.RGBA; AscFormat.CColorModifiers.prototype.RGB2HSL(255, 255, 255, backgroundColorHSL); let compareWithOneColor = lineColorRGBA === undefined || fillColorRGBA === undefined; if (lineColorRGBA !== undefined && fillColorRGBA !== undefined && textColorRGBA !== undefined) { AscFormat.CColorModifiers.prototype.RGB2HSL(lineColorRGBA.R, lineColorRGBA.G, lineColorRGBA.B, lineColorHSL); AscFormat.CColorModifiers.prototype.RGB2HSL(fillColorRGBA.R, fillColorRGBA.G, fillColorRGBA.B, fillColorHSL); AscFormat.CColorModifiers.prototype.RGB2HSL(textColorRGBA.R, textColorRGBA.G, textColorRGBA.B, textColorHSL); // covert L to percents backgroundColorHSL.L = backgroundColorHSL.L / 255 * 100; lineColorHSL.L = lineColorHSL.L / 255 * 100; fillColorHSL.L = fillColorHSL.L / 255 * 100; textColorHSL.L = textColorHSL.L / 255 * 100; let quickStyleVariationCell = shape.getCell("QuickStyleVariation"); if (quickStyleVariationCell) { let quickStyleVariationCellValue = Number(quickStyleVariationCell.v); if ((quickStyleVariationCellValue & 2) === 2) { // text color variation enabled (bit mask used) // let fillPattern = shape.getCellNumberValue("FillPattern"); // if (fillPattern !== 0) { // AscCommon.consoleLog("TextQuickStyleVariation for shapes with FillPattern !== 0 is disabled"); // // consider example https://disk.yandex.ru/d/2fbgXRrCBThlCw // return; // } if (Math.abs(backgroundColorHSL.L - textColorHSL.L) < 16.66) { if (backgroundColorHSL.L <= 72.92) { // if background is dark set stroke to white textColorRGBA.R = 255; textColorRGBA.G = 255; textColorRGBA.B = 255; } else { // return the color with the largest absolute difference in luminance from the // formula evaluation of the "TextColor", "FillColor", and "LineColor" let fillDifferenceIsTheLargest = Math.abs(backgroundColorHSL.L - fillColorHSL.L) > Math.abs(backgroundColorHSL.L - lineColorHSL.L) && Math.abs(backgroundColorHSL.L - fillColorHSL.L) > Math.abs(backgroundColorHSL.L - textColorHSL.L); if (fillDifferenceIsTheLargest) { textColorRGBA.R = fillColorRGBA.R; textColorRGBA.G = fillColorRGBA.G; textColorRGBA.B = fillColorRGBA.B; } else { if (Math.abs(backgroundColorHSL.L - lineColorHSL.L) > Math.abs(backgroundColorHSL.L - textColorHSL.L)) { textColorRGBA.R = lineColorRGBA.R; textColorRGBA.G = lineColorRGBA.G; textColorRGBA.B = lineColorRGBA.B; } // else leave text color } } } } } } else if (compareWithOneColor) { let compareColorRGBA = lineColorRGBA || fillColorRGBA; let compareColorHSL = {H: undefined, S: undefined, L: undefined}; AscFormat.CColorModifiers.prototype.RGB2HSL(compareColorRGBA.R, compareColorRGBA.G, compareColorRGBA.B, compareColorHSL); AscFormat.CColorModifiers.prototype.RGB2HSL(textColorRGBA.R, textColorRGBA.G, textColorRGBA.B, textColorHSL); // covert L to percents backgroundColorHSL.L = backgroundColorHSL.L / 255 * 100; compareColorHSL.L = compareColorHSL.L / 255 * 100; textColorHSL.L = textColorHSL.L / 255 * 100; let quickStyleVariationCell = shape.getCell("QuickStyleVariation"); if (quickStyleVariationCell) { let quickStyleVariationCellValue = Number(quickStyleVariationCell.v); if ((quickStyleVariationCellValue & 2) === 2) { // text color variation enabled (bit mask used) // let fillPattern = shape.getCellNumberValue("FillPattern"); // if (fillPattern !== 0) { // AscCommon.consoleLog("TextQuickStyleVariation for shapes with FillPattern !== 0 is disabled"); // // consider example https://disk.yandex.ru/d/2fbgXRrCBThlCw // return; // } if (Math.abs(backgroundColorHSL.L - textColorHSL.L) < 16.66) { if (backgroundColorHSL.L <= 72.92) { // if background is dark set stroke to white textColorRGBA.R = 255; textColorRGBA.G = 255; textColorRGBA.B = 255; } else { // return the color with the largest absolute difference in luminance from the // formula evaluation of the "TextColor" and "FillColor" or "LineColor" i.e. compareColor if (Math.abs(backgroundColorHSL.L - compareColorHSL.L) > Math.abs(backgroundColorHSL.L - textColorHSL.L)) { textColorRGBA.R = compareColorRGBA.R; textColorRGBA.G = compareColorRGBA.G; textColorRGBA.B = compareColorRGBA.B; } // else leave text color } } } } } } /** * Searches for pp element after passed element in textElements * @param {[]} textElements - elements with pp tp cp and text with \n * @param {number} currentIndex * @param {string?} afterDropText - afterDropText without \n * @param {boolean?} isNextElementLineDrop * @return {number | undefined} row num */ function searchForPP(textElements, currentIndex, afterDropText, isNextElementLineDrop) { if (afterDropText === undefined) { afterDropText = ""; } let afterNext = textElements[currentIndex + 2]; let next = textElements[currentIndex + 1]; // if there is something after \n // also check for isNextElementLineDrop - means new paragraph start if (afterDropText.length > 0 || isNextElementLineDrop) { return undefined; } else if (afterNext && afterNext.kind === AscVisio.c_oVsdxTextKind.PP) { return afterNext.ix; } else if (next && next.kind === AscVisio.c_oVsdxTextKind.PP) { return next.ix; } return undefined; } /** * @param propsRowNum * @param {?Section_Type} paragraphPropsCommon * @param textCShape */ function parseParagraphAndAddToShapeContent(propsRowNum, paragraphPropsCommon, textCShape) { if (paragraphPropsCommon === null || paragraphPropsCommon === undefined) { AscCommon.consoleLog("paragraphPropsCommon is null or undefined. Creating default paragraph"); // create new paragraph to hold new properties let oContent = textCShape.getDocContent(); let paragraph = new Paragraph(textCShape.getDrawingDocument(), true); // Set defaultParagraph justify/align text - center paragraph.Pr.SetJc(AscCommon.align_Center); oContent.Content.push(paragraph); paragraph.SetParent(oContent); return; } let paragraphPropsFinal = propsRowNum !== null && paragraphPropsCommon.getRow(propsRowNum); // handle horizontal align // 0 Specifies that the defaultParagraph is left aligned. // 1 Specifies that the defaultParagraph is centered. // 2 Specifies that the defaultParagraph is right aligned. // 3 Specifies that the defaultParagraph is justified. // 4 Specifies that the defaultParagraph is distributed. let hAlignCell = paragraphPropsFinal && paragraphPropsFinal.getCell("HorzAlign"); let horizontalAlign = AscCommon.align_Left; if (hAlignCell && hAlignCell.kind === AscVisio.c_oVsdxSheetStorageKind.Cell_Type) { // omit calculateCellValue here // let fontColor = calculateCellValue(theme, shape, characterColorCell); let horAlignTryParse = Number(hAlignCell.v); if (!isNaN(horAlignTryParse)) { switch (horAlignTryParse) { case 0: horizontalAlign = AscCommon.align_Left; break; case 1: horizontalAlign = AscCommon.align_Center; break; case 2: horizontalAlign = AscCommon.align_Right; break; case 3: horizontalAlign = AscCommon.align_Justify; break; case 4: horizontalAlign = AscCommon.align_Distributed; break; } } else { AscCommon.consoleLog("horizontal align was not parsed so default is set (left)"); } } else { AscCommon.consoleLog("horizontal align cell was not found so default is set (left)"); } // handle bullet list let bulletTypeCell = paragraphPropsFinal && paragraphPropsFinal.getCell("Bullet"); let bulletType; if (bulletTypeCell) { bulletType = bulletTypeCell.getNumberValue(); } let bulletChar; let bulletFont; if (bulletType === 0) { // none } else if (bulletType === 1) { bulletChar = "•"; bulletFont = "Symbol"; } else if (bulletType === 2) { bulletChar = "◆"; bulletFont = "Courier New"; } else if (bulletType === 3) { bulletChar = "▪"; bulletFont = "Wingdings"; } else if (bulletType === 4) { bulletChar = "□"; bulletFont = "Wingdings"; } else if (bulletType === 5) { bulletChar = "❖"; bulletFont = "Wingdings"; } else if (bulletType === 6) { bulletChar = "➢"; bulletFont = "Wingdings"; } else if (bulletType === 7) { bulletChar = "✓"; bulletFont = "Wingdings"; } // handle left indentation let indentationLeftCell = paragraphPropsFinal && paragraphPropsFinal.getCell("IndLeft"); let indentationLeft; if (indentationLeftCell) { indentationLeft = indentationLeftCell.getNumberValue() * AscCommonWord.g_dKoef_in_to_mm; } // handle first line indentation let indentationFirstLineCell = paragraphPropsFinal && paragraphPropsFinal.getCell("IndFirst"); let indentationFirstLine; if (indentationLeftCell) { indentationFirstLine = indentationFirstLineCell.getNumberValue() * AscCommonWord.g_dKoef_in_to_mm; } // create new paragraph to hold new properties let oContent = textCShape.getDocContent(); let paragraph = new Paragraph(textCShape.getDrawingDocument(), true); // https://learn.microsoft.com/en-us/office/client-developer/visio/spline-cell-paragraph-section // const lineSpacingVsdx = paragraphPropsFinal && // paragraphPropsFinal.getCellNumberValue("SpLine"); // let lineSpacing; // if (lineSpacingVsdx < 0) { // paragraph.Pr.Spacing.LineRule = window['Asc'].linerule_Auto; // lineSpacing = -lineSpacingVsdx; // } else if (lineSpacingVsdx === 0) { // paragraph.Pr.Spacing.LineRule = window['Asc'].linerule_Auto; // lineSpacing = 1; // } else { // paragraph.Pr.Spacing.LineRule = window['Asc'].linerule_Exact; // lineSpacing = lineSpacingVsdx; // } // paragraph.Pr.Spacing.Line = lineSpacing; // Set defaultParagraph justify/align text - center paragraph.Pr.SetJc(horizontalAlign); // // CPresentationBullet if (bulletChar) { paragraph.Pr.Bullet = new AscFormat.CBullet(); // smth wrong with Symbol font see: // https://disk.yandex.ru/d/uNQ2eMfNyVtUFQ // https://disk.yandex.ru/i/2a0drnBXaVxJNw // paragraph.Pr.Bullet.fillBulletFromCharAndFont(bulletChar, bulletFont); paragraph.Pr.Bullet.fillBulletFromCharAndFont(bulletChar, "Arial"); } // paragraph.PresentationPr.Bullet.m_nType = AscFormat.numbering_presentationnumfrmt_Blip; // // let Bullet = new AscFormat.CBullet(); // Bullet.bulletType = new AscFormat.CBulletType(); // Bullet.bulletType.type = AscFormat.BULLET_TYPE_BULLET_AUTONUM; // paragraph.Add_PresentationNumbering(Bullet); paragraph.Pr.Ind.Left = indentationLeft; paragraph.Pr.Ind.FirstLine = indentationFirstLine; oContent.Content.push(paragraph); paragraph.SetParent(oContent); // paragraph.Pr.Spacing.Before = 0; // paragraph.Pr.Spacing.After = 0; } /** * Parses run props and set them * @param characterRowNum * @param {?Section_Type} characterPropsCommon * @param {ParaRun | AscCommonWord.CPresentationField} oRun * @param lineUniFill * @param fillUniFill * @param theme * @param shape * @param visioDocument * @param {Page_Type} pageInfo */ function setRunProps(characterRowNum, characterPropsCommon, oRun, lineUniFill, fillUniFill, theme, shape, visioDocument, pageInfo) { let characterPropsFinal = characterRowNum !== null && characterPropsCommon.getRow(characterRowNum); /** * Let's memorize what color properties used themeVal because quickStyleVariation can change only those * color props that used themeVal function. * @type {{fontColor:boolean}} */ let themeValWasUsedFor = { fontColor: false } // handle Color let textColor; if (layerColor !== undefined && layerColor !== null) { textColor = new CDocumentColor(layerColor.fill.color.color.RGBA.R, layerColor.fill.color.color.RGBA.G, layerColor.fill.color.color.RGBA.B, false); } else { let characterColorCell = characterPropsFinal && characterPropsFinal.getCell("Color"); let fontColor; if (characterColorCell && characterColorCell.kind === AscVisio.c_oVsdxSheetStorageKind.Cell_Type) { fontColor = characterColorCell.calculateValue(shape, pageInfo, visioDocument.themes, themeValWasUsedFor); } else { AscCommon.consoleLog("text color cell not found! set text color as themed"); fontColor = AscVisio.themeval(null, shape, pageInfo, visioDocument.themes, "TextColor"); themeValWasUsedFor.fontColor = true; } handleTextQuickStyleVariation(fontColor, lineUniFill, fillUniFill, themeValWasUsedFor); textColor = new CDocumentColor(fontColor.color.RGBA.R, fontColor.color.RGBA.G, fontColor.color.RGBA.B, false); } oRun.Set_Color(textColor); // handle lang let oNewLang = new CLang(); let languageCell = characterPropsFinal && characterPropsFinal.getCell("LangID"); let languageId = languageCell ? Asc.g_oLcidNameToIdMap[languageCell.v] : 1033; // switch (languageCell.v) { // case "ru-RU": // languageId = 1049; // break; // default: // languageId = 1033; // break; // } oNewLang.Val = languageId; oRun.Set_Lang(oNewLang); let fontSizeCell = characterPropsFinal && characterPropsFinal.getCell("Size"); let fontSizePt; if (fontSizeCell && fontSizeCell.kind === AscVisio.c_oVsdxSheetStorageKind.Cell_Type) { // omit calculateCellValue here // let fontColor = calculateCellValue(theme, shape, characterColorCell); const fontSizeIn = Number(fontSizeCell.v); if (!isNaN(fontSizeIn)) { fontSizePt = fontSizeIn * 72; // convert from in to pt } else { AscCommon.consoleLog("font size was not parsed so default is set (9 pt)"); } } else { AscCommon.consoleLog("font size was not found so default is set (9 pt)"); } oRun.SetFontSize(fontSizePt); // handle font /** * returns CRFont object for fontName. Also checks if font is loaded and if it is not * uses default font from stylesheet "NoStyle". If stylesheet "NoStyle" font is not available uses Calibri. * @param fontName * @param {CVisioDocument} visioDocument * @return {CRFonts} */ function getRFonts(fontName, visioDocument) { let cRFonts = new CRFonts(); // see file https://disk.yandex.ru/d/AjpLrcamAzDeKg // if themed font is not available in user PC visio sets default font from stylesheets let loadedFonts = visioDocument.loadedFonts; if (!fontName || loadedFonts.findIndex(function (cFont) { return cFont.name === fontName; }) === -1) { AscCommon.consoleLog("Tried to use font that is not loaded: " + fontName + ". Loading Stylesheets font."); let styleSheetsFont = visioDocument.styleSheets[0].getSection("Character").getRow(0).getCellStringValue("Font") if (!styleSheetsFont || loadedFonts.findIndex(function (cFont) { return cFont.name === styleSheetsFont; }) === -1) { AscCommon.consoleLog("Failed to use styleSheetsFont. Loading Calibri."); cRFonts.Ascii = {Name: "Calibri", Index: 1}; cRFonts.HAnsi = {Name: "Calibri", Index: 1}; cRFonts.CS = {Name: "Calibri", Index: 1}; cRFonts.EastAsia = {Name: "Calibri", Index: 1}; } else { AscCommon.consoleLog("Loaded styleSheetsFont: " + styleSheetsFont); cRFonts.Ascii = {Name: styleSheetsFont, Index: 1}; cRFonts.HAnsi = {Name: styleSheetsFont, Index: 1}; cRFonts.CS = {Name: styleSheetsFont, Index: 1}; cRFonts.EastAsia = {Name: styleSheetsFont, Index: 1}; } } else { cRFonts.Ascii = {Name: fontName, Index: 1}; cRFonts.HAnsi = {Name: fontName, Index: 1}; cRFonts.CS = {Name: fontName, Index: 1}; cRFonts.EastAsia = {Name: fontName, Index: 1}; } return cRFonts; } let fontCell = characterPropsFinal && characterPropsFinal.getCell("Font"); let cRFonts = new CRFonts(); if (fontCell && fontCell.kind === AscVisio.c_oVsdxSheetStorageKind.Cell_Type) { // all document fonts all loaded already in CVisioDocument.prototype.loadFonts let fontName = fontCell.calculateValue(shape, pageInfo, visioDocument.themes, undefined, true); cRFonts = getRFonts(fontName, visioDocument); } else { AscCommon.consoleLog("fontCell was not found so default is set (Calibri). Check mb AsianFont or ScriptFont"); } oRun.Set_RFonts(cRFonts); // handle style (bold italic underline small caps) const styleVsdx = characterPropsFinal && characterPropsFinal.getCellStringValue("Style"); if (styleVsdx === "Themed") { AscCommon.consoleLog("Themed style text is unhandled"); } else { oRun.Pr.Bold = Boolean(Number(styleVsdx) & 1); oRun.Pr.Italic = Boolean(Number(styleVsdx) & 2); oRun.Pr.Underline = Boolean(Number(styleVsdx) & 4); oRun.Pr.SmallCaps = Boolean(Number(styleVsdx) & 8); } // handle Strikethru const strikeVsdx = characterPropsFinal && characterPropsFinal.getCellStringValue("Strikethru"); oRun.Pr.Strikeout = strikeVsdx === "1"; // handle DoubleStrikethrough const doubleStrikeVsdx = characterPropsFinal && characterPropsFinal.getCellStringValue("DoubleStrikethrough"); oRun.Pr.DStrikeout = doubleStrikeVsdx === "1"; // handle Caps const caseVsdx = characterPropsFinal && characterPropsFinal.getCellStringValue("Case"); oRun.Pr.Caps = caseVsdx === "1"; // handle VertAlign (doesn't work I don't know why) const posVsdx = characterPropsFinal && characterPropsFinal.getCellStringValue("Pos"); if (posVsdx === "1") { oRun.Pr.VertAlign = AscCommon.vertalign_SuperScript; } else if (posVsdx === "2") { oRun.Pr.VertAlign = AscCommon.vertalign_SubScript; } else { oRun.Pr.VertAlign = AscCommon.vertalign_Baseline; } } function initPresentationField(oFld, fieldRow, isTextInherited) { const valueCell = fieldRow.getCell("Value"); oFld.SetFieldType(valueCell.f); oFld.vsdxFieldValue = valueCell; // inits new class variable oFld.isTextInherited = isTextInherited; // then format it according to Format cell oFld.vsdxFieldFormat = fieldRow.getCell("Format"); } /** * Get row from section Field and transform it into text usings its cells. * @param {Row_Type} fieldRow * @param {string} fldTagText * @param {number} currentPageIndex * @param {number} pagesCount * @return {string} text */ function parseTextFromFieldRow(fieldRow, fldTagText, currentPageIndex, pagesCount) { /** * format inches to feet and inches with decimal fraction. * @param inchesTotal * @return {string} */ function formatInches(inchesTotal) { const feet = Math.floor(inchesTotal / 12); // Calculate whole feet const inches = inchesTotal % 12; // Remaining inches // Get the whole inch part and the fractional part const formatedInchesStr = formatDecimalFraction(inches); // Combine feet, whole inches, and fractional inches into the final format // return `${feet}' ${wholeInches}${fractionStr}"`; return feet + "\' " + formatedInchesStr + "\""; } /** * * @param number * @return {string} */ function formatDecimalFraction(number) { // Get the whole inch part and the fractional part const wholePart = Math.floor(number); const fractionPart = number - wholePart; // Find the best fraction with a denominator up to 9 let bestNumerator = 0; let bestDenominator = 1; let minDifference = Infinity; for (let denominator = 1; denominator <= 9; denominator++) { const numerator = Math.round(fractionPart * denominator); const difference = Math.abs(fractionPart - numerator / denominator); if (difference < minDifference) { bestNumerator = numerator; bestDenominator = denominator; minDifference = difference; } } // Format the fraction part if it's not zero let fractionStr; let wholePartAfterFractionHandle = wholePart; if (bestNumerator > 0 && bestNumerator !== bestDenominator) { fractionStr = " " + bestNumerator + "/" + bestDenominator; } else if (bestDenominator === 0) { fractionStr = ""; } else if (bestNumerator === bestDenominator) { fractionStr = ""; wholePartAfterFractionHandle += 1; } return String(wholePartAfterFractionHandle) + fractionStr; } /** * Example usage: * console.log(formatDate("2023-11-23T11:19:36")); // Output: "23.11.2023 11:19:36" * @param {string} dateString * @return {string} */ function formatDate(dateString) { // Create a Date object from the input string const date = new Date(dateString); // Extract day, month, year, hours, minutes, and seconds const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are 0-based const year = date.getFullYear(); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); // Format to "dd.mm.yyyy hh:mm:ss" return day + '.' + month + '.' + year + ' ' + hours + ':' + minutes + ':' + seconds; } /** * For example, 41879 corresponds to 8/28/2014. * @param {string} serial * @return {string} */ function excelSerialToDate(serial) { // Excel date serials start from 1900-01-01, so calculate the base date const baseDate = new Date(1900, 0, 1); // January 1, 1900 const serialNum = Number(serial); // Adjust for Excel's incorrect leap year handling (Excel includes 29th February 1900) const adjustedSerial = serialNum - 1; // Add the number of days represented by the serial number baseDate.setDate(baseDate.getDate() + adjustedSerial); // Format the date as "m/d/yyyy" const month = baseDate.getMonth() + 1; const day = baseDate.getDate(); const year = baseDate.getFullYear(); return month + '/' + day + '/' + year; } const valueCell = fieldRow.getCell("Value"); // const valueFunction = valueCell.f; const valueV = valueCell.v; const valueUnits = valueCell.u; // let's not use formula (valueCell.f) for now // first convert value (valueCell.v) which is inches by default to units set in valueCell.u /** * @type {(number|string)} */ let valueInProperUnits; if (valueUnits === "CM") { valueInProperUnits = Number(valueV) * g_dKoef_in_to_mm / 10; } else if (valueUnits === "MM") { valueInProperUnits = Number(valueV) * g_dKoef_in_to_mm; } else if (valueUnits === "DATE") { valueInProperUnits = formatDate(valueV); } else { valueInProperUnits = valueV; } // then format it according to Format cell const formatCell = fieldRow.getCell("Format"); const formatValue = formatCell.v; let formatedString; if (formatValue === "esc(13)") { // take original value in inches and return feet + inches formatedString = formatInches(valueV); } else if (formatValue === "esc(15)") { // take original value despite of units convert fractional part to decimal fraction formatedString = formatDecimalFraction(valueInProperUnits); } else if (formatValue === "T") { // take time only // formatValue === "T" is N='Format' F='FIELDPICTURE(30)' formatedString = valueInProperUnits.split(" ")[1]; } else if (formatValue === "ddddd") { // take date only // formatValue === "ddddd" is N='Format' F='FIELDPICTURE(20)' formatedString = valueInProperUnits.split(" ")[0]; } else if (formatValue === "{{M/d/yyyy}}") { formatedString = excelSerialToDate(valueInProperUnits); } else { formatedString = valueInProperUnits; } return formatedString; // if (valueFunction === "PAGENUMBER()") { // return String(currentPageIndex); // } else if (valueFunction === "PAGECOUNT()") { // return String(pagesCount); // } // return valueV ? valueV : fldTagText; } let textElement = shape.getTextElement(); if (!textElement) { return null; } // see https://disk.yandex.ru/d/xy2yxhAQHlUHsA shape with number-text has HideText cell v=1 and when we // open file and display all unit numbers // like number-text in that file these numbers can overlap like there https://disk.yandex.ru/d/G_GaAB2yH9OMDg if (shape.getCellNumberValue("HideText") === 1) { return null; } /** * text shape saves text only no fill and no line. it goes along with CShape with the same id + "Text". * It has not local coordinates but the same cord system like shape. * @type {CShape} */ let textCShape = new AscFormat.CShape(); textCShape.Id = cShape.Id + "Text"; textCShape.setParent(visioDocument); // set default settings // see sdkjs/common/Drawings/CommonController.js createTextArt: function (nStyle, bWord, wsModel, sStartString) // for examples // https://api.onlyoffice.com/docbuilder/textdocumentapi just some related info let bWord = false; textCShape.setWordShape(bWord); textCShape.setBDeleted(false); if (bWord) { textCShape.createTextBoxContent(); } else { textCShape.createTextBody(); } textCShape.setVerticalAlign(1); // sets text vert align center. equal to anchor set to txBody bodyPr textCShape.bSelectedText = false; // instead of AscFormat.AddToContentFromString(oContent, sText); // use https://api.onlyoffice.com/docbuilder/presentationapi/apishape api implementation code // to work with text separated into ParaRuns to split properties use // read propsCommonObjects let characterPropsCommon = shape.getSection("Character"); let paragraphPropsCommon = shape.getSection("Paragraph"); let fieldPropsCommon = shape.getSection("Field"); // to store last entries of cp/pp/tp like // or // // character properties are used until another element specifies new character properties. // TODO tp_Type is not parsed? let propsCP = null; let propsTP = null; let currentParagraphPropsRow; let currentParagraph; let oContent = textCShape.getDocContent(); oContent.Content = []; /** * if text is inherited so we consider that text fields in it have wrong values * and we recalculate values them */ const isTextInherited = textElement.isInherited; // UPD: now with binary file read \r\n in original is replaced with \n! Focus is made for binary \n // read text: // consider CRLF (\r\n) (UPD: \n for binary read) as new paragraph start. // Right after CRLF visio searches for pp // which will be properties for new paragraph. // (Or if it is something after CRLF it doesn't search for pp. E.g. if pp comes in text, it ignores) textElement.elements.forEach(function(textElementPart, i) { // init currentParagraph: if there is text in first element use default paragraph with // properties from 0 paragraph props row // otherwise search for pp tag witch can set paragraph properties for future text if (i === 0) { if (typeof textElementPart === "string" || textElementPart.kind === AscVisio.c_oVsdxTextKind.FLD) { currentParagraphPropsRow = 0; } else { currentParagraphPropsRow = searchForPP(textElement.elements, i); currentParagraphPropsRow = currentParagraphPropsRow === undefined ? 0 : currentParagraphPropsRow; } parseParagraphAndAddToShapeContent(currentParagraphPropsRow, paragraphPropsCommon, textCShape); currentParagraph = oContent.Content.slice(-1)[0]; // last paragraph } // visio set extra \r\n at the end of each Text tag: see fix below. // UPD: fix below works for both binary read with \n and xml with \r\n if (i === textElement.elements.length - 1) { if (typeof textElementPart === "string") { if (textElementPart.endsWith("\r\n")) { textElementPart = textElementPart.slice(0, textElementPart.length - 2); } else if (textElementPart.endsWith("\n")) { textElementPart = textElementPart.slice(0, textElementPart.length - 1); } } } if (typeof textElementPart === "string" || textElementPart.kind === AscVisio.c_oVsdxTextKind.FLD) { if (typeof textElementPart === "string") { // Split on CRLF, or LF let textArr = textElementPart.split(/\r\n|\n/); for (let j = 0; j < textArr.length; j++) { let text = textArr[j]; // if j > 0 CR exists in textArr and should be handled as new paragraph start if (j > 0) { // instead of lineDrop we get "" after split(); let isNextElementLineDrop = textArr[j + 1] === ""; let nextPP = searchForPP(textElement.elements, i, text, isNextElementLineDrop); currentParagraphPropsRow = nextPP ? nextPP : currentParagraphPropsRow; parseParagraphAndAddToShapeContent(currentParagraphPropsRow, paragraphPropsCommon, textCShape); currentParagraph = oContent.Content.slice(-1)[0]; // last paragraph } // equal to ApiParagraph.prototype.AddText method let oRun = new ParaRun(currentParagraph, false); let textWithLineDrops = convertVsdxTextToPptxText(text); oRun.AddText(textWithLineDrops); // check character properties: get cp_Type object and in characterPropsCommon get needed Row let characterRowNum = propsCP && propsCP.ix; if (propsCP === null) { characterRowNum = 0; } // setup Run props setRunProps(characterRowNum, characterPropsCommon, oRun, lineUniFill, fillUniFill, theme, shape, visioDocument, pageInfo); currentParagraph.Add_ToContent(currentParagraph.Content.length - 1, oRun); } } else if (textElementPart.kind === AscVisio.c_oVsdxTextKind.FLD) { // text field let oFld = new AscCommonWord.CPresentationField(currentParagraph); let fieldRowNum = textElementPart.ix; let fieldPropsFinal = fieldRowNum !== null && fieldPropsCommon.getRow(fieldRowNum); initPresentationField(oFld, fieldPropsFinal, isTextInherited); let fldTagText = textElementPart.value; if (fldTagText) { fldTagText = convertVsdxTextToPptxText(fldTagText); } oFld.CanAddToContent = true; oFld.AddText(fldTagText, -1); oFld.CanAddToContent = false; // setup Run // check character properties: get cp_Type object and in characterPropsCommon get needed Row let characterRowNum = propsCP && propsCP.ix; if (propsCP === null) { characterRowNum = 0; } setRunProps(characterRowNum, characterPropsCommon, oFld, lineUniFill, fillUniFill, theme, shape, visioDocument, pageInfo); currentParagraph.AddToContent(currentParagraph.Content.length - 1, new ParaRun(currentParagraph, false)); currentParagraph.AddToContent(currentParagraph.Content.length - 1, oFld); currentParagraph.AddToContent(currentParagraph.Content.length - 1, new ParaRun(currentParagraph, false)); } } else if (textElementPart.kind === AscVisio.c_oVsdxTextKind.PP) { // search for pp only after CRLF and in the beginning of text element // currentParagraphPropsRow = textElementPart.ix; // parseParagraphAndAddToShapeContent(currentParagraphPropsRow, paragraphPropsCommon, textCShape); } else if (textElementPart.kind === AscVisio.c_oVsdxTextKind.CP) { propsCP = textElementPart; } else if (textElementPart.kind === AscVisio.c_oVsdxTextKind.TP) { propsTP = textElementPart; } else { AscCommon.consoleLog("unknown type in text tag"); } }); // create defaultParagraph if no strings found if (oContent.Content.length === 0) { // create defaultParagraph parseParagraphAndAddToShapeContent(0, paragraphPropsCommon, textCShape); } // handle horizontal align i. e. defaultParagraph align // handle vertical align let verticalAlignCell = shape.getCell("VerticalAlign"); if (verticalAlignCell) { // 0 - top, 1 - middle, 2 - bottom let verticalAlign = Number(verticalAlignCell.v); if (!isNaN(verticalAlign)) { // 0 - bottom, 1, 2, 3 - ctr, 4, - top // but baseMatrix transformations changes values to // 0 - top, 1, 2, 3 - center, 4 - bottom // NO REVERT NOW TOP IS TOP BOTTOM IS BOTTOM if (verticalAlign === 0) { textCShape.setVerticalAlign(4); // sets text vert align center equal to anchor set to txBody bodyPr } else if (verticalAlign === 2) { textCShape.setVerticalAlign(0); // sets text vert align center equal to anchor set to txBody bodyPr } // else leave center align } else { AscCommon.consoleLog("vertical align cell was not parsed for shape. align set to center. Shape:", shape); } } else { AscCommon.consoleLog("vertical align cell was not found for shape. align set to center. Shape:", shape); } // setup text properties let oTextPr; oTextPr = new CTextPr(); // i dont know why but when i set font size not for overall shape but for runs text shifts to the top // oTextPr.FontSize = nFontSize; // oTextPr.RFonts.Ascii = {Name: "Calibri", Index: -1}; // oTextPr.RFonts.HAnsi = {Name: "Calibri", Index: -1}; // oTextPr.RFonts.CS = {Name: "Calibri", Index: -1}; // oTextPr.RFonts.EastAsia = {Name: "Calibri", Index: -1}; oTextPr.VertAlign = AscCommon.vertalign_Baseline; // apply text properties oContent.SetApplyToAll(true); oContent.AddToParagraph(new ParaTextPr(oTextPr)); // oContent.SetParagraphAlign(AscCommon.align_Center); oContent.SetApplyToAll(false); let oBodyPr = textCShape.getBodyPr().createDuplicate(); oBodyPr.rot = 0; // oBodyPr.spcFirstLastPara = false; // oBodyPr.vertOverflow = AscFormat.nVOTOverflow; // oBodyPr.horzOverflow = AscFormat.nHOTOverflow; // oBodyPr.vert = AscFormat.nVertTThorz; // default //( ( Horizontal )) oBodyPr.wrap = AscFormat.nTWTSquare; // default // oBodyPr.setDefaultInsets(); // oBodyPr.numCol = 1; // oBodyPr.spcCol = 0; // oBodyPr.rtlCol = 0; // oBodyPr.fromWordArt = false; // oBodyPr.anchor = 1; // 4 - bottom, 1,2,3 - center // oBodyPr.anchorCtr = false; // oBodyPr.forceAA = false; // oBodyPr.compatLnSpc = true; // // oBodyPr.prstTxWarp = AscFormat.CreatePrstTxWarpGeometry("textNoShape"); // cShape.bCheckAutoFitFlag = true; // oBodyPr.textFit = new AscFormat.CTextFit(); // oBodyPr.textFit.type = AscFormat.text_fit_Auto; // oBodyPr.upright = false; // default let leftMarginInch = shape.getCellNumberValueWithScale("LeftMargin", 1); let topMarginInch = shape.getCellNumberValueWithScale("TopMargin", 1); let rightMarginInch = shape.getCellNumberValueWithScale("RightMargin", 1); let bottomMarginInch = shape.getCellNumberValueWithScale("BottomMargin", 1); // CHECKS SIGN but positive tIns gives bottom inset. // Check https://disk.yandex.ru/d/IU1vdjzcF9p3IQ , https://disk.yandex.ru/d/0l7elFyX5INcXg // it is may global graphics transform issue so set bottom inset as top and opposite // NO REVERT NOW. TOP IS TOP BOTTOM IS BOTTOM oBodyPr.tIns = topMarginInch * g_dKoef_in_to_mm; oBodyPr.bIns = bottomMarginInch * g_dKoef_in_to_mm; oBodyPr.lIns = leftMarginInch * g_dKoef_in_to_mm; oBodyPr.rIns = rightMarginInch * g_dKoef_in_to_mm; if (bWord) { textCShape.setBodyPr(oBodyPr); } else { textCShape.txBody.setBodyPr(oBodyPr); } // handle cords let shapeAngle = shape.getCellNumberValue("Angle"); let textAngle = shape.getCellNumberValue("TxtAngle"); if (isInvertCoords) { shapeAngle = -shapeAngle; textAngle = -textAngle; } // to rotate around point we 1) add one more offset 2) rotate around center // https://www.figma.com/design/SJSKMY5dGoAvRg75YnHpdX/newRotateScheme?node-id=0-1&node-type=canvas&t=UTtoZyLRItzaQvS9-0 let txtPinX_inch = shape.getCellNumberValueWithScale("TxtPinX", drawingPageScale); let txtPinY_inch = shape.getCellNumberValueWithScale("TxtPinY", drawingPageScale); // consider https://disk.yandex.ru/d/2XzRaPTKzKHFjA // where TxtHeight and TxtWidth get all shape height and width and txtPinX_inch and txtPinY_inch are not defined // also check for {}, undefined, NaN, null let oSpPr = new AscFormat.CSpPr(); let oXfrm = new AscFormat.CXfrm(); let globalXmm = cShape.spPr.xfrm.offX; let globalYmm = cShape.spPr.xfrm.offY; let shapeWidth = shape.getCellNumberValueWithScale("Width", drawingPageScale); let shapeHeight =shape.getCellNumberValueWithScale("Height", drawingPageScale); if (!(isNaN(txtPinX_inch) || txtPinX_inch === null) && !(isNaN(txtPinY_inch) || txtPinY_inch === null)) { // https://www.figma.com/file/WiAC4sxQuJaq65h6xppMYC/cloudFare?type=design&node-id=0%3A1&mode=design&t=SZbio0yIyxq0YnMa-1s let shapeLocPinX = shape.getCellNumberValueWithScale("LocPinX", drawingPageScale); let shapeLocPinY = shape.getCellNumberValueWithScale("LocPinY", drawingPageScale); let txtWidth_inch = shape.getCellNumberValueWithScale("TxtWidth", drawingPageScale); let txtHeight_inch = shape.getCellNumberValueWithScale("TxtHeight", drawingPageScale); let txtLocPinX_inch = shape.getCellNumberValueWithScale("TxtLocPinX", drawingPageScale); let txtLocPinY_inch = shape.getCellNumberValueWithScale("TxtLocPinY", drawingPageScale); // defaultParagraph.Pr.SetJc(AscCommon.align_Left); let oBodyPr = textCShape.getBodyPr().createDuplicate(); // oBodyPr.anchor = 4; // 4 - bottom, 1,2,3 - center let localXmm = (txtPinX_inch - txtLocPinX_inch) * g_dKoef_in_to_mm; // back to MS coords if (isInvertCoords) { let topLeftCornerYNewCoords = maxHeightScaledIn * g_dKoef_in_to_mm - globalYmm; // now it is bottom left corner y coord globalYmm = topLeftCornerYNewCoords - cShape.spPr.xfrm.extY; } let localYmm; // Don't recalculate text pos on flip manually, shape with text combined to group whose flipV is applied // let flipYCell = shape.getCell("FlipY"); // let flipVertically = flipYCell ? flipYCell.v === "1" : false; // if (flipVertically) { // // if we flip figure we flip text pinY around shape pinY // if (txtPinY_inch > 0) { // // y cord of text block start. when cord system starts in left bottom corner on shape // let blockCord = txtPinY_inch - txtLocPinY_inch; // // (y part of vector) from shape center to txt block start // let fromShapeCenterToBlockStart = blockCord - shapeLocPinY; // // // mirror distance fromBlock start ToShapeCenter then add text block height to it // // + shapeLocPinY made shift from shape center to shape bottom bcs we calculate // // localYmm starting from bottom of shape not from center // localYmm = (-fromShapeCenterToBlockStart - txtHeight_inch + shapeLocPinY) * g_dKoef_in_to_mm; // } else { // // negative, y part of vector. y cord of text block start. when cord system starts in left bottom corner on shape // let blockCord = txtPinY_inch + (txtHeight_inch - txtLocPinY_inch); // // // lets make it negative like y part of vector. It comes from top to bottom. // // It is vector that comes from shape center to text block start. // let fromBlockToShapeCenter = blockCord - shapeLocPinY; // // // Finally we mirror fromBlockToShapeCenter by multiplying by -1 and add shapeLocPinY to move its // // start to bottom on shape // localYmm = (-fromBlockToShapeCenter + shapeLocPinY) * g_dKoef_in_to_mm; // } // oXfrm.setRot(- textAngle); // } else { // // do calculations // localYmm = (txtPinY_inch - txtLocPinY_inch) * g_dKoef_in_to_mm; // oXfrm.setRot(textAngle); // } // do calculations localYmm = (txtPinY_inch - txtLocPinY_inch) * g_dKoef_in_to_mm; oXfrm.setRot(textAngle); let offY = globalYmm + localYmm; // back to presentation coords if (isInvertCoords) { let bottomCornerOffY = maxHeightScaledIn * g_dKoef_in_to_mm - offY; let topCornerOffY = bottomCornerOffY - txtHeight_inch * g_dKoef_in_to_mm; offY = topCornerOffY; } oXfrm.setOffX(globalXmm + localXmm); // mm oXfrm.setOffY(shapeHeight < 0 ? offY + 2 * shapeHeight * g_dKoef_in_to_mm : offY); oXfrm.setExtX(txtWidth_inch * g_dKoef_in_to_mm); oXfrm.setExtY(txtHeight_inch * g_dKoef_in_to_mm); } else { // create text block with shape sizes oXfrm.setOffX(globalXmm); oXfrm.setOffY(shapeHeight < 0 ? globalYmm + 2 * shapeHeight * g_dKoef_in_to_mm : globalYmm); oXfrm.setExtX(Math.abs(shapeWidth) * g_dKoef_in_to_mm); oXfrm.setExtY(Math.abs(shapeHeight) * g_dKoef_in_to_mm); oXfrm.setRot(0); } oSpPr.setXfrm(oXfrm); oXfrm.setParent(oSpPr); oSpPr.setFill(AscFormat.CreateNoFillUniFill()); oSpPr.setLn(AscFormat.CreateNoFillLine()); textCShape.setSpPr(oSpPr); oSpPr.setParent(textCShape); // just trash below // // // placeholder // let oUniNvPr = new AscFormat.UniNvPr(); // oUniNvPr.nvPr.ph = Asc.VisioEditorApi.prototype.CreatePlaceholder("object"); // // // cShape.txBody.content2 = cShape.txBody.content; // // cShape.setNvSpPr(oUniNvPr); // copy settings from presentations debug // cShape.txBody.content.CurPos.TableMove = 1; // cShape.txBody.content.ReindexStartPos = 0; // cShape.txBody.content.Content[0].Content[1].CollPrChangeMine = false; // cShape.txBody.content.Content[0].Content[1].State.ContentPos = 10; // cShape.txBody.content.Content[0].Index = -1; // cShape.txBody.compiledBodyPr = null; // Set Paragraph (the only one defaultParagraph exist) justify/align text - center // textCShape.txBody.content.Content[0].Pr.SetJc(AscCommon.align_Left); // use ParaRun.prototype.Set_Color // cShape.txBody.content.Content[0].Content[1].Pr.Color = TextColor1; // cShape.txBody.content.Content[0].Content[0].Pr.Color = TextColor1; return textCShape; } /** * endArrow can be beginning or ending * @param {string} arrowType * @param {number} arrowSize * @return {AscFormat.EndArrow} endArrowObject */ function getEndArrow(arrowType, arrowSize) { // 2.4.4.20 BeginArrow in MS-VSDX and 20.1.10.33 ST_LineEndType (Line End Type) in ECMA let endArrow = new AscFormat.EndArrow(); switch (arrowType) { case "0": endArrow.type = AscFormat.LineEndType.vsdxNone; break; case "1": endArrow.type = AscFormat.LineEndType.vsdxOpen90Arrow; break; case "2": endArrow.type = AscFormat.LineEndType.vsdxFilled90Arrow; break; case "3": endArrow.type = AscFormat.LineEndType.vsdxOpenSharpArrow; break; case "4": endArrow.type = AscFormat.LineEndType.vsdxFilledSharpArrow; break; case "5": endArrow.type = AscFormat.LineEndType.vsdxIndentedFilledArrow; break; case "6": endArrow.type = AscFormat.LineEndType.vsdxOutdentedFilledArrow; break; case "7": endArrow.type = AscFormat.LineEndType.vsdxOpenFletch; break; case "8": endArrow.type = AscFormat.LineEndType.vsdxFilledFletch; break; case "9": endArrow.type = AscFormat.LineEndType.vsdxDimensionLine; break; case "10": endArrow.type = AscFormat.LineEndType.vsdxFilledDot; break; case "11": endArrow.type = AscFormat.LineEndType.vsdxFilledSquare; break; case "12": endArrow.type = AscFormat.LineEndType.vsdxOpenASMEArrow; break; case "13": endArrow.type = AscFormat.LineEndType.vsdxFilledASMEArrow; break; case "14": endArrow.type = AscFormat.LineEndType.vsdxClosedASMEArrow; break; case "15": endArrow.type = AscFormat.LineEndType.vsdxClosed90Arrow; break; case "16": endArrow.type = AscFormat.LineEndType.vsdxClosedSharpArrow; break; case "17": endArrow.type = AscFormat.LineEndType.vsdxIndentedClosedArrow; break; case "18": endArrow.type = AscFormat.LineEndType.vsdxOutdentedClosedArrow; break; case "19": endArrow.type = AscFormat.LineEndType.vsdxClosedFletch; break; case "20": endArrow.type = AscFormat.LineEndType.vsdxClosedDot; break; case "21": endArrow.type = AscFormat.LineEndType.vsdxClosedSquare; break; case "22": endArrow.type = AscFormat.LineEndType.vsdxDiamond; break; case "23": endArrow.type = AscFormat.LineEndType.vsdxBackslash; break; case "24": endArrow.type = AscFormat.LineEndType.vsdxOpenOneDash; break; case "25": endArrow.type = AscFormat.LineEndType.vsdxOpenTwoDash; break; case "26": endArrow.type = AscFormat.LineEndType.vsdxOpenThreeDash; break; case "27": endArrow.type = AscFormat.LineEndType.vsdxFork; break; case "28": endArrow.type = AscFormat.LineEndType.vsdxDashFork; break; case "29": endArrow.type = AscFormat.LineEndType.vsdxClosedFork; break; case "30": endArrow.type = AscFormat.LineEndType.vsdxClosedPlus; break; case "31": endArrow.type = AscFormat.LineEndType.vsdxClosedOneDash; break; case "32": endArrow.type = AscFormat.LineEndType.vsdxClosedTwoDash; break; case "33": endArrow.type = AscFormat.LineEndType.vsdxClosedThreeDash; break; case "34": endArrow.type = AscFormat.LineEndType.vsdxClosedDiamond; break; case "35": endArrow.type = AscFormat.LineEndType.vsdxFilledOneDash; break; case "36": endArrow.type = AscFormat.LineEndType.vsdxFilledTwoDash; break; case "37": endArrow.type = AscFormat.LineEndType.vsdxFilledThreeDash; break; case "38": endArrow.type = AscFormat.LineEndType.vsdxFilledDiamond; break; case "39": endArrow.type = AscFormat.LineEndType.vsdxFilledDoubleArrow; break; case "40": endArrow.type = AscFormat.LineEndType.vsdxClosedDoubleArrow; break; case "41": endArrow.type = AscFormat.LineEndType.vsdxClosedNoDash; break; case "42": endArrow.type = AscFormat.LineEndType.vsdxFilledNoDash; break; case "43": endArrow.type = AscFormat.LineEndType.vsdxOpenDoubleArrow; break; case "44": endArrow.type = AscFormat.LineEndType.vsdxOpenArrowSingleDash; break; case "45": endArrow.type = AscFormat.LineEndType.vsdxOpenDoubleArrowSingleDash; break; case "Themed": endArrow.type = AscFormat.LineEndType.vsdxNone; break; case !isNaN(Number(arrowType)) && arrowType: // is unhandled number endArrow.type = AscFormat.LineEndType.vsdxOpen90Arrow; break; default: endArrow.type = AscFormat.LineEndType.vsdxNone; } if (arrowSize >= 0 && arrowSize <= 6 ) { let sizeEnumVsdxShift = 3; // see AscFormat.LineEndSize endArrow.len = arrowSize + sizeEnumVsdxShift; endArrow.w = arrowSize + sizeEnumVsdxShift; } else { AscCommon.consoleLog("arrowSize unknown:", arrowSize); endArrow.len = AscFormat.LineEndSize.vsdxMedium; endArrow.w = AscFormat.LineEndSize.vsdxMedium; } return endArrow; } function createEmptyShape() { let emptyCShape = new AscFormat.CShape(); emptyCShape.setWordShape(false); emptyCShape.setBDeleted(false); var oSpPr = new AscFormat.CSpPr(); var oXfrm = new AscFormat.CXfrm(); oSpPr.setXfrm(oXfrm); oXfrm.setParent(oSpPr); emptyCShape.setSpPr(oSpPr); oSpPr.setParent(emptyCShape); emptyCShape.setParent2(visioDocument); return emptyCShape; } /** * @param {Shape_Type} shape * @param {Page_Type} pageInfo * @param {CVisioDocument} visioDocument * @return {number} line width EMUs */ function getLineWidth(shape, pageInfo, visioDocument) { let lineWidthEmu = null; let lineWeightCell = shape.getCell("LineWeight"); if (lineWeightCell) { // to cell.v visio always saves inches // let lineWeightInches = Number(lineWeightCell.v); let lineWeightInches = lineWeightCell.calculateValue(shape, pageInfo, visioDocument.themes); if (!isNaN(lineWeightInches)) { lineWidthEmu = lineWeightInches * AscCommonWord.g_dKoef_in_to_mm * AscCommonWord.g_dKoef_mm_to_emu; } else { AscCommon.consoleLog("caught unknown error. line will be painted 9525 emus"); // 9255 emus = 0.01041666666666667 inches is document.xml StyleSheet ID=0 LineWeight e. g. default value lineWidthEmu = 9525; } } else { AscCommon.consoleLog("LineWeight cell was not calculated. line will be painted 9525 emus"); lineWidthEmu = 9525; } return lineWidthEmu; } /** * @param {Shape_Type} shape * @param {Page_Type} pageInfo * @param {CVisioDocument} visioDocument * @return {number} cap type */ function getLineCap(shape, pageInfo, visioDocument) { let lineCapCell = shape.getCell("LineCap"); let lineCapNumber; if (lineCapCell) { lineCapNumber = lineCapCell.calculateValue(shape, pageInfo, visioDocument.themes); if (isNaN(lineCapNumber)) { lineCapNumber = 2; } } else { lineCapNumber = 2; } return lineCapNumber; } /** * Calculate line pattern. See some issues inside. * @param {Shape_Type} shape * @param {Page_Type} pageInfo * @param {CVisioDocument} visioDocument * @param {number} lineCap * @return {number} cap type */ function getPresetDash(shape, pageInfo, visioDocument, lineCap) { let linePattern = shape.getCell("LinePattern"); let prstDash; if (linePattern) { // see ECMA-376-1 - L.4.8.5.2 Line Dash Properties and [MS-VSDX]-220215 (1) - 2.4.4.180 LinePattern let linePatternNumber = linePattern.calculateValue(shape, pageInfo, visioDocument.themes); if (isNaN(linePatternNumber)) { prstDash = AscFormat.CLn.prototype.GetDashCode("vsdxSolid"); } else { const shift = 11; const visioLineCode = linePatternNumber + shift; let dashTypeName = AscFormat.CLn.prototype.GetDashByCode(visioLineCode); if (dashTypeName !== null) { if (lineCap !== 2) { AscCommon.consoleLog("linePattern may be wrong. Because visio cap is not square" + "Now only flat cap is supported in sdkjs but Line patterns were made " + "for visio cap square looks correct" + "So when visio cap is not square line pattern will not fit." ) if ("vsdxHalfHalfDash" === dashTypeName) { // vsdxHalfHalfDash looks like solid on visio cap square but if cap is not square in visio // vsdxHalfHalfDash should be dotted // set 10th visio pattern return 10 + shift; } } prstDash = visioLineCode; } else { prstDash = AscFormat.CLn.prototype.GetDashCode("vsdxDash"); } } } else { prstDash = AscFormat.CLn.prototype.GetDashCode("vsdxSolid"); } return prstDash; } } /** * converts !Shape TypeGroup! To CGroupShape Recursively. * Shape can only have subshapes if its Type='Group'. * Can be called on shapes Type not 'Group' then shape just adds to currentGroupHandling * @memberOf Shape_Type * @param {CVisioDocument} visioDocument * @param {Page_Type} pageInfo * @param {Number} drawingPageScale * @param {CGroupShape?} currentGroupHandling * @param {number[]?} delMasterShapes - shapes with MasterShape included in array will not be converted * @return {CGroupShape | undefined} */ Shape_Type.prototype.convertGroup = function (visioDocument, pageInfo, drawingPageScale, currentGroupHandling, delMasterShapes) { // if we need to create CGroupShape create CShape first then copy its properties to CGroupShape object // so anyway create CShapes let cShapeOrCGroupShape = this.convertShape(visioDocument, pageInfo, drawingPageScale, currentGroupHandling); // handle ShapeShdwShow to hide shadow if shape has ShapeShdwShow 1 and shape is in group let shapeShdwShowCell = this.getCell("ShapeShdwShow"); let shapeShdwShow = shapeShdwShowCell && shapeShdwShowCell.calculateValue(this, pageInfo, visioDocument.themes); if (shapeShdwShow === 1 && currentGroupHandling) { let shape; if (cShapeOrCGroupShape.getObjectType() === AscDFH.historyitem_type_GroupShape) { shape = cShapeOrCGroupShape.spTree[0]; } else { shape = cShapeOrCGroupShape; } if (shape && shape.spPr.effectProps && shape.spPr.effectProps.EffectLst && shape.spPr.effectProps.EffectLst.outerShdw) { // hide shadow // TODO dont delete shadow object but create ShapeShdwShow property for CShape to handle it on draw shape.spPr.effectProps.EffectLst.outerShdw = null; shape.spPr.effectProps.EffectLst.innerShdw = null; shape.spPr.effectProps.EffectLst.prstShdw = null; } } // if it is group in vsdx if (this.type === AscVisio.SHAPE_TYPES_GROUP) { // CGroupShape cant support text. So cShape will represent everything related to Shape Type="Group". // Let's push cShape into CGroupShape object. if (cShapeOrCGroupShape) { let groupShape = new AscFormat.CGroupShape(); // this.graphicObjectsController = new AscFormat.DrawingObjectsController(); // let groupShape = AscFormat.builder_CreateGroup(); groupShape.setLocks(0); groupShape.setBDeleted(false); // Create CGroupShape with SpPr from cShape but with no fill and line let noLineFillSpPr = cShapeOrCGroupShape.spPr.createDuplicate(); noLineFillSpPr.setFill(AscFormat.CreateNoFillUniFill()); noLineFillSpPr.setLn(AscFormat.CreateNoFillLine()); groupShape.setSpPr(noLineFillSpPr); groupShape.spPr.setParent(groupShape); // these props came to group cShapeOrCGroupShape.spPr.xfrm.rot = 0; cShapeOrCGroupShape.spPr.xfrm.flipH = false; cShapeOrCGroupShape.spPr.xfrm.flipV = false; cShapeOrCGroupShape.spPr.xfrm.setOffX(0); cShapeOrCGroupShape.spPr.xfrm.setOffY(0); // cShapeOrCGroupShape.setLocks(1)?; groupShape.brush = cShapeOrCGroupShape.brush; groupShape.bounds = cShapeOrCGroupShape.bounds; groupShape.localTransform = cShapeOrCGroupShape.localTransform; groupShape.pen = cShapeOrCGroupShape.pen; groupShape.Id = cShapeOrCGroupShape.Id + "_Group"; groupShape.setParent2(visioDocument); // if DisplayMode is 1 add group geometry to bottom layer if (this.getCellNumberValue("DisplayMode") === 1) { // if it is group so there is geometry and text in it. We take geometry if (cShapeOrCGroupShape instanceof CGroupShape) { groupShape.addToSpTree(groupShape.spTree.length, cShapeOrCGroupShape.spTree[0]); } else { groupShape.addToSpTree(groupShape.spTree.length, cShapeOrCGroupShape); } groupShape.spTree[groupShape.spTree.length - 1].setGroup(groupShape); } // handle sub-shapes let subShapes = this.getSubshapes(); /** * see bug for Del attribute handle: https://bugzilla.onlyoffice.com/show_bug.cgi?id=76050 * let's collect dels first and then traverse through all the group again in groupShape.deleteShapes(). * Dels appear rarely so it is ok. * @type {number[]} */ let delMasterShapesCurrent = []; for (let i = 0; i < subShapes.length; i++) { const subShape = subShapes[i]; if (subShape.del && subShape.masterShape !== null) { delMasterShapesCurrent.push(subShape.masterShape); } } if (delMasterShapes === undefined) { delMasterShapes = delMasterShapesCurrent; } else { delMasterShapes = delMasterShapes.concat(delMasterShapesCurrent); } for (let i = 0; i < subShapes.length; i++) { const subShape = subShapes[i]; // if group - remove // if shape and fully inherited - remove // if shape/group is not inherited check for masterShape // if shape/group is inherited check for id // TODO Optimization: use Set() because has() is faster than includes() // TODO try changing shape Del='1' IDs and see what happens const delIdCheck = this.inheritedShapes.includes(subShape) && delMasterShapes.includes(subShape.id) || delMasterShapes.includes(subShape.masterShape); const isDeleted = delIdCheck && (subShape.type === AscVisio.SHAPE_TYPES_GROUP || subShape.type === AscVisio.SHAPE_TYPES_SHAPE && this.inheritedShapes.includes(subShape)) if (!isDeleted) { subShape.convertGroup(visioDocument, pageInfo, drawingPageScale, groupShape, delMasterShapes); } } // if group geometry should be on the top layer if (this.getCellNumberValue("DisplayMode") === 2) { if (cShapeOrCGroupShape instanceof CGroupShape) { // if it is group so there is geometry and text in it. We take geometry groupShape.addToSpTree(groupShape.spTree.length, cShapeOrCGroupShape.spTree[0]); } else { groupShape.addToSpTree(groupShape.spTree.length, cShapeOrCGroupShape); } groupShape.spTree[groupShape.spTree.length - 1].setGroup(groupShape); } // add group text to top if (cShapeOrCGroupShape instanceof CGroupShape) { groupShape.addToSpTree(groupShape.spTree.length, cShapeOrCGroupShape.spTree[1]); groupShape.spTree[groupShape.spTree.length - 1].setGroup(groupShape); } if (currentGroupHandling) { // insert group to currentGroupHandling currentGroupHandling.addToSpTree(currentGroupHandling.spTree.length, groupShape); currentGroupHandling.spTree[currentGroupHandling.spTree.length - 1].setGroup(currentGroupHandling); } return groupShape; } } else { // if read cShape not CGroupShape if (!currentGroupHandling) { throw new Error("Group handler was called on simple shape"); } else { // add shape and text (shapeAndTextGroup or shape) to currentGroupHandling if (cShapeOrCGroupShape) { currentGroupHandling.addToSpTree(currentGroupHandling.spTree.length, cShapeOrCGroupShape); currentGroupHandling.spTree[currentGroupHandling.spTree.length-1].setGroup(currentGroupHandling); } } } } /** * @memberOf Shape_Type * @param {{x_mm, y_mm, w_mm, h_mm, rot, oFill, oStroke, flipHorizontally, flipVertically, cVisioDocument, * drawingPageScale, isInvertCoords, isShapeDeleted, id}} paramsObj * @return {CShape} CShape */ Shape_Type.prototype.convertToCShapeUsingParamsObj = function(paramsObj) { let x = paramsObj.x_mm; let y = paramsObj.y_mm; let w_mm = paramsObj.w_mm; let h_mm = paramsObj.h_mm; let rot = paramsObj.rot; let oFill = paramsObj.oFill; let oStroke = paramsObj.oStroke; let cVisioDocument = paramsObj.cVisioDocument; let flipHorizontally = paramsObj.flipHorizontally; let flipVertically = paramsObj.flipVertically; let drawingPageScale = paramsObj.drawingPageScale; let isInvertCoords = paramsObj.isInvertCoords; let isShapeDeleted = paramsObj.isShapeDeleted; let id = paramsObj.id; let shapeGeom = AscVisio.getGeometryFromShape(this, drawingPageScale, isInvertCoords); let sType = "rect"; let nWidth_mm = w_mm; let nHeight_mm = h_mm; //let oDrawingDocument = new AscCommon.CDrawingDocument(); let shape = AscFormat.builder_CreateShape(sType, nWidth_mm, nHeight_mm, oFill, oStroke, cVisioDocument, cVisioDocument.themes[0], null, false); shape.spPr.xfrm.setOffX(x); shape.spPr.xfrm.setOffY(y); shape.spPr.xfrm.setRot(rot); shape.spPr.xfrm.setFlipH(flipHorizontally); shape.spPr.xfrm.setFlipV(flipVertically); shape.spPr.setGeometry(shapeGeom); if (isShapeDeleted) { shape.setBDeleted(true); } shape.Id = String(id); // it was string in cShape return shape; }; //-------------------------------------------------------------export------------------------------------------------- window['Asc'] = window['Asc'] || {}; window['AscCommon'] = window['AscCommon'] || {}; window['AscCommonWord'] = window['AscCommonWord'] || {}; window['AscCommonSlide'] = window['AscCommonSlide'] || {}; window['AscCommonExcel'] = window['AscCommonExcel'] || {}; window['AscVisio'] = window['AscVisio'] || {}; window['AscFormat'] = window['AscFormat'] || {}; window['AscWord'] = window['AscWord'] || {}; })(window, window.document);