| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| |
| package org.apache.poi.hssf.usermodel; |
| |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.poi.hssf.util.HSSFColor; |
| import org.apache.poi.util.NotImplemented; |
| import org.apache.poi.util.SuppressForbidden; |
| |
| import java.awt.*; |
| import java.awt.image.ImageObserver; |
| import java.text.AttributedCharacterIterator; |
| |
| /** |
| * Translates Graphics calls into escher calls. The translation is lossy so |
| * many features are not supported and some just aren't implemented yet. If |
| * in doubt test the specific calls you wish to make. Graphics calls are |
| * always performed into an EscherGroup so one will need to be created. |
| * <p> |
| * <b>Important:</b> |
| * <blockquote> |
| * One important concept worth considering is that of font size. One of the |
| * difficulties in converting Graphics calls into escher drawing calls is that |
| * Excel does not have the concept of absolute pixel positions. It measures |
| * it's cell widths in 'characters' and the cell heights in points. |
| * Unfortunately it's not defined exactly what a type of character it's |
| * measuring. Presumably this is due to the fact that the Excel will be |
| * using different fonts on different platforms or even within the same |
| * platform. |
| * <p> |
| * Because of this constraint we've had to calculate the |
| * verticalPointsPerPixel. This the amount the font should be scaled by when |
| * you issue commands such as drawString(). A good way to calculate this |
| * is to use the follow formula: |
| * <p> |
| * <pre> |
| * multipler = groupHeightInPoints / heightOfGroup |
| * </pre> |
| * <p> |
| * The height of the group is calculated fairly simply by calculating the |
| * difference between the y coordinates of the bounding box of the shape. The |
| * height of the group can be calculated by using a convenience called |
| * <code>HSSFClientAnchor.getAnchorHeightInPoints()</code>. |
| * </blockquote> |
| */ |
| public class EscherGraphics extends Graphics { |
| private static final Logger LOG = LogManager.getLogger(EscherGraphics.class); |
| |
| private final HSSFShapeGroup escherGroup; |
| private final HSSFWorkbook workbook; |
| private float verticalPointsPerPixel = 1.0f; |
| private final float verticalPixelsPerPoint; |
| private Color foreground; |
| private Color background = Color.white; |
| private Font font; |
| |
| /** |
| * Construct an escher graphics object. |
| * |
| * @param escherGroup The escher group to write the graphics calls into. |
| * @param workbook The workbook we are using. |
| * @param forecolor The foreground color to use as default. |
| * @param verticalPointsPerPixel The font multiplier. (See class description for information on how this works.). |
| */ |
| public EscherGraphics(HSSFShapeGroup escherGroup, HSSFWorkbook workbook, Color forecolor, float verticalPointsPerPixel ) |
| { |
| this.escherGroup = escherGroup; |
| this.workbook = workbook; |
| this.verticalPointsPerPixel = verticalPointsPerPixel; |
| this.verticalPixelsPerPoint = 1 / verticalPointsPerPixel; |
| this.font = new Font("Arial", 0, 10); |
| this.foreground = forecolor; |
| // background = backcolor; |
| } |
| |
| /** |
| * Constructs an escher graphics object. |
| * |
| * @param escherGroup The escher group to write the graphics calls into. |
| * @param workbook The workbook we are using. |
| * @param foreground The foreground color to use as default. |
| * @param verticalPointsPerPixel The font multiplier. (See class description for information on how this works.). |
| * @param font The font to use. |
| */ |
| EscherGraphics( HSSFShapeGroup escherGroup, HSSFWorkbook workbook, Color foreground, Font font, float verticalPointsPerPixel ) |
| { |
| this.escherGroup = escherGroup; |
| this.workbook = workbook; |
| this.foreground = foreground; |
| // this.background = background; |
| this.font = font; |
| this.verticalPointsPerPixel = verticalPointsPerPixel; |
| this.verticalPixelsPerPoint = 1 / verticalPointsPerPixel; |
| } |
| |
| // /** |
| // * Constructs an escher graphics object. |
| // * |
| // * @param escherGroup The escher group to write the graphics calls into. |
| // * @param workbook The workbook we are using. |
| // * @param forecolor The default foreground color. |
| // */ |
| // public EscherGraphics( HSSFShapeGroup escherGroup, HSSFWorkbook workbook, Color forecolor) |
| // { |
| // this(escherGroup, workbook, forecolor, 1.0f); |
| // } |
| |
| |
| @Override |
| public void clearRect(int x, int y, int width, int height) |
| { |
| Color color = foreground; |
| setColor(background); |
| fillRect(x,y,width,height); |
| setColor(color); |
| } |
| |
| @Override |
| @NotImplemented |
| public void clipRect(int x, int y, int width, int height) |
| { |
| LOG.atWarn().log("clipRect not supported"); |
| } |
| |
| @Override |
| @NotImplemented |
| public void copyArea(int x, int y, int width, int height, int dx, int dy) |
| { |
| LOG.atWarn().log("copyArea not supported"); |
| } |
| |
| @Override |
| public Graphics create() |
| { |
| return new EscherGraphics(escherGroup, workbook, |
| foreground, font, verticalPointsPerPixel ); |
| } |
| |
| @Override |
| public void dispose() |
| { |
| } |
| |
| @Override |
| @NotImplemented |
| public void drawArc(int x, int y, int width, int height, |
| int startAngle, int arcAngle) |
| { |
| LOG.atWarn().log("drawArc not supported"); |
| } |
| |
| @Override |
| @NotImplemented |
| public boolean drawImage(Image img, |
| int dx1, int dy1, int dx2, int dy2, |
| int sx1, int sy1, int sx2, int sy2, |
| Color bgcolor, |
| ImageObserver observer) |
| { |
| LOG.atWarn().log("drawImage not supported"); |
| |
| return true; |
| } |
| |
| @Override |
| @NotImplemented |
| public boolean drawImage(Image img, |
| int dx1, int dy1, int dx2, int dy2, |
| int sx1, int sy1, int sx2, int sy2, |
| ImageObserver observer) |
| { |
| LOG.atWarn().log("drawImage not supported"); |
| return true; |
| } |
| |
| @Override |
| public boolean drawImage(Image image, int i, int j, int k, int l, Color color, ImageObserver imageobserver) |
| { |
| return drawImage(image, i, j, i + k, j + l, 0, 0, image.getWidth(imageobserver), image.getHeight(imageobserver), color, imageobserver); |
| } |
| |
| @Override |
| public boolean drawImage(Image image, int i, int j, int k, int l, ImageObserver imageobserver) |
| { |
| return drawImage(image, i, j, i + k, j + l, 0, 0, image.getWidth(imageobserver), image.getHeight(imageobserver), imageobserver); |
| } |
| |
| @Override |
| public boolean drawImage(Image image, int i, int j, Color color, ImageObserver imageobserver) |
| { |
| return drawImage(image, i, j, image.getWidth(imageobserver), image.getHeight(imageobserver), color, imageobserver); |
| } |
| |
| @Override |
| public boolean drawImage(Image image, int i, int j, ImageObserver imageobserver) |
| { |
| return drawImage(image, i, j, image.getWidth(imageobserver), image.getHeight(imageobserver), imageobserver); |
| } |
| |
| @Override |
| public void drawLine(int x1, int y1, int x2, int y2) |
| { |
| drawLine(x1,y1,x2,y2,0); |
| } |
| |
| public void drawLine(int x1, int y1, int x2, int y2, int width) |
| { |
| HSSFSimpleShape shape = escherGroup.createShape(new HSSFChildAnchor(x1, y1, x2, y2) ); |
| shape.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); |
| shape.setLineWidth(width); |
| shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); |
| } |
| |
| @Override |
| public void drawOval(int x, int y, int width, int height) |
| { |
| HSSFSimpleShape shape = escherGroup.createShape(new HSSFChildAnchor(x,y,x+width,y+height) ); |
| shape.setShapeType(HSSFSimpleShape.OBJECT_TYPE_OVAL); |
| shape.setLineWidth(0); |
| shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); |
| shape.setNoFill(true); |
| } |
| |
| @Override |
| public void drawPolygon(int[] xPoints, int[] yPoints, |
| int nPoints) |
| { |
| int right = findBiggest(xPoints); |
| int bottom = findBiggest(yPoints); |
| int left = findSmallest(xPoints); |
| int top = findSmallest(yPoints); |
| HSSFPolygon shape = escherGroup.createPolygon(new HSSFChildAnchor(left,top,right,bottom) ); |
| shape.setPolygonDrawArea(right - left, bottom - top); |
| shape.setPoints(addToAll(xPoints, -left), addToAll(yPoints, -top)); |
| shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); |
| shape.setLineWidth(0); |
| shape.setNoFill(true); |
| } |
| |
| private int[] addToAll( int[] values, int amount ) |
| { |
| int[] result = new int[values.length]; |
| for ( int i = 0; i < values.length; i++ ) |
| result[i] = values[i] + amount; |
| return result; |
| } |
| |
| @Override |
| @NotImplemented |
| public void drawPolyline(int[] xPoints, int[] yPoints, |
| int nPoints) |
| { |
| LOG.atWarn().log("drawPolyline not supported"); |
| } |
| |
| @Override |
| @NotImplemented |
| public void drawRect(int x, int y, int width, int height) |
| { |
| LOG.atWarn().log("drawRect not supported"); |
| } |
| |
| @Override |
| @NotImplemented |
| public void drawRoundRect(int x, int y, int width, int height, |
| int arcWidth, int arcHeight) |
| { |
| LOG.atWarn().log("drawRoundRect not supported"); |
| } |
| |
| @Override |
| public void drawString(String str, int x, int y) |
| { |
| if (str == null || str.isEmpty()) |
| return; |
| |
| Font excelFont = font; |
| if ( font.getName().equals( "SansSerif" ) ) |
| { |
| excelFont = new Font( "Arial", font.getStyle(), (int) ( font.getSize() / verticalPixelsPerPoint ) ); |
| } |
| else |
| { |
| excelFont = new Font( font.getName(), font.getStyle(), (int) ( font.getSize() / verticalPixelsPerPoint )); |
| } |
| FontDetails d = StaticFontMetrics.getFontDetails( excelFont ); |
| int width = d.getStringWidth( str ) * 8 + 12; |
| int height = (int) ( ( font.getSize() / verticalPixelsPerPoint ) + 6 ) * 2; |
| y -= ( font.getSize() / verticalPixelsPerPoint ) + 2 * verticalPixelsPerPoint; // we want to draw the shape from the top-left |
| HSSFTextbox textbox = escherGroup.createTextbox( new HSSFChildAnchor( x, y, x + width, y + height ) ); |
| textbox.setNoFill( true ); |
| textbox.setLineStyle( HSSFShape.LINESTYLE_NONE ); |
| HSSFRichTextString s = new HSSFRichTextString( str ); |
| HSSFFont hssfFont = matchFont( excelFont ); |
| s.applyFont( hssfFont ); |
| textbox.setString( s ); |
| } |
| |
| private HSSFFont matchFont( Font matchFont ) |
| { |
| HSSFColor hssfColor = workbook.getCustomPalette() |
| .findColor((byte)foreground.getRed(), (byte)foreground.getGreen(), (byte)foreground.getBlue()); |
| if (hssfColor == null) |
| hssfColor = workbook.getCustomPalette().findSimilarColor((byte)foreground.getRed(), (byte)foreground.getGreen(), (byte)foreground.getBlue()); |
| boolean bold = (matchFont.getStyle() & Font.BOLD) != 0; |
| boolean italic = (matchFont.getStyle() & Font.ITALIC) != 0; |
| HSSFFont hssfFont = workbook.findFont(bold, |
| hssfColor.getIndex(), |
| (short)(matchFont.getSize() * 20), |
| matchFont.getName(), |
| italic, |
| false, |
| (short)0, |
| (byte)0); |
| if (hssfFont == null) |
| { |
| hssfFont = workbook.createFont(); |
| hssfFont.setBold(bold); |
| hssfFont.setColor(hssfColor.getIndex()); |
| hssfFont.setFontHeight((short)(matchFont.getSize() * 20)); |
| hssfFont.setFontName(matchFont.getName()); |
| hssfFont.setItalic(italic); |
| hssfFont.setStrikeout(false); |
| hssfFont.setTypeOffset((short) 0); |
| hssfFont.setUnderline((byte) 0); |
| } |
| |
| return hssfFont; |
| } |
| |
| |
| @Override |
| public void drawString(AttributedCharacterIterator iterator, |
| int x, int y) |
| { |
| LOG.atWarn().log("drawString not supported"); |
| } |
| |
| @Override |
| public void fillArc(int x, int y, int width, int height, |
| int startAngle, int arcAngle) |
| { |
| LOG.atWarn().log("fillArc not supported"); |
| } |
| |
| @Override |
| public void fillOval(int x, int y, int width, int height) |
| { |
| HSSFSimpleShape shape = escherGroup.createShape(new HSSFChildAnchor( x, y, x + width, y + height ) ); |
| shape.setShapeType(HSSFSimpleShape.OBJECT_TYPE_OVAL); |
| shape.setLineStyle(HSSFShape.LINESTYLE_NONE); |
| shape.setFillColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); |
| shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); |
| shape.setNoFill(false); |
| } |
| |
| /** |
| * Fills a (closed) polygon, as defined by a pair of arrays, which |
| * hold the <i>x</i> and <i>y</i> coordinates. |
| * <p> |
| * This draws the polygon, with <code>nPoint</code> line segments. |
| * The first <code>nPoint - 1</code> line segments are |
| * drawn between sequential points |
| * (<code>xPoints[i],yPoints[i],xPoints[i+1],yPoints[i+1]</code>). |
| * The final line segment is a closing one, from the last point to |
| * the first (assuming they are different). |
| * <p> |
| * The area inside of the polygon is defined by using an |
| * even-odd fill rule (also known as the alternating rule), and |
| * the area inside of it is filled. |
| * @param xPoints array of the <code>x</code> coordinates. |
| * @param yPoints array of the <code>y</code> coordinates. |
| * @param nPoints the total number of points in the polygon. |
| * @see Graphics#drawPolygon(int[], int[], int) |
| */ |
| @Override |
| public void fillPolygon(int[] xPoints, int[] yPoints, |
| int nPoints) |
| { |
| int right = findBiggest(xPoints); |
| int bottom = findBiggest(yPoints); |
| int left = findSmallest(xPoints); |
| int top = findSmallest(yPoints); |
| HSSFPolygon shape = escherGroup.createPolygon(new HSSFChildAnchor(left,top,right,bottom) ); |
| shape.setPolygonDrawArea(right - left, bottom - top); |
| shape.setPoints(addToAll(xPoints, -left), addToAll(yPoints, -top)); |
| shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); |
| shape.setFillColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); |
| } |
| |
| private int findBiggest( int[] values ) |
| { |
| int result = Integer.MIN_VALUE; |
| for ( int i = 0; i < values.length; i++ ) |
| { |
| if (values[i] > result) |
| result = values[i]; |
| } |
| return result; |
| } |
| |
| private int findSmallest( int[] values ) |
| { |
| int result = Integer.MAX_VALUE; |
| for ( int i = 0; i < values.length; i++ ) |
| { |
| if (values[i] < result) |
| result = values[i]; |
| } |
| return result; |
| } |
| |
| @Override |
| public void fillRect(int x, int y, int width, int height) |
| { |
| HSSFSimpleShape shape = escherGroup.createShape(new HSSFChildAnchor( x, y, x + width, y + height ) ); |
| shape.setShapeType(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE); |
| shape.setLineStyle(HSSFShape.LINESTYLE_NONE); |
| shape.setFillColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); |
| shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); |
| } |
| |
| @Override |
| public void fillRoundRect(int x, int y, int width, int height, |
| int arcWidth, int arcHeight) |
| { |
| LOG.atWarn().log("fillRoundRect not supported"); |
| } |
| |
| @Override |
| public Shape getClip() |
| { |
| return getClipBounds(); |
| } |
| |
| @Override |
| public Rectangle getClipBounds() |
| { |
| return null; |
| } |
| |
| @Override |
| public Color getColor() |
| { |
| return foreground; |
| } |
| |
| @Override |
| public Font getFont() |
| { |
| return font; |
| } |
| |
| @Override |
| @SuppressWarnings("deprecation") |
| @SuppressForbidden |
| public FontMetrics getFontMetrics(Font f) |
| { |
| return Toolkit.getDefaultToolkit().getFontMetrics(f); |
| } |
| |
| @Override |
| public void setClip(int x, int y, int width, int height) |
| { |
| setClip(new Rectangle(x,y,width,height)); |
| } |
| |
| @Override |
| @NotImplemented |
| public void setClip(Shape shape) |
| { |
| LOG.atWarn().log("setClip not supported"); |
| } |
| |
| @Override |
| public void setColor(Color color) |
| { |
| foreground = color; |
| } |
| |
| @Override |
| public void setFont(Font f) |
| { |
| font = f; |
| } |
| |
| @Override |
| @NotImplemented |
| public void setPaintMode() |
| { |
| LOG.atWarn().log("setPaintMode not supported"); |
| } |
| |
| @Override |
| @NotImplemented |
| public void setXORMode(Color color) |
| { |
| LOG.atWarn().log("setXORMode not supported"); |
| } |
| |
| @Override |
| @NotImplemented |
| public void translate(int x, int y) |
| { |
| LOG.atWarn().log("translate not supported"); |
| } |
| |
| public Color getBackground() |
| { |
| return background; |
| } |
| |
| public void setBackground( Color background ) |
| { |
| this.background = background; |
| } |
| |
| HSSFShapeGroup getEscherGraphics() |
| { |
| return escherGroup; |
| } |
| } |
| |