@kieler/klighd-core
Version:
Core KLighD diagram visualization with Sprotty
761 lines • 68.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getJunctionPointRenderings = exports.renderKRendering = exports.getRendering = exports.renderSingleSVGLine = exports.renderSVGLine = exports.renderSingleSVGEllipse = exports.renderSVGEllipse = exports.renderSingleSVGArc = exports.renderSVGArc = exports.renderSingleSVGUse = exports.renderSingleSVGImage = exports.renderSVGUse = exports.renderSVGImage = exports.renderSVGImageDef = exports.renderSingleSVGRect = exports.renderSVGRect = exports.renderWithShadow = exports.renderError = exports.renderChildRenderings = exports.renderKText = exports.renderLine = exports.renderRectangularShape = exports.renderChildArea = void 0;
const sprotty_1 = require("sprotty"); // eslint-disable-line @typescript-eslint/no-unused-vars
const depth_map_1 = require("./depth-map");
const render_options_registry_1 = require("./options/render-options-registry");
const skgraph_models_1 = require("./skgraph-models");
const skgraph_utils_1 = require("./skgraph-utils");
const views_common_1 = require("./views-common");
const views_styles_1 = require("./views-styles");
/* global sessionStorage */
// ----------------------------- Functions for rendering different KRendering as VNodes in svg --------------------------------------------
/**
* Translates a KChildArea rendering into an SVG rendering.
* @param rendering The rendering.
* @param parent The parent element.
* @param propagatedStyles The styles propagated from parent elements that should be taken into account.
* @param context The rendering context for this element.
*/
function renderChildArea(rendering, parent, boundsAndTransformation, context) {
// Sprotty expects the graph elements to always be relative to the parent element, while KLighD usually has the graph elements relative to the child area.
// Here we expect the graph elements to behave as Sprotty expects, thus requiring to reverse offset the transformation towards this child area again.
// First, we need to find the total transformations that were applied to the child area.
const totalTansformation = [...context.titleStorage.getTransformations()];
// Second, we need to find the transformation only applicable to the current child area.
const childAreaTransformation = boundsAndTransformation.transformation;
// Finally, we need to reverse the translation that was applied to every element hierarchially above of the child area.
// Note that this causes a little difference in what the coordinates are relative to the parent graph element, as entire child area rotations that are possible in KLighD are
// not possible in Sprotty.
totalTansformation.splice(totalTansformation.length - childAreaTransformation.length, childAreaTransformation.length);
const reverseTranslation = (0, views_common_1.reverseTransformations)(totalTansformation.filter((transformation) => (0, views_common_1.isTranslation)(transformation)));
const gAttrs = Object.assign({}, (reverseTranslation.length !== 0
? { transform: reverseTranslation.map(views_common_1.transformationToSVGString).join('') }
: {}));
if (parent.areChildAreaChildrenRendered) {
console.error('This element contains multiple child areas, skipping this one.');
return (0, sprotty_1.svg)("g", null);
}
// remember, that this parent's children are now already rendered
parent.areChildAreaChildrenRendered = true;
const element = ((0, sprotty_1.svg)("g", Object.assign({}, gAttrs, { id: rendering.properties['klighd.lsp.rendering.id'] }), context.renderChildAreaChildren(parent)));
// get scale factor and apply to child area
if (parent.properties === undefined ||
parent.properties['org.eclipse.elk.topdown.scaleFactor'] === undefined) {
return element;
}
const topdownScaleFactor = parent.properties['org.eclipse.elk.topdown.scaleFactor'];
return (0, sprotty_1.svg)("g", { transform: `scale (${topdownScaleFactor})` },
"$",
element);
}
exports.renderChildArea = renderChildArea;
/**
* Translates a rectangular rendering into an SVG rendering.
* This includes KEllipse, KRectangle and KRoundedRectangle.
* @param rendering The rendering.
* @param parent The parent element.
* @param propagatedStyles The styles propagated from parent elements that should be taken into account.
* @param context The rendering context for this element.
* @param childOfNodeTitle If this rendering is a child of a node title. May override special renderings
*/
function renderRectangularShape(rendering, parent, boundsAndTransformation, styles, stylesToPropagate, context, childOfNodeTitle) {
var _a, _b;
const gAttrs = Object.assign({}, (boundsAndTransformation.transformation.length !== 0
? { transform: boundsAndTransformation.transformation.map(views_common_1.transformationToSVGString).join('') }
: {}));
// Check the invisibility first. If this rendering is supposed to be invisible, do not render it,
// only render its children transformed by the transformation already calculated.
if ((0, views_styles_1.isInvisible)(styles)) {
return ((0, sprotty_1.svg)("g", Object.assign({}, gAttrs), renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle)));
}
// Default case. Calculate all svg objects and attributes needed to build this rendering from the styles and the rendering.
const colorStyles = (0, views_styles_1.getSvgColorStyles)(styles, context, parent);
// objects rendered here that have no background should get a invisible, but clickable background so that users do not click through the non-available background.
if (colorStyles.background === views_styles_1.DEFAULT_FILL) {
colorStyles.background = views_styles_1.DEFAULT_CLICKABLE_FILL;
}
const paperShadows = context.renderOptionsRegistry.getValueOrDefault(render_options_registry_1.Shadows) === render_options_registry_1.ShadowOption.PAPER_MODE;
const shadowStyles = paperShadows ? (0, views_styles_1.getSvgShadowStyles)(styles, context) : undefined;
const lineStyles = (0, views_styles_1.getSvgLineStyles)(styles, parent, context);
const lineWidth = (_b = (_a = styles.kLineWidth) === null || _a === void 0 ? void 0 : _a.lineWidth) !== null && _b !== void 0 ? _b : views_styles_1.DEFAULT_LINE_WIDTH;
// Create the svg element for this rendering.
let element;
switch (rendering.type) {
case skgraph_models_1.K_ARC: {
const kArcRendering = rendering;
let sweepFlag = 0;
let angle = kArcRendering.arcAngle;
// For a negative angle, rotate the other way around.
if (angle < 0) {
angle = -angle;
sweepFlag = 1;
}
// If the angle is bigger than or equal to 360 degrees, use the same rendering as a KEllipse via fallthrough to that rendering instead.
if (angle < 360) {
// Calculation to get the start and endpoint of the arc from the angles given.
// Reduce the width and height by half the linewidth on both sides, so the ellipse really stays within the given bounds.
const width = boundsAndTransformation.bounds.width - lineWidth;
const height = boundsAndTransformation.bounds.height - lineWidth;
const rX = width / 2;
const rY = height / 2;
const midX = rX + lineWidth / 2;
const midY = rY + lineWidth / 2;
const startX = midX + rX * Math.cos((kArcRendering.startAngle * Math.PI) / 180);
const startY = midY - rY * Math.sin((kArcRendering.startAngle * Math.PI) / 180);
const endAngle = kArcRendering.startAngle + kArcRendering.arcAngle;
const endX = midX + rX * Math.cos((endAngle * Math.PI) / 180);
const endY = midY - rY * Math.sin((endAngle * Math.PI) / 180);
// If the angle is bigger or equal 180 degrees, use the large arc as of the w3c path specification
// https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
const largeArcFlag = angle >= 180 ? 1 : 0;
// Rotation is not handled via KArcs but via KRotations, so leave this value as 0.
const rotate = 0;
// The main arc.
let d = `M${startX},${startY}A${rX},${rY},${rotate},${largeArcFlag},${sweepFlag},${endX},${endY}`;
switch (kArcRendering.arcType) {
case skgraph_models_1.Arc.OPEN: {
// Open chords do not have any additional lines.
break;
}
case skgraph_models_1.Arc.CHORD: {
// Add a straight line from the end to the beginning point.
d += `L${startX},${startY}`;
break;
}
case skgraph_models_1.Arc.PIE: {
// Add a straight line from the end to the center and then back to the beginning point.
d += `L${midX},${midY}L${startX},${startY}`;
break;
}
default: {
console.error('error in views-rendering.tsx, unexpected Arc in switch');
}
}
element = ((0, sprotty_1.svg)("g", Object.assign({ id: rendering.properties['klighd.lsp.rendering.id'] }, gAttrs),
...renderSVGArc(lineStyles, colorStyles, shadowStyles, d, styles.kShadow),
renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle)));
break;
}
else {
// Fallthrough to KEllipse case.
}
}
// eslint-disable-next-line
case skgraph_models_1.K_ELLIPSE: {
element = ((0, sprotty_1.svg)("g", Object.assign({ id: rendering.properties['klighd.lsp.rendering.id'] }, gAttrs),
...renderSVGEllipse(boundsAndTransformation.bounds, lineWidth, lineStyles, colorStyles, shadowStyles, styles.kShadow),
renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle)));
break;
}
case skgraph_models_1.K_RECTANGLE:
case skgraph_models_1.K_ROUNDED_RECTANGLE: {
// like this the rx and ry will be undefined during the rendering of a roundedRectangle and therefore those fields will be left out.
// Rounded rectangles work in svg just like regular rectangles just with those two added variables, so this call will result in a regular rectangle.
// Rendering-specific attributes
const rx = rendering.cornerWidth;
const ry = rendering.cornerHeight;
element = ((0, sprotty_1.svg)("g", Object.assign({ id: rendering.properties['klighd.lsp.rendering.id'] }, gAttrs),
...renderSVGRect(boundsAndTransformation.bounds, lineWidth, rx, ry, lineStyles, colorStyles, shadowStyles, styles.kShadow),
renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle)));
break;
}
case skgraph_models_1.K_IMAGE: {
const kImage = rendering;
const { clipShape } = kImage;
const id = rendering.properties['klighd.lsp.rendering.id'];
const clipId = `${id}$clip`;
let href;
if (kImage.bundleName === 'URI') {
// Bundle name "URI" is a special handling to interpret the imagePath as a URI.
// Here, we just add that URI to the SVG, expecting that it will be available.
// Note, that this does mean the URI has to be available whereever the SVG will be opened, even after saving.
// An alternative here would be to download and cache that image in code and include it as an embedded base64 data URI instead.
href = kImage.imagePath;
}
else {
// Other images have been cached in session storage and can be embedded in the top-level defs and referenced directly.
const fullImagePath = `${rendering.bundleName}:${rendering.imagePath}`;
const imageId = `image$${fullImagePath}`;
href = `#${imageId}`;
// Remember the shadow definition to be added at the top level of the SVG, if the same shadow has not been defined previously.
if (!context.renderingDefs.has(imageId)) {
const extension = fullImagePath.slice(fullImagePath.lastIndexOf('.') + 1);
const imageDataURI = `data:image/${extension};base64,${sessionStorage.getItem(fullImagePath)}`;
context.renderingDefs.set(imageId, renderSVGImageDef(imageId, imageDataURI));
}
}
let clipPath;
// Render the clip shape within an SVG clipPath element to be used as a clipping mask for the image.
if (clipShape !== undefined) {
clipShape.isClipRendering = true;
const outerClipShape = renderKRendering(clipShape, parent, stylesToPropagate, context, childOfNodeTitle);
// renderings start with an outermost <g> element. If that is the case, remove that element and use its child instead.
if ((outerClipShape === null || outerClipShape === void 0 ? void 0 : outerClipShape.sel) === 'g' && (outerClipShape === null || outerClipShape === void 0 ? void 0 : outerClipShape.children) !== undefined) {
clipPath = (0, sprotty_1.svg)("clipPath", { id: clipId }, outerClipShape === null || outerClipShape === void 0 ? void 0 : outerClipShape.children[0]);
}
gAttrs.style = {
clipPath: `url(#${clipId})`,
};
}
// Render the image.
let imageRenderings;
if (kImage.bundleName === 'URI') {
imageRenderings = renderSVGImage(boundsAndTransformation.bounds, shadowStyles, href, styles.kShadow);
}
else {
imageRenderings = renderSVGUse(boundsAndTransformation.bounds, shadowStyles, href, styles.kShadow);
}
element = ((0, sprotty_1.svg)("g", Object.assign({ id: id }, gAttrs),
...clipPath ? [clipPath] : [],
...imageRenderings));
break;
}
default: {
// This case can never happen. If it still does, happy debugging!
throw new Error('Rendering is neither an KArc, KEllipse, KImage, nor a KRectangle or KRoundedRectangle!');
}
}
return element;
}
exports.renderRectangularShape = renderRectangularShape;
/**
* Translates a line rendering into an SVG rendering.
* This includes all subclasses of and the KPolyline rendering itself.
* @param rendering The rendering.
* @param parent The parent element.
* @param propagatedStyles The styles propagated from parent elements that should be taken into account.
* @param context The rendering context for this element.
* @param childOfNodeTitle If this rendering is a child of a node title. May override special renderings
*/
function renderLine(rendering, parent, boundsAndTransformation, styles, stylesToPropagate, context, childOfNodeTitle) {
const gAttrs = Object.assign({}, (boundsAndTransformation.transformation.length !== 0
? { transform: boundsAndTransformation.transformation.map(views_common_1.transformationToSVGString).join('') }
: {}));
// Check the invisibility first. If this rendering is supposed to be invisible, do not render it,
// only render its children transformed by the transformation already calculated.
if ((0, views_styles_1.isInvisible)(styles)) {
return ((0, sprotty_1.svg)("g", Object.assign({}, gAttrs), renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle)));
}
// Default case. Calculate all svg objects and attributes needed to build this rendering from the styles and the rendering.
const colorStyles = (0, views_styles_1.getSvgColorStyles)(styles, context, parent);
// Any non-closed line segment cannot be filled with any color.
if (rendering.type !== skgraph_models_1.K_POLYGON) {
colorStyles.background = views_styles_1.DEFAULT_FILL;
}
const paperShadows = context.renderOptionsRegistry.getValueOrDefault(render_options_registry_1.Shadows) === render_options_registry_1.ShadowOption.PAPER_MODE;
const shadowStyles = paperShadows ? (0, views_styles_1.getSvgShadowStyles)(styles, context) : undefined;
const lineStyles = (0, views_styles_1.getSvgLineStyles)(styles, parent, context);
const points = (0, views_common_1.getPoints)(parent, rendering, boundsAndTransformation);
if (points.length === 0) {
return (0, sprotty_1.svg)("g", null, renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle));
}
// now define the line's path.
let path = '';
switch (rendering.type) {
case skgraph_models_1.K_SPLINE: {
path += `M${points[0].x},${points[0].y}`;
for (let i = 1; i < points.length; i += 3) {
const remainingPoints = points.length - i;
if (remainingPoints === 1) {
// if one routing point is left, draw a straight line to there.
path += `L${points[i].x},${points[i].y}`;
}
else if (remainingPoints === 2) {
// if two routing points are left, draw a quadratic bezier curve with those two points.
path += `Q${points[i].x},${points[i].y} ${points[i + 1].x},${points[i + 1].y}`;
}
else {
// if three or more routing points are left, draw a cubic bezier curve with those points.
path +=
`C${points[i].x},${points[i].y} ` +
`${points[i + 1].x},${points[i + 1].y} ` +
`${points[i + 2].x},${points[i + 2].y}`;
}
}
break;
}
case skgraph_models_1.K_POLYLINE: // Fall through to next case. KPolylines are just KPolygons without the closing end.
case skgraph_models_1.K_POLYGON: {
path += `M${points[0].x},${points[0].y}`;
for (let i = 1; i < points.length; i++) {
path += `L${points[i].x},${points[i].y}`;
}
if (rendering.type === skgraph_models_1.K_POLYGON) {
path += 'Z';
}
break;
}
case skgraph_models_1.K_ROUNDED_BENDS_POLYLINE: {
// Rendering-specific attributes
const { bendRadius } = rendering;
// now define the rounded polyline's path.
path += `M${points[0].x},${points[0].y}`;
for (let i = 1; i < points.length - 1; i++) {
const p0 = points[i - 1];
const p = points[i];
const p1 = points[i + 1];
// last point
const x0 = p0.x;
const y0 = p0.y;
// current point where a bend should be rendered
const xp = p.x;
const yp = p.y;
// next point
const x1 = p1.x;
const y1 = p1.y;
// distance between the last point and the current point
const dist0 = Math.sqrt((x0 - xp) * (x0 - xp) + (y0 - yp) * (y0 - yp));
// distance between the current point and the next point
const dist1 = Math.sqrt((x1 - xp) * (x1 - xp) + (y1 - yp) * (y1 - yp));
// If the previous / next point is too close, use a smaller bend radius
const usedBendRadius = Math.min(bendRadius, dist0 / 2, dist1 / 2);
// start and end points of the bend
let xs;
let ys;
let xe;
let ye;
if (usedBendRadius === 0) {
// Avoid division by zero if two points are identical.
xs = xp;
ys = yp;
xe = xp;
ye = yp;
}
else {
xs = xp + (usedBendRadius * (x0 - xp)) / dist0;
ys = yp + (usedBendRadius * (y0 - yp)) / dist0;
xe = xp + (usedBendRadius * (x1 - xp)) / dist1;
ye = yp + (usedBendRadius * (y1 - yp)) / dist1;
}
// draw a line to the start of the bend point (from the last end of its bend)
// and then draw the bend with the control points of the point itself and the bend end point.
path += `L${xs},${ys}Q${xp},${yp} ${xe},${ye}`;
}
if (points.length > 1) {
const lastPoint = points[points.length - 1];
path += `L${lastPoint.x},${lastPoint.y}`;
}
break;
}
default: {
console.error('error in views-rendering.tsx, unexpected rendering type in switch');
}
}
// Create the svg element for this rendering.
// Only apply the fast shadow to KPolygons, other shadows are not allowed there.
const element = ((0, sprotty_1.svg)("g", Object.assign({ id: rendering.properties['klighd.lsp.rendering.id'] }, gAttrs),
...renderSVGLine(lineStyles, colorStyles, shadowStyles, path, rendering.type === skgraph_models_1.K_POLYGON ? styles.kShadow : undefined),
renderChildRenderings(rendering, parent, stylesToPropagate, context, childOfNodeTitle)));
return element;
}
exports.renderLine = renderLine;
/**
* Translates a text rendering into an SVG text rendering.
* @param rendering The rendering.
* @param parent The parent element.
* @param propagatedStyles The styles propagated from parent elements that should be taken into account.
* @param context The rendering context for this element.
* @param childOfNodeTitle If this rendering is a child of a node title. May override special renderings
*/
function renderKText(rendering, parent, boundsAndTransformation, styles, context, childOfNodeTitle) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
// Find the text to write first.
let text;
// KText elements as renderings of labels have their text in the KLabel, not the KText
if ('text' in parent) {
// if parent is KLabel
text = parent.text;
}
else {
text = rendering.text;
}
if (parent.properties['de.cau.cs.kieler.klighd.labels.textOverride'] !== undefined) {
text = parent.properties['de.cau.cs.kieler.klighd.labels.textOverride'];
}
// If no text can be found, return here.
if (text === undefined)
return (0, sprotty_1.svg)("g", null);
// The text split into an array for each individual line
const lines = text.split('\n');
// Check the invisibility first. If this rendering is supposed to be invisible, do not render it,
// only render its children transformed by the transformation already calculated.
if ((0, views_styles_1.isInvisible)(styles)) {
return (0, sprotty_1.svg)("g", null);
}
// Default case. Calculate all svg objects and attributes needed to build this rendering from the styles and the rendering.
const colorStyles = (0, views_styles_1.getSvgColorStyles)(styles, context, parent);
// Calculate the background, if needed, as a rectangle to be placed behind the text.
let background;
if (colorStyles.background.color !== 'none') {
const boundingBoxAndTransformation = (0, views_common_1.findBoundsAndTransformationData)(rendering, styles, parent, context, false, true);
background = ((0, sprotty_1.svg)("rect", { x: (_b = (_a = boundingBoxAndTransformation === null || boundingBoxAndTransformation === void 0 ? void 0 : boundingBoxAndTransformation.bounds) === null || _a === void 0 ? void 0 : _a.x) !== null && _b !== void 0 ? _b : 0, y: (_d = (_c = boundingBoxAndTransformation === null || boundingBoxAndTransformation === void 0 ? void 0 : boundingBoxAndTransformation.bounds) === null || _c === void 0 ? void 0 : _c.y) !== null && _d !== void 0 ? _d : 0, width: (_f = (_e = boundingBoxAndTransformation === null || boundingBoxAndTransformation === void 0 ? void 0 : boundingBoxAndTransformation.bounds) === null || _e === void 0 ? void 0 : _e.width) !== null && _f !== void 0 ? _f : 0, height: (_h = (_g = boundingBoxAndTransformation === null || boundingBoxAndTransformation === void 0 ? void 0 : boundingBoxAndTransformation.bounds) === null || _g === void 0 ? void 0 : _g.height) !== null && _h !== void 0 ? _h : 0, fill: colorStyles.background.color, style: { opacity: (_j = colorStyles.background.opacity) !== null && _j !== void 0 ? _j : '1' } }));
}
const paperShadows = context.renderOptionsRegistry.getValueOrDefault(render_options_registry_1.Shadows) === render_options_registry_1.ShadowOption.PAPER_MODE;
const shadowStyles = paperShadows ? (0, views_styles_1.getSvgShadowStyles)(styles, context) : undefined;
const textStyles = (0, views_styles_1.getSvgTextStyles)(styles);
// Replace text with rectangle, if the text is too small.
const simplifySmallTextOption = context.renderOptionsRegistry.getValue(render_options_registry_1.SimplifySmallText);
const simplifySmallText = simplifySmallTextOption !== null && simplifySmallTextOption !== void 0 ? simplifySmallTextOption : false; // Only enable, if option is found.
if (!context.forceRendering &&
simplifySmallText &&
!rendering.properties['klighd.isNodeTitle'] &&
!childOfNodeTitle) {
const simplificationThreshold = context.renderOptionsRegistry.getValueOrDefault(render_options_registry_1.TextSimplificationThreshold);
const proportionalHeight = 0.5; // height of replacement compared to full text height
if (context.viewport &&
rendering.properties['klighd.calculated.text.bounds'] &&
rendering.properties['klighd.calculated.text.bounds'].height * context.viewport.zoom <=
simplificationThreshold) {
const replacements = background ? [background] : [];
lines.forEach((line, index) => {
var _a, _b, _c, _d;
const xPos = (_b = (_a = boundsAndTransformation === null || boundsAndTransformation === void 0 ? void 0 : boundsAndTransformation.bounds) === null || _a === void 0 ? void 0 : _a.x) !== null && _b !== void 0 ? _b : 0;
const yPos = ((_c = boundsAndTransformation === null || boundsAndTransformation === void 0 ? void 0 : boundsAndTransformation.bounds) === null || _c === void 0 ? void 0 : _c.y) &&
rendering.properties['klighd.calculated.text.line.heights'] &&
((_d = boundsAndTransformation === null || boundsAndTransformation === void 0 ? void 0 : boundsAndTransformation.bounds) === null || _d === void 0 ? void 0 : _d.height)
? boundsAndTransformation.bounds.y -
boundsAndTransformation.bounds.height / 2 +
(rendering.properties['klighd.calculated.text.line.heights'][index] / 2) *
proportionalHeight
: 0;
const width = rendering.properties['klighd.calculated.text.line.widths']
? rendering.properties['klighd.calculated.text.line.widths'][index]
: 0;
const height = rendering.properties['klighd.calculated.text.line.heights']
? rendering.properties['klighd.calculated.text.line.heights'][index] *
proportionalHeight
: 0;
// Generate rectangle for each line with color style.
const curLine = colorStyles.foreground ? ((0, sprotty_1.svg)("rect", { x: xPos, y: yPos, width: width, height: height, fill: colorStyles.foreground.color, opacity: "0.5" })) : ((0, sprotty_1.svg)("rect", { x: xPos, y: yPos, width: width, height: height, fill: "#000000", opacity: "0.5" }));
replacements.push(curLine);
});
return (0, sprotty_1.svg)("g", { id: rendering.properties['klighd.lsp.rendering.id'] }, ...replacements);
}
}
// The svg style of the resulting text element.
const opacity = context.mListener.hasDragged ? 0.1 : parent.opacity;
const style = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ 'dominant-baseline': textStyles.dominantBaseline }, { 'font-family': textStyles.fontFamily }), { 'font-size': textStyles.fontSize }), { 'font-style': textStyles.fontStyle }), { 'font-weight': textStyles.fontWeight }), { 'text-decoration-line': textStyles.textDecorationLine }), { 'text-decoration-style': textStyles.textDecorationStyle }), { opacity }), (colorStyles.foreground ? { 'fill-opacity': colorStyles.foreground.opacity } : {}));
// The attributes to be contained in the returned text node.
const attrs = Object.assign(Object.assign(Object.assign({ x: boundsAndTransformation.bounds.x, style }, (colorStyles.foreground ? { fill: colorStyles.foreground.color } : {})), (shadowStyles ? { filter: shadowStyles } : {})), { 'xml:space': 'preserve' });
const elements = background ? [background] : [];
if (lines.length === 1) {
// If the text has only one line, just put the text in the text node directly.
attrs.y = boundsAndTransformation.bounds.y;
// Force any SVG renderer rendering this text to use the exact width calculated for it.
// This avoids overlapping texts or too big gaps at the cost of slightly bigger/tighter glyph spacings
// when viewed in a different SVG viewer after exporting.
if (rendering.properties['klighd.calculated.text.line.widths']) {
attrs.textLength = rendering.properties['klighd.calculated.text.line.widths'];
attrs.lengthAdjust = 'spacingAndGlyphs';
}
elements.push((0, sprotty_1.svg)("text", Object.assign({}, attrs), ...lines));
}
else {
// Otherwise, put each line of text in a separate <text> element.
const calculatedTextLineWidths = rendering.properties['klighd.calculated.text.line.widths'];
const calculatedTextLineHeights = rendering.properties['klighd.calculated.text.line.heights'];
let currentY = (_k = boundsAndTransformation.bounds.y) !== null && _k !== void 0 ? _k : 0;
if (calculatedTextLineWidths) {
attrs.lengthAdjust = 'spacingAndGlyphs';
}
lines.forEach((line, index) => {
const currentElement = ((0, sprotty_1.svg)("text", Object.assign({}, attrs, { y: currentY }, (calculatedTextLineWidths ? { textLength: calculatedTextLineWidths[index] } : {})), line));
elements.push(currentElement);
currentY = calculatedTextLineHeights ? currentY + calculatedTextLineHeights[index] : currentY;
});
}
const gAttrs = Object.assign({}, (boundsAndTransformation.transformation.length !== 0
? { transform: boundsAndTransformation.transformation.map(views_common_1.transformationToSVGString).join('') }
: {}));
// build the element from the above defined attributes and children
return ((0, sprotty_1.svg)("g", Object.assign({ id: rendering.properties['klighd.lsp.rendering.id'] }, gAttrs), ...elements));
}
exports.renderKText = renderKText;
/**
* Renders all child renderings of the given container rendering.
* @param parentRendering The parent rendering.
* @param parent The parent element containing this rendering.
* @param propagatedStyles The styles propagated from parent elements that should be taken into account.
* @param context The rendering context for this element.
* @param childOfNodeTitle If this rendering is a child of a node title. May override special renderings
*/
function renderChildRenderings(parentRendering, parentElement, propagatedStyles, context, childOfNodeTitle) {
// children only should be rendered if the parentElement is not a shadow or the rendering is not a clip rendering.
if (!(parentElement instanceof skgraph_models_1.SKNode) || (!parentElement.shadow && !parentRendering.isClipRendering)) {
const renderings = [];
for (const childRendering of parentRendering.children) {
const rendering = getRendering([childRendering], parentElement, propagatedStyles, context, childOfNodeTitle);
renderings.push(rendering);
}
return renderings;
}
return [];
}
exports.renderChildRenderings = renderChildRenderings;
function renderError(rendering) {
return ((0, sprotty_1.svg)("text", null, (`Rendering cannot be drawn!\n` +
`Type: ${rendering.type}\n` +
`ID: ${rendering.properties['klighd.lsp.rendering.id']}`)));
}
exports.renderError = renderError;
/**
* Renders some SVG shape, possibly with an added shadow, as given by the svgFunction. If a simple shadow
* should be added, it is added as four copies of the SVG shape with rgba(0,0,0,0.1) and the
* offsets defined by the kShadow, if a nice shadow should be added, it is added via SVG filter.
*
* @param kShadow The shadow definition for the rendering, or undefined if no shadow should be added
* @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one.
* @param svgFunction The callback function rendering the wanted SVG shape. x and y are the offsets
* for the renderings additional to any other offsets, here for the shadows, kShadow is the shadow
* definition as given to this function as well and the params are the other params given to this
* function.
* @param params The further parameters needed to call the svgFunction other than an x, y, shadowStyles, and
* kShadow.
* @returns the svg shapes generated by the svg function, with the correct shadow.
*/
function renderWithShadow(kShadow, shadowStyles, svgFunction, ...params) {
// If a shadowStyle is given to this function, we want 'nice' shadows using the filter string.
if (shadowStyles !== undefined) {
return [svgFunction(undefined, undefined, shadowStyles, undefined, ...params)];
}
// Otherwise see if there is a shadow definition and render it the 'fast' KIELER-style.
if (kShadow) {
// Shadow rendered as four copies of the SVG shape with some defaults set due to the shadow and the original rectangle.
return [
svgFunction((4 * kShadow.xOffset) / 4, (4 * kShadow.yOffset) / 4, undefined, kShadow, ...params),
svgFunction((3 * kShadow.xOffset) / 4, (3 * kShadow.yOffset) / 4, undefined, kShadow, ...params),
svgFunction((2 * kShadow.xOffset) / 4, (2 * kShadow.yOffset) / 4, undefined, kShadow, ...params),
svgFunction((1 * kShadow.xOffset) / 4, (1 * kShadow.yOffset) / 4, undefined, kShadow, ...params),
svgFunction(undefined, undefined, undefined, undefined, ...params),
];
}
return [svgFunction(undefined, undefined, undefined, undefined, ...params)];
}
exports.renderWithShadow = renderWithShadow;
// ------- All functions turning the rendering data into an SVG with potential shadow style. ---------- //
/**
* Renders a rectangle with all given information.
*
* @param bounds bounds data calculated for this rectangle.
* @param lineWidth width of the line to offset the rectangle's position and size by.
* @param rx rx parameter of SVG rect
* @param ry ry parameter of SVG rect
* @param lineStyles style information for lines (stroke etc.)
* @param colorStyles style information for color
* @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one.
* @param kShadow general shadow information.
* @returns An array of SVG <rects> resulting from this. Only multiple <rect>s if a simple shadow effect should be applied.
*/
function renderSVGRect(bounds, lineWidth, rx, ry, lineStyles, colorStyles, shadowStyles, kShadow) {
return renderWithShadow(kShadow, shadowStyles, renderSingleSVGRect, bounds, rx, ry, lineWidth, lineStyles, colorStyles);
}
exports.renderSVGRect = renderSVGRect;
/**
* Renders a rectangle with all given information.
* If the rendering is a shadow (has a kShadow parameter), all stroke attributes are ignored (no stroke on the shadow) and a
* black fill with 0.1 alpha is returned.
*
* @param x x offset of the rectangle, to be used for shadows only.
* @param y y offset of the rectangle, to be used for shadows only.
* @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one.
* @param kShadow shadow information. Controls what this method does.
* @param bounds bounds data calculated for this rectangle.
* @param lineWidth width of the line to offset the rectangle's position and size by.
* @param rx rx parameter of SVG rect
* @param ry ry parameter of SVG rect
* @param lineStyles style information for lines (stroke etc.)
* @param colorStyles style information for color
* @returns A single SVG <rect>.
*/
function renderSingleSVGRect(x, y, shadowStyles, kShadow, bounds, rx, ry, lineWidth, lineStyles, colorStyles) {
// Offset the x/y by the lineWidth.
let theX = x || 0;
theX += lineWidth / 2;
theX = theX === 0 ? undefined : theX;
let theY = y || 0;
theY += lineWidth / 2;
theY = theY === 0 ? undefined : theY;
return ((0, sprotty_1.svg)("rect", Object.assign({ width: bounds.width - lineWidth, height: bounds.height - lineWidth }, (theX ? { x: theX } : {}), (theY ? { y: theY } : {}), (rx ? { rx } : {}), (ry ? { ry } : {}), { style: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (kShadow ? {} : { 'stroke-linecap': lineStyles.lineCap })), (kShadow ? {} : { 'stroke-linejoin': lineStyles.lineJoin })), (kShadow ? {} : { 'stroke-width': lineStyles.lineWidth })), (kShadow ? {} : { 'stroke-dasharray': lineStyles.dashArray })), (kShadow ? {} : { 'stroke-miterlimit': lineStyles.miterLimit })), { opacity: kShadow
? colorStyles.opacity
? String(Number(colorStyles.opacity) * 0.1)
: '0.1'
: colorStyles.opacity }), (kShadow ? {} : { 'stroke-opacity': colorStyles.foreground.opacity })), (kShadow || colorStyles.background.opacity
? { 'fill-opacity': kShadow ? '1' : colorStyles.background.opacity }
: {})) }, (kShadow ? {} : { stroke: colorStyles.foreground.color }), (kShadow ? { fill: 'rgb(0,0,0)' } : { fill: colorStyles.background.color }), (shadowStyles ? { filter: shadowStyles } : {}))));
}
exports.renderSingleSVGRect = renderSingleSVGRect;
/**
* Renders an definition for an image to be used later with all given information. The image will have a size of 1x1 to be scaled later in its <use>.
*
* @param imageURI The image href string
* @returns An array of SVG elements, here <image>s and <rect>s resulting from this. <rect>s are added if a shadow effect should be applied.
*/
function renderSVGImageDef(imageId, imageURI) {
return (0, sprotty_1.svg)("image", { width: 1, height: 1, id: imageId, href: imageURI });
}
exports.renderSVGImageDef = renderSVGImageDef;
/**
* Renders an image with all given information.
*
* @param bounds bounds data calculated for this image.
* @param image The image href string
* @param kShadow shadow information.
* @returns An array of SVG elements, here <image>s and <rect>s resulting from this. <rect>s are added if a shadow effect should be applied.
*/
function renderSVGImage(bounds, shadowStyles, image, kShadow) {
return renderWithShadow(kShadow, shadowStyles, renderSingleSVGImage, bounds, image);
}
exports.renderSVGImage = renderSVGImage;
/**
* Renders the use of a previously defined SVG shape.
*
* @param bounds bounds data calculated for this shape.
* @param image The use href string
* @param kShadow shadow information.
* @returns An array of SVG elements, here <use>s and <rect>s resulting from this. <rect>s are added if a shadow effect should be applied.
*/
function renderSVGUse(bounds, shadowStyles, image, kShadow) {
return renderWithShadow(kShadow, shadowStyles, renderSingleSVGUse, bounds, image);
}
exports.renderSVGUse = renderSVGUse;
/**
* Renders an image with all given information.
* If the rendering is a shadow, a shadow rect is drawn instead.
*
* @param x x offset of the image, to be used for shadows only.
* @param y y offset of the image, to be used for shadows only.
* @param kShadow shadow information. Controls what this method does.
* @param bounds bounds data calculated for this image.
* @param image The image href string
* @returns A single SVG <image> or <rect>.
*/
function renderSingleSVGImage(x, y, shadowStyles, kShadow, bounds, image) {
// A shadow of an image is just a rectangle.
if (kShadow) {
return ((0, sprotty_1.svg)("rect", Object.assign({}, (x ? { x } : {}), (y ? { y } : {}), { width: bounds.width, height: bounds.height, style: {
opacity: '0.1',
}, fill: "rgb(0,0,0)" })));
}
return ((0, sprotty_1.svg)("image", Object.assign({ width: bounds.width, height: bounds.height, href: image }, (shadowStyles ? { filter: shadowStyles } : {}))));
}
exports.renderSingleSVGImage = renderSingleSVGImage;
/**
* Renders a pre-defined SVG element usage with all given information.
* If the rendering is a shadow, a shadow rect is drawn instead.
*
* @param x x offset of the element, to be used for shadows only.
* @param y y offset of the element, to be used for shadows only.
* @param kShadow shadow information. Controls what this method does.
* @param bounds bounds data calculated for this element.
* @param href The use href string
* @returns A single SVG <use> or <rect>.
*/
function renderSingleSVGUse(x, y, shadowStyles, kShadow, bounds, href) {
// A shadow of an image is just a rectangle.
if (kShadow) {
return ((0, sprotty_1.svg)("rect", Object.assign({}, (x ? { x } : {}), (y ? { y } : {}), { width: bounds.width, height: bounds.height, style: {
opacity: '0.1',
}, fill: "rgb(0,0,0)" })));
}
return ((0, sprotty_1.svg)("use", Object.assign({ transform: `scale(${bounds.width}, ${bounds.height})`, href: href }, (shadowStyles ? { filter: shadowStyles } : {}))));
}
exports.renderSingleSVGUse = renderSingleSVGUse;
/**
* Renders an arc with all given information.
*
* @param lineStyles style information for lines (stroke etc.)
* @param colorStyles style information for color
* @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one.
* @param path The 'd' attribute for SVG <path>
* @param kShadow general shadow information.
* @returns An array of SVG <path>s resulting from this. Only multiple <path>s if a simple shadow effect should be applied.
*/
function renderSVGArc(lineStyles, colorStyles, shadowStyles, path, kShadow) {
return renderWithShadow(kShadow, shadowStyles, renderSingleSVGArc, lineStyles, colorStyles, path);
}
exports.renderSVGArc = renderSVGArc;
/**
* Renders an arc with all given information.
* If the rendering is a shadow (has a kShadow parameter), all stroke attributes are ignored (no stroke on the shadow) and a
* black fill with 0.1 alpha is returned.
*
* @param x x offset of the arc, to be used for shadows only.
* @param y y offset of the arc, to be used for shadows only.
* @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one.
* @param kShadow shadow information. Controls what this method does.
* @param lineStyles style information for lines (stroke etc.)
* @param colorStyles style information for color
* @param path The 'd' attribute for the SVG <path>
* @returns A single SVG <path>.
*/
function renderSingleSVGArc(x, y, shadowStyles, kShadow, lineStyles, colorStyles, path) {
return ((0, sprotty_1.svg)("path", Object.assign({}, (x && y ? { transform: `translate(${x},${y})` } : {}), { d: path, style: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (kShadow ? {} : { 'stroke-linecap': lineStyles.lineCap })), (kShadow ? {} : { 'stroke-linejoin': lineStyles.lineJoin })), (kShadow ? {} : { 'stroke-width': lineStyles.lineWidth })), (kShadow ? {} : { 'stroke-dasharray': lineStyles.dashArray })), (kShadow ? {} : { 'stroke-miterlimit': lineStyles.miterLimit })), { opacity: kShadow
? colorStyles.opacity
? String(Number(colorStyles.opacity) * 0.1)
: '0.1'
: colorStyles.opacity }), (kShadow ? {} : { 'stroke-opacity': colorStyles.foreground.opacity })), (kShadow || colorStyles.background.opacity
? { 'fill-opacity': kShadow ? '1' : colorStyles.background.opacity }
: {})) }, (kShadow ? {} : { stroke: colorStyles.foreground.color }), (kShadow ? { fill: 'rgb(0,0,0)' } : { fill: colorStyles.background.color }), (shadowStyles ? { filter: shadowStyles } : {}))));
}
exports.renderSingleSVGArc = renderSingleSVGArc;
/**
* Renders an ellipse with all given information.
*
* @param lineWidth width of the line to offset the ellipse's position and size by.
* @param lineStyles style information for lines (stroke etc.)
* @param colorStyles style information for color
* @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one.
* @param kShadow general shadow information.
* @returns An array of SVG <ellipse>s resulting from this. Only multiple <ellipse>s if a simple shadow effect should be applied.
*/
function renderSVGEllipse(bounds, lineWidth, lineStyles, colorStyles, shadowStyles, kShadow) {
return renderWithShadow(kShadow, shadowStyles, renderSingleSVGEllipse, bounds, lineWidth, lineStyles, colorStyles);
}
exports.renderSVGEllipse = renderSVGEllipse;
/**
* Renders an ellipse with all given information.
* If the rendering is a shadow (has a kShadow parameter), all stroke attributes are ignored (no stroke on the shadow) and a
* black fill with 0.1 alpha is returned.
*
* @param x x offset of the ellipse, to be used for shadows only.
* @param y y offset of the ellipse, to be used for shadows only.
* @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one.
* @param kShadow shadow information. Controls what this method does.
* @param bounds bounds data calculated for this ellipse.
* @param lineWidth width of the line to offset the ellipse's position and size by.
* @param lineStyles style information for lines (stroke etc.)
* @param colorStyles style information for color
* @returns A single SVG <ellipse>.
*/
function renderSingleSVGEllipse(x, y, shadowStyles, kShadow, bounds, lineWidth, lineStyles, colorStyles) {
return ((0, sprotty_1.svg)("ellipse", Object.assign({}, (x && y ? { transform: `translate(${x},${y})` } : {}), { cx: bounds.width / 2, cy: bounds.height / 2, rx: bounds.width / 2 - lineWidth / 2, ry: bounds.height / 2 - lineWidth / 2, style: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (kShadow ? {} : { 'stroke-linecap': lineStyles.lineCap })), (kShadow ? {} : { 'stroke-linejoin': lineStyles.lineJoin })), (kShadow ? {} : { 'stroke-width': lineStyles.lineWidth })), (kShadow ? {} : { 'stroke-dasharray': lineStyles.dashArray })), (kShadow ? {} : { 'stroke-miterlimit': lineStyles.miterLimit })), { opacity: kShadow
? colorStyles.opacity
? String(Number(colorStyles.opacity) * 0.1)
: '0.1'
: colorStyles.opacity }), (kShadow ? {} : { 'stroke-opacity': colorStyles.foreground.opacity })), (kShadow || colorStyles.background.opacity
? { 'fill-opacity': kShadow ? '1' : colorStyles.background.opacity }
: {})) }, (kShadow ? {} : { stroke: colorStyles.foreground.color }), (kShadow ? { fill: 'rgb(0,0,0)' } : { fill: colorStyles.background.color }), (shadowStyles ? { filter: shadowStyles } : {}))));
}
exports.renderSingleSVGEllipse = renderSingleSVGEllipse;
/**
* Renders a rendering with a specific path (polyline, polygon, etc.) with all given information.
*
* @param lineStyles style information for lines (stroke etc.)
* @param colorStyles style information for color
* @param shadowStyles specific shadow filter ID, if this element should be drawn with a smooth shadow and no simple one.
* @param path The 'd' attribute for the SVG <path>
* @param kShadow general shadow information.
* @returns An array of SVG <path>s resulting from this. Only multiple <path>s if a simple shadow effect should be applied.
*/
function renderSVGLine(lineStyles, colorStyles, shadowStyles, path, kShadow) {
return renderWithShadow(kShadow, shadowStyles, renderSingleSVGLine, lineStyles, colorStyles, path);
}
exports.renderSVGLine = renderSVGLine;
/**
* Renders a rendering with a specific path (polyline, polygon, etc.) with all given information.
* If the rendering is a shadow (has a kShadow parameter), all stroke attributes are ignored (no stroke on the shadow) and a
* black fill with 0.1 alpha is returned.
*
* @param x x offset of the line, to be used for shadows only.
* @param y y offset of the line, to be used for shadows only