Files
Yajbir Singh f1b860b25c
Some checks failed
check / markdownlint (push) Has been cancelled
check / spellchecker (push) Has been cancelled
updated
2025-12-11 19:03:17 +05:30

2964 lines
114 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* (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
// <cp IX='0'/> or
// <tp IX='0'/>
// 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);