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 @@ + -
0..1
0..1
0..1
0..1
0..1
0..1
2..*
2..*
1
1
1
1
1
1
1
1
1..*
1..*
1
1
0
0
0..*
0..*
Extends
Extends
1
1
0..1
0..1
Edge- id: string- bpmnElement: SequenceFlow- waypoints: Waypoint[]- label: Label- messageVisibleKind: MessageVisibleKind = MessageVisibleKind.NONEShape- id: string- bpmnElement: ShapeBpmnElement- bounds: Bounds- label: Label- isHorizontal?: boolean- isMarkerVisible?: boolean- isMessageVisible?: booleanShapes- flowNodes: Shape[]- lanes: Shape[]- pools: Shape[]
Extends
Extends
Extends
Extends
1
1
Extends
Extends
1
1
0
0
Extends
Extends
Extends
Extends
Participant- name?: string- id: string- processRef?: stringShapeBpmnElement- id?: string- name?: string- kind: ShapeBpmnElementKind- parentId?: string- instantiate?: boolean = false- incomingIds?: string[] = []- outgoingIds?: string[] = []Definitions- collaboration?: Collaboration- process?: Process- bpmnModel: BpmnModel+ bpmnModel(): BpmnModelCollaborationProcessBpmnModel- edges: Edge[]Label- font?: Font- bounds?: BoundsFont- name?: string- size?: number- isBold?: boolean- isItalic?: boolean- isUnderline?: boolean- isStrikeThrough?: booleanBounds- x: number- y: number- width: number- height: numberFlow- id: string- name: string- sourceRefId: string- targetRefId: string- kind: FlowKindSequenceFlow- sequenceFlowKind: SequenceFlowKindSequenceFlowKindMessageVisibleKindWaypoint- x: number- y: number
1
1
1
1
FlowKindAssociationFlow
Extends
Extends
MessageFlow
1
1
AssociationDirectionKind
0..3
0..3
ShapeBpmnEvent- eventDefinitionKind: ShapeBpmnEventDefinitionKindShapeBpmnEventDefinitionKindShapeBpmnMarkerKindShapeBpmnSubProcess- subProcessKind: ShapeBpmnSubProcessKind
Extends
Extends
ShapeBpmnActivity- markers?: ShapeBpmnMarkerKind[] = []ShapeBpmnCallActivity- callActivityKind: ShapeBpmnCallActivityKind- globalTaskKind?: GlobalTaskKindShapeBpmnElementKindShapeBpmnCallActivityKind
1
1
ShapeBpmnBoundaryEvent- isInterrupting?: boolean = trueShapeBpmnStartEvent- isInterrupting?: boolean
Extends
Extends
ShapeBpmnEventBasedGateway- gatewayKind?: ShapeBpmnEventBasedGatewayKindShapeBpmnEventBasedGatewayKind
1
1
Extends
Extends
Extends
Extends
ShapeBpmnSubProcessKind
Text is not SVG - cannot display
\ No newline at end of file +
0..1
0..1
0..1
0..1
0..1
0..1
2..*
2..*
1
1
1
1
1
1
1
1
1..*
1..*
1
1
0
0
0..*
0..*
Extends
Extends
1
1
0..1
0..1
EdgeShape- id: string- bpmnElement: ShapeBpmnElement- bounds: Bounds- label: Label- isHorizontal?: boolean- isMarkerVisible?: boolean- isMessageVisible?: boolean- extensions: ShapeExtensionsShapes- flowNodes: Shape[]- lanes: Shape[]- pools: Shape[]
Extends
Extends
Extends
Extends
1
1
Extends
Extends
1
1
0
0
Extends
Extends
Extends
Extends
Participant- name?: string- id: string- processRef?: stringShapeBpmnElement- id?: string- name?: string- kind: ShapeBpmnElementKind- parentId?: string- instantiate?: boolean = false- incomingIds?: string[] = []- outgoingIds?: string[] = []Definitions- collaboration?: Collaboration- process?: Process- bpmnModel: BpmnModel+ bpmnModel(): BpmnModelCollaborationProcessBpmnModel- edges: Edge[]Label- font?: Font- bounds?: Bounds- extensions: LabelExtensionsFont- name?: string- size?: number- isBold?: boolean- isItalic?: boolean- isUnderline?: boolean- isStrikeThrough?: booleanBounds- x: number- y: number- width: number- height: numberFlow- id: string- name: string- sourceRefId: string- targetRefId: string- kind: FlowKindSequenceFlow- sequenceFlowKind: SequenceFlowKindSequenceFlowKindMessageVisibleKindWaypoint- x: number- y: number
1
1
1
1
FlowKindAssociationFlow
Extends
Extends
MessageFlow
1
1
AssociationDirectionKind
0..3
0..3
ShapeBpmnEvent- eventDefinitionKind: ShapeBpmnEventDefinitionKindShapeBpmnEventDefinitionKindShapeBpmnMarkerKindShapeBpmnSubProcess- subProcessKind: ShapeBpmnSubProcessKind
Extends
Extends
ShapeBpmnActivity- markers?: ShapeBpmnMarkerKind[] = []ShapeBpmnCallActivity- callActivityKind: ShapeBpmnCallActivityKind- globalTaskKind?: GlobalTaskKindShapeBpmnElementKindShapeBpmnCallActivityKind
1
1
ShapeBpmnBoundaryEvent- isInterrupting?: boolean = trueShapeBpmnStartEvent- isInterrupting?: boolean
Extends
Extends
ShapeBpmnEventBasedGateway- gatewayKind?: ShapeBpmnEventBasedGatewayKindShapeBpmnEventBasedGatewayKind
1
1
Extends
Extends
Extends
Extends
ShapeBpmnSubProcessKind- extensions: EdgeExtensions- id: string- bpmnElement: SequenceFlow- waypoints: Waypoint[]- label: Label- messageVisibleKind: MessageVisibleKind = MessageVisibleKind.NONE
Text is not SVG - cannot display
\ 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);