/*
* (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);