All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mxgraph.canvas.mxGraphicsCanvas2D Maven / Gradle / Ivy

Go to download

This project contains the apt processor that implements all the checks enumerated in @Verify. It is a self contained, and shaded jar.

The newest version!
package com.mxgraph.canvas;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.AttributedString;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.CellRendererPane;
import javax.swing.JLabel;

import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxLightweightLabel;
import com.mxgraph.util.mxUtils;

/**
 * Used for exporting images. To render to an image from a given XML string,
 * graph size and background color, the following code is used:
 * 
 * 
 * BufferedImage image = mxUtils.createBufferedImage(width, height, background);
 * Graphics2D g2 = image.createGraphics();
 * mxUtils.setAntiAlias(g2, true, true);
 * XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
 * reader.setContentHandler(new mxSaxOutputHandler(new mxGraphicsCanvas2D(g2)));
 * reader.parse(new InputSource(new StringReader(xml)));
 * 
 * 
 * Text rendering is available for plain text and HTML markup, the latter with optional
 * word wrapping. CSS support is limited to the following:
 * http://docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html
 */
public class mxGraphicsCanvas2D implements mxICanvas2D
{

	private static final Logger log = Logger.getLogger(mxGraphicsCanvas2D.class.getName());

	/**
	 * Specifies the image scaling quality. Default is Image.SCALE_SMOOTH.
	 * See {@link #scaleImage(Image, int, int)}
	 */
	public static int IMAGE_SCALING = Image.SCALE_SMOOTH;

	/**
	 * Specifies the additional pixels when computing the text width for HTML labels.
	 * Default is 5.
	 */
	public static int JAVA_TEXT_WIDTH_DELTA = 6;

	/**
	 * Scale for rendering HTML output. Default is 1.
	 */
	public static double HTML_SCALE = 1;

	/**
	 * Unit to be used for HTML labels. Default is "pt". If you units within
	 * HTML labels are used, this should match those units to produce a
	 * consistent output. If the value is "px", then HTML_SCALE should be
	 * changed the match the ratio between px units for rendering HTML and
	 * the units used for rendering other graphics elements. This value is
	 * 0.6 on Linux and 0.75 on all other platforms.
	 */
	public static String HTML_UNIT = "pt";

	/**
	 * Specifies the size of the cache used to store parsed colors
	 */
	public static int COLOR_CACHE_SIZE = 100;

	/**
	 * Reference to the graphics instance for painting.
	 */
	protected Graphics2D graphics;

	/**
	 * Specifies if text output should be rendered. Default is true.
	 */
	protected boolean textEnabled = true;

	/**
	 * Represents the current state of the canvas.
	 */
	protected transient CanvasState state = new CanvasState();

	/**
	 * Stack of states for save/restore.
	 */
	protected transient Stack stack = new Stack();

	/**
	 * Holds the current path.
	 */
	protected transient GeneralPath currentPath;

	/**
	 * Optional renderer pane to be used for HTML label rendering.
	 */
	protected CellRendererPane rendererPane;

	/**
	 * Font caching.
	 */
	protected transient Font lastFont = null;

	/**
	 * Font caching.
	 */
	protected transient int lastFontStyle = 0;

	/**
	 * Font caching.
	 */
	protected transient int lastFontSize = 0;

	/**
	 * Font caching.
	 */
	protected transient String lastFontFamily = "";

	/**
	 * Stroke caching.
	 */
	protected transient Stroke lastStroke = null;

	/**
	 * Stroke caching.
	 */
	protected transient float lastStrokeWidth = 0;

	/**
	 * Stroke caching.
	 */
	protected transient int lastCap = 0;

	/**
	 * Stroke caching.
	 */
	protected transient int lastJoin = 0;

	/**
	 * Stroke caching.
	 */
	protected transient float lastMiterLimit = 0;

	/**
	 * Stroke caching.
	 */
	protected transient boolean lastDashed = false;

	/**
	 * Stroke caching.
	 */
	protected transient Object lastDashPattern = "";

	/**
	 * Caches parsed colors.
	 */
	@SuppressWarnings("serial")
	protected transient LinkedHashMap colorCache = new LinkedHashMap()
	{
		@Override
		protected boolean removeEldestEntry(Map.Entry eldest)
		{
			return size() > COLOR_CACHE_SIZE;
		}
	};

	/**
	 * Constructs a new graphics export canvas.
	 */
	public mxGraphicsCanvas2D(Graphics2D g)
	{
		setGraphics(g);
		state.g = g;

		// Initializes the cell renderer pane for drawing HTML markup
		try
		{
			rendererPane = new CellRendererPane();
		}
		catch (Exception e)
		{
			log.log(Level.WARNING, "Failed to initialize renderer pane", e);
		}
	}

	/**
	 * Sets the graphics instance.
	 */
	public void setGraphics(Graphics2D value)
	{
		graphics = value;
	}

	/**
	 * Returns the graphics instance.
	 */
	public Graphics2D getGraphics()
	{
		return graphics;
	}

	/**
	 * Returns true if text should be rendered.
	 */
	public boolean isTextEnabled()
	{
		return textEnabled;
	}

	/**
	 * Disables or enables text rendering.
	 */
	public void setTextEnabled(boolean value)
	{
		textEnabled = value;
	}

	/**
	 * Saves the current canvas state.
	 */
	public void save()
	{
		stack.push(state);
		state = cloneState(state);
		state.g = (Graphics2D) state.g.create();
	}

	/**
	 * Restores the last canvas state.
	 */
	public void restore()
	{
		state.g.dispose();
		state = stack.pop();
	}

	/**
	 * Returns a clone of the given state.
	 */
	protected CanvasState cloneState(CanvasState state)
	{
		try
		{
			return (CanvasState) state.clone();
		}
		catch (CloneNotSupportedException e)
		{
			log.log(Level.SEVERE, "Failed to clone the state", e);
		}

		return null;
	}

	/**
	 * 
	 */
	public void scale(double value)
	{
		// This implementation uses custom scale/translate and built-in rotation
		state.scale = state.scale * value;
	}

	/**
	 * 
	 */
	public void translate(double dx, double dy)
	{
		// This implementation uses custom scale/translate and built-in rotation
		state.dx += dx;
		state.dy += dy;
	}

	/**
	 * 
	 */
	public void rotate(double theta, boolean flipH, boolean flipV, double cx,
			double cy)
	{
		cx += state.dx;
		cy += state.dy;
		cx *= state.scale;
		cy *= state.scale;
		state.g.rotate(Math.toRadians(theta), cx, cy);

		// This implementation uses custom scale/translate and built-in rotation
		// Rotation state is part of the AffineTransform in state.transform
		if (flipH && flipV)
		{
			theta += 180;
		}
		else if (flipH ^ flipV)
		{
			double tx = (flipH) ? cx : 0;
			int sx = (flipH) ? -1 : 1;

			double ty = (flipV) ? cy : 0;
			int sy = (flipV) ? -1 : 1;

			state.g.translate(tx, ty);
			state.g.scale(sx, sy);
			state.g.translate(-tx, -ty);
		}

		state.theta = theta;
		state.rotationCx = cx;
		state.rotationCy = cy;
		state.flipH = flipH;
		state.flipV = flipV;
	}

	/**
	 * 
	 */
	public void setStrokeWidth(double value)
	{
		// Lazy and cached instantiation strategy for all stroke properties
		if (value != state.strokeWidth)
		{
			state.strokeWidth = value;
		}
	}

	/**
	 * Caches color conversion as it is expensive.
	 */
	public void setStrokeColor(String value)
	{
		// Lazy and cached instantiation strategy for all stroke properties
		if (state.strokeColorValue == null
				|| !state.strokeColorValue.equals(value))
		{
			state.strokeColorValue = value;
			state.strokeColor = null;
		}
	}

	/**
	 * 
	 */
	public void setDashed(boolean value)
	{
		this.setDashed(value, state.fixDash);
	}

	/**
	 * 
	 */
	public void setDashed(boolean value, boolean fixDash)
	{
		// Lazy and cached instantiation strategy for all stroke properties
		state.dashed = value;
		state.fixDash = fixDash;
	}

	/**
	 * 
	 */
	public void setDashPattern(String value)
	{
		if (value != null && value.length() > 0)
		{
			state.dashPattern = mxUtils.parseDashPattern(value);
		}
	}

	/**
	 * 
	 */
	public void setLineCap(String value)
	{
		if (!state.lineCap.equals(value))
		{
			state.lineCap = value;
		}
	}

	/**
	 * 
	 */
	public void setLineJoin(String value)
	{
		if (!state.lineJoin.equals(value))
		{
			state.lineJoin = value;
		}
	}

	/**
	 * 
	 */
	public void setMiterLimit(double value)
	{
		if (value != state.miterLimit)
		{
			state.miterLimit = value;
		}
	}

	/**
	 * 
	 */
	public void setFontSize(double value)
	{
		if (value != state.fontSize)
		{
			state.fontSize = value;
		}
	}

