diff --git a/dev/ts/main.ts b/dev/ts/main.ts
index b3425e61de..1e226112db 100644
--- a/dev/ts/main.ts
+++ b/dev/ts/main.ts
@@ -273,6 +273,16 @@ function configurePoolsFilteringFromParameters(parameters: URLSearchParams): Mod
export function startBpmnVisualization(config: BpmnVisualizationDemoConfiguration): void {
const log = logStartup;
log(`Initializing BpmnVisualization with container '${config.globalOptions.container}'...`);
+
+ const parameters = new URLSearchParams(window.location.search);
+ const rendererIgnoreBpmnColors = parameters.get('renderer.ignore.bpmn.colors');
+ if (rendererIgnoreBpmnColors) {
+ const ignoreBpmnColors = rendererIgnoreBpmnColors === 'true';
+ log('Ignore support for "BPMN in Color"?', ignoreBpmnColors);
+ !config.globalOptions.renderer && (config.globalOptions.renderer = {});
+ config.globalOptions.renderer.ignoreBpmnColors = ignoreBpmnColors;
+ }
+
bpmnVisualization = new ThemedBpmnVisualization(config.globalOptions);
log('Initialization completed');
new DropFileUserInterface(window, 'drop-container', bpmnVisualization.graph.container, readAndLoadFile);
@@ -280,8 +290,6 @@ export function startBpmnVisualization(config: BpmnVisualizationDemoConfiguratio
statusKoNotifier = config.statusKoNotifier ?? logOnlyStatusKoNotifier;
- const parameters = new URLSearchParams(window.location.search);
-
log('Configuring Load Options');
loadOptions = config.loadOptions || {};
loadOptions.fit = getFitOptionsFromParameters(config, parameters);
diff --git a/docs/users/architecture/images/architecture/internal-model.drawio b/docs/users/architecture/images/architecture/internal-model.drawio
index f382051606..e8cd23bce7 100644
--- a/docs/users/architecture/images/architecture/internal-model.drawio
+++ b/docs/users/architecture/images/architecture/internal-model.drawio
@@ -1 +1 @@
-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
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/users/architecture/images/architecture/internal-model.svg b/docs/users/architecture/images/architecture/internal-model.svg
index e25154dc24..01e4013cf7 100644
--- a/docs/users/architecture/images/architecture/internal-model.svg
+++ b/docs/users/architecture/images/architecture/internal-model.svg
@@ -1,3 +1,4 @@
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/users/images/bpmn-in-color-C.1.0.png b/docs/users/images/bpmn-in-color-C.1.0.png
new file mode 100644
index 0000000000..7e25e34c26
Binary files /dev/null and b/docs/users/images/bpmn-in-color-C.1.0.png differ
diff --git a/docs/users/overview.adoc b/docs/users/overview.adoc
index 7bbf59c5f9..f693f0960c 100644
--- a/docs/users/overview.adoc
+++ b/docs/users/overview.adoc
@@ -129,6 +129,18 @@ image::images/bpmn-theme-custom-colors.png[Custom BPMN Theme]
_Custom BPMN Theme_
+==== `BPMN in Color` Support
+
+As of version 0.35.0, `bpmn-visualization` supports the https://github.com/bpmn-miwg/bpmn-in-color[BPMN in Color Specification] with a fallback to the
+https://github.com/bpmn-io/bpmn-moddle/blob/ea7fa6a94c55f49fe1da1f019dc9a40d62967252/resources/bpmn-io/json/bioc.json[bpmn.io specific BPMN extensions for colors] (a.k.a `bioc`).
+
+This support is disabled by default, and it can be enabled at the library initialization.
+
+image::images/bpmn-in-color-C.1.0.png[C.1.0 with "BPMN in Color"]
+
+_miwg-test-suite C.1.0 reference diagram using "BPMN in Color"_
+
+
[[diagram-navigation]]
=== Diagram Navigation
diff --git a/src/component/BpmnVisualization.ts b/src/component/BpmnVisualization.ts
index b8454d1d96..6451810e81 100644
--- a/src/component/BpmnVisualization.ts
+++ b/src/component/BpmnVisualization.ts
@@ -18,7 +18,7 @@ import GraphConfigurator from './mxgraph/GraphConfigurator';
import { newBpmnRenderer } from './mxgraph/BpmnRenderer';
import { newBpmnParser } from './parser/BpmnParser';
import type { BpmnGraph } from './mxgraph/BpmnGraph';
-import type { GlobalOptions, LoadOptions, ParserOptions } from './options';
+import type { GlobalOptions, LoadOptions, ParserOptions, RendererOptions } from './options';
import type { BpmnElementsRegistry } from './registry';
import { newBpmnElementsRegistry } from './registry/bpmn-elements-registry';
import { BpmnModelRegistry } from './registry/bpmn-model-registry';
@@ -69,7 +69,10 @@ export class BpmnVisualization {
private readonly parserOptions: ParserOptions;
+ private readonly rendererOptions: RendererOptions;
+
constructor(options: GlobalOptions) {
+ this.rendererOptions = options?.renderer;
// mxgraph configuration
const configurator = new GraphConfigurator(htmlElement(options?.container));
this.graph = configurator.configure(options);
@@ -89,7 +92,7 @@ export class BpmnVisualization {
load(xml: string, options?: LoadOptions): void {
const bpmnModel = newBpmnParser(this.parserOptions).parse(xml);
const renderedModel = this.bpmnModelRegistry.load(bpmnModel, options?.modelFilter);
- newBpmnRenderer(this.graph).render(renderedModel, options?.fit);
+ newBpmnRenderer(this.graph, this.rendererOptions).render(renderedModel, options?.fit);
}
getVersion(): Version {
diff --git a/src/component/mxgraph/BpmnRenderer.ts b/src/component/mxgraph/BpmnRenderer.ts
index 835d5bd6ab..ca21a386c2 100644
--- a/src/component/mxgraph/BpmnRenderer.ts
+++ b/src/component/mxgraph/BpmnRenderer.ts
@@ -23,7 +23,7 @@ import { MessageVisibleKind, ShapeUtil } from '../../model/bpmn/internal';
import CoordinatesTranslator from './renderer/CoordinatesTranslator';
import StyleComputer from './renderer/StyleComputer';
import type { BpmnGraph } from './BpmnGraph';
-import type { FitOptions } from '../options';
+import type { FitOptions, RendererOptions } from '../options';
import type { RenderedModel } from '../registry/bpmn-model-registry';
import { mxgraph } from './initializer';
import type { mxCell } from 'mxgraph';
@@ -139,8 +139,8 @@ export class BpmnRenderer {
/**
* @internal
*/
-export function newBpmnRenderer(graph: BpmnGraph): BpmnRenderer {
- return new BpmnRenderer(graph, new CoordinatesTranslator(graph), new StyleComputer());
+export function newBpmnRenderer(graph: BpmnGraph, options: RendererOptions): BpmnRenderer {
+ return new BpmnRenderer(graph, new CoordinatesTranslator(graph), new StyleComputer(options));
}
/**
diff --git a/src/component/mxgraph/renderer/StyleComputer.ts b/src/component/mxgraph/renderer/StyleComputer.ts
index 58b3b90304..c814516de8 100644
--- a/src/component/mxgraph/renderer/StyleComputer.ts
+++ b/src/component/mxgraph/renderer/StyleComputer.ts
@@ -31,39 +31,45 @@ import { BpmnStyleIdentifier } from '../style';
import { MessageVisibleKind, ShapeBpmnCallActivityKind, ShapeBpmnElementKind, ShapeBpmnMarkerKind, ShapeUtil } from '../../../model/bpmn/internal';
import { AssociationFlow, SequenceFlow } from '../../../model/bpmn/internal/edge/flows';
import type { Font } from '../../../model/bpmn/internal/Label';
+import type { RendererOptions } from '../../options';
/**
* @internal
*/
export default class StyleComputer {
+ private readonly ignoreBpmnColors: boolean;
+
+ constructor(options?: RendererOptions) {
+ this.ignoreBpmnColors = options?.ignoreBpmnColors ?? true;
+ }
+
computeStyle(bpmnCell: Shape | Edge, labelBounds: Bounds): string {
const styles: string[] = [bpmnCell.bpmnElement.kind as string];
- let shapeStyleValues;
+ let mainStyleValues;
if (bpmnCell instanceof Shape) {
- shapeStyleValues = StyleComputer.computeShapeStyle(bpmnCell);
+ mainStyleValues = this.computeShapeStyleValues(bpmnCell);
} else {
- styles.push(...StyleComputer.computeEdgeStyle(bpmnCell));
- shapeStyleValues = new Map();
+ styles.push(...StyleComputer.computeEdgeBaseStyles(bpmnCell));
+ mainStyleValues = this.computeEdgeStyleValues(bpmnCell);
}
- const fontStyleValues = StyleComputer.computeFontStyleValues(bpmnCell);
+ const fontStyleValues = this.computeFontStyleValues(bpmnCell);
const labelStyleValues = StyleComputer.computeLabelStyleValues(bpmnCell, labelBounds);
- return [] //
- .concat([...styles])
- .concat([...shapeStyleValues, ...fontStyleValues, ...labelStyleValues].filter(([, v]) => v && v != 'undefined').map(([key, value]) => key + '=' + value))
+ return styles //
+ .concat(toArrayOfMxGraphStyleEntries([...mainStyleValues, ...fontStyleValues, ...labelStyleValues]))
.join(';');
}
- private static computeShapeStyle(shape: Shape): Map {
+ private computeShapeStyleValues(shape: Shape): Map {
const styleValues = new Map();
const bpmnElement = shape.bpmnElement;
if (bpmnElement instanceof ShapeBpmnEvent) {
- this.computeEventShapeStyle(bpmnElement, styleValues);
+ StyleComputer.computeEventShapeStyle(bpmnElement, styleValues);
} else if (bpmnElement instanceof ShapeBpmnActivity) {
- this.computeActivityShapeStyle(bpmnElement, styleValues);
+ StyleComputer.computeActivityShapeStyle(bpmnElement, styleValues);
} else if (ShapeUtil.isPoolOrLane(bpmnElement.kind)) {
// mxgraph.mxConstants.STYLE_HORIZONTAL is for the label
// In BPMN, isHorizontal is for the Shape
@@ -74,6 +80,18 @@ export default class StyleComputer {
styleValues.set(BpmnStyleIdentifier.EVENT_BASED_GATEWAY_KIND, String(bpmnElement.gatewayKind));
}
+ if (!this.ignoreBpmnColors) {
+ const extensions = shape.extensions;
+ const fillColor = extensions.fillColor;
+ if (fillColor) {
+ styleValues.set(mxgraph.mxConstants.STYLE_FILLCOLOR, fillColor);
+ if (ShapeUtil.isPoolOrLane(bpmnElement.kind)) {
+ styleValues.set(mxgraph.mxConstants.STYLE_SWIMLANE_FILLCOLOR, fillColor);
+ }
+ }
+ extensions.strokeColor && styleValues.set(mxgraph.mxConstants.STYLE_STROKECOLOR, extensions.strokeColor);
+ }
+
return styleValues;
}
@@ -100,7 +118,7 @@ export default class StyleComputer {
}
}
- private static computeEdgeStyle(edge: Edge): string[] {
+ private static computeEdgeBaseStyles(edge: Edge): string[] {
const styles: string[] = [];
const bpmnElement = edge.bpmnElement;
@@ -114,7 +132,18 @@ export default class StyleComputer {
return styles;
}
- private static computeFontStyleValues(bpmnCell: Shape | Edge): Map {
+ private computeEdgeStyleValues(edge: Edge): Map {
+ const styleValues = new Map();
+
+ if (!this.ignoreBpmnColors) {
+ const extensions = edge.extensions;
+ extensions.strokeColor && styleValues.set(mxgraph.mxConstants.STYLE_STROKECOLOR, extensions.strokeColor);
+ }
+
+ return styleValues;
+ }
+
+ private computeFontStyleValues(bpmnCell: Shape | Edge): Map {
const styleValues = new Map();
const font = bpmnCell.label?.font;
@@ -124,6 +153,11 @@ export default class StyleComputer {
styleValues.set(mxgraph.mxConstants.STYLE_FONTSTYLE, getFontStyleValue(font));
}
+ if (!this.ignoreBpmnColors) {
+ const extensions = bpmnCell.label?.extensions;
+ extensions?.color && styleValues.set(mxgraph.mxConstants.STYLE_FONTCOLOR, extensions.color);
+ }
+
return styleValues;
}
@@ -162,7 +196,14 @@ export default class StyleComputer {
}
computeMessageFlowIconStyle(edge: Edge): string {
- return `shape=${BpmnStyleIdentifier.MESSAGE_FLOW_ICON};${BpmnStyleIdentifier.IS_INITIATING}=${edge.messageVisibleKind === MessageVisibleKind.INITIATING}`;
+ const styleValues: Array<[string, string]> = [];
+ styleValues.push(['shape', BpmnStyleIdentifier.MESSAGE_FLOW_ICON]);
+ styleValues.push([BpmnStyleIdentifier.IS_INITIATING, String(edge.messageVisibleKind === MessageVisibleKind.INITIATING)]);
+ if (!this.ignoreBpmnColors) {
+ edge.extensions.strokeColor && styleValues.push([mxgraph.mxConstants.STYLE_STROKECOLOR, edge.extensions.strokeColor]);
+ }
+
+ return toArrayOfMxGraphStyleEntries(styleValues).join(';');
}
}
@@ -186,3 +227,7 @@ export function getFontStyleValue(font: Font): number {
}
return value;
}
+
+function toArrayOfMxGraphStyleEntries(styleValues: Array<[string, string | number]>): string[] {
+ return styleValues.filter(([, v]) => v && v != 'undefined').map(([key, value]) => `${key}=${value}`);
+}
diff --git a/src/component/options.ts b/src/component/options.ts
index 57c723121a..2662dc3e9a 100644
--- a/src/component/options.ts
+++ b/src/component/options.ts
@@ -25,6 +25,8 @@ export interface GlobalOptions {
navigation?: NavigationConfiguration;
/** Configure the BPMN parser. */
parser?: ParserOptions;
+ /** Configure how the BPMN diagram and its elements are rendered. */
+ renderer?: RendererOptions;
}
/**
@@ -180,3 +182,21 @@ export type ParserOptions = {
*/
additionalXmlAttributeProcessor?: (val: string) => string;
};
+
+/**
+ * Global configuration for the rendering of the BPMN elements.
+ *
+ * @category Initialization & Configuration
+ * @since 0.35.0
+ */
+export type RendererOptions = {
+ /**
+ * If set to `false`, support the "BPMN in Color" specification with a fallback with bpmn.io colors. For more details about the support, see
+ * {@link https://github.com/process-analytics/bpmn-visualization-js/pull/2614}.
+ *
+ * Otherwise, disable the support.
+ *
+ * @default true
+ */
+ ignoreBpmnColors?: boolean;
+};
diff --git a/src/component/parser/json/converter/DiagramConverter.ts b/src/component/parser/json/converter/DiagramConverter.ts
index 11de6d24de..8557ab689e 100644
--- a/src/component/parser/json/converter/DiagramConverter.ts
+++ b/src/component/parser/json/converter/DiagramConverter.ts
@@ -22,11 +22,10 @@ import { Edge, Waypoint } from '../../../../model/bpmn/internal/edge/edge';
import type { Shapes } from '../../../../model/bpmn/internal/BpmnModel';
import type BpmnModel from '../../../../model/bpmn/internal/BpmnModel';
import Label, { Font } from '../../../../model/bpmn/internal/Label';
-import { MessageVisibleKind } from '../../../../model/bpmn/internal/edge/kinds';
+import { MessageVisibleKind, ShapeBpmnCallActivityKind, ShapeBpmnMarkerKind, ShapeUtil } from '../../../../model/bpmn/internal';
import type { BPMNDiagram, BPMNEdge, BPMNLabel, BPMNLabelStyle, BPMNShape } from '../../../../model/bpmn/json/BPMNDI';
import type { Point } from '../../../../model/bpmn/json/DC';
import type { ConvertedElements } from './utils';
-import { ShapeBpmnCallActivityKind, ShapeBpmnMarkerKind, ShapeUtil } from '../../../../model/bpmn/internal';
import { ensureIsArray } from '../../../helpers/array-utils';
import type { ParsingMessageCollector } from '../../parsing-messages';
import { EdgeUnknownBpmnElementWarning, LabelStyleMissingFontWarning, ShapeUnknownBpmnElementWarning } from '../warnings';
@@ -104,26 +103,44 @@ export default class DiagramConverter {
return false;
}
- private deserializeShape(shape: BPMNShape, findShapeElement: (bpmnElement: string) => ShapeBpmnElement): Shape | undefined {
- const bpmnElement = findShapeElement(shape.bpmnElement);
+ private deserializeShape(bpmnShape: BPMNShape, findShapeElement: (bpmnElement: string) => ShapeBpmnElement): Shape | undefined {
+ const bpmnElement = findShapeElement(bpmnShape.bpmnElement);
if (bpmnElement) {
- const bounds = DiagramConverter.deserializeBounds(shape);
+ const bounds = DiagramConverter.deserializeBounds(bpmnShape);
if (
(bpmnElement instanceof ShapeBpmnSubProcess ||
(bpmnElement instanceof ShapeBpmnCallActivity && bpmnElement.callActivityKind === ShapeBpmnCallActivityKind.CALLING_PROCESS)) &&
- !shape.isExpanded
+ !bpmnShape.isExpanded
) {
bpmnElement.markers.push(ShapeBpmnMarkerKind.EXPAND);
}
let isHorizontal;
if (ShapeUtil.isPoolOrLane(bpmnElement.kind)) {
- isHorizontal = shape.isHorizontal ?? true;
+ isHorizontal = bpmnShape.isHorizontal ?? true;
}
- const label = this.deserializeLabel(shape.BPMNLabel, shape.id);
- return new Shape(shape.id, bpmnElement, bounds, label, isHorizontal);
+ const bpmnLabel = bpmnShape.BPMNLabel;
+ const label = this.deserializeLabel(bpmnLabel, bpmnShape.id);
+ const shape = new Shape(bpmnShape.id, bpmnElement, bounds, label, isHorizontal);
+ DiagramConverter.setColorExtensionsOnShape(shape, bpmnShape);
+
+ return shape;
+ }
+ }
+
+ // 'BPMN in Color' extensions with fallback to bpmn.io colors
+ private static setColorExtensionsOnShape(shape: Shape, bpmnShape: BPMNShape): void {
+ if ('background-color' in bpmnShape) {
+ shape.extensions.fillColor = bpmnShape['background-color'];
+ } else if ('fill' in bpmnShape) {
+ shape.extensions.fillColor = bpmnShape['fill'];
+ }
+ if ('border-color' in bpmnShape) {
+ shape.extensions.strokeColor = bpmnShape['border-color'];
+ } else if ('stroke' in bpmnShape) {
+ shape.extensions.strokeColor = bpmnShape['stroke'];
}
}
@@ -136,26 +153,37 @@ export default class DiagramConverter {
private deserializeEdges(edges: BPMNEdge | BPMNEdge[]): Edge[] {
return ensureIsArray(edges)
- .map(edge => {
+ .map(bpmnEdge => {
const flow =
- this.convertedElements.findSequenceFlow(edge.bpmnElement) ||
- this.convertedElements.findMessageFlow(edge.bpmnElement) ||
- this.convertedElements.findAssociationFlow(edge.bpmnElement);
+ this.convertedElements.findSequenceFlow(bpmnEdge.bpmnElement) ||
+ this.convertedElements.findMessageFlow(bpmnEdge.bpmnElement) ||
+ this.convertedElements.findAssociationFlow(bpmnEdge.bpmnElement);
if (!flow) {
- this.parsingMessageCollector.warning(new EdgeUnknownBpmnElementWarning(edge.bpmnElement));
+ this.parsingMessageCollector.warning(new EdgeUnknownBpmnElementWarning(bpmnEdge.bpmnElement));
return;
}
- const waypoints = this.deserializeWaypoints(edge.waypoint);
- const label = this.deserializeLabel(edge.BPMNLabel, edge.id);
- const messageVisibleKind = edge.messageVisibleKind ? (edge.messageVisibleKind as unknown as MessageVisibleKind) : MessageVisibleKind.NONE;
+ const waypoints = this.deserializeWaypoints(bpmnEdge.waypoint);
+ const label = this.deserializeLabel(bpmnEdge.BPMNLabel, bpmnEdge.id);
+ const messageVisibleKind = bpmnEdge.messageVisibleKind ? (bpmnEdge.messageVisibleKind as unknown as MessageVisibleKind) : MessageVisibleKind.NONE;
- return new Edge(edge.id, flow, waypoints, label, messageVisibleKind);
+ const edge = new Edge(bpmnEdge.id, flow, waypoints, label, messageVisibleKind);
+ DiagramConverter.setColorExtensionsOnEdge(edge, bpmnEdge);
+ return edge;
})
.filter(Boolean);
}
+ // 'BPMN in Color' extensions with fallback to bpmn.io colors
+ private static setColorExtensionsOnEdge(edge: Edge, bpmnEdge: BPMNEdge): void {
+ if ('border-color' in bpmnEdge) {
+ edge.extensions.strokeColor = bpmnEdge['border-color'];
+ } else if ('stroke' in bpmnEdge) {
+ edge.extensions.strokeColor = bpmnEdge['stroke'];
+ }
+ }
+
private deserializeWaypoints(waypoints: Point[]): Waypoint[] {
return ensureIsArray(waypoints).map(waypoint => new Waypoint(waypoint.x, waypoint.y));
}
@@ -164,9 +192,13 @@ export default class DiagramConverter {
if (bpmnLabel && typeof bpmnLabel === 'object') {
const font = this.findFont(bpmnLabel.labelStyle, id);
const bounds = DiagramConverter.deserializeBounds(bpmnLabel);
-
+ const label = new Label(font, bounds);
+ if ('color' in bpmnLabel) {
+ label.extensions.color = bpmnLabel.color;
+ return label;
+ }
if (font || bounds) {
- return new Label(font, bounds);
+ return label;
}
}
}
diff --git a/src/component/registry/types.ts b/src/component/registry/types.ts
index afe4d12528..4ca3d4afde 100644
--- a/src/component/registry/types.ts
+++ b/src/component/registry/types.ts
@@ -162,7 +162,7 @@ export type ShapeStyleUpdate = EdgeStyleUpdate & { fill?: Fill };
export type Stroke = StyleWithOpacity & {
/**
* Possible values are all HTML color names or HEX codes, as well as special keywords such as:
- * - `default` to use the color defined in the BPMN element default style
+ * - `default` to use the color defined in the BPMN element default style. **WARN**: this doesn't use the color set in the BPMN source when the "BPMN in Color" support is enabled.
* - `none` for no color
*/
color?: 'default' | 'none' | string;
@@ -183,15 +183,15 @@ export type Stroke = StyleWithOpacity & {
/**
* Note about properties that can be reset to default values.
*
- * Except for color, all style properties can be set in the BPMN diagram via LabelStyle and can then override the default values. Currently, there is no way to know if
- * they are overridden. So it is not possible to reset each property with the "Update Style" API.
+ * Except for color (when the "BPMN in Color" support is disabled), all style properties can be set in the BPMN diagram via LabelStyle and can then override the default values.
+ * Currently, there is no way to know if they are overridden. So it is not possible to reset each property with the "Update Style" API.
*
* @category Element Style
*/
export type Font = StyleWithOpacity & {
/**
* Possible values are all HTML color names or HEX codes, as well as special keywords such as:
- * - `default` to use the color defined in the BPMN element default style
+ * - `default` to use the color defined in the BPMN element default style. **WARN**: this doesn't use the color set in the BPMN source when the "BPMN in Color" support is enabled.
*/
color?: 'default' | string;
@@ -229,7 +229,7 @@ export type Font = StyleWithOpacity & {
export type Fill = StyleWithOpacity & {
/**
* Possible values are all HTML color names or HEX codes, as well as special keywords such as:
- * - `default` to use the color defined in the BPMN element default style
+ * - `default` to use the color defined in the BPMN element default style. **WARN**: this doesn't use the color set in the BPMN source when the "BPMN in Color" support is enabled.
* - `none` for no color
*/
color?: 'default' | 'none' | string;
diff --git a/src/model/bpmn/internal/Label.ts b/src/model/bpmn/internal/Label.ts
index 2abb9bf89a..7c7e35264c 100644
--- a/src/model/bpmn/internal/Label.ts
+++ b/src/model/bpmn/internal/Label.ts
@@ -15,11 +15,14 @@ limitations under the License.
*/
import type Bounds from './Bounds';
+import type { LabelExtensions } from './types';
/**
* @internal
*/
export default class Label {
+ readonly extensions: LabelExtensions = {};
+
constructor(readonly font?: Font, readonly bounds?: Bounds) {}
}
diff --git a/src/model/bpmn/internal/edge/edge.ts b/src/model/bpmn/internal/edge/edge.ts
index 9309b6b106..630817ff6e 100644
--- a/src/model/bpmn/internal/edge/edge.ts
+++ b/src/model/bpmn/internal/edge/edge.ts
@@ -17,11 +17,14 @@ limitations under the License.
import type Label from '../Label';
import type { Flow } from './flows';
import { MessageVisibleKind } from './kinds';
+import type { EdgeExtensions } from '../types';
/**
* @internal
*/
export class Edge {
+ readonly extensions: EdgeExtensions = {};
+
constructor(
readonly id: string,
readonly bpmnElement: Flow,
diff --git a/src/model/bpmn/internal/shape/Shape.ts b/src/model/bpmn/internal/shape/Shape.ts
index bc572bd2cf..4675bc0851 100644
--- a/src/model/bpmn/internal/shape/Shape.ts
+++ b/src/model/bpmn/internal/shape/Shape.ts
@@ -17,10 +17,13 @@ limitations under the License.
import type ShapeBpmnElement from './ShapeBpmnElement';
import type Bounds from '../Bounds';
import type Label from '../Label';
+import type { ShapeExtensions } from '../types';
/**
* @internal
*/
export default class Shape {
+ readonly extensions: ShapeExtensions = {};
+
constructor(readonly id: string, readonly bpmnElement: ShapeBpmnElement, readonly bounds?: Bounds, readonly label?: Label, readonly isHorizontal?: boolean) {}
}
diff --git a/src/model/bpmn/internal/types.ts b/src/model/bpmn/internal/types.ts
new file mode 100644
index 0000000000..d35287532d
--- /dev/null
+++ b/src/model/bpmn/internal/types.ts
@@ -0,0 +1,37 @@
+/*
+Copyright 2023 Bonitasoft S.A.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+/**
+ * @internal
+ */
+export type ShapeExtensions = {
+ fillColor?: string;
+ strokeColor?: string;
+};
+
+/**
+ * @internal
+ */
+export type EdgeExtensions = {
+ strokeColor?: string;
+};
+
+/**
+ * @internal
+ */
+export type LabelExtensions = {
+ color?: string;
+};
diff --git a/test/e2e/__image_snapshots__/bpmn-colors/enabled/elements.colors.01.no.label.png b/test/e2e/__image_snapshots__/bpmn-colors/enabled/elements.colors.01.no.label.png
new file mode 100644
index 0000000000..98173e7589
Binary files /dev/null and b/test/e2e/__image_snapshots__/bpmn-colors/enabled/elements.colors.01.no.label.png differ
diff --git a/test/e2e/__image_snapshots__/bpmn-colors/enabled/elements.colors.02.labels.png b/test/e2e/__image_snapshots__/bpmn-colors/enabled/elements.colors.02.labels.png
new file mode 100644
index 0000000000..15de7ca927
Binary files /dev/null and b/test/e2e/__image_snapshots__/bpmn-colors/enabled/elements.colors.02.labels.png differ
diff --git a/test/e2e/__image_snapshots__/bpmn-colors/ignored/elements.colors.01.no.label.png b/test/e2e/__image_snapshots__/bpmn-colors/ignored/elements.colors.01.no.label.png
new file mode 100644
index 0000000000..379ae4056c
Binary files /dev/null and b/test/e2e/__image_snapshots__/bpmn-colors/ignored/elements.colors.01.no.label.png differ
diff --git a/test/e2e/__image_snapshots__/bpmn-colors/ignored/elements.colors.02.labels.png b/test/e2e/__image_snapshots__/bpmn-colors/ignored/elements.colors.02.labels.png
new file mode 100644
index 0000000000..fc1da3c5bc
Binary files /dev/null and b/test/e2e/__image_snapshots__/bpmn-colors/ignored/elements.colors.02.labels.png differ
diff --git a/test/e2e/bpmn.colors.test.ts b/test/e2e/bpmn.colors.test.ts
new file mode 100644
index 0000000000..b9571fc1cb
--- /dev/null
+++ b/test/e2e/bpmn.colors.test.ts
@@ -0,0 +1,131 @@
+/*
+Copyright 2023 Bonitasoft S.A.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import type { ImageSnapshotThresholdConfig } from './helpers/visu/image-snapshot-config';
+import { ImageSnapshotConfigurator, MultiBrowserImageSnapshotThresholds } from './helpers/visu/image-snapshot-config';
+import { AvailableTestPages, PageTester } from '@test/shared/visu/bpmn-page-utils';
+import { getBpmnDiagramNames } from '@test/shared/visu/test-utils';
+import type { Page } from 'playwright';
+
+class ImageSnapshotThresholdsModelColors extends MultiBrowserImageSnapshotThresholds {
+ constructor() {
+ // threshold for webkit is taken from macOS only
+ super({ chromium: 0 / 100, firefox: 0.006 / 100, webkit: 0.07 / 100 });
+ }
+
+ protected override getChromiumThresholds(): Map {
+ return new Map([
+ [
+ 'elements.colors.02.labels',
+ {
+ linux: 0.0004 / 100, // 0.0003629132815818892%
+ macos: 0.15 / 100, // 0.14367268757742302%
+ windows: 0.1 / 100, // 0.09608375248252311%
+ },
+ ],
+ ]);
+ }
+
+ protected override getFirefoxThresholds(): Map {
+ return new Map([
+ [
+ 'elements.colors.02.labels',
+ {
+ linux: 0.06 / 100, // 0.053125174438761746%
+ macos: 0.31 / 100, // 0.3043122668906051%
+ windows: 1.76 / 100, // 1.7546495847922894%
+ },
+ ],
+ ]);
+ }
+
+ protected override getWebkitThresholds(): Map {
+ return new Map([
+ [
+ 'elements.colors.02.labels',
+ {
+ macos: 0.41 / 100, // 0.40831372531949794%
+ },
+ ],
+ ]);
+ }
+}
+
+class ImageSnapshotThresholdsIgnoreBpmnColors extends MultiBrowserImageSnapshotThresholds {
+ constructor() {
+ // threshold for webkit is taken from macOS only
+ super({ chromium: 0 / 100, firefox: 0.007 / 100, webkit: 0.07 / 100 });
+ }
+
+ protected override getChromiumThresholds(): Map {
+ return new Map([
+ [
+ 'elements.colors.02.labels',
+ {
+ linux: 0.0004 / 100, // 0.000370781000469389%
+ macos: 0.12 / 100, // 0.1103917951008726%
+ windows: 0.12 / 100, // 0.117545223258686%
+ },
+ ],
+ ]);
+ }
+
+ protected override getFirefoxThresholds(): Map {
+ return new Map([
+ [
+ 'elements.colors.02.labels',
+ {
+ linux: 0.06 / 100, // 0.05323299012142124%
+ macos: 0.16 / 100, // 0.15175768637681886%
+ windows: 1.91 / 100, // 1.9033668295464934%
+ },
+ ],
+ ]);
+ }
+
+ protected override getWebkitThresholds(): Map {
+ return new Map([
+ [
+ 'elements.colors.02.labels',
+ {
+ macos: 0.49 / 100, // 0.483122009334358%
+ },
+ ],
+ ]);
+ }
+}
+
+describe('BPMN in color', () => {
+ const diagramSubfolder = 'bpmn-in-color';
+ const pageTester = new PageTester({ targetedPage: AvailableTestPages.BPMN_RENDERING, diagramSubfolder }, page);
+ const bpmnDiagramNames = getBpmnDiagramNames(diagramSubfolder);
+
+ describe.each([false, true])('Ignore BPMN colors: %s', (ignoreBpmnColors: boolean) => {
+ const imageSnapshotConfigurator = ignoreBpmnColors
+ ? new ImageSnapshotConfigurator(new ImageSnapshotThresholdsIgnoreBpmnColors(), 'bpmn-colors/ignored')
+ : new ImageSnapshotConfigurator(new ImageSnapshotThresholdsModelColors(), 'bpmn-colors/enabled');
+
+ it.each(bpmnDiagramNames)(`%s`, async (bpmnDiagramName: string) => {
+ await pageTester.gotoPageAndLoadBpmnDiagram(bpmnDiagramName, {
+ rendererIgnoreBpmnColors: ignoreBpmnColors,
+ });
+
+ const image = await page.screenshot({ fullPage: true });
+ const config = imageSnapshotConfigurator.getConfig(bpmnDiagramName);
+ expect(image).toMatchImageSnapshot(config);
+ });
+ });
+});
diff --git a/test/fixtures/bpmn/bpmn-in-color/elements.colors.01.no.label.bpmn b/test/fixtures/bpmn/bpmn-in-color/elements.colors.01.no.label.bpmn
new file mode 100644
index 0000000000..4c3a32641c
--- /dev/null
+++ b/test/fixtures/bpmn/bpmn-in-color/elements.colors.01.no.label.bpmn
@@ -0,0 +1,283 @@
+
+
+
+
+
+
+
+
+
+
+
+ Flow_00l9sfs
+
+
+ Flow_00l9sfs
+ Flow_1yr9txu
+
+
+ Flow_1yr9txu
+ Flow_0o2eyjm
+ Flow_0cu5s7f
+
+
+ Flow_0o2eyjm
+ Flow_1ver8t2
+
+
+ Flow_1ver8t2
+ Flow_00nvh1p
+ Flow_0olcg77
+
+
+ Flow_0olcg77
+
+
+ Flow_0cu5s7f
+ Flow_00nvh1p
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Flow_18vib1w
+
+
+ Flow_18vib1w
+ Flow_08tijh0
+
+
+
+ Flow_0fdyaaj
+
+
+ Flow_08tijh0
+ Flow_0fdyaaj
+
+ Flow_0d8h9mt
+
+
+ Flow_0d8h9mt
+ Flow_0zi4tez
+
+
+
+ Flow_0zi4tez
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Event_1qun40t
+ Activity_1dixmdj
+ Event_0nxsgkt
+
+
+ Activity_1qaspqe
+ Event_1r3cdfx
+
+
+
+ Flow_0rfekxl
+
+
+
+ Flow_0rfekxl
+ Flow_1ic37xl
+
+
+ Flow_1ic37xl
+ Flow_0vmb4c8
+
+
+
+
+ Flow_05emxs9
+
+
+
+ Flow_0vmb4c8
+ Flow_05emxs9
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/bpmn/bpmn-in-color/elements.colors.02.labels.bpmn b/test/fixtures/bpmn/bpmn-in-color/elements.colors.02.labels.bpmn
new file mode 100644
index 0000000000..756debab17
--- /dev/null
+++ b/test/fixtures/bpmn/bpmn-in-color/elements.colors.02.labels.bpmn
@@ -0,0 +1,102 @@
+
+
+
+
+ Flow_0bxrkvy
+
+
+ Flow_0bxrkvy
+ Flow_1hx9ajq
+
+
+
+ Flow_1hx9ajq
+ Flow_103f0ry
+ Flow_117pbz0
+
+
+
+ Flow_103f0ry
+ Flow_0zoqfe0
+
+
+
+ Flow_0zoqfe0
+
+
+
+ Flow_117pbz0
+ Flow_1ms7sac
+
+
+
+ Flow_1ms7sac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/bpmn/xml-parsing/bpmn-in-color/bpmn-in-color-spec-sample.bpmn b/test/fixtures/bpmn/xml-parsing/bpmn-in-color/bpmn-in-color-spec-sample.bpmn
new file mode 100644
index 0000000000..864d731718
--- /dev/null
+++ b/test/fixtures/bpmn/xml-parsing/bpmn-in-color/bpmn-in-color-spec-sample.bpmn
@@ -0,0 +1,113 @@
+
+
+
+
+ _703bc99e-e861-4841-9883-977244bf69a3
+
+
+
+ _703bc99e-e861-4841-9883-977244bf69a3
+ sequence_flow_1
+
+
+
+ _82e55eb0-f279-48e2-bbda-1f2617613586
+
+
+
+ sequence_flow_1
+ _e3aaaf68-3efa-47ff-b668-d4b06ed5ba92
+
+
+
+ _e3aaaf68-3efa-47ff-b668-d4b06ed5ba92
+ _82e55eb0-f279-48e2-bbda-1f2617613586
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ _54daf565-ab8f-495b-affa-1c2271e9ceb4
+ _54daf565-ab8f-495b-affa-1c2271e9ceb4
+
+
diff --git a/test/fixtures/bpmn/xml-parsing/bpmn-in-color/issue-2588-both-bpmn-in-color-and-bpmnio_infor-modeler.bpmn b/test/fixtures/bpmn/xml-parsing/bpmn-in-color/issue-2588-both-bpmn-in-color-and-bpmnio_infor-modeler.bpmn
new file mode 100644
index 0000000000..1c5b43b6f5
--- /dev/null
+++ b/test/fixtures/bpmn/xml-parsing/bpmn-in-color/issue-2588-both-bpmn-in-color-and-bpmnio_infor-modeler.bpmn
@@ -0,0 +1,642 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ StartEvent_1
+ Gateway_18umgze
+ Activity_1xbvij8
+ Activity_1u28o4v
+
+
+
+
+
+
+ Activity_1tqoq0m
+ Activity_10yabir
+ Activity_1emrcvt
+ Gateway_0gz71nz
+ Activity_0xdpuvu
+ Event_1wb4fqt
+ Event_1qdiom5
+ Event_0uagjvi
+ Event_1pwbyee
+ Activity_1rd7t4o
+
+
+
+
+
+
+ Activity_1emrcvt
+ Gateway_0gz71nz
+
+
+
+
+
+
+ Activity_1tqoq0m
+ Activity_10yabir
+ Event_1wb4fqt
+ Event_1qdiom5
+
+
+
+
+
+
+ Activity_0xdpuvu
+ Event_0uagjvi
+ Event_1pwbyee
+ Activity_1rd7t4o
+
+
+
+
+
+
+
+
+
+
+ Flow_0gb3efk
+
+
+
+
+
+
+
+
+ Flow_0gb3efk
+ Flow_1v3tww9
+
+
+
+
+
+
+ Flow_1v3tww9
+ Flow_047ue01
+ Flow_0ngzmge
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Flow_13j3r0o
+
+
+
+
+
+
+
+
+
+
+
+
+ Flow_047ue01
+ Flow_1jg44jo
+
+
+
+
+
+
+ Flow_0ngzmge
+ Flow_07l1ass
+
+
+
+
+
+
+
+ Flow_07l1ass
+ Flow_10obkll
+
+
+
+
+
+
+
+ Flow_10obkll
+ Flow_0kec6du
+
+
+
+
+
+ Flow_0kec6du
+ Flow_1jg44jo
+ Flow_13j3r0o
+ Flow_0p4tjgd
+
+
+
+
+
+
+ Flow_1dp2n84
+
+
+
+
+
+
+
+
+ Flow_1dp2n84
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Flow_133etz3
+ Flow_0lzo1rz
+
+
+
+
+
+
+
+ Flow_0p4tjgd
+ Flow_133etz3
+
+
+
+
+
+
+ Flow_1mue7fk
+
+
+
+
+
+
+ Flow_1mue7fk
+ Flow_1z085yv
+ Flow_0satjv8
+
+
+
+
+
+
+
+ Flow_1z085yv
+
+
+
+
+
+
+
+ Flow_0satjv8
+
+
+
+
+
+
+
+
+
+ Flow_0lzo1rz
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ annotation
+
+
+
+
+
+
+
+
+ random message
+
+
+
+
+
+
+
+ random it system
+
+
+
+
+
+
+
+
+
+
+ random imge
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/integration/mxGraph.model.bpmn.colors.test.ts b/test/integration/mxGraph.model.bpmn.colors.test.ts
new file mode 100644
index 0000000000..383670c5cb
--- /dev/null
+++ b/test/integration/mxGraph.model.bpmn.colors.test.ts
@@ -0,0 +1,46 @@
+/*
+Copyright 2023 Bonitasoft S.A.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { bpmnVisualization } from './helpers/model-expect';
+import { readFileSync } from '@test/shared/file-helper';
+
+describe('mxGraph model - BPMN colors', () => {
+ it('Colors are ignored by default', () => {
+ bpmnVisualization.load(readFileSync('../fixtures/bpmn/xml-parsing/bpmn-in-color/bpmn-in-color-spec-sample.bpmn'));
+
+ expect('task_orange_border').toBeTask({
+ // no colors
+ font: {
+ family: 'Arial',
+ size: 8,
+ },
+ // not under test
+ label: 'Orange Border',
+ parentId: '1',
+ verticalAlign: 'top',
+ });
+
+ expect('sequence_flow_1').toBeSequenceFlow({
+ // no colors
+ font: {
+ family: 'Arial',
+ size: 8,
+ },
+ // not under test
+ label: 'Orange link',
+ });
+ });
+});
diff --git a/test/shared/visu/bpmn-page-utils.ts b/test/shared/visu/bpmn-page-utils.ts
index 639989fd10..6b3da31077 100644
--- a/test/shared/visu/bpmn-page-utils.ts
+++ b/test/shared/visu/bpmn-page-utils.ts
@@ -133,6 +133,7 @@ export interface PageOptions {
styleOptions?: StyleOptions;
bpmnElementIdToCollapse?: string;
poolIdsToFilter?: string | string[];
+ rendererIgnoreBpmnColors?: boolean;
}
export interface Point {
@@ -229,11 +230,11 @@ export class PageTester {
}
// other options
- const bpmnElementIdToCollapse = otherPageOptions?.bpmnElementIdToCollapse;
- bpmnElementIdToCollapse && (url += `&bpmn.element.id.collapsed=${bpmnElementIdToCollapse}`);
- const poolIdsToFilter = otherPageOptions?.poolIdsToFilter;
+ otherPageOptions?.bpmnElementIdToCollapse && (url += `&bpmn.element.id.collapsed=${otherPageOptions.bpmnElementIdToCollapse}`);
// the array is transformed into string with the 'comma' separator, as expected by the page
- poolIdsToFilter && (url += `&bpmn.filter.pool.ids=${poolIdsToFilter}`);
+ otherPageOptions?.poolIdsToFilter && (url += `&bpmn.filter.pool.ids=${otherPageOptions.poolIdsToFilter}`);
+ // renderer options
+ otherPageOptions?.rendererIgnoreBpmnColors !== undefined && (url += `&renderer.ignore.bpmn.colors=${otherPageOptions.rendererIgnoreBpmnColors}`);
return url;
}
diff --git a/test/unit/component/mxgraph/renderer/StyleComputer.test.ts b/test/unit/component/mxgraph/renderer/StyleComputer.test.ts
index f6ad5c60ab..1cf06fdccd 100644
--- a/test/unit/component/mxgraph/renderer/StyleComputer.test.ts
+++ b/test/unit/component/mxgraph/renderer/StyleComputer.test.ts
@@ -39,6 +39,7 @@ import {
ShapeBpmnEventDefinitionKind,
ShapeBpmnMarkerKind,
ShapeBpmnSubProcessKind,
+ ShapeUtil,
} from '@lib/model/bpmn/internal';
import Label, { Font } from '@lib/model/bpmn/internal/Label';
import { Edge } from '@lib/model/bpmn/internal/edge/edge';
@@ -54,6 +55,12 @@ function newLabel(font: ExpectedFont, bounds?: Bounds): Label {
return new Label(toFont(font), bounds);
}
+function newLabelExtension(color: string): Label {
+ const label = new Label(undefined, undefined);
+ label.extensions.color = color;
+ return label;
+}
+
/**
* Returns a new `Shape` instance with arbitrary id and `undefined` bounds.
*/
@@ -118,6 +125,7 @@ function newAssociationFlow(kind: AssociationDirectionKind): AssociationFlow {
}
describe('Style Computer', () => {
+ // use a shared instance to check that there is no state stored in the implementation
const styleComputer = new StyleComputer();
// shortcut as the current computeStyle implementation requires to pass the BPMN label bounds as extra argument
@@ -459,4 +467,68 @@ describe('Style Computer', () => {
},
);
});
+
+ describe('compute style - colors', () => {
+ describe.each([[undefined], [false], [true]])(`Ignore BPMN colors: %s`, (ignoreBpmnColors: boolean) => {
+ // 'undefined' RendererOptions tested in other tests in this file
+ const styleComputer = new StyleComputer(ignoreBpmnColors === undefined ? {} : { ignoreBpmnColors: ignoreBpmnColors });
+ const expectAdditionalColorsStyle = !(ignoreBpmnColors ?? true);
+
+ function computeStyleWithRendererOptions(element: Shape | Edge): string {
+ return styleComputer.computeStyle(element, element.label?.bounds);
+ }
+
+ function computeMessageFlowIconStyleWithRendererOptions(edge: Edge): string {
+ return styleComputer.computeMessageFlowIconStyle(edge);
+ }
+
+ describe('shapes', () => {
+ it.each(Object.values(ShapeUtil.flowNodeKinds()))('%s', (kind: ShapeBpmnElementKind) => {
+ const shape = newShape(newShapeBpmnElement(kind), newLabelExtension('#010101'));
+ shape.extensions.fillColor = '#000003';
+ shape.extensions.strokeColor = '#FF0203';
+ const additionalColorsStyle = expectAdditionalColorsStyle ? ';fillColor=#000003;strokeColor=#FF0203;fontColor=#010101' : '';
+ expect(computeStyleWithRendererOptions(shape)).toBe(`${kind}${additionalColorsStyle}`);
+ });
+ it.each([ShapeBpmnElementKind.LANE, ShapeBpmnElementKind.POOL])('%s', (kind: ShapeBpmnElementKind) => {
+ const shape = newShape(newShapeBpmnElement(kind), newLabelExtension('#aa0101'));
+ shape.extensions.fillColor = '#AA0003';
+ shape.extensions.strokeColor = '#FF02AA';
+ const additionalColorsStyle = expectAdditionalColorsStyle ? ';fillColor=#AA0003;swimlaneFillColor=#AA0003;strokeColor=#FF02AA;fontColor=#aa0101' : '';
+ expect(computeStyleWithRendererOptions(shape)).toBe(`${kind};horizontal=1${additionalColorsStyle}`);
+ });
+ it('no extension', () => {
+ const shape = newShape(newShapeBpmnElement(ShapeBpmnElementKind.TASK));
+ expect(computeStyleWithRendererOptions(shape)).toBe(`task`);
+ });
+ });
+
+ describe('edges', () => {
+ it('sequence flow', () => {
+ const edge = new Edge('id', newSequenceFlow(SequenceFlowKind.DEFAULT), undefined, newLabelExtension('#aaaaaa'));
+ edge.extensions.strokeColor = '#111111';
+ const additionalColorsStyle = expectAdditionalColorsStyle ? ';strokeColor=#111111;fontColor=#aaaaaa' : '';
+ expect(computeStyleWithRendererOptions(edge)).toBe(`sequenceFlow;default${additionalColorsStyle}`);
+ });
+ it('message flow', () => {
+ const edge = new Edge('id', newMessageFlow(), undefined, newLabelExtension('#aaaabb'));
+ edge.extensions.strokeColor = '#1111bb';
+ const additionalColorsStyle = expectAdditionalColorsStyle ? ';strokeColor=#1111bb;fontColor=#aaaabb' : '';
+ expect(computeStyleWithRendererOptions(edge)).toBe(`messageFlow${additionalColorsStyle}`);
+ });
+ it('message flow icon', () => {
+ const edge = new Edge('id', newMessageFlow());
+ edge.extensions.strokeColor = '#11aabb';
+ const additionalColorsStyle = expectAdditionalColorsStyle ? ';strokeColor=#11aabb' : '';
+ expect(computeMessageFlowIconStyleWithRendererOptions(edge)).toBe(`shape=bpmn.messageFlowIcon;bpmn.isInitiating=false${additionalColorsStyle}`);
+ });
+ it('association flow', () => {
+ const edge = new Edge('id', newAssociationFlow(AssociationDirectionKind.ONE), undefined, newLabelExtension('#aaaacc'));
+ edge.extensions.strokeColor = '#1111cc';
+ const additionalColorsStyle = expectAdditionalColorsStyle ? ';strokeColor=#1111cc;fontColor=#aaaacc' : '';
+ expect(computeStyleWithRendererOptions(edge)).toBe(`association;One${additionalColorsStyle}`);
+ });
+ });
+ });
+ });
});
diff --git a/test/unit/component/parser/BpmnParser.test.ts b/test/unit/component/parser/BpmnParser.test.ts
index 328ef40e86..337f040e2a 100644
--- a/test/unit/component/parser/BpmnParser.test.ts
+++ b/test/unit/component/parser/BpmnParser.test.ts
@@ -14,10 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { ShapeBpmnElementKind } from '@lib/model/bpmn/internal';
+import type BpmnModel from '@lib/model/bpmn/internal/BpmnModel';
import { newBpmnParser } from '@lib/component/parser/BpmnParser';
-
import { readFileSync } from '@test/shared/file-helper';
-import { expectPoolLaneEdgeFlowNode } from '../../helpers/JsonTestUtils';
+import { expectPoolLaneEdgeFlowNode, verifyLabel } from '../../helpers/JsonTestUtils';
+import { getEdgeByBpmnElementId, getFlowNodeByBpmnElementId, getLaneByBpmnElementId, getPoolByBpmnElementId, verifyEdge, verifyShape } from '../../helpers/bpmn-model-expect';
describe('parse xml to model', () => {
it('Single process with no participant', () => {
@@ -46,4 +48,245 @@ describe('parse xml to model', () => {
expect(() => newBpmnParser().parse(readFileSync('../fixtures/bpmn/xml-parsing/special/xml-but-not-bpmn.xml'))).toThrow(parsingErrorMessage);
});
});
+
+ describe('BPMN In Color support', () => {
+ // pools and lanes are tested with "bpmn.io colors attributes"
+
+ describe('Parse specification sample', () => {
+ let model: BpmnModel;
+ beforeEach(() => {
+ model = newBpmnParser().parse(readFileSync('../fixtures/bpmn/xml-parsing/bpmn-in-color/bpmn-in-color-spec-sample.bpmn'));
+ });
+
+ it('Task', () => {
+ const shape = getFlowNodeByBpmnElementId(model, 'task_orange_border');
+ verifyShape(shape, {
+ shapeId: `task_orange_border_shape_id`,
+ bpmnElementId: `task_orange_border`,
+ bpmnElementName: 'Orange Border',
+ bpmnElementKind: ShapeBpmnElementKind.TASK,
+ bpmnElementIncomingIds: ['_703bc99e-e861-4841-9883-977244bf69a3'],
+ bpmnElementOutgoingIds: ['sequence_flow_1'],
+ bounds: { x: 230, y: 168, width: 96, height: 76 },
+ extensions: {
+ fillColor: '#ffffff',
+ strokeColor: '#FF6600',
+ },
+ });
+ verifyLabel(shape.label, {
+ bounds: { x: 230, y: 200, width: 96, height: 12 },
+ extensions: { color: '#000000' },
+ font: { isBold: false, isItalic: false, isStrikeThrough: false, isUnderline: false, name: 'Arial', size: 8 },
+ });
+ });
+
+ it('End Event', () => {
+ const shape = getFlowNodeByBpmnElementId(model, 'endEvent_red_font');
+ verifyShape(shape, {
+ shapeId: `endEvent_red_font_shape_id`,
+ bpmnElementId: `endEvent_red_font`,
+ bpmnElementName: 'Red Font',
+ bpmnElementKind: ShapeBpmnElementKind.EVENT_END,
+ bpmnElementIncomingIds: ['_82e55eb0-f279-48e2-bbda-1f2617613586'],
+ bounds: { x: 788, y: 188, width: 36, height: 36 },
+ extensions: {
+ fillColor: '#ffffff',
+ strokeColor: '#000000',
+ },
+ });
+ verifyLabel(shape.label, {
+ bounds: { x: 751, y: 234, width: 110, height: 12 },
+ extensions: { color: '#FF0000' },
+ font: { isBold: false, isItalic: false, isStrikeThrough: false, isUnderline: false, name: 'Arial', size: 8 },
+ });
+ });
+
+ it('Sequence Flow', () => {
+ const edge = getEdgeByBpmnElementId(model, 'sequence_flow_1');
+ verifyEdge(edge, {
+ edgeId: `sequence_flow_1_edge_id`,
+ bpmnElementId: `sequence_flow_1`,
+ bpmnElementName: 'Orange link',
+ bpmnElementSourceRefId: 'task_orange_border',
+ bpmnElementTargetRefId: '_228e45d8-ece8-42a3-b11c-89695e468954',
+ waypoints: [
+ { x: 325, y: 206 },
+ { x: 417, y: 206 },
+ ],
+ extensions: {
+ strokeColor: '#FF6600',
+ },
+ });
+ verifyLabel(edge.label, {
+ bounds: { x: 316.359375, y: 211, width: 110, height: 12 },
+ extensions: { color: '#000000' },
+ font: { isBold: false, isItalic: false, isStrikeThrough: false, isUnderline: false, name: 'Arial', size: 8 },
+ });
+ });
+ });
+ });
+
+ // Test fallback to bpmn.io colors when not set with bpmn-in-color
+ describe('Parse sample with both bpmn.io and bpmn-in-color attributes', () => {
+ let model: BpmnModel;
+ beforeEach(() => {
+ model = newBpmnParser().parse(readFileSync('../fixtures/bpmn/xml-parsing/bpmn-in-color/issue-2588-both-bpmn-in-color-and-bpmnio_infor-modeler.bpmn'));
+ });
+
+ it('Gateway with only bpmn.io color attributes', () => {
+ const shape = getFlowNodeByBpmnElementId(model, 'Gateway_18umgze');
+ verifyShape(shape, {
+ shapeId: `Gateway_18umgze_di`,
+ bpmnElementId: `Gateway_18umgze`,
+ bpmnElementName: 'question?',
+ bpmnElementKind: ShapeBpmnElementKind.GATEWAY_EXCLUSIVE,
+ bpmnElementIncomingIds: ['Flow_1v3tww9'],
+ bpmnElementOutgoingIds: ['Flow_047ue01', 'Flow_0ngzmge'],
+ bounds: { x: 135, y: 115, width: 50, height: 50 },
+ extensions: {
+ fillColor: '#FFD726',
+ strokeColor: '#000000',
+ },
+ parentId: 'Lane_0jqhnmr',
+ });
+ verifyLabel(shape.label, {
+ bounds: { x: 136, y: 175, width: 48, height: 14 },
+ });
+ });
+
+ it('Business Rule Task with both bpmn-in-color and bpmn.io color attributes', () => {
+ const shape = getFlowNodeByBpmnElementId(model, 'Activity_1emrcvt');
+ verifyShape(shape, {
+ shapeId: `Activity_1uhp7fi_di`,
+ bpmnElementId: `Activity_1emrcvt`,
+ bpmnElementName: 'Task red',
+ bpmnElementKind: ShapeBpmnElementKind.TASK_BUSINESS_RULE,
+ bpmnElementIncomingIds: ['Flow_10obkll'],
+ bpmnElementOutgoingIds: ['Flow_0kec6du'],
+ bounds: { x: 480, y: -250, width: 100, height: 80 },
+ extensions: {
+ fillColor: '#da1217',
+ strokeColor: '#8abff7',
+ },
+ parentId: 'Lane_0t0ihv3',
+ });
+ verifyLabel(shape.label, {
+ extensions: {
+ color: '#8abff7',
+ },
+ });
+ });
+
+ it('Sequence Flow with only bpmn.io color attributes', () => {
+ const edge = getEdgeByBpmnElementId(model, 'Flow_0satjv8');
+ verifyEdge(edge, {
+ edgeId: `Flow_0satjv8_di`,
+ bpmnElementId: `Flow_0satjv8`,
+ bpmnElementSourceRefId: 'Gateway_1r1qcq2',
+ bpmnElementTargetRefId: 'Event_1jnrrxt',
+ waypoints: [
+ { x: 920, y: -465 },
+ { x: 920, y: -380 },
+ { x: 1012, y: -380 },
+ ],
+ extensions: {
+ strokeColor: '#ff00ff',
+ },
+ });
+ verifyLabel(edge.label, undefined);
+ });
+
+ it('Sequence Flow with both bpmn-in-color and bpmn.io color attributes', () => {
+ const edge = getEdgeByBpmnElementId(model, 'Flow_1z085yv');
+ verifyEdge(edge, {
+ edgeId: `Flow_1z085yv_di`,
+ bpmnElementId: `Flow_1z085yv`,
+ bpmnElementSourceRefId: 'Gateway_1r1qcq2',
+ bpmnElementTargetRefId: 'Event_0g32488',
+ waypoints: [
+ { x: 945, y: -490 },
+ { x: 979, y: -490 },
+ { x: 979, y: -520 },
+ { x: 1012, y: -520 },
+ ],
+ extensions: {
+ strokeColor: '#8abff7',
+ },
+ });
+ verifyLabel(edge.label, {
+ extensions: { color: '#8abff7' },
+ });
+ });
+
+ it('Participant/Pool with only bpmn.io color attributes', () => {
+ const shape = getPoolByBpmnElementId(model, 'Participant_1f8y1kd');
+ verifyShape(shape, {
+ shapeId: `Participant_1f8y1kd_di`,
+ bpmnElementId: `Participant_1f8y1kd`,
+ bpmnElementName: 'IPC for Infor STuff',
+ bpmnElementKind: ShapeBpmnElementKind.POOL,
+ isHorizontal: true,
+ bounds: { x: -230, y: -630, width: 1720, height: 910 },
+ extensions: {
+ fillColor: '#ffffff',
+ strokeColor: '#000000',
+ },
+ });
+ verifyLabel(shape.label, undefined);
+ });
+
+ it('Participant/Pool with both bpmn-in-color and bpmn.io color attributes', () => {
+ const shape = getPoolByBpmnElementId(model, 'Participant_1hnwl5i');
+ verifyShape(shape, {
+ shapeId: `Participant_1s4tfww_di`,
+ bpmnElementId: `Participant_1hnwl5i`,
+ bpmnElementName: 'collapsed pool',
+ bpmnElementKind: ShapeBpmnElementKind.POOL,
+ isHorizontal: true,
+ bounds: { x: -230, y: -740, width: 1720, height: 60 },
+ extensions: {
+ fillColor: '#55a3f3',
+ strokeColor: '#000000',
+ },
+ bpmnElementIncomingIds: ['Association_1buem20'],
+ });
+ verifyLabel(shape.label, undefined);
+ });
+
+ it('Lane with only bpmn.io color attributes', () => {
+ const shape = getLaneByBpmnElementId(model, 'Lane_0jqhnmr');
+ verifyShape(shape, {
+ shapeId: `Lane_0jqhnmr_di`,
+ bpmnElementId: `Lane_0jqhnmr`,
+ bpmnElementName: 'Role 1 1st Hierarchy',
+ bpmnElementKind: ShapeBpmnElementKind.LANE,
+ isHorizontal: true,
+ bounds: { x: -200, y: 20, width: 1690, height: 260 },
+ extensions: {
+ fillColor: '#f1f1f1',
+ strokeColor: '#000000',
+ },
+ parentId: 'Participant_1f8y1kd',
+ });
+ verifyLabel(shape.label, undefined);
+ });
+
+ it('Lane with both bpmn-in-color and bpmn.io color attributes', () => {
+ const shape = getLaneByBpmnElementId(model, 'Lane_0t0ihv3');
+ verifyShape(shape, {
+ shapeId: `Lane_0t0ihv3_di`,
+ bpmnElementId: `Lane_0t0ihv3`,
+ bpmnElementName: 'Role 2',
+ bpmnElementKind: ShapeBpmnElementKind.LANE,
+ isHorizontal: true,
+ bounds: { x: -170, y: -280, width: 1660, height: 140 },
+ extensions: {
+ fillColor: '#f1f1f1',
+ strokeColor: '#000000',
+ },
+ parentId: 'Lane_0yqg0gw',
+ });
+ verifyLabel(shape.label, undefined);
+ });
+ });
});
diff --git a/test/unit/helpers/JsonTestUtils.ts b/test/unit/helpers/JsonTestUtils.ts
index a67562c758..6091277e7a 100644
--- a/test/unit/helpers/JsonTestUtils.ts
+++ b/test/unit/helpers/JsonTestUtils.ts
@@ -22,7 +22,7 @@ import type Label from '@lib/model/bpmn/internal/Label';
import type { BpmnJsonModel } from '@lib/model/bpmn/json/BPMN20';
import type { JsonParsingWarning } from '@lib/component/parser/parsing-messages';
import { ParsingMessageCollector } from '@lib/component/parser/parsing-messages';
-import type { ExpectedBounds, ExpectedFont } from './bpmn-model-expect';
+import type { ExpectedBounds, ExpectedFont, ExpectedLabel } from './bpmn-model-expect';
class ParsingMessageCollectorTester extends ParsingMessageCollector {
private warnings: Array = [];
@@ -152,6 +152,17 @@ export function verifyLabelBounds(label: Label, expectedBounds?: ExpectedBounds)
}
}
+export function verifyLabel(label: Label, expectedLabel: ExpectedLabel): void {
+ if (!expectedLabel) {
+ expect(label).toBeUndefined();
+ return;
+ }
+
+ verifyLabelBounds(label, expectedLabel.bounds);
+ expect(label.extensions).toEqual(expectedLabel.extensions ?? {});
+ verifyLabelFont(label, expectedLabel.font);
+}
+
export function parseJsonAndExpectEvent(json: BpmnJsonModel, eventDefinitionKind: ShapeBpmnEventDefinitionKind, expectedNumber: number): BpmnModel {
const model = parseJson(json);
diff --git a/test/unit/helpers/bpmn-model-expect.ts b/test/unit/helpers/bpmn-model-expect.ts
index 3ffac2aeec..f30161e21a 100644
--- a/test/unit/helpers/bpmn-model-expect.ts
+++ b/test/unit/helpers/bpmn-model-expect.ts
@@ -15,11 +15,13 @@ limitations under the License.
*/
import type { GlobalTaskKind, ShapeBpmnCallActivityKind, ShapeBpmnElementKind, ShapeBpmnEventDefinitionKind, ShapeBpmnMarkerKind } from '@lib/model/bpmn/internal';
+import { FlowKind, MessageVisibleKind, SequenceFlowKind } from '@lib/model/bpmn/internal';
+import type BpmnModel from '@lib/model/bpmn/internal/BpmnModel';
import type { Edge, Waypoint } from '@lib/model/bpmn/internal/edge/edge';
import type Shape from '@lib/model/bpmn/internal/shape/Shape';
import { ShapeBpmnActivity, ShapeBpmnBoundaryEvent, ShapeBpmnCallActivity, ShapeBpmnEvent } from '@lib/model/bpmn/internal/shape/ShapeBpmnElement';
import { SequenceFlow } from '@lib/model/bpmn/internal/edge/flows';
-import { FlowKind, MessageVisibleKind, SequenceFlowKind } from '@lib/model/bpmn/internal';
+import type { EdgeExtensions, LabelExtensions, ShapeExtensions } from '@lib/model/bpmn/internal/types';
export interface ExpectedShape {
shapeId: string;
@@ -31,6 +33,7 @@ export interface ExpectedShape {
parentId?: string;
bounds?: ExpectedBounds;
isHorizontal?: boolean;
+ extensions?: ShapeExtensions;
}
export interface ExpectedActivityShape extends ExpectedShape {
@@ -58,12 +61,19 @@ export interface ExpectedEdge {
bpmnElementTargetRefId: string;
waypoints: Waypoint[];
messageVisibleKind?: MessageVisibleKind;
+ extensions?: EdgeExtensions;
}
export interface ExpectedSequenceEdge extends ExpectedEdge {
bpmnElementSequenceFlowKind?: SequenceFlowKind;
}
+export type ExpectedLabel = {
+ bounds?: ExpectedBounds;
+ extensions?: LabelExtensions;
+ font?: ExpectedFont;
+};
+
export interface ExpectedFont {
name?: string;
size?: number;
@@ -128,6 +138,8 @@ export const verifyShape = (
expect(bounds.width).toEqual(expectedBounds.width);
expect(bounds.height).toEqual(expectedBounds.height);
}
+
+ expect(shape.extensions).toEqual(expectedShape.extensions ?? {});
};
export const verifyEdge = (edge: Edge, expectedValue: ExpectedEdge | ExpectedSequenceEdge): void => {
@@ -155,4 +167,14 @@ export const verifyEdge = (edge: Edge, expectedValue: ExpectedEdge | ExpectedSeq
expect(bpmnElement.sequenceFlowKind).toEqual(SequenceFlowKind.NORMAL);
}
}
+
+ expect(edge.extensions).toEqual(expectedValue.extensions ?? {});
};
+
+export const getPoolByBpmnElementId = (model: BpmnModel, id: string): Shape => model.pools.find(shape => shape.bpmnElement.id === id);
+
+export const getLaneByBpmnElementId = (model: BpmnModel, id: string): Shape => model.lanes.find(shape => shape.bpmnElement.id === id);
+
+export const getFlowNodeByBpmnElementId = (model: BpmnModel, id: string): Shape => model.flowNodes.find(shape => shape.bpmnElement.id === id);
+
+export const getEdgeByBpmnElementId = (model: BpmnModel, id: string): Edge => model.edges.find(edge => edge.bpmnElement.id === id);