diff --git a/plugins/de.cau.cs.kieler.klighd.piccolo.freehep/src/de/cau/cs/kieler/klighd/piccolo/freehep/SemanticSVGGraphics2D.java b/plugins/de.cau.cs.kieler.klighd.piccolo.freehep/src/de/cau/cs/kieler/klighd/piccolo/freehep/SemanticSVGGraphics2D.java index 7b858a507..625ecf18c 100644 --- a/plugins/de.cau.cs.kieler.klighd.piccolo.freehep/src/de/cau/cs/kieler/klighd/piccolo/freehep/SemanticSVGGraphics2D.java +++ b/plugins/de.cau.cs.kieler.klighd.piccolo.freehep/src/de/cau/cs/kieler/klighd/piccolo/freehep/SemanticSVGGraphics2D.java @@ -46,6 +46,7 @@ import java.util.zip.GZIPOutputStream; import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Path; import org.eclipse.swt.graphics.PathData; @@ -69,6 +70,7 @@ import de.cau.cs.kieler.klighd.Klighd; import de.cau.cs.kieler.klighd.KlighdConstants; +import de.cau.cs.kieler.klighd.microlayout.PlacementUtil; import de.cau.cs.kieler.klighd.piccolo.export.KlighdAbstractSVGGraphics.KlighdGradientPaint; import de.cau.cs.kieler.klighd.util.KlighdSemanticDiagramData; @@ -822,6 +824,8 @@ public void drawString(String string, double x, double y) { writeString(string, x, y); } catch (IOException e) { handleException(e); + } finally { + this.nextTextLength = null; } } } @@ -862,58 +866,51 @@ protected void writeString(String str, double x, double y) str = " " + str.substring(1); } - os.println(getTransformedString( - // general transformation - getTransform(), - // general clip -// getClippedString( - getTransformedString( - // text offset - new AffineTransform(1, 0, 0, 1, x, y), + final AffineTransform textOffset = new AffineTransform(1, 0, 0, 1, x, y); + textOffset.concatenate(getTransform()); + final boolean isSingleLine = str.indexOf('\n') == -1; + os.println( + isSingleLine + ? getTransformedString( + // general transformation + text offset + textOffset, + getTransformedString( + // font transformation and text + getFont().getTransform(), + getTextsString( + str, style, + // indentation + textOffset.isIdentity() ? "" : " " + ) + ) + ) : getTransformedString( + // general transformation + text offset + textOffset, + // style + style(style) + // semantic data + + attributes(false), getTransformedString( // font transformation and text getFont().getTransform(), - " \n" - // text - + insertTSpan(str) - + " "))) -// ) + getTextsString( + str, null /* properties are added to the group */, + // indentation + " " + ) + ) + ) ); resetSemanticData(); } - protected String textLength() { - if (this.nextTextLength == null) - return ""; - else { - final float textLength = this.nextTextLength.floatValue(); - this.nextTextLength = null; - return " textLength=\"" + textLength + "px\" lengthAdjust=\"spacingAndGlyphs\""; - } - } - - /** - * Insert TSpan elements into a multiline text string. - * @param text string where lines are indicated by "\n" - * @return the string enriched by TSpan elements. - */ - private String insertTSpan(final String text) { - - // empty string - if (Strings.isNullOrEmpty(text)) { - return ""; - } - - int i = 0; + private String getTextsString(String text, Properties style, String indentation) { final String[] lines = text.split("\\r?\\n|\\r"); + final boolean isMultiLine = lines.length > 1; final StringBuffer content = new StringBuffer(); + + int i = 0; if (display != null) { // Translate the font back to an swt font // KLighD used SWT to determine font sizes and as SWT and AWT font metrics @@ -930,39 +927,64 @@ private String insertTSpan(final String text) { // distance to each remaining line final float lineHeight = getAdjustedFontHeight(height, ascent, descent, false); - // use tspans to emulate multiline text - boolean first = true; + final Double nextLength = this.nextTextLength; + this.nextTextLength = null; + + // if we have a multi-line text and a configured nextTextLength + // we need to recalculate the designated textLength per line + // otherwise we can stick to the given 'nextTextLength' value + boolean noTextLengthPerLineCalcRequired = nextLength == null || !isMultiLine; + final FontData fontData = noTextLengthPerLineCalcRequired ? null : getFontData(getFont()); + + float y = firstLineHeight; for (final String line : lines) { - content.append(" "); content.append(line); - content.append("\n"); - first = false; + content.append(""); + + y += lineHeight; } } else { // without a display just use the pt size as line height for multiline text // use tspans to emulate multiline text + final int fontSize = getFont().getSize(); + float y = fontSize; for (final String line : lines) { - content.append(" "); content.append(line); - content.append("\n"); + content.append(""); } } - return content.toString(); + if (content.length() == 0) + return ""; + else + return content.toString().substring(1); // trim the leading line break } - protected float getAdjustedFontHeight(int height, int ascent, int descent, boolean firstLine) { // FIXME @@ -977,6 +999,14 @@ protected float getAdjustedFontHeight(int height, int ascent, int descent, boole return displayScaleY * (leading + ascent + (firstLine ? 0f : descent)); } + protected String textLength(Double nextLength) { + if (nextLength == null) + return ""; + + else + return " textLength=\"" + nextLength.floatValue() + "px\" lengthAdjust=\"spacingAndGlyphs\""; + } + /** * KLighD uses SWT to estimate font sizes, hence we do * the same when exporting svgs. @@ -1347,6 +1377,20 @@ public String toString() { * SVG-Tag */ private String getTransformedString(AffineTransform t, String s) { + return getTransformedString(t, null, s); + } + + /** + * Encapsulates a SVG-Tag by the given transformation matrix + * + * @param t + * Transformation + * @param additionalProperties + * additional properties to be set on the element + * @param s + * SVG-Tag + */ + private String getTransformedString(AffineTransform t, String additionalProperties, String s) { StringBuffer result = new StringBuffer(); if (t != null && !t.isIdentity()) { @@ -1362,12 +1406,18 @@ private String getTransformedString(AffineTransform t, String s) { result.append(fixedPrecision(t.getTranslateX())); result.append(", "); result.append(fixedPrecision(t.getTranslateY())); - result.append(")\">\n"); + result.append(")\""); + if (additionalProperties != null) + result.append(" ").append(additionalProperties); + result.append(">\n"); + + } else if (additionalProperties != null) { + result.append("\n"); } result.append(s); - if (t != null && !t.isIdentity()) { + if (t != null && !t.isIdentity() || additionalProperties != null) { result.append("\n"); if (writeComments) { result.append(" "); @@ -1730,15 +1780,18 @@ private org.eclipse.swt.graphics.Font getSWTFont(final Font font) { if (!awtSwtFontCache.containsKey(font) && display != null) { // note that the style constants for swt and awt are equal org.eclipse.swt.graphics.Font swtFont = - new org.eclipse.swt.graphics.Font(display, getFont().getName(), getFont() - .getSize(), getSWTFontStyle()); + new org.eclipse.swt.graphics.Font(display, getFontData(font)); awtSwtFontCache.put(font, swtFont); } return awtSwtFontCache.get(font); } - private int getSWTFontStyle() { - switch (getFont().getStyle()) { + protected FontData getFontData(Font font) { + return new FontData(font.getName(), font.getSize(), getSWTFontStyle(font)); + } + + private int getSWTFontStyle(Font font) { + switch (font.getStyle()) { case Font.PLAIN: return SWT.NORMAL; case Font.BOLD: diff --git a/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/freehep/FreeHEPSVGOffscreenRenderingTest.xtend b/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/freehep/FreeHEPSVGOffscreenRenderingTest.xtend index 6b412c78a..8c64ab92f 100644 --- a/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/freehep/FreeHEPSVGOffscreenRenderingTest.xtend +++ b/test/de.cau.cs.kieler.klighd.piccolo.test/src/de/cau/cs/kieler/klighd/piccolo/test/freehep/FreeHEPSVGOffscreenRenderingTest.xtend @@ -65,9 +65,7 @@ class FreeHEPSVGOffscreenRenderingTest { '''.equalsSVGof[ - children += createInitializedNode() => [ - setSize(100, 100) - ] + addKNodeWithSizeOf(100, 100) ] } @@ -85,11 +83,10 @@ class FreeHEPSVGOffscreenRenderingTest { '''.equalsSVGof[ - children += createInitializedNode() => [ - setSize(100, 100) + addKNodeWithSizeOf(100, 100) => [ setProperty(PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE) - ports += createInitializedPort => [ - setSize(5, 5) + + addKPortWithSizeOf(5, 5) => [ setProperty(PORT_SIDE, PortSide.EAST) addKRectangleWithStrokeOnlyColoring ] @@ -113,11 +110,10 @@ class FreeHEPSVGOffscreenRenderingTest { '''.equalsSVGof[ - children += createInitializedNode() => [ - setSize(100, 100) + addKNodeWithSizeOf(100, 100) => [ setProperty(PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE) - ports += createInitializedPort => [ - setSize(5, 5) + + addKPortWithSizeOf(5, 5) => [ setProperty(PORT_SIDE, PortSide.WEST) addKRectangleWithStrokeOnlyColoring ] @@ -132,13 +128,10 @@ class FreeHEPSVGOffscreenRenderingTest { - - NodeLabel - + NodeLabel '''.equalsSVGwithTextLengthsOf[ - children += createInitializedNode() => [ - setSize(100, 100) + addKNodeWithSizeOf(100, 100) => [ createInitializedLabel(it) => [ text = "NodeLabel" addKTextWithAssumedSizeOf(50, 10) @@ -148,7 +141,7 @@ class FreeHEPSVGOffscreenRenderingTest { } @Test - def void test04a_singleKNodeWithEastPortWithInsideLabel() { + def void test04a_singleKNodeWithEastPortWithOutsideLabel() { ''' @@ -160,19 +153,16 @@ class FreeHEPSVGOffscreenRenderingTest { - - PortLabel - + PortLabel '''.equalsSVGwithTextLengthsOf[ - children += createInitializedNode() => [ + addKNodeWithSizeOf(100, 100) => [ setProperty(PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE) setProperty(PORT_LABELS_PLACEMENT, PortLabelPlacement.OUTSIDE) setProperty(PORT_LABELS_NEXT_TO_PORT_IF_POSSIBLE, Boolean.TRUE) - setSize(100, 100) - ports += createInitializedPort => [ - setSize(5, 5) + + addKPortWithSizeOf(5, 5) => [ setProperty(PORT_SIDE, PortSide.EAST) addKRectangleWithStrokeOnlyColoring createInitializedLabel(it) => [ @@ -185,7 +175,7 @@ class FreeHEPSVGOffscreenRenderingTest { } @Test - def void test04b_singleKNodeWithWestPortWithInsideLabel() { + def void test04b_singleKNodeWithWestPortWithOutsideLabel() { ''' @@ -199,19 +189,16 @@ class FreeHEPSVGOffscreenRenderingTest { - - PortLabel - + PortLabel '''.equalsSVGwithTextLengthsOf[ - children += createInitializedNode() => [ + addKNodeWithSizeOf(100, 100) => [ setProperty(PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE) setProperty(PORT_LABELS_PLACEMENT, PortLabelPlacement.OUTSIDE) setProperty(PORT_LABELS_NEXT_TO_PORT_IF_POSSIBLE, Boolean.TRUE) - setSize(100, 100) - ports += createInitializedPort => [ - setSize(5, 5) + + addKPortWithSizeOf(5, 5) => [ setProperty(PORT_SIDE, PortSide.WEST) addKRectangleWithStrokeOnlyColoring createInitializedLabel(it) => [ @@ -234,8 +221,7 @@ class FreeHEPSVGOffscreenRenderingTest { '''.equalsSVGof[ - children += createInitializedNode() => [ - setSize(100, 100) + addKNodeWithSizeOf(100, 100) => [ semanticData = KlighdSemanticDiagramData.of(it).putID("MyNode").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyNodeClass").put("MyKey", "MyValue").put("My2ndKey") [ viewModelElement.eClass.name + ":" + (viewModelElement as KNode).parent.children.indexOf(viewModelElement) ] @@ -259,11 +245,10 @@ class FreeHEPSVGOffscreenRenderingTest { '''.equalsSVGof[ - children += createInitializedNode() => [ - setSize(100, 100) + addKNodeWithSizeOf(100, 100) => [ setProperty(PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE) - ports += createInitializedPort => [ - setSize(5, 5) + + addKPortWithSizeOf(5, 5) => [ setProperty(PORT_SIDE, PortSide.EAST) addKRectangleWithStrokeOnlyColoring semanticData = KlighdSemanticDiagramData.of(it).putID("MyPort").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyPortClass").put("MyKey", "MyValue").put("My2ndKey") [ @@ -275,28 +260,23 @@ class FreeHEPSVGOffscreenRenderingTest { } @Test - def void test13_singleKNodeWithLabelWithSemanticData() { + def void test13a_singleKNodeWithLabelWithSemanticData() { ''' - - NodeLabel - + NodeLabel '''.equalsSVGwithTextLengthsOf[ - children += createInitializedNode() => [ - setSize(100, 100) + addKNodeWithSizeOf(100, 100) => [ createInitializedLabel(it) => [ text = "NodeLabel" addKTextWithAssumedSizeOf(50, 10) semanticData = KlighdSemanticDiagramData.of(it).putID("MyLabel").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyLabelClass").put("MyKey", "MyValue").put("My2ndKey") [ viewModelElement.eClass.name + ":" + (viewModelElement as KLabel).parent.labels.indexOf(viewModelElement) - ].putAtTextLine("MyTextLineKey")[ - noOfLine.toString ] ] ] @@ -304,25 +284,54 @@ class FreeHEPSVGOffscreenRenderingTest { } @Test - def void test13b_singleKNodeWithLabelWithMultiLineTextWithSemanticData() { + def void test13b_singleKNodeWithLabelWithSemanticDataWithMultiLineText() { ''' - - NodeLabel: - Some additional info - + + NodeLabel: + Some additional info - '''.equalsSVGwithTextLengthsOf[ - children += createInitializedNode() => [ - setSize(100, 100) + + '''.equalsSVGof[ // cs: deactivated generation of 'textLength' property settings on purpose, as we cannot inject reliable test size data per text line as of now + addKNodeWithSizeOf(100, 100) => [ createInitializedLabel(it) => [ text = "NodeLabel:\nSome additional info" addKTextWithAssumedSizeOf(50, 10) - .semanticData = KlighdSemanticDiagramData.of(it).putID("MyText").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyTextClass").put("MyKey", "MyValue").put("My2ndKey") [ - viewModelElement.eClass.name + ":" + (viewModelElement as KLabel).parent.labels.indexOf(viewModelElement) + .semanticData = KlighdSemanticDiagramData.of(it.data.head).putID("MyText").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyTextClass").put("MyKey", "MyValue").put("My2ndKey") [ + viewModelElement.eClass.name + ":" + (viewModelElement.eContainer as KGraphElement).data.indexOf(viewModelElement) + ] + ] + ] + ] + } + + @Test + def void test13c_singleKNodeWithLabelWithSemanticData_And_MultiLineTextWithSemanticData() { + ''' + + + + + + + NodeLabel: + Some additional info + + + + '''.equalsSVGof[ // cs: deactivated generation of 'textLength' property settings on purpose, as we cannot inject reliable test size data per text line as of now + addKNodeWithSizeOf(100, 100) => [ + createInitializedLabel(it) => [ + text = "NodeLabel:\nSome additional info" + semanticData = KlighdSemanticDiagramData.of(it).putID("MyLabel").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyLabelClass").put("MyKey", "MyValue").put("My2ndKey") [ + viewModelElement.eClass.name + ":" + (viewModelElement as KLabel).parent.labels.indexOf(viewModelElement) + ] + addKTextWithAssumedSizeOf(50, 10) + .semanticData = KlighdSemanticDiagramData.of(it.data.head).putID("MyText").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyTextClass").put("MyKey", "MyValue").put("My2ndKey") [ + viewModelElement.eClass.name + ":" + (viewModelElement.eContainer as KGraphElement).data.indexOf(viewModelElement) ].putAtTextLine("MyTextLineKey")[ noOfLine.toString ] @@ -331,7 +340,151 @@ class FreeHEPSVGOffscreenRenderingTest { ] } + @Test + def void test14a_singleKNodeWithEastPortWithSemanticData_And_WithOutsideLabelWithSemanticData() { + ''' + + + + + + + + + + + + + PortLabel + + + + + '''.equalsSVGwithTextLengthsOf[ + addKNodeWithSizeOf(100, 100) => [ + setProperty(PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE) + setProperty(PORT_LABELS_PLACEMENT, PortLabelPlacement.OUTSIDE) + setProperty(PORT_LABELS_NEXT_TO_PORT_IF_POSSIBLE, Boolean.TRUE) + + addKPortWithSizeOf(5, 5) => [ + setProperty(PORT_SIDE, PortSide.EAST) + addKRectangleWithStrokeOnlyColoring + semanticData = KlighdSemanticDiagramData.of(it).putID("MyPort").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyPortClass").put("MyKey", "MyValue").put("My2ndKey") [ + viewModelElement.eClass.name + ":" + (viewModelElement as KPort).node.ports.indexOf(viewModelElement) + ] + createInitializedLabel(it) => [ + text = "PortLabel" + semanticData = KlighdSemanticDiagramData.of(it).putID("MyLabel").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyLabelClass").put("MyKey", "MyValue").put("My2ndKey") [ + viewModelElement.eClass.name + ":" + (viewModelElement as KLabel).parent.labels.indexOf(viewModelElement) + ] + addKTextWithAssumedSizeOf(50, 10) + ] + ] + ] + ] + } + @Test + def void test14b_singleKNodeWithEastPortWithSemanticData_And_WithOutsideLabelWithSemanticData_And_SingleLineTextWithSemanticData() { + ''' + + + + + + + + + + + + + PortLabel + + + + + '''.equalsSVGwithTextLengthsOf[ + addKNodeWithSizeOf(100, 100) => [ + setProperty(PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE) + setProperty(PORT_LABELS_PLACEMENT, PortLabelPlacement.OUTSIDE) + setProperty(PORT_LABELS_NEXT_TO_PORT_IF_POSSIBLE, Boolean.TRUE) + + addKPortWithSizeOf(5, 5) => [ + setProperty(PORT_SIDE, PortSide.EAST) + addKRectangleWithStrokeOnlyColoring + semanticData = KlighdSemanticDiagramData.of(it).putID("MyPort").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyPortClass").put("MyKey", "MyValue").put("My2ndKey") [ + viewModelElement.eClass.name + ":" + (viewModelElement as KPort).node.ports.indexOf(viewModelElement) + ] + createInitializedLabel(it) => [ + text = "PortLabel" + semanticData = KlighdSemanticDiagramData.of(it).putID("MyLabel").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyLabelClass").put("MyKey", "MyValue").put("My2ndKey") [ + viewModelElement.eClass.name + ":" + (viewModelElement as KLabel).parent.labels.indexOf(viewModelElement) + ] + addKTextWithAssumedSizeOf(50, 10) + .semanticData = KlighdSemanticDiagramData.of(it.data.head).putID("MyText").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyTextClass").put("MyKey", "MyValue").put("My2ndKey") [ + viewModelElement.eClass.name + ":" + (viewModelElement.eContainer as KGraphElement).data.indexOf(viewModelElement) + ].putAtTextLine("MyTextLineKey")[ + noOfLine.toString + ] + ] + ] + ] + ] + } + + @Test + def void test14c_singleKNodeWithEastPortWithSemanticData_And_WithOutsideLabelWithSemanticData_And_MultiLineTextWithSemanticData() { + ''' + + + + + + + + + + + + + PortLabel: + Some additional info + + + + + '''.equalsSVGof[ // cs: deactivated generation of 'textLength' property settings on purpose, as we cannot inject reliable test size data per text line as of now + addKNodeWithSizeOf(100, 100) => [ + setProperty(PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE) + setProperty(PORT_LABELS_PLACEMENT, PortLabelPlacement.OUTSIDE) + setProperty(PORT_LABELS_NEXT_TO_PORT_IF_POSSIBLE, Boolean.TRUE) + + addKPortWithSizeOf(5, 5) => [ + setProperty(PORT_SIDE, PortSide.EAST) + addKRectangleWithStrokeOnlyColoring + semanticData = KlighdSemanticDiagramData.of(it).putID("MyPort").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyPortClass").put("MyKey", "MyValue").put("My2ndKey") [ + viewModelElement.eClass.name + ":" + (viewModelElement as KPort).node.ports.indexOf(viewModelElement) + ] + createInitializedLabel(it) => [ + text = "PortLabel:\nSome additional info" + semanticData = KlighdSemanticDiagramData.of(it).putID("MyLabel").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyLabelClass").put("MyKey", "MyValue").put("My2ndKey") [ + viewModelElement.eClass.name + ":" + (viewModelElement as KLabel).parent.labels.indexOf(viewModelElement) + ] + addKTextWithAssumedSizeOf(50, 10) + .semanticData = KlighdSemanticDiagramData.of(it.data.head).putID("MyText").put(KlighdConstants.SEMANTIC_DATA_CLASS, "MyTextClass").put("MyKey", "MyValue").put("My2ndKey") [ + viewModelElement.eClass.name + ":" + (viewModelElement.eContainer as KGraphElement).data.indexOf(viewModelElement) + ].putAtTextLine("MyTextLineKey")[ + noOfLine.toString + ] + ] + ] + ] + ] + } + + + def equalsSVGof(CharSequence expectation, (KNode) => void viewModelBuilder) { expectation.equalsSVGof(viewModelBuilder, false) } @@ -352,6 +505,9 @@ class FreeHEPSVGOffscreenRenderingTest { .setProperty(IOffscreenRenderer.SET_TEXT_LENGTHS, Boolean.valueOf(withTextLength)) ) + if (status.exception !== null) + throw status.exception + OK_STATUS.assertEquals(status) expectation.toString.assertEquals( @@ -373,10 +529,26 @@ class FreeHEPSVGOffscreenRenderingTest { static val P_FONT_SIZE = Pattern.compile('(font-size=")[^"]*(")') static val P_DY = Pattern.compile('(dy=")[^"]*(")') + static val P_TEXT_Y = Pattern.compile('($2') - P_DY.matcher(fontSizeMasked).replaceAll('$1$2') + val textYMasked = P_TEXT_Y.matcher(fontSizeMasked).replaceAll('$1$2') + P_DY.matcher(textYMasked).replaceAll('$1$2') + } + + def addKNodeWithSizeOf(KNode parent, float width, float height) { + val node = createInitializedNode + node.setSize(width, height) + parent.children += node + node + } + + def addKPortWithSizeOf(KNode parent, float width, float height) { + val port = createInitializedPort + port.setSize(width, height) + parent.ports += port + port } def addKRectangleWithStrokeOnlyColoring(KGraphElement kge) {