	/**
	 * 
	 */
	public void setFontColor(String value)
	{
		if (state.fontColorValue == null || !state.fontColorValue.equals(value))
		{
			state.fontColorValue = value;
			state.fontColor = null;
		}
	}

	/**
	 * 
	 */
	public void setFontBackgroundColor(String value)
	{
		if (state.fontBackgroundColorValue == null
				|| !state.fontBackgroundColorValue.equals(value))
		{
			state.fontBackgroundColorValue = value;
			state.fontBackgroundColor = null;
		}
	}

	/**
	 * 
	 */
	public void setFontBorderColor(String value)
	{
		if (state.fontBorderColorValue == null
				|| !state.fontBorderColorValue.equals(value))
		{
			state.fontBorderColorValue = value;
			state.fontBorderColor = null;
		}
	}

	/**
	 * 
	 */
	public void setFontFamily(String value)
	{
		if (!state.fontFamily.equals(value))
		{
			state.fontFamily = value;
		}
	}

	/**
	 * 
	 */
	public void setFontStyle(int value)
	{
		if (value != state.fontStyle)
		{
			state.fontStyle = value;
		}
	}

	/**
	 * 
	 */
	public void setAlpha(double value)
	{
		if (state.alpha != value)
		{
			state.g.setComposite(AlphaComposite
					.getInstance(AlphaComposite.SRC_OVER, (float) (value)));
			state.alpha = value;
		}
	}

	/**
	 * 
	 */
	public void setFillAlpha(double value)
	{
		if (state.fillAlpha != value)
		{
			state.fillAlpha = value;
			state.fillColor = null;
		}
	}

	/**
	 * 
	 */
	public void setStrokeAlpha(double value)
	{
		if (state.strokeAlpha != value)
		{
			state.strokeAlpha = value;
			state.strokeColor = null;
		}
	}

	/**
	 * 
	 */
	public void setFillColor(String value)
	{
		if (state.fillColorValue == null || !state.fillColorValue.equals(value))
		{
			state.fillColorValue = value;
			state.fillColor = null;

			// Setting fill color resets gradient paint
			state.gradientPaint = null;
		}
	}

	/**
	 * 
	 */
	public void setGradient(String color1, String color2, double x, double y,
			double w, double h, String direction, double alpha1, double alpha2)
	{
		// LATER: Add lazy instantiation and check if paint already created
		float x1 = (float) ((state.dx + x) * state.scale);
		float y1 = (float) ((state.dy + y) * state.scale);
		float x2 = (float) x1;
		float y2 = (float) y1;
		h *= state.scale;
		w *= state.scale;

		if (direction == null || direction.length() == 0
				|| direction.equals(mxConstants.DIRECTION_SOUTH))
		{
			y2 = (float) (y1 + h);
		}
		else if (direction.equals(mxConstants.DIRECTION_EAST))
		{
			x2 = (float) (x1 + w);
		}
		else if (direction.equals(mxConstants.DIRECTION_NORTH))
		{
			y1 = (float) (y1 + h);
		}
		else if (direction.equals(mxConstants.DIRECTION_WEST))
		{
			x1 = (float) (x1 + w);
		}

		Color c1 = parseColor(color1);

		if (alpha1 != 1)
		{
			c1 = new Color(c1.getRed(), c1.getGreen(), c1.getBlue(),
					(int) (alpha1 * 255));
		}

		Color c2 = parseColor(color2);

		if (alpha2 != 1)
		{
			c2 = new Color(c2.getRed(), c2.getGreen(), c2.getBlue(),
					(int) (alpha2 * 255));
		}

		state.gradientPaint = new GradientPaint(x1, y1, c1, x2, y2, c2, true);

		// Resets fill color
		state.fillColorValue = null;
	}

	/**
	 * Helper method that uses {@link mxUtils#parseColor(String)}.
	 */
	protected Color parseColor(String hex)
	{
		return parseColor(hex, 1);
	};

	/**
	 * Helper method that uses {@link mxUtils#parseColor(String)}.
	 */
	protected Color parseColor(String hex, double alpha)
	{
		Color result = colorCache.get(hex);

		if (result == null)
		{
			result = mxUtils.parseColor(hex, alpha);
			colorCache.put(hex + "-" + (int) (alpha * 255), result);
		}

		return result;
	};

	/**
	 *
	 */
	public void rect(double x, double y, double w, double h)
	{
		currentPath = new GeneralPath();
		currentPath.append(new Rectangle2D.Double((state.dx + x) * state.scale,
				(state.dy + y) * state.scale, w * state.scale, h * state.scale),
				false);
	}

