/* ======================================================================== * PlantUML : a free UML diagram generator * ======================================================================== * * (C) Copyright 2009-2017, Arnaud Roques * * Project Info: http://plantuml.com * * If you like this project or if you find it useful, you can support us at: * * http://plantuml.com/patreon (only 1$ per month!) * http://plantuml.com/paypal * * This file is part of PlantUML. * * PlantUML is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * PlantUML distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public * License for more details. * * You should have received a copy of the GNU General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * * Original Author: Arnaud Roques * * */ package net.sourceforge.plantuml.svek; import java.awt.geom.Dimension2D; import java.awt.geom.Point2D; import java.util.Collection; import java.util.List; import net.sourceforge.plantuml.ColorParam; import net.sourceforge.plantuml.Dimension2DDouble; import net.sourceforge.plantuml.Direction; import net.sourceforge.plantuml.Hideable; import net.sourceforge.plantuml.ISkinParam; import net.sourceforge.plantuml.LineParam; import net.sourceforge.plantuml.Log; import net.sourceforge.plantuml.Pragma; import net.sourceforge.plantuml.StringUtils; import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.command.Position; import net.sourceforge.plantuml.cucadiagram.Display; import net.sourceforge.plantuml.cucadiagram.EntityPort; import net.sourceforge.plantuml.cucadiagram.IEntity; import net.sourceforge.plantuml.cucadiagram.IGroup; import net.sourceforge.plantuml.cucadiagram.Link; import net.sourceforge.plantuml.cucadiagram.LinkArrow; import net.sourceforge.plantuml.cucadiagram.LinkDecor; import net.sourceforge.plantuml.cucadiagram.LinkMiddleDecor; import net.sourceforge.plantuml.cucadiagram.LinkType; import net.sourceforge.plantuml.cucadiagram.NoteLinkStrategy; import net.sourceforge.plantuml.cucadiagram.dot.GraphvizVersion; import net.sourceforge.plantuml.graphic.AbstractTextBlock; import net.sourceforge.plantuml.graphic.FontConfiguration; import net.sourceforge.plantuml.graphic.HorizontalAlignment; import net.sourceforge.plantuml.graphic.HtmlColor; import net.sourceforge.plantuml.graphic.HtmlColorUtils; import net.sourceforge.plantuml.graphic.StringBounder; import net.sourceforge.plantuml.graphic.TextBlock; import net.sourceforge.plantuml.graphic.TextBlockArrow; import net.sourceforge.plantuml.graphic.TextBlockUtils; import net.sourceforge.plantuml.graphic.UDrawable; import net.sourceforge.plantuml.graphic.USymbolFolder; import net.sourceforge.plantuml.graphic.VerticalAlignment; import net.sourceforge.plantuml.graphic.color.ColorType; import net.sourceforge.plantuml.posimo.BezierUtils; import net.sourceforge.plantuml.posimo.DotPath; import net.sourceforge.plantuml.posimo.Moveable; import net.sourceforge.plantuml.posimo.Positionable; import net.sourceforge.plantuml.posimo.PositionableUtils; import net.sourceforge.plantuml.skin.VisibilityModifier; import net.sourceforge.plantuml.skin.rose.Rose; import net.sourceforge.plantuml.svek.extremity.Extremity; import net.sourceforge.plantuml.svek.extremity.ExtremityFactory; import net.sourceforge.plantuml.svek.extremity.ExtremityFactoryExtends; import net.sourceforge.plantuml.svek.image.EntityImageNoteLink; import net.sourceforge.plantuml.ugraphic.UChangeBackColor; import net.sourceforge.plantuml.ugraphic.UChangeColor; import net.sourceforge.plantuml.ugraphic.UComment; import net.sourceforge.plantuml.ugraphic.UGraphic; import net.sourceforge.plantuml.ugraphic.ULine; import net.sourceforge.plantuml.ugraphic.UPolygon; import net.sourceforge.plantuml.ugraphic.UShape; import net.sourceforge.plantuml.ugraphic.UStroke; import net.sourceforge.plantuml.ugraphic.UTranslate; public class Line implements Moveable, Hideable { private final Cluster ltail; private final Cluster lhead; private final Link link; private final EntityPort startUid; private final EntityPort endUid; private final TextBlock startTailText; private final TextBlock endHeadText; private final TextBlock labelText; private boolean divideLabelWidthByTwo = false; private final int lineColor; private final int noteLabelColor; private final int startTailColor; private final int endHeadColor; private final StringBounder stringBounder; private final Bibliotekon bibliotekon; private DotPath dotPath; private Positionable startTailLabelXY; private Positionable endHeadLabelXY; private Positionable labelXY; private UDrawable extremity2; private UDrawable extremity1; private double dx; private double dy; private boolean opale; private Cluster projectionCluster; private final Pragma pragma; private final HtmlColor backgroundColor; private final boolean useRankSame; private final UStroke defaultThickness; private HtmlColor arrowLollipopColor; @Override public String toString() { return super.toString() + " color=" + lineColor; } class DirectionalTextBlock extends AbstractTextBlock implements TextBlock { private final TextBlock right; private final TextBlock left; private final TextBlock up; private final TextBlock down; DirectionalTextBlock(TextBlock right, TextBlock left, TextBlock up, TextBlock down) { this.right = right; this.left = left; this.up = up; this.down = down; } public void drawU(UGraphic ug) { Direction dir = getDirection(); if (getLinkArrow() == LinkArrow.BACKWARD) { dir = dir.getInv(); } switch (dir) { case RIGHT: right.drawU(ug); break; case LEFT: left.drawU(ug); break; case UP: up.drawU(ug); break; case DOWN: down.drawU(ug); break; default: throw new UnsupportedOperationException(); } } public Dimension2D calculateDimension(StringBounder stringBounder) { return right.calculateDimension(stringBounder); } private Direction getDirection() { if (isAutolink()) { final double startAngle = dotPath.getStartAngle(); return Direction.LEFT; } final Point2D start = dotPath.getStartPoint(); final Point2D end = dotPath.getEndPoint(); final double ang = Math.atan2(end.getX() - start.getX(), end.getY() - start.getY()); if (ang > -Math.PI / 4 && ang < Math.PI / 4) { return Direction.DOWN; } if (ang > Math.PI * 3 / 4 || ang < -Math.PI * 3 / 4) { return Direction.UP; } return end.getX() > start.getX() ? Direction.RIGHT : Direction.LEFT; } } private Cluster getCluster2(Bibliotekon bibliotekon, IEntity entityMutable) { for (Cluster cl : bibliotekon.allCluster()) { if (entityMutable == cl.getGroup()) { return cl; } } throw new IllegalArgumentException(); } public Line(Link link, ColorSequence colorSequence, ISkinParam skinParam, StringBounder stringBounder, FontConfiguration labelFont, Bibliotekon bibliotekon, Pragma pragma) { if (link == null) { throw new IllegalArgumentException(); } this.useRankSame = skinParam.useRankSame(); this.startUid = link.getEntityPort1(bibliotekon); this.endUid = link.getEntityPort2(bibliotekon); Cluster ltail = null; if (startUid.startsWith(Cluster.CENTER_ID)) { ltail = getCluster2(bibliotekon, link.getEntity1()); } Cluster lhead = null; if (endUid.startsWith(Cluster.CENTER_ID)) { lhead = getCluster2(bibliotekon, link.getEntity2()); } if (link.getColors() != null) { skinParam = link.getColors().mute(skinParam); labelFont = labelFont.mute(link.getColors()); } this.backgroundColor = skinParam.getBackgroundColor(); this.defaultThickness = skinParam.getThickness(LineParam.arrow, null); this.arrowLollipopColor = skinParam.getHtmlColor(ColorParam.arrowLollipop, null, false); if (arrowLollipopColor == null) { this.arrowLollipopColor = HtmlColorUtils.WHITE; } this.pragma = pragma; this.bibliotekon = bibliotekon; this.stringBounder = stringBounder; this.link = link; this.ltail = ltail; this.lhead = lhead; this.lineColor = colorSequence.getValue(); this.noteLabelColor = colorSequence.getValue(); this.startTailColor = colorSequence.getValue(); this.endHeadColor = colorSequence.getValue(); final TextBlock labelOnly; if (Display.isNull(link.getLabel())) { if (getLinkArrow() == LinkArrow.NONE) { labelOnly = null; } else { final TextBlockArrow right = new TextBlockArrow(Direction.RIGHT, labelFont); final TextBlockArrow left = new TextBlockArrow(Direction.LEFT, labelFont); final TextBlockArrow up = new TextBlockArrow(Direction.UP, labelFont); final TextBlockArrow down = new TextBlockArrow(Direction.DOWN, labelFont); labelOnly = new DirectionalTextBlock(right, left, up, down); } } else { final TextBlock label = getLineLabel(link, skinParam, labelFont); if (getLinkArrow() == LinkArrow.NONE) { labelOnly = label; } else { TextBlock right = new TextBlockArrow(Direction.RIGHT, labelFont); right = TextBlockUtils.mergeLR(label, right, VerticalAlignment.CENTER); TextBlock left = new TextBlockArrow(Direction.LEFT, labelFont); left = TextBlockUtils.mergeLR(left, label, VerticalAlignment.CENTER); TextBlock up = new TextBlockArrow(Direction.UP, labelFont); up = TextBlockUtils.mergeTB(up, label, HorizontalAlignment.CENTER); TextBlock down = new TextBlockArrow(Direction.DOWN, labelFont); down = TextBlockUtils.mergeTB(label, down, HorizontalAlignment.CENTER); labelOnly = new DirectionalTextBlock(right, left, up, down); } } final TextBlock noteOnly; if (link.getNote() == null) { noteOnly = null; } else { noteOnly = new EntityImageNoteLink(link.getNote(), link.getNoteColors(), skinParam); if (link.getNoteLinkStrategy() == NoteLinkStrategy.HALF_NOT_PRINTED || link.getNoteLinkStrategy() == NoteLinkStrategy.HALF_PRINTED_FULL) { divideLabelWidthByTwo = true; } } if (labelOnly != null && noteOnly != null) { if (link.getNotePosition() == Position.LEFT) { labelText = TextBlockUtils.mergeLR(noteOnly, labelOnly, VerticalAlignment.CENTER); } else if (link.getNotePosition() == Position.RIGHT) { labelText = TextBlockUtils.mergeLR(labelOnly, noteOnly, VerticalAlignment.CENTER); } else if (link.getNotePosition() == Position.TOP) { labelText = TextBlockUtils.mergeTB(noteOnly, labelOnly, HorizontalAlignment.CENTER); } else { labelText = TextBlockUtils.mergeTB(labelOnly, noteOnly, HorizontalAlignment.CENTER); } } else if (labelOnly != null) { labelText = labelOnly; } else if (noteOnly != null) { labelText = noteOnly; } else { labelText = null; } if (link.getQualifier1() == null) { startTailText = null; } else { startTailText = Display.getWithNewlines(link.getQualifier1()).create(labelFont, HorizontalAlignment.CENTER, skinParam); } if (link.getQualifier2() == null) { endHeadText = null; } else { endHeadText = Display.getWithNewlines(link.getQualifier2()).create(labelFont, HorizontalAlignment.CENTER, skinParam); } } private TextBlock getLineLabel(Link link, ISkinParam skinParam, FontConfiguration labelFont) { final double marginLabel = startUid.equalsId(endUid) ? 6 : 1; TextBlock label = link.getLabel().create(labelFont, skinParam.getDefaultTextAlignment(HorizontalAlignment.CENTER), skinParam, skinParam.maxMessageSize()); final VisibilityModifier visibilityModifier = link.getVisibilityModifier(); if (visibilityModifier != null) { final Rose rose = new Rose(); // final HtmlColor back = visibilityModifier.getBackground() == null ? null : rose.getHtmlColor(skinParam, // visibilityModifier.getBackground()); final HtmlColor fore = rose.getHtmlColor(skinParam, visibilityModifier.getForeground()); TextBlock visibility = visibilityModifier.getUBlock(skinParam.classAttributeIconSize(), fore, null, false); visibility = TextBlockUtils.withMargin(visibility, 0, 1, 2, 0); label = TextBlockUtils.mergeLR(visibility, label, VerticalAlignment.CENTER); } label = TextBlockUtils.withMargin(label, marginLabel, marginLabel); return label; } public boolean hasNoteLabelText() { return labelText != null; } private LinkArrow getLinkArrow() { return link.getLinkArrow(); } public void appendLine(GraphvizVersion graphvizVersion, StringBuilder sb, DotMode dotMode) { // Log.println("inverted=" + isInverted()); // if (isInverted()) { // sb.append(endUid); // sb.append("->"); // sb.append(startUid); // } else { sb.append(startUid.getFullString()); sb.append("->"); sb.append(endUid.getFullString()); // } sb.append("["); final LinkType linkType = link.getTypePatchCluster(); String decoration = linkType.getSpecificDecorationSvek(); if (decoration.length() > 0 && decoration.endsWith(",") == false) { decoration += ","; } sb.append(decoration); int length = link.getLength(); if (graphvizVersion.ignoreHorizontalLinks() && length == 1) { length = 2; } if (useRankSame) { if (pragma.horizontalLineBetweenDifferentPackageAllowed() || link.isInvis() || length != 1) { // if (graphvizVersion.isJs() == false) { sb.append("minlen=" + (length - 1)); sb.append(","); // } } } else { sb.append("minlen=" + (length - 1)); sb.append(","); } sb.append("color=\"" + StringUtils.getAsHtml(lineColor) + "\""); if (labelText != null) { sb.append(","); if (graphvizVersion.useXLabelInsteadOfLabel() || dotMode == DotMode.NO_LEFT_RIGHT_AND_XLABEL) { sb.append("xlabel=<"); } else { sb.append("label=<"); } appendTable(sb, eventuallyDivideByTwo(labelText.calculateDimension(stringBounder)), noteLabelColor, graphvizVersion); sb.append(">"); // sb.append(",labelfloat=true"); } if (startTailText != null) { sb.append(","); sb.append("taillabel=<"); appendTable(sb, startTailText.calculateDimension(stringBounder), startTailColor, graphvizVersion); sb.append(">"); // sb.append(",labelangle=0"); } if (endHeadText != null) { sb.append(","); sb.append("headlabel=<"); appendTable(sb, endHeadText.calculateDimension(stringBounder), endHeadColor, graphvizVersion); sb.append(">"); // sb.append(",labelangle=0"); } if (link.isInvis()) { sb.append(","); sb.append("style=invis"); } if (link.isConstraint() == false || link.hasTwoEntryPointsSameContainer()) { sb.append(",constraint=false"); } if (link.getSametail() != null) { sb.append(",sametail=" + link.getSametail()); } sb.append("];"); SvekUtils.println(sb); } private Dimension2D eventuallyDivideByTwo(Dimension2D dim) { if (divideLabelWidthByTwo) { return new Dimension2DDouble(dim.getWidth() / 2, dim.getHeight()); } return dim; } public String rankSame() { // if (graphvizVersion == GraphvizVersion.V2_34_0) { // return null; // } if (pragma.horizontalLineBetweenDifferentPackageAllowed() == false && link.getLength() == 1 /* && graphvizVersion.isJs() == false */) { return "{rank=same; " + getStartUidPrefix() + "; " + getEndUidPrefix() + "}"; } return null; } public static void appendTable(StringBuilder sb, Dimension2D dim, int col, GraphvizVersion graphvizVersion) { final int w = (int) dim.getWidth(); final int h = (int) dim.getHeight(); appendTable(sb, w, h, col); } public static void appendTable(StringBuilder sb, int w, int h, int col) { sb.append("
"); sb.append(" | "); sb.append("