/*******************************************************************************
* Copyright (c) 2016 Weasis Team and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nicolas Roduit - initial API and implementation
*******************************************************************************/
package org.weasis.core.ui.model.graphic;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Objects;
import java.util.Optional;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.weasis.core.api.gui.util.GeomUtil;
import org.weasis.core.api.util.StringUtil;
import org.weasis.core.ui.editor.image.ViewCanvas;
import org.weasis.core.ui.model.utils.imp.DefaultGraphicLabel;
public abstract class AbstractGraphicLabel implements GraphicLabel {
protected String[] labels;
protected Rectangle2D labelBounds;
protected Double labelWidth;
protected Double labelHeight;
protected Double offsetX;
protected Double offsetY;
public AbstractGraphicLabel() {
this(DEFAULT_OFFSET_X, DEFAULT_OFFSET_Y);
}
public AbstractGraphicLabel(Double offsetX, Double offsetY) {
this.offsetX = Optional.ofNullable(offsetX).orElse(DEFAULT_OFFSET_X);
this.offsetY = Optional.ofNullable(offsetY).orElse(DEFAULT_OFFSET_Y);
reset();
}
public AbstractGraphicLabel(AbstractGraphicLabel object) {
this.offsetX = object.offsetX;
this.offsetY = object.offsetY;
this.labels = Optional.ofNullable(object.labels).map(l -> l.clone()).orElse(null);
this.labelBounds = Optional.ofNullable(object.labelBounds).map(lb -> lb.getBounds2D()).orElse(null);
this.labelWidth = object.labelWidth;
this.labelHeight = object.labelHeight;
}
@Override
public void reset() {
labels = null;
labelBounds = null;
labelHeight = 0d;
labelWidth = 0d;
}
@XmlElementWrapper(name = "labels")
@XmlElement(name = "label", required = false)
@Override
public String[] getLabels() {
return labels;
}
@XmlElement(name = "offsetX", required = false)
@Override
public Double getOffsetX() {
return offsetX;
}
@XmlElement(name = "offsetY", required = false)
@Override
public Double getOffsetY() {
return offsetY;
}
public void setLabels(String[] labels) {
this.labels = labels;
}
public void setOffsetX(Double offsetX) {
this.offsetX = offsetX;
}
public void setOffsetY(Double offsetY) {
this.offsetY = offsetY;
}
@Override
public Rectangle2D getBounds(AffineTransform transform) {
return getArea(transform).getBounds2D();
}
@Override
public Area getArea(AffineTransform transform) {
if (Objects.isNull(labelBounds)) {
return new Area();
}
if (Objects.isNull(transform)) {
return new Area(labelBounds);
}
AffineTransform invTransform = new AffineTransform(); // Identity transformation.
Point2D anchorPt = new Point2D.Double(labelBounds.getX(), labelBounds.getY());
Double scale = GeomUtil.extractScalingFactor(transform);
Double angleRad = GeomUtil.extractAngleRad(transform);
invTransform.translate(anchorPt.getX(), anchorPt.getY());
if (!Objects.equals(scale, 1d)) {
invTransform.scale(1 / scale, 1 / scale);
}
if (!Objects.equals(angleRad, 0d)) {
invTransform.rotate(-angleRad);
}
invTransform.translate(-anchorPt.getX(), -anchorPt.getY());
if ((transform.getType() & AffineTransform.TYPE_FLIP) != 0) {
invTransform.translate(0, -labelBounds.getHeight());
}
Area areaBounds = new Area(invTransform.createTransformedShape(labelBounds));
areaBounds.transform(AffineTransform.getTranslateInstance(offsetX, offsetY));
return areaBounds;
}
@Override
public Rectangle2D getTransformedBounds(AffineTransform transform) {
// Only translates origin because no rotation or scaling is applied
Point2D.Double anchorPoint = new Point2D.Double(labelBounds.getX() + offsetX, labelBounds.getY() + offsetY);
Optional.ofNullable(transform).ifPresent(t -> transform.transform(anchorPoint, anchorPoint));
return new Rectangle2D.Double(anchorPoint.getX(), anchorPoint.getY(), labelBounds.getWidth(),
labelBounds.getHeight());
}
@Override
public void setLabel(ViewCanvas<?> view2d, Double xPos, Double yPos, String... labels) {
if (view2d == null || labels == null || labels.length == 0) {
reset();
} else {
Graphics2D g2d = (Graphics2D) view2d.getJComponent().getGraphics();
if (g2d == null) {
return;
}
this.labels = labels;
Font defaultFont = g2d.getFont();
FontRenderContext fontRenderContext =
((Graphics2D) view2d.getJComponent().getGraphics()).getFontRenderContext();
updateBoundsSize(defaultFont, fontRenderContext);
labelBounds = new Rectangle.Double(xPos + GROWING_BOUND, yPos + GROWING_BOUND, labelWidth + GROWING_BOUND,
(labelHeight * labels.length) + GROWING_BOUND);
GeomUtil.growRectangle(labelBounds, GROWING_BOUND);
}
}
protected void updateBoundsSize(Font defaultFont, FontRenderContext fontRenderContext) {
Optional.ofNullable(defaultFont).orElseThrow(() -> new RuntimeException("Font should not be null")); //$NON-NLS-1$
Optional.ofNullable(fontRenderContext)
.orElseThrow(() -> new RuntimeException("FontRenderContext should not be null")); //$NON-NLS-1$
if (labels == null || labels.length == 0) {
reset();
} else {
double maxWidth = 0;
for (String label : labels) {
if (StringUtil.hasText(label)) {
TextLayout layout = new TextLayout(label, defaultFont, fontRenderContext);
maxWidth = Math.max(layout.getBounds().getWidth(), maxWidth);
}
}
labelHeight = new TextLayout("Tg", defaultFont, fontRenderContext).getBounds().getHeight() + 2; //$NON-NLS-1$
labelWidth = maxWidth;
}
}
@Override
public void move(Double deltaX, Double deltaY) {
Optional.ofNullable(deltaX).ifPresent(delta -> this.offsetX += delta);
Optional.ofNullable(deltaY).ifPresent(delta -> this.offsetY += delta);
}
@Override
public void paint(Graphics2D g2d, AffineTransform transform, boolean selected) {
if (labels != null && labelBounds != null) {
Paint oldPaint = g2d.getPaint();
Point2D pt = new Point2D.Double(labelBounds.getX() + offsetX, labelBounds.getY() + offsetY);
if (transform != null) {
transform.transform(pt, pt);
}
float px = (float) pt.getX() + GROWING_BOUND;
float py = (float) pt.getY() + GROWING_BOUND;
for (String label : labels) {
if (StringUtil.hasText(label)) {
py += labelHeight;
paintColorFontOutline(g2d, label, px, py, Color.WHITE);
}
}
// Graphics DEBUG
// Point2D pt2 = new Point2D.Double(labelBounds.getX(), labelBounds.getY());
// if (transform != null) {
// transform.transform(pt2, pt2);
// }
//
// g2d.setPaint(Color.RED);
// g2d.draw(new Line2D.Double(pt2.getX() - 5, pt2.getY(), pt2.getX() + 5, pt2.getY()));
// g2d.draw(new Line2D.Double(pt2.getX(), pt2.getY() - 5, pt2.getX(), pt2.getY() + 5));
//
// if (transform != null) {
// g2d.setPaint(Color.GREEN);
// g2d.draw(transform.createTransformedShape(getBounds(transform)));
// }
// if (transform != null) {
// g2d.setPaint(Color.RED);
// g2d.draw(transform.createTransformedShape(getArea(transform)));
// }
// Graphics DEBUG
if (selected) {
paintBoundOutline(g2d, transform);
}
g2d.setPaint(oldPaint);
}
}
protected void paintBoundOutline(Graphics2D g2d, AffineTransform transform) {
Rectangle2D boundingRect = getTransformedBounds(transform);
Paint oldPaint = g2d.getPaint();
g2d.setPaint(Color.BLACK);
g2d.draw(boundingRect);
GeomUtil.growRectangle(boundingRect, -1);
g2d.setPaint(Color.WHITE);
g2d.draw(boundingRect);
g2d.setPaint(oldPaint);
}
public static void paintColorFontOutline(Graphics2D g2, String str, float x, float y, Color color) {
g2.setPaint(Color.BLACK);
if (RenderingHints.VALUE_TEXT_ANTIALIAS_ON.equals(g2.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING))) {
TextLayout layout = new TextLayout(str, g2.getFont(), g2.getFontRenderContext());
Rectangle2D b = layout.getBounds();
b.setRect(x + b.getX() - 0.75, y + b.getY() - 0.75, b.getWidth() + 1.5, b.getHeight() + 1.5);
g2.fill(b);
} else {
g2.drawString(str, x - 1f, y - 1f);
g2.drawString(str, x - 1f, y);
g2.drawString(str, x - 1f, y + 1f);
g2.drawString(str, x, y - 1f);
g2.drawString(str, x, y + 1f);
g2.drawString(str, x + 1f, y - 1f);
g2.drawString(str, x + 1f, y);
g2.drawString(str, x + 1f, y + 1f);
}
g2.setPaint(color);
g2.drawString(str, x, y);
}
public static void paintFontOutline(Graphics2D g2, String str, float x, float y) {
paintColorFontOutline(g2, str, x, y, Color.WHITE);
}
@Override
public Rectangle2D getLabelBounds() {
return labelBounds;
}
public static class Adapter extends XmlAdapter<DefaultGraphicLabel, GraphicLabel> {
@Override
public GraphicLabel unmarshal(DefaultGraphicLabel v) throws Exception {
return v;
}
@Override
public DefaultGraphicLabel marshal(GraphicLabel v) throws Exception {
return (DefaultGraphicLabel) v;
}
}
}