	/**
	 * Implements a rounded rectangle using a path.
	 */
	public void roundrect(double x, double y, double w, double h, double dx,
			double dy)
	{
		// LATER: Use arc here or quad in VML/SVG for exact match
		begin();
		moveTo(x + dx, y);
		lineTo(x + w - dx, y);
		quadTo(x + w, y, x + w, y + dy);
		lineTo(x + w, y + h - dy);
		quadTo(x + w, y + h, x + w - dx, y + h);
		lineTo(x + dx, y + h);
		quadTo(x, y + h, x, y + h - dy);
		lineTo(x, y + dy);
		quadTo(x, y, x + dx, y);
	}

	/**
	 * 
	 */
	public void ellipse(double x, double y, double w, double h)
	{
		currentPath = new GeneralPath();
		currentPath.append(new Ellipse2D.Double((state.dx + x) * state.scale,
				(state.dy + y) * state.scale, w * state.scale, h * state.scale),
				false);
	}

	/**
	 * 
	 */
	public void image(double x, double y, double w, double h, String src,
			boolean aspect, boolean flipH, boolean flipV)
	{
		if (src != null && w > 0 && h > 0)
		{
			Image img = loadImage(src);

			if (img != null)
			{
				Rectangle bounds = getImageBounds(img, x, y, w, h, aspect);
				img = scaleImage(img, bounds.width, bounds.height);

				if (img != null)
				{
					drawImage(
							createImageGraphics(bounds.x, bounds.y,
									bounds.width, bounds.height, flipH, flipV),
							img, bounds.x, bounds.y);
				}
			}
		}
	}

	/**
	 * 
	 */
	protected void drawImage(Graphics2D graphics, Image image, int x, int y)
	{
		graphics.drawImage(image, x, y, null);
	}

	/**
	 * Hook for image caching.
	 */
	protected Image loadImage(String src)
	{
		return mxUtils.loadImage(src);
	}

	/**
	 * 
	 */
	protected final Rectangle getImageBounds(Image img, double x, double y,
			double w, double h, boolean aspect)
	{
		x = (state.dx + x) * state.scale;
		y = (state.dy + y) * state.scale;
		w *= state.scale;
		h *= state.scale;

		if (aspect)
		{
			Dimension size = getImageSize(img);
			double s = Math.min(w / size.width, h / size.height);
			int sw = (int) Math.round(size.width * s);
			int sh = (int) Math.round(size.height * s);
			x += (w - sw) / 2;
			y += (h - sh) / 2;
			w = sw;
			h = sh;
		}
		else
		{
			w = Math.round(w);
			h = Math.round(h);
		}

		return new Rectangle((int) x, (int) y, (int) w, (int) h);
	}

	/**
	 * Returns the size for the given image.
	 */
	protected Dimension getImageSize(Image image)
	{
		return new Dimension(image.getWidth(null), image.getHeight(null));
	}

	/**
	 * Uses {@link #IMAGE_SCALING} to scale the given image.
	 */
	protected Image scaleImage(Image img, int w, int h)
	{
		Dimension size = getImageSize(img);

		if (w == size.width && h == size.height)
		{
			return img;
		}
		else
		{
			return img.getScaledInstance(w, h, IMAGE_SCALING);
		}
	}

	/**
	 * Creates a graphic instance for rendering an image.
	 */
	protected final Graphics2D createImageGraphics(double x, double y, double w,
			double h, boolean flipH, boolean flipV)
	{
		Graphics2D g2 = state.g;

		if (flipH || flipV)
		{
			g2 = (Graphics2D) g2.create();

			if (flipV && flipH)
			{
				g2.rotate(Math.toRadians(180), x + w / 2, y + h / 2);
			}
			else
			{
				int sx = 1;
				int sy = 1;
				int dx = 0;
				int dy = 0;

				if (flipH)
				{
					sx = -1;
					dx = (int) (-w - 2 * x);
				}

				if (flipV)
				{
					sy = -1;
					dy = (int) (-h - 2 * y);
				}

				g2.scale(sx, sy);
				g2.translate(dx, dy);
			}
		}

		return g2;
	}

