chore(draw): unify text rendering into service
A newly introduced TextRenderer is responsible for text rendering and text related bounds computation. This removes a bunch of code duplication, too.
This commit is contained in:
parent
4cd0a01df6
commit
4bb270f192
|
@ -7,7 +7,6 @@ import {
|
|||
} from 'min-dash';
|
||||
|
||||
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';
|
||||
import TextUtil from 'diagram-js/lib/util/Text';
|
||||
|
||||
import {
|
||||
isExpanded,
|
||||
|
@ -58,18 +57,13 @@ var RENDERER_IDS = new Ids();
|
|||
var TASK_BORDER_RADIUS = 10;
|
||||
var INNER_OUTER_DIST = 3;
|
||||
|
||||
var LABEL_STYLE = {
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontSize: 12
|
||||
};
|
||||
|
||||
var DEFAULT_FILL_OPACITY = .95,
|
||||
HIGH_FILL_OPACITY = .35;
|
||||
|
||||
|
||||
export default function BpmnRenderer(
|
||||
config, eventBus, styles, pathMap,
|
||||
canvas, priority) {
|
||||
canvas, textRenderer, priority) {
|
||||
|
||||
BaseRenderer.call(this, eventBus, priority);
|
||||
|
||||
|
@ -78,11 +72,6 @@ export default function BpmnRenderer(
|
|||
|
||||
var rendererId = RENDERER_IDS.next();
|
||||
|
||||
var textUtil = new TextUtil({
|
||||
style: LABEL_STYLE,
|
||||
size: { width: 100 }
|
||||
});
|
||||
|
||||
var markers = {};
|
||||
|
||||
var computeStyle = styles.computeStyle;
|
||||
|
@ -450,7 +439,14 @@ export default function BpmnRenderer(
|
|||
}
|
||||
|
||||
function renderLabel(parentGfx, label, options) {
|
||||
var text = textUtil.createText(label || '', options);
|
||||
|
||||
options = assign({
|
||||
size: {
|
||||
width: 100
|
||||
}
|
||||
}, options);
|
||||
|
||||
var text = textRenderer.createText(label || '', options);
|
||||
|
||||
svgClasses(text).add('djs-label');
|
||||
|
||||
|
@ -484,16 +480,22 @@ export default function BpmnRenderer(
|
|||
return renderLabel(parentGfx, semantic.name, {
|
||||
box: box,
|
||||
fitBox: true,
|
||||
style: {
|
||||
fontSize: '11px',
|
||||
style: assign(
|
||||
{},
|
||||
textRenderer.getExternalStyle(),
|
||||
{
|
||||
fill: getStrokeColor(element, defaultStrokeColor)
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
function renderLaneLabel(parentGfx, text, element) {
|
||||
var textBox = renderLabel(parentGfx, text, {
|
||||
box: { height: 30, width: element.height },
|
||||
box: {
|
||||
height: 30,
|
||||
width: element.height
|
||||
},
|
||||
align: 'center-middle',
|
||||
style: {
|
||||
fill: getStrokeColor(element, defaultStrokeColor)
|
||||
|
@ -1846,7 +1848,8 @@ BpmnRenderer.$inject = [
|
|||
'eventBus',
|
||||
'styles',
|
||||
'pathMap',
|
||||
'canvas'
|
||||
'canvas',
|
||||
'textRenderer'
|
||||
];
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import { assign } from 'min-dash';
|
||||
|
||||
import TextUtil from 'diagram-js/lib/util/Text';
|
||||
|
||||
|
||||
export default function TextRenderer(config) {
|
||||
|
||||
var defaultStyle = assign({
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontSize: 12
|
||||
}, config && config.defaultStyle || {});
|
||||
|
||||
var externalStyle = assign({}, defaultStyle, {
|
||||
fontSize: parseInt(defaultStyle.fontSize, 10) - 1
|
||||
}, config && config.externalStyle || {});
|
||||
|
||||
var textUtil = new TextUtil({
|
||||
style: defaultStyle
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Get the new bounds of an externally rendered,
|
||||
* layouted label.
|
||||
*
|
||||
* @param {Bounds} bounds
|
||||
* @param {String} text
|
||||
*
|
||||
* @return {Bounds}
|
||||
*/
|
||||
this.getLayoutedBounds = function(bounds, text) {
|
||||
|
||||
var layoutedDimensions = textUtil.getDimensions(text, {
|
||||
box: {
|
||||
width: 90,
|
||||
height: 30,
|
||||
x: bounds.width / 2 + bounds.x,
|
||||
y: bounds.height / 2 + bounds.y
|
||||
},
|
||||
style: externalStyle
|
||||
});
|
||||
|
||||
// resize label shape to fit label text
|
||||
return {
|
||||
x: Math.round(bounds.x + bounds.width / 2 - layoutedDimensions.width / 2),
|
||||
y: Math.round(bounds.y),
|
||||
width: Math.ceil(layoutedDimensions.width),
|
||||
height: Math.ceil(layoutedDimensions.height)
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a layouted text element.
|
||||
*
|
||||
* @param {String} text
|
||||
* @param {Object} [options]
|
||||
*
|
||||
* @return {SVGElement} rendered text
|
||||
*/
|
||||
this.createText = function(text, options) {
|
||||
return textUtil.createText(text, options || {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get default text style.
|
||||
*/
|
||||
this.getDefaultStyle = function() {
|
||||
return defaultStyle;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the external text style.
|
||||
*/
|
||||
this.getExternalStyle = function() {
|
||||
return externalStyle;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
TextRenderer.$inject = [
|
||||
'config.textRenderer'
|
||||
];
|
|
@ -1,8 +1,11 @@
|
|||
import BpmnRenderer from './BpmnRenderer';
|
||||
import TextRenderer from './TextRenderer';
|
||||
|
||||
import PathMap from './PathMap';
|
||||
|
||||
export default {
|
||||
__init__: [ 'bpmnRenderer' ],
|
||||
bpmnRenderer: [ 'type', BpmnRenderer ],
|
||||
textRenderer: [ 'type', TextRenderer ],
|
||||
pathMap: [ 'type', PathMap ]
|
||||
};
|
||||
|
|
|
@ -3,8 +3,6 @@ import {
|
|||
getLabel
|
||||
} from '../LabelUtil';
|
||||
|
||||
import TextUtil from 'diagram-js/lib/util/Text';
|
||||
|
||||
import {
|
||||
getExternalLabelMid,
|
||||
isLabelExternal,
|
||||
|
@ -26,9 +24,7 @@ var NULL_DIMENSIONS = {
|
|||
/**
|
||||
* A handler that updates the text of a BPMN element.
|
||||
*/
|
||||
export default function UpdateLabelHandler(modeling) {
|
||||
|
||||
var textUtil = new TextUtil();
|
||||
export default function UpdateLabelHandler(modeling, textRenderer) {
|
||||
|
||||
/**
|
||||
* Set the label and return the changed elements.
|
||||
|
@ -114,7 +110,7 @@ export default function UpdateLabelHandler(modeling) {
|
|||
|
||||
// resize element based on label _or_ pre-defined bounds
|
||||
if (typeof newBounds === 'undefined') {
|
||||
newBounds = getLayoutedBounds(label, text, textUtil);
|
||||
newBounds = textRenderer.getLayoutedBounds(label, text);
|
||||
}
|
||||
|
||||
// setting newBounds to false or _null_ will
|
||||
|
@ -132,33 +128,7 @@ export default function UpdateLabelHandler(modeling) {
|
|||
this.postExecute = postExecute;
|
||||
}
|
||||
|
||||
UpdateLabelHandler.$inject = [ 'modeling' ];
|
||||
|
||||
|
||||
// TODO(nikku): repeating code (search for <getLayoutedBounds>)
|
||||
|
||||
var EXTERNAL_LABEL_STYLE = {
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontSize: '11px'
|
||||
};
|
||||
|
||||
function getLayoutedBounds(bounds, text, textUtil) {
|
||||
|
||||
var layoutedLabelDimensions = textUtil.getDimensions(text, {
|
||||
box: {
|
||||
width: 90,
|
||||
height: 30,
|
||||
x: bounds.width / 2 + bounds.x,
|
||||
y: bounds.height / 2 + bounds.y
|
||||
},
|
||||
style: EXTERNAL_LABEL_STYLE
|
||||
});
|
||||
|
||||
// resize label shape to fit label text
|
||||
return {
|
||||
x: Math.round(bounds.x + bounds.width / 2 - layoutedLabelDimensions.width / 2),
|
||||
y: Math.round(bounds.y),
|
||||
width: Math.ceil(layoutedLabelDimensions.width),
|
||||
height: Math.ceil(layoutedLabelDimensions.height)
|
||||
};
|
||||
}
|
||||
UpdateLabelHandler.$inject = [
|
||||
'modeling',
|
||||
'textRenderer'
|
||||
];
|
|
@ -20,8 +20,6 @@ import {
|
|||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import TextUtil from 'diagram-js/lib/util/Text';
|
||||
|
||||
var DEFAULT_LABEL_DIMENSIONS = {
|
||||
width: 90,
|
||||
height: 20
|
||||
|
@ -36,13 +34,14 @@ var DEFAULT_LABEL_DIMENSIONS = {
|
|||
* @param {EventBus} eventBus
|
||||
* @param {Modeling} modeling
|
||||
* @param {BpmnFactory} bpmnFactory
|
||||
* @param {TextRenderer} textRenderer
|
||||
*/
|
||||
export default function LabelBehavior(eventBus, modeling, bpmnFactory) {
|
||||
export default function LabelBehavior(
|
||||
eventBus, modeling, bpmnFactory,
|
||||
textRenderer) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
var textUtil = new TextUtil();
|
||||
|
||||
// update label if name property was updated
|
||||
this.postExecute('element.updateProperties', function(e) {
|
||||
var context = e.context,
|
||||
|
@ -73,10 +72,9 @@ export default function LabelBehavior(eventBus, modeling, bpmnFactory) {
|
|||
var labelCenter = getExternalLabelMid(element);
|
||||
|
||||
// we don't care about x and y
|
||||
var labelDimensions = getLayoutedBounds(
|
||||
var labelDimensions = textRenderer.getLayoutedBounds(
|
||||
DEFAULT_LABEL_DIMENSIONS,
|
||||
businessObject.name || '',
|
||||
textUtil
|
||||
businessObject.name || ''
|
||||
);
|
||||
|
||||
modeling.createLabel(element, labelCenter, {
|
||||
|
@ -200,34 +198,6 @@ inherits(LabelBehavior, CommandInterceptor);
|
|||
LabelBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling',
|
||||
'bpmnFactory'
|
||||
'bpmnFactory',
|
||||
'textRenderer'
|
||||
];
|
||||
|
||||
|
||||
// TODO(nikku): repeating code (search for <getLayoutedBounds>)
|
||||
|
||||
var EXTERNAL_LABEL_STYLE = {
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontSize: '11px'
|
||||
};
|
||||
|
||||
function getLayoutedBounds(bounds, text, textUtil) {
|
||||
|
||||
var layoutedLabelDimensions = textUtil.getDimensions(text, {
|
||||
box: {
|
||||
width: 90,
|
||||
height: 30,
|
||||
x: bounds.width / 2 + bounds.x,
|
||||
y: bounds.height / 2 + bounds.y
|
||||
},
|
||||
style: EXTERNAL_LABEL_STYLE
|
||||
});
|
||||
|
||||
// resize label shape to fit label text
|
||||
return {
|
||||
x: Math.round(bounds.x + bounds.width / 2 - layoutedLabelDimensions.width / 2),
|
||||
y: Math.round(bounds.y),
|
||||
width: Math.ceil(layoutedLabelDimensions.width),
|
||||
height: Math.ceil(layoutedLabelDimensions.height)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@ import {
|
|||
getBusinessObject
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
import TextUtil from 'diagram-js/lib/util/Text';
|
||||
|
||||
var DEFAULT_FLOW = 'default',
|
||||
ID = 'id',
|
||||
DI = 'di';
|
||||
|
@ -29,20 +27,23 @@ var NULL_DIMENSIONS = {
|
|||
* Use respective diagram-js provided handlers if you would
|
||||
* like to perform automated modeling.
|
||||
*/
|
||||
export default function UpdatePropertiesHandler(elementRegistry, moddle, translate, modeling) {
|
||||
export default function UpdatePropertiesHandler(
|
||||
elementRegistry, moddle, translate,
|
||||
modeling, textRenderer) {
|
||||
|
||||
this._elementRegistry = elementRegistry;
|
||||
this._moddle = moddle;
|
||||
this._translate = translate;
|
||||
this._modeling = modeling;
|
||||
|
||||
this._textUtil = new TextUtil();
|
||||
this._textRenderer = textRenderer;
|
||||
}
|
||||
|
||||
UpdatePropertiesHandler.$inject = [
|
||||
'elementRegistry',
|
||||
'moddle',
|
||||
'translate',
|
||||
'modeling'
|
||||
'modeling',
|
||||
'textRenderer'
|
||||
];
|
||||
|
||||
|
||||
|
@ -119,7 +120,7 @@ UpdatePropertiesHandler.prototype.postExecute = function(context) {
|
|||
|
||||
// get layouted text bounds and resize external
|
||||
// external label accordingly
|
||||
var newLabelBounds = getLayoutedBounds(label, text, this._textUtil);
|
||||
var newLabelBounds = this._textRenderer.getLayoutedBounds(label, text);
|
||||
|
||||
this._modeling.resizeShape(label, newLabelBounds, NULL_DIMENSIONS);
|
||||
};
|
||||
|
@ -231,32 +232,3 @@ function unwrapBusinessObjects(properties) {
|
|||
|
||||
return unwrappedProps;
|
||||
}
|
||||
|
||||
|
||||
// TODO(nikku): repeating code (search for <getLayoutedBounds>)
|
||||
|
||||
var EXTERNAL_LABEL_STYLE = {
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontSize: '11px'
|
||||
};
|
||||
|
||||
function getLayoutedBounds(bounds, text, textUtil) {
|
||||
|
||||
var layoutedLabelDimensions = textUtil.getDimensions(text, {
|
||||
box: {
|
||||
width: 90,
|
||||
height: 30,
|
||||
x: bounds.width / 2 + bounds.x,
|
||||
y: bounds.height / 2 + bounds.y
|
||||
},
|
||||
style: EXTERNAL_LABEL_STYLE
|
||||
});
|
||||
|
||||
// resize label shape to fit label text
|
||||
return {
|
||||
x: Math.round(bounds.x + bounds.width / 2 - layoutedLabelDimensions.width / 2),
|
||||
y: Math.round(bounds.y),
|
||||
width: Math.ceil(layoutedLabelDimensions.width),
|
||||
height: Math.ceil(layoutedLabelDimensions.height)
|
||||
};
|
||||
}
|
|
@ -3,8 +3,6 @@ import {
|
|||
map
|
||||
} from 'min-dash';
|
||||
|
||||
import TextUtil from 'diagram-js/lib/util/Text';
|
||||
|
||||
import { is } from '../util/ModelUtil';
|
||||
|
||||
import {
|
||||
|
@ -56,19 +54,18 @@ function notYetDrawn(translate, semantic, refSemantic, property) {
|
|||
* @param {ElementFactory} elementFactory
|
||||
* @param {ElementRegistry} elementRegistry
|
||||
* @param {Function} translate
|
||||
* @param {TextRenderer} textRenderer
|
||||
*/
|
||||
export default function BpmnImporter(
|
||||
eventBus, canvas, elementFactory,
|
||||
elementRegistry, translate) {
|
||||
elementRegistry, translate, textRenderer) {
|
||||
|
||||
this._eventBus = eventBus;
|
||||
this._canvas = canvas;
|
||||
|
||||
this._elementFactory = elementFactory;
|
||||
this._elementRegistry = elementRegistry;
|
||||
this._translate = translate;
|
||||
|
||||
this._textUtil = new TextUtil();
|
||||
this._textRenderer = textRenderer;
|
||||
}
|
||||
|
||||
BpmnImporter.$inject = [
|
||||
|
@ -76,7 +73,8 @@ BpmnImporter.$inject = [
|
|||
'canvas',
|
||||
'elementFactory',
|
||||
'elementRegistry',
|
||||
'translate'
|
||||
'translate',
|
||||
'textRenderer'
|
||||
];
|
||||
|
||||
|
||||
|
@ -239,7 +237,7 @@ BpmnImporter.prototype.addLabel = function(semantic, element) {
|
|||
|
||||
if (text) {
|
||||
// get corrected bounds from actual layouted text
|
||||
bounds = getLayoutedBounds(bounds, text, this._textUtil);
|
||||
bounds = this._textRenderer.getLayoutedBounds(bounds, text);
|
||||
}
|
||||
|
||||
label = this._elementFactory.createLabel(elementData(semantic, {
|
||||
|
@ -312,33 +310,7 @@ BpmnImporter.prototype._getElement = function(semantic) {
|
|||
};
|
||||
|
||||
|
||||
// TODO(nikku): repeating code (search for <getLayoutedBounds>)
|
||||
|
||||
var EXTERNAL_LABEL_STYLE = {
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontSize: '11px'
|
||||
};
|
||||
|
||||
function getLayoutedBounds(bounds, text, textUtil) {
|
||||
|
||||
var layoutedLabelDimensions = textUtil.getDimensions(text, {
|
||||
box: {
|
||||
width: 90,
|
||||
height: 30,
|
||||
x: bounds.width / 2 + bounds.x,
|
||||
y: bounds.height / 2 + bounds.y
|
||||
},
|
||||
style: EXTERNAL_LABEL_STYLE
|
||||
});
|
||||
|
||||
// resize label shape to fit label text
|
||||
return {
|
||||
x: Math.round(bounds.x + bounds.width / 2 - layoutedLabelDimensions.width / 2),
|
||||
y: Math.round(bounds.y),
|
||||
width: Math.ceil(layoutedLabelDimensions.width),
|
||||
height: Math.ceil(layoutedLabelDimensions.height)
|
||||
};
|
||||
}
|
||||
// helpers ////////////////////
|
||||
|
||||
function isPointInsideBBox(bbox, point) {
|
||||
var x = point.x,
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="sid-38422fae-e03e-43a3-bef4-bd33b32041b2" targetNamespace="http://bpmn.io/bpmn" exporter="http://bpmn.io" exporterVersion="0.0.0">
|
||||
<process id="Process_1" isExecutable="false">
|
||||
<startEvent id="StartEvent_1" name="EVENT" />
|
||||
<task id="Task_1bjzik3" name="TASK" />
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BpmnDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BpmnPlane_1" bpmnElement="Process_1">
|
||||
<bpmndi:BPMNShape id="StartEvent_16g5oa3_di" bpmnElement="StartEvent_1">
|
||||
<omgdc:Bounds x="239" y="184" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds x="237" y="226" width="39" height="12" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_1bjzik3_di" bpmnElement="Task_1bjzik3">
|
||||
<omgdc:Bounds x="347" y="162" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
|
@ -0,0 +1,86 @@
|
|||
import {
|
||||
bootstrapViewer,
|
||||
inject
|
||||
} from 'test/TestHelper';
|
||||
|
||||
|
||||
describe('draw - TextRenderer', function() {
|
||||
|
||||
var diagramXML = require('./TextRenderer.bpmn');
|
||||
|
||||
|
||||
describe('API', function() {
|
||||
|
||||
beforeEach(bootstrapViewer(diagramXML));
|
||||
|
||||
it('should expose #createText', inject(function(textRenderer) {
|
||||
|
||||
// when
|
||||
var text = textRenderer.createText('FOO');
|
||||
|
||||
// then
|
||||
expect(text).to.exist;
|
||||
}));
|
||||
|
||||
|
||||
it('should expose #getLayoutedBounds', inject(function(textRenderer) {
|
||||
|
||||
// given
|
||||
var bounds = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100
|
||||
};
|
||||
|
||||
// when
|
||||
var layoutedBounds = textRenderer.getLayoutedBounds(
|
||||
bounds,
|
||||
'FOO\nBar\nFOOBAR'
|
||||
);
|
||||
|
||||
// then
|
||||
expect(layoutedBounds).to.exist;
|
||||
|
||||
expect(layoutedBounds.x).to.exist;
|
||||
expect(layoutedBounds.y).to.exist;
|
||||
expect(layoutedBounds.width).to.exist;
|
||||
expect(layoutedBounds.height).to.exist;
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('style override', function() {
|
||||
|
||||
beforeEach(bootstrapViewer(diagramXML, {
|
||||
textRenderer: {
|
||||
defaultStyle: {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '15px'
|
||||
},
|
||||
externalStyle: {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
it('should render', inject(function(textRenderer) {
|
||||
|
||||
// when
|
||||
var defaultStyle = textRenderer.getDefaultStyle();
|
||||
var externalStyle = textRenderer.getExternalStyle();
|
||||
|
||||
// then
|
||||
expect(defaultStyle.fontFamily).to.eql('monospace');
|
||||
expect(defaultStyle.fontSize).to.eql('15px');
|
||||
|
||||
expect(externalStyle.fontFamily).to.eql('monospace');
|
||||
expect(externalStyle.fontSize).to.eql(14);
|
||||
expect(externalStyle.fontWeight).to.eql('bold');
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue