package boxrenderer; import graphics.GraphUtils; import java.awt.Color; import java.awt.Dimension; import java.awt.GradientPaint; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.osbcp.cssparser.Rule; public abstract class AbstractBox implements Box { private static final Logger logger = LoggerFactory.getLogger(AbstractBox.class); private Margin margin = new Margin(); private Border border = new Border(); private Padding padding = new Padding(); private Paint background; private int width; private int height; private Box parent; private String id; private String tag; private Set<String> cssClasses = Sets.newHashSet(); private Map<String, String> attributes; private List<Rule> cssRules = Lists.newArrayList(); private String fontFamily; private int fontSize; private Paint color; private boolean bold; private boolean inline = false; private double rotate; private String rotationPoint = "left top"; private ImageRenderer backgroundImage; // TODO also support vertical align of background private Align backgroundPosition = Align.LEFT; private Size backgroundSize; private Paint backgroundTexture; private Color linearGradientFrom; private Color linearGradientTo; private boolean printdebug = false; public AbstractBox() { } @Override public Dimension getDimension(Graphics2D g2) throws Exception { Dimension d = getRotationBoxDimension(g2); int w = d.width; int h = d.height; if(rotate!=0) { Dimension dim = getRotatedDimension(new Dimension(w,h)); w = dim.width; h = dim.height; } if(margin.isApplicable()) { w+=margin.getLeft()+margin.getRight(); h+=margin.getTop()+margin.getBottom(); } return new Dimension(w,h); } private Dimension getRotatedDimension(Dimension d) { Rectangle2D r2 = getRotatedBounds(d); int w = (int)Math.round(r2.getMaxX() - r2.getMinX()); int h = (int)Math.round(r2.getMaxY() - r2.getMinY()); logger.debug(String.format("rotated (%s deg) dimensions are w: %s h: %s", GraphUtils.toDegrees(rotate), w, h)); return new Dimension(w,h); } private Rectangle2D getRotatedBounds(Dimension d) { logger.debug(String.format("dimensions are w: %s h: %s", d.width, d.height)); Rectangle r = new Rectangle(0,0,d.width,d.height); AffineTransform rot = AffineTransform.getRotateInstance(rotate); Shape rotated = rot.createTransformedShape(r); Rectangle2D r2 = rotated.getBounds2D(); logger.debug(String.format("maxX %s", r2.getMaxX())); logger.debug(String.format("minX %s", r2.getMinX())); logger.debug(String.format("maxY %s", r2.getMaxY())); logger.debug(String.format("minY %s", r2.getMinY())); return r2; } private void setRotation(Graphics2D g, int width, int height, Rectangle2D rotatedBounds) { Point rotationPoint = getDecodedRotationPoint(width, height); AffineTransform transform = g.getTransform(); transform.concatenate(AffineTransform.getTranslateInstance(-rotatedBounds.getMinX(), -rotatedBounds.getMinY())); transform.concatenate(AffineTransform.getRotateInstance(rotate, rotationPoint.x, rotationPoint.y)); g.setTransform(transform); } private Point getDecodedRotationPoint(int width, int height) { int px; int py; String[] s = StringUtils.split(rotationPoint); String s0 = s[0]; if(StringUtils.endsWith(s0, "%")) { px = Math.round((float)width/100*Float.parseFloat(StringUtils.removeEnd(s0, "%"))); } else if("left".equals(s0)) { px = 0; } else if("right".equals(s0)) { px = width; } else if("center".equals(s0)) { px = width/2; } else { throw new RuntimeException("unknown rotation point "+s0); } String s1; if(s.length>=2) { s1 = s[1]; } else { s1 = "center"; } if(StringUtils.endsWith(s1, "%")) { py = Math.round((float)height/100*Float.parseFloat(StringUtils.removeEnd(s1, "%"))); } else if("top".equals(s1)) { py = 0; } else if("bottom".equals(s1)) { py = height; } else if("center".equals(s1)) { py = height/2; } else { throw new RuntimeException("unknown rotation point "+s1); } return new Point(px,py); } private Dimension getRotationBoxDimension(Graphics2D g2) throws Exception { Dimension d = getContentDimension(g2); int w = Math.max(d.width, getWidth()); int h = Math.max(d.height, getHeight()); if(padding.isApplicable()) { w+=padding.getLeft()+padding.getRight(); h+=padding.getTop()+padding.getBottom(); } if(border.isApplicable()) { w+=border.getLeft()+border.getRight(); h+=border.getTop()+border.getBottom(); } return new Dimension(w,h); } @Override public void render(Graphics2D g2) throws Exception { Graphics2D mInner = margin.render(g2); if(rotate!=0) { Dimension d = getRotationBoxDimension(g2); Rectangle2D rotatedBounds = getRotatedBounds(d); setRotation(mInner, d.width, d.height, rotatedBounds); } Graphics2D bInner = border.render(mInner); // padding.setPaint(getBackground()); padding.setRender(false); Paint background = getBackground(); if(background!=null) { bInner.setPaint(background); bInner.fill(bInner.getClipBounds()); } if(backgroundTexture!=null) { bInner.setPaint(backgroundTexture); bInner.fill(bInner.getClipBounds()); } if(linearGradientFrom!=null && linearGradientTo!=null) { float x1 = bInner.getClipBounds().x; float x2 = bInner.getClipBounds().width; float y = 0; GradientPaint gradient = new GradientPaint( x1,y,linearGradientFrom, x2, y, linearGradientTo); bInner.setPaint(gradient); bInner.fill(bInner.getClip()); } Graphics2D pInner = padding.render(bInner); if(border.getRadius() > 0) { Shape clipShape = border.makeInnerShape( mInner.getClipBounds().width, mInner.getClipBounds().height); AffineTransform t = AffineTransform.getTranslateInstance(-border.getLeft(), -border.getTop()); pInner.setClip(t.createTransformedShape(clipShape)); } drawBackgroundImage(pInner); renderContent(pInner); pInner.dispose(); bInner.dispose(); mInner.dispose(); if(printdebug) { if(StringUtils.isNotBlank(getId())) { System.out.println("print debug for element id: " + getId()); } else if(StringUtils.isNotBlank(getTag())) { System.out.println("print debug for tag: " + getTag()); } Dimension d = getDimension(g2); System.out.println(String.format("dimension %sx%s", d.width, d.height)); System.out.println(String.format("margin %s", margin.toString())); System.out.println(String.format("border %s", border.toString())); System.out.println(String.format("padding %s", padding.toString())); Dimension cd = getContentDimension(g2); System.out.println(String.format("content %sx%s", cd.width, cd.height)); } } private void drawBackgroundImage(Graphics2D g2) { Graphics2D g0 = null; try { g0 = (Graphics2D)g2.create(); if(backgroundImage!=null) { AffineTransform t = g0.getTransform(); Dimension d = backgroundImage.getDimension(g0); double cw = g0.getClipBounds().getWidth(); double ch = g0.getClipBounds().getHeight(); if(backgroundSize != null) { Pair<Double, Double> scale = backgroundSize.getScale( cw, ch, d.getWidth(), d.getHeight()); t.concatenate(AffineTransform.getTranslateInstance((cw/2.0), (ch/2.0))); t.concatenate(AffineTransform.getScaleInstance(scale.getLeft(), scale.getRight())); t.concatenate(AffineTransform.getTranslateInstance(-(cw/2.0), -(ch/2.0))); } if(backgroundPosition == Align.CENTER) { t.concatenate(AffineTransform.getTranslateInstance( (cw - d.getWidth()) / 2.0, (ch - d.getHeight()) / 2.0)); } //TODO also support align right g0.setTransform(t); backgroundImage.render(g0); } } catch(Exception e) { throw new RuntimeException("failed to render background image", e); } finally { if(g0 != null) { g0.dispose(); } } } public void setBackground(Paint background) { this.background = background; } public abstract void renderContent(Graphics2D g2) throws Exception; public Paint getBackground() { return background; } public void setMargin(Margin margin) { this.margin = margin; } public void setPadding(Padding padding) { this.padding = padding; } public void setBorder(Border border) { this.border = border; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public Margin getMargin() { return margin; } public Border getBorder() { return border; } public Padding getPadding() { return padding; } public void addCssClass(String cssClass) { cssClasses.add(cssClass); } public boolean hasCssClass(String cssClass) { return cssClasses.contains(cssClass); } public Box getParent() { return parent; } public void setParent(Box parent) { this.parent = parent; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } public Map<String, String> getAttributes() { return attributes; } public void setAttributes(Map<String, String> attributes) { this.attributes = attributes; if(attributes != null) { this.printdebug = StringUtils.equalsIgnoreCase(attributes.get("printdebug"), "true"); } } public void addRule(Rule cssrule) { cssRules.add(cssrule); } public List<Rule> getCssRules() { return cssRules; } public String getFontFamily() { return fontFamily; } public void setFontFamily(String fontFamily) { this.fontFamily = fontFamily; } public int getFontSize() { return fontSize; } public void setFontSize(int fontSize) { this.fontSize = fontSize; } public Paint getColor() { return color; } public void setColor(Paint color) { this.color = color; } public boolean isBold() { return bold; } public void setBold(boolean bold) { this.bold = bold; } public boolean isInline() { return this.inline; } public void setInline(boolean inline) { this.inline = inline; } public double getRotate() { return rotate; } public void setRotate(double rotate) { this.rotate = rotate; } public String getRotationPoint() { return rotationPoint; } public void setRotationPoint(String rotationPoint) { this.rotationPoint = rotationPoint; } public ImageRenderer getBackgroundImage() { return backgroundImage; } public void setBackgroundImage(ImageRenderer backgroundImage) { this.backgroundImage = backgroundImage; } public Paint getBackgroundTexture() { return backgroundTexture; } public void setBackgroundTexture(Paint backgroundTexture) { this.backgroundTexture = backgroundTexture; } public void setLinearGradient(Color from, Color to) { linearGradientFrom = from; linearGradientTo = to; } public void setBackgroundPosition(Align backgroundPosition) { this.backgroundPosition = backgroundPosition; } public void setBackgroundSize(Size backgroundSize) { this.backgroundSize = backgroundSize; } }