	/**
	 * Creates a HTML document around the given markup.
	 */
	protected String createHtmlDocument(String text, String align,
			String valign, int w, int h, boolean wrap, String overflow,
			boolean clip)
	{
		StringBuffer css = new StringBuffer();
		css.append("display:inline;");
		css.append("font-family:" + state.fontFamily + ";");
		css.append("font-size:" + Math.round(state.fontSize) + HTML_UNIT
				+ ";");
		css.append("color:" + state.fontColorValue + ";");
		// KNOWN: Line-height ignored in JLabel
		css.append("line-height:"
				+ ((mxConstants.ABSOLUTE_LINE_HEIGHT)
						? Math.round(state.fontSize * mxConstants.LINE_HEIGHT)
								+ " " + HTML_UNIT
						: mxConstants.LINE_HEIGHT)
				+ ";");

		boolean setWidth = false;

		if ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
		{
			css.append("font-weight:bold;");
		}

		if ((state.fontStyle
				& mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
		{
			css.append("font-style:italic;");
		}

		if ((state.fontStyle
				& mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
		{
			css.append("text-decoration:underline;");
		}

		if (align != null)
		{
			if (align.equals(mxConstants.ALIGN_CENTER))
			{
				css.append("text-align:center;");
			}
			else if (align.equals(mxConstants.ALIGN_RIGHT))
			{
				css.append("text-align:right;");
			}
		}

		if (state.fontBackgroundColorValue != null)
		{
			css.append(
					"background-color:" + state.fontBackgroundColorValue + ";");
		}

		// KNOWN: Border ignored in JLabel
		if (state.fontBorderColorValue != null)
		{
			css.append("border:1pt solid " + state.fontBorderColorValue + ";");
		}

		// KNOWN: max-width/-height ignored in JLabel
		if (clip)
		{
			css.append("overflow:hidden;");
			setWidth = true;
		}
		else if (overflow != null)
		{
			if (overflow.equals("fill"))
			{
				css.append("height:" + Math.round(h) + HTML_UNIT + ";");
				setWidth = true;
			}
			else if (overflow.equals("width"))
			{
				setWidth = true;

				if (h > 0)
				{
					css.append(
							"height:" + Math.round(h) + HTML_UNIT + ";");
				}
			}
		}

		if (wrap)
		{
			if (!clip)
			{
				// NOTE: Max-width not available in Java
				setWidth = true;
			}

			css.append("white-space:normal;");
		}
		else
		{
			css.append("white-space:nowrap;");
		}

		if (setWidth && w > 0)
		{
			css.append("width:" + Math.round(w) + HTML_UNIT + ";");
		}

		return createHtmlDocument(text, css.toString());
	}

	/**
	 * Creates a HTML document for the given text and CSS style.
	 */
	protected String createHtmlDocument(String text, String style)
	{
		return "
" + text + "
"; } /** * Hook to return the renderer for HTML formatted text. This implementation returns * the shared instance of mxLighweightLabel. */ protected JLabel getTextRenderer() { return mxLightweightLabel.getSharedInstance(); } /** * */ protected Point2D getMargin(String align, String valign) { double dx = 0; double dy = 0; if (align != null) { if (align.equals(mxConstants.ALIGN_CENTER)) { dx = -0.5; } else if (align.equals(mxConstants.ALIGN_RIGHT)) { dx = -1; } } if (valign != null) { if (valign.equals(mxConstants.ALIGN_MIDDLE)) { dy = -0.5; } else if (valign.equals(mxConstants.ALIGN_BOTTOM)) { dy = -1; } } return new Point2D.Double(dx, dy); } /** * Draws the given HTML text. */ protected void htmlText(double x, double y, double w, double h, String str, String align, String valign, boolean wrap, String format, String overflow, boolean clip, double rotation) { x += state.dx; y += state.dy; JLabel textRenderer = getTextRenderer(); if (textRenderer != null && rendererPane != null) { // Use native scaling for HTML AffineTransform previous = state.g.getTransform(); double rad = rotation * (Math.PI / 180); state.g.rotate(rad, x, y); state.g.scale(state.scale * HTML_SCALE, state.scale * HTML_SCALE); // Renders the scaled text with a correction factor // HTML_SCALE for the given HTML_UNIT boolean widthFill = false; boolean fill = false; String original = str; if (overflow != null) { widthFill = overflow.equals("width"); fill = overflow.equals("fill"); } str = createHtmlDocument(str, align, valign, (widthFill || fill) ? (int) Math.round(w) : 0, (fill) ? (int) Math.round(h) : 0, wrap, overflow, clip); textRenderer.setText(str); Dimension pref = textRenderer.getPreferredSize(); int prefWidth = pref.width; int prefHeight = pref.height; // Poor man's max-width // TODO: Is this still needed? if (((clip || wrap) && prefWidth > w / HTML_SCALE && w > 0) || (clip && prefHeight > h / HTML_SCALE && h > 0)) { // TextWidthDelta is workaround for inconsistent word wrapping in Java int cw = (int) Math .round((w) + ((wrap) ? JAVA_TEXT_WIDTH_DELTA : 0)); int ch = (int) Math.round(h); str = createHtmlDocument(original, align, valign, cw, ch, wrap, overflow, clip); textRenderer.setText(str); pref = textRenderer.getPreferredSize(); prefWidth = pref.width; prefHeight = pref.height + 2; } // Matches HTML output if (clip && w > 0 && h > 0) { prefWidth = Math.min(pref.width, (int) (w / HTML_SCALE)); prefHeight = Math.min(prefHeight, (int) (h / HTML_SCALE)); h = prefHeight * HTML_SCALE; } else if (!clip && wrap && w > 0 && h > 0) { prefWidth = pref.width; w = Math.max(pref.width, (int) (w / HTML_SCALE)); h = prefHeight * HTML_SCALE; prefHeight = Math.max(prefHeight, (int) (h / HTML_SCALE)); } else if (!clip && !wrap) { if (w > 0 && w / HTML_SCALE < prefWidth) { w = prefWidth * HTML_SCALE; } if (h > 0 && h / HTML_SCALE < prefHeight) { h = prefHeight * HTML_SCALE; } } Point2D margin = getMargin(align, valign); x += margin.getX() * prefWidth * HTML_SCALE; y += margin.getY() * prefHeight * HTML_SCALE; if (w == 0) { w = prefWidth * HTML_SCALE; } if (h == 0) { h = prefHeight * HTML_SCALE; } rendererPane.paintComponent(state.g, textRenderer, rendererPane, (int) Math.round(x / HTML_SCALE), (int) Math.round(y / HTML_SCALE), (int) Math.round(w / HTML_SCALE), (int) Math.round(h / HTML_SCALE), true); state.g.setTransform(previous); } } /** * Draws the given text. */ public void text(double x, double y, double w, double h, String str, String align, String valign, boolean wrap, String format, String overflow, boolean clip, double rotation, String textDirection) { // TODO: Add support for text direction if (format != null && format.equals("html")) { htmlText(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation); } else { plainText(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation); } } /** * Draws the given text. */ public void plainText(double x, double y, double w, double h, String str, String align, String valign, boolean wrap, String format, String overflow, boolean clip, double rotation) { if (state.fontColor == null) { state.fontColor = parseColor(state.fontColorValue); } if (state.fontColor != null) { x = (state.dx + x) * state.scale; y = (state.dy + y) * state.scale; w *= state.scale; h *= state.scale; // Font-metrics needed below this line Graphics2D g2 = createTextGraphics(x, y, w, h, rotation, clip, align, valign); FontMetrics fm = g2.getFontMetrics(); String[] lines = str.split("\n"); int[] stringWidths = new int[lines.length]; int textWidth = 0; for (int i = 0; i < lines.length; i++) { stringWidths[i] = fm.stringWidth(lines[i]); textWidth = Math.max(textWidth, stringWidths[i]); } int textHeight = (int) Math.round(lines.length * (fm.getFont().getSize() * mxConstants.LINE_HEIGHT)); if (clip && textHeight > h && h > 0) { textHeight = (int) h; } Point2D margin = getMargin(align, valign); x += margin.getX() * textWidth; y += margin.getY() * textHeight; if (state.fontBackgroundColorValue != null) { if (state.fontBackgroundColor == null) { state.fontBackgroundColor = parseColor( state.fontBackgroundColorValue); } if (state.fontBackgroundColor != null) { g2.setColor(state.fontBackgroundColor); g2.fillRect((int) Math.round(x), (int) Math.round(y - 1), textWidth + 1, textHeight + 2); } } if (state.fontBorderColorValue != null) { if (state.fontBorderColor == null) { state.fontBorderColor = parseColor( state.fontBorderColorValue); } if (state.fontBorderColor != null) { g2.setColor(state.fontBorderColor); g2.drawRect((int) Math.round(x), (int) Math.round(y - 1), textWidth + 1, textHeight + 2); } } g2.setColor(state.fontColor); y += fm.getHeight() - fm.getDescent() - (margin.getY() + 0.5); for (int i = 0; i < lines.length; i++) { double dx = 0; if (align != null) { if (align.equals(mxConstants.ALIGN_CENTER)) { dx = (textWidth - stringWidths[i]) / 2; } else if (align.equals(mxConstants.ALIGN_RIGHT)) { dx = textWidth - stringWidths[i]; } } // Adds support for underlined text via attributed character iterator if (!lines[i].isEmpty()) { if ((state.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { AttributedString as = new AttributedString(lines[i]); as.addAttribute(TextAttribute.FONT, g2.getFont()); as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); g2.drawString(as.getIterator(), (int) Math.round(x + dx), (int) Math.round(y)); } else { g2.drawString(lines[i], (int) Math.round(x + dx), (int) Math.round(y)); } } y += (int) Math.round( fm.getFont().getSize() * mxConstants.LINE_HEIGHT); } } } /** * Returns a new graphics instance with the correct color and font for * text rendering. */ protected final Graphics2D createTextGraphics(double x, double y, double w, double h, double rotation, boolean clip, String align, String valign) { Graphics2D g2 = state.g; updateFont(); if (rotation != 0) { g2 = (Graphics2D) state.g.create(); double rad = rotation * (Math.PI / 180); g2.rotate(rad, x, y); } if (clip && w > 0 && h > 0) { if (g2 == state.g) { g2 = (Graphics2D) state.g.create(); } Point2D margin = getMargin(align, valign); x += margin.getX() * w; y += margin.getY() * h; g2.clip(new Rectangle2D.Double(x, y, w, h)); } return g2; } /** * */ public void begin() { currentPath = new GeneralPath(); } /** * */ public void moveTo(double x, double y) { if (currentPath != null) { currentPath.moveTo((float) ((state.dx + x) * state.scale), (float) ((state.dy + y) * state.scale)); } } /** * */ public void lineTo(double x, double y) { if (currentPath != null) { currentPath.lineTo((float) ((state.dx + x) * state.scale), (float) ((state.dy + y) * state.scale)); } } /** * */ public void quadTo(double x1, double y1, double x2, double y2) { if (currentPath != null) { currentPath.quadTo((float) ((state.dx + x1) * state.scale), (float) ((state.dy + y1) * state.scale), (float) ((state.dx + x2) * state.scale), (float) ((state.dy + y2) * state.scale)); } } /** * */ public void curveTo(double x1, double y1, double x2, double y2, double x3, double y3) { if (currentPath != null) { currentPath.curveTo((float) ((state.dx + x1) * state.scale), (float) ((state.dy + y1) * state.scale), (float) ((state.dx + x2) * state.scale), (float) ((state.dy + y2) * state.scale), (float) ((state.dx + x3) * state.scale), (float) ((state.dy + y3) * state.scale)); } } /** * Closes the current path. */ public void close() { if (currentPath != null) { currentPath.closePath(); } } /** * */ public void stroke() { paintCurrentPath(false, true); } /** * */ public void fill() { paintCurrentPath(true, false); } /** * */ public void fillAndStroke() { paintCurrentPath(true, true); } /** * */ protected void paintCurrentPath(boolean filled, boolean stroked) { if (currentPath != null) { if (stroked) { if (state.strokeColor == null) { state.strokeColor = parseColor(state.strokeColorValue, state.strokeAlpha); } if (state.strokeColor != null) { updateStroke(); } } if (filled) { if (state.gradientPaint == null && state.fillColor == null) { state.fillColor = parseColor(state.fillColorValue, state.fillAlpha); } } if (state.shadow) { paintShadow(filled, stroked); } if (filled) { if (state.gradientPaint != null) { state.g.setPaint(state.gradientPaint); state.g.fill(currentPath); } else { if (state.fillColor != null) { state.g.setColor(state.fillColor); state.g.setPaint(null); state.g.fill(currentPath); } } } if (stroked && state.strokeColor != null) { state.g.setColor(state.strokeColor); state.g.draw(currentPath); } } } /** * */ protected void paintShadow(boolean filled, boolean stroked) { if (state.shadowColor == null) { state.shadowColor = parseColor(state.shadowColorValue); } if (state.shadowColor != null) { double rad = -state.theta * (Math.PI / 180); double cos = Math.cos(rad); double sin = Math.sin(rad); double dx = state.shadowOffsetX * state.scale; double dy = state.shadowOffsetY * state.scale; if (state.flipH) { dx *= -1; } if (state.flipV) { dy *= -1; } double tx = dx * cos - dy * sin; double ty = dx * sin + dy * cos; state.g.setColor(state.shadowColor); state.g.translate(tx, ty); double alpha = state.alpha * state.shadowAlpha; Composite comp = state.g.getComposite(); state.g.setComposite(AlphaComposite .getInstance(AlphaComposite.SRC_OVER, (float) (alpha))); if (filled && (state.gradientPaint != null || state.fillColor != null)) { state.g.fill(currentPath); } // FIXME: Overlaps with fill in composide mode if (stroked && state.strokeColor != null) { state.g.draw(currentPath); } state.g.translate(-tx, -ty); state.g.setComposite(comp); } } /** * */ public void setShadow(boolean value) { state.shadow = value; } /** * */ public void setShadowColor(String value) { state.shadowColorValue = value; } /** * */ public void setShadowAlpha(double value) { state.shadowAlpha = value; } /** * */ public void setShadowOffset(double dx, double dy) { state.shadowOffsetX = dx; state.shadowOffsetY = dy; } /** * */ protected void updateFont() { int size = (int) Math.round(state.fontSize * state.scale); int style = ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) ? Font.BOLD : Font.PLAIN; style += ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) ? Font.ITALIC : Font.PLAIN; if (lastFont == null || !lastFontFamily.equals(state.fontFamily) || size != lastFontSize || style != lastFontStyle) { lastFont = createFont(state.fontFamily, style, size); lastFontFamily = state.fontFamily; lastFontStyle = style; lastFontSize = size; } state.g.setFont(lastFont); } /** * Hook for subclassers to implement font caching. */ protected Font createFont(String family, int style, int size) { return new Font(getFontName(family), style, size); } /** * Returns a font name for the given CSS values for font-family. * This implementation returns the first entry for comma-separated * lists of entries. */ protected String getFontName(String family) { if (family != null) { int comma = family.indexOf(','); if (comma >= 0) { family = family.substring(0, comma); } } return family; } /** * */ protected void updateStroke() { float sw = (float) Math.max(1, state.strokeWidth * state.scale); int cap = BasicStroke.CAP_BUTT; if (state.lineCap.equals("round")) { cap = BasicStroke.CAP_ROUND; } else if (state.lineCap.equals("square")) { cap = BasicStroke.CAP_SQUARE; } int join = BasicStroke.JOIN_MITER; if (state.lineJoin.equals("round")) { join = BasicStroke.JOIN_ROUND; } else if (state.lineJoin.equals("bevel")) { join = BasicStroke.JOIN_BEVEL; } float miterlimit = (float) state.miterLimit; if (lastStroke == null || lastStrokeWidth != sw || lastCap != cap || lastJoin != join || lastMiterLimit != miterlimit || lastDashed != state.dashed || (state.dashed && lastDashPattern != state.dashPattern)) { float[] dash = null; if (state.dashed) { dash = new float[state.dashPattern.length]; for (int i = 0; i < dash.length; i++) { dash[i] = (float) (state.dashPattern[i] * ((state.fixDash) ? state.scale : sw)); } } lastStroke = new BasicStroke(sw, cap, join, miterlimit, dash, 0); lastStrokeWidth = sw; lastCap = cap; lastJoin = join; lastMiterLimit = miterlimit; lastDashed = state.dashed; lastDashPattern = state.dashPattern; } state.g.setStroke(lastStroke); } /** * */ protected class CanvasState implements Cloneable { /** * */ protected double alpha = 1; /** * */ protected double fillAlpha = 1; /** * */ protected double strokeAlpha = 1; /** * */ protected double scale = 1; /** * */ protected double dx = 0; /** * */ protected double dy = 0; /** * */ protected double theta = 0; /** * */ protected double rotationCx = 0; /** * */ protected double rotationCy = 0; /** * */ protected boolean flipV = false; /** * */ protected boolean flipH = false; /** * */ protected double miterLimit = 10; /** * */ protected int fontStyle = 0; /** * */ protected double fontSize = mxConstants.DEFAULT_FONTSIZE; /** * */ protected String fontFamily = mxConstants.DEFAULT_FONTFAMILIES; /** * */ protected String fontColorValue = "#000000"; /** * */ protected Color fontColor; /** * */ protected String fontBackgroundColorValue; /** * */ protected Color fontBackgroundColor; /** * */ protected String fontBorderColorValue; /** * */ protected Color fontBorderColor; /** * */ protected String lineCap = "flat"; /** * */ protected String lineJoin = "miter"; /** * */ protected double strokeWidth = 1; /** * */ protected String strokeColorValue; /** * */ protected Color strokeColor; /** * */ protected String fillColorValue; /** * */ protected Color fillColor; /** * */ protected Paint gradientPaint; /** * */ protected boolean dashed = false; /** * */ protected boolean fixDash = false; /** * */ protected float[] dashPattern = { 3, 3 }; /** * */ protected boolean shadow = false; /** * */ protected String shadowColorValue = mxConstants.W3C_SHADOWCOLOR; /** * */ protected Color shadowColor; /** * */ protected double shadowAlpha = 1; /** * */ protected double shadowOffsetX = mxConstants.SHADOW_OFFSETX; /** * */ protected double shadowOffsetY = mxConstants.SHADOW_OFFSETY; /** * Stores the actual state. */ protected transient Graphics2D g; /** * */ public Object clone() throws CloneNotSupportedException { return super.clone(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy