com.mxgraph.canvas.mxGraphicsCanvas2D Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of AptSpringProcessor Show documentation
Show all versions of AptSpringProcessor Show documentation
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