/* * ShapeStyle.java * Transform * * Copyright (c) 2001-2010 Flagstone Software Ltd. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Flagstone Software Ltd. nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.flagstone.transform.shape; import java.io.IOException; import java.util.ArrayList; import java.util.List; import com.flagstone.transform.coder.Coder; import com.flagstone.transform.coder.Context; import com.flagstone.transform.coder.SWFDecoder; import com.flagstone.transform.coder.SWFEncoder; import com.flagstone.transform.coder.SWFFactory; import com.flagstone.transform.exception.IllegalArgumentRangeException; import com.flagstone.transform.fillstyle.FillStyle; import com.flagstone.transform.linestyle.LineStyle1; /** * ShapeStyle is used to change the drawing environment when a shape is drawn. * Three operations can be performed: * * <ul> * <li>Select a line style or fill style.</li> * <li>Move the current drawing point.</li> * <li>Define a new set of line and fill styles.</li> * </ul> * * <p> * An ShapeStyle object can specify one or more of the operations rather than * specifying them in separate ShapeStyle objects - compacting the size of the * binary data when the object is encoded. Conversely if an operation is not * defined then the values may be omitted. * </p> * * <p> * Line and Fill styles are selected by the index position, starting at 1, of * the style in a list of styles. An index of zero means that no style is * used. Two types of fill style are supported: fillStyle is used where a * shape does not contain overlapping areas and altFillStyle is used where areas * overlap. This differs from graphics environments that only support one fill * style as the overlapping area would form a hole in the shape and not be * filled. * </p> * * <p> * A new drawing point is specified using the absolute x and y coordinates. If * an ShapeStyle object is the first in a shape then the current drawing point * is the origin of the shape (0,0). As with the line and fill styles, * specifying a move is optional. * </p> * * <p> * Finally the line or fill style lists may left empty if no new styles are * being specified. * </p> */ @SuppressWarnings("PMD.CyclomaticComplexity") public final class ShapeStyle implements ShapeRecord { /** * Reserved length for style counts indicated that the number of line * or fill styles is encoded in the next 16-bit word. */ private static final int EXTENDED = 255; /** Format string used in toString() method. */ private static final String FORMAT = "ShapeStyle: { move=(%d, %d);" + " fill=%d; alt=%d; line=%d; fillStyles=%s; lineStyles=%s}"; /** Relative move along the x-axis. */ private Integer moveX; /** Relative move along the y-axis. */ private Integer moveY; /** Selected fill style. */ private Integer fillStyle; /** Selected alternate fill style. */ private Integer altFillStyle; /** Selected line style. */ private Integer lineStyle; /** List of fill styles. */ private List<FillStyle> fillStyles; /** List of line styles. */ private List<LineStyle1> lineStyles; /** Indicates whether new line or fill styles are specified. */ private transient boolean hasStyles; /** Indicates whether a line is specified. */ private transient boolean hasLine; /** Indicates whether an alternate fill style is specified. */ private transient boolean hasAlt; /** Indicates whether an fill style is specified. */ private transient boolean hasFill; /** Indicates whether a relative move is specified. */ private transient boolean hasMove; /** * Creates and initialises a ShapeStyle object using values encoded * in the Flash binary format. * * @param flags * contains fields identifying which fields are optionally * encoded in the data - decoded by parent object. * @param coder * an SWFDecoder object that contains the encoded Flash data. * * @param context * a Context object used to manage the decoders for different * type of object and to pass information on how objects are * decoded. * * @throws IOException * if an error occurs while decoding the data. */ public ShapeStyle(final int flags, final SWFDecoder coder, final Context context) throws IOException { int numberOfFillBits = context.get(Context.FILL_SIZE); int numberOfLineBits = context.get(Context.LINE_SIZE); hasStyles = (flags & Coder.BIT4) != 0; hasLine = (flags & Coder.BIT3) != 0; hasAlt = (flags & Coder.BIT2) != 0; hasFill = (flags & Coder.BIT1) != 0; hasMove = (flags & Coder.BIT0) != 0; if (hasMove) { final int moveFieldSize = coder.readBits(5, false); moveX = coder.readBits(moveFieldSize, true); moveY = coder.readBits(moveFieldSize, true); } fillStyles = new ArrayList<FillStyle>(); lineStyles = new ArrayList<LineStyle1>(); if (hasFill) { fillStyle = coder.readBits(numberOfFillBits, false); } if (hasAlt) { altFillStyle = coder.readBits(numberOfFillBits, false); } if (hasLine) { lineStyle = coder.readBits(numberOfLineBits, false); } if (hasStyles) { coder.alignToByte(); int fillStyleCount = coder.readByte(); if (context.contains(Context.ARRAY_EXTENDED) && (fillStyleCount == EXTENDED)) { fillStyleCount = coder.readUnsignedShort(); } final SWFFactory<FillStyle> decoder = context.getRegistry() .getFillStyleDecoder(); for (int i = 0; i < fillStyleCount; i++) { decoder.getObject(fillStyles, coder, context); } int lineStyleCount = coder.readByte(); if (context.contains(Context.ARRAY_EXTENDED) && (lineStyleCount == EXTENDED)) { lineStyleCount = coder.readUnsignedShort(); } for (int i = 0; i < lineStyleCount; i++) { lineStyles.add(new LineStyle1(coder, context)); } final int sizes = coder.readByte(); numberOfFillBits = (sizes & Coder.NIB1) >> Coder.TO_LOWER_NIB; numberOfLineBits = sizes & Coder.NIB0; context.put(Context.FILL_SIZE, numberOfFillBits); context.put(Context.LINE_SIZE, numberOfLineBits); } } /** * Creates an uninitialised ShapeStyle object. */ public ShapeStyle() { fillStyles = new ArrayList<FillStyle>(); lineStyles = new ArrayList<LineStyle1>(); } /** * Creates and initialises a ShapeStyle object using the values copied * from another ShapeStyle object. * * @param object * a ShapeStyle object from which the values will be * copied. */ public ShapeStyle(final ShapeStyle object) { moveX = object.moveX; moveY = object.moveY; lineStyle = object.lineStyle; fillStyle = object.fillStyle; altFillStyle = object.altFillStyle; lineStyles = new ArrayList<LineStyle1>(object.lineStyles.size()); for (final LineStyle1 style : object.lineStyles) { lineStyles.add(style.copy()); } fillStyles = new ArrayList<FillStyle>(object.fillStyles.size()); for (final FillStyle style : object.fillStyles) { fillStyles.add(style.copy()); } } /** * Add a LineStyle object to the list of line styles. * * @param style * and LineStyle object. Must not be null. * @return this object. */ public ShapeStyle add(final LineStyle1 style) { if (style == null) { throw new IllegalArgumentException(); } lineStyles.add(style); return this; } /** * Add the fill style object to the list of fill styles. * * @param style * and FillStyle object. Must not be null. * @return this object. */ public ShapeStyle add(final FillStyle style) { if (style == null) { throw new IllegalArgumentException(); } fillStyles.add(style); return this; } /** * Get the x-coordinate of any relative move or null if no move is * specified. * * @return the relative move in the x direction. */ public Integer getMoveX() { return moveX; } /** * Get the y-coordinate of any relative move or null if no move is * specified. * * @return the relative move in the y direction. */ public Integer getMoveY() { return moveY; } /** * Get the index of the line style that will be applied to any line * drawn. Returns null if no line style is defined. * * @return the selected line style. */ public Integer getLineStyle() { return lineStyle; } /** * Get the index of the fill style that will be applied to any area * filled. Returns null if no fill style is defined. * * @return the selected fill style. */ public Integer getFillStyle() { return fillStyle; } /** * Get the index of the fill style that will be applied to any * overlapping area filled. Returns null if no alternate fill style is * defined. * * @return the selected alternate fill style. */ public Integer getAltFillStyle() { return altFillStyle; } /** * Get the list of new line styles. * * @return the list of line styles. */ public List<LineStyle1> getLineStyles() { return lineStyles; } /** * Returns the list of new fill styles. * * @return the list of fill styles. */ public List<FillStyle> getFillStyles() { return fillStyles; } /** * Sets the x-coordinate of any relative move. * * @param coord * move the current point by aNumber in the x direction. Must be * in the range -65535..65535. * @return this object. */ public ShapeStyle setMoveX(final Integer coord) { if ((coord != null) && ((coord < Shape.MIN_COORD) || (coord > Shape.MAX_COORD))) { throw new IllegalArgumentRangeException( Shape.MIN_COORD, Shape.MAX_COORD, coord); } moveX = coord; return this; } /** * Sets the x-coordinate of any relative move. * * @param coord * move the current point by aNumber in the x direction. Must be * in the range -65535..65535. * @return this object. */ public ShapeStyle setMoveY(final Integer coord) { if ((coord != null) && ((coord < Shape.MIN_COORD) || (coord > Shape.MAX_COORD))) { throw new IllegalArgumentRangeException( Shape.MIN_COORD, Shape.MAX_COORD, coord); } moveY = coord; return this; } /** * Sets the coordinates of any relative move. * * @param xCoord * move the current point by aNumber in the x direction. Must be * in the range -65535..65535. * * @param yCoord * move the current point by aNumber in the y direction. Must be * in the range -65535..65535. * @return this object. */ public ShapeStyle setMove(final Integer xCoord, final Integer yCoord) { if (((xCoord == null) && (yCoord != null)) || ((xCoord != null) && (yCoord == null))) { throw new IllegalArgumentException(); } if ((xCoord != null) && ((xCoord < Shape.MIN_COORD) || (xCoord > Shape.MAX_COORD))) { throw new IllegalArgumentRangeException( Shape.MIN_COORD, Shape.MAX_COORD, xCoord); } if ((yCoord != null) && ((yCoord < Shape.MIN_COORD) || (yCoord > Shape.MAX_COORD))) { throw new IllegalArgumentRangeException( Shape.MIN_COORD, Shape.MAX_COORD, yCoord); } moveX = xCoord; moveY = yCoord; return this; } /** * Sets the index of the fill style that will be applied to any area filled. * May be set to zero if no style is selected or null if the line style * remains unchanged. * * @param anIndex * selects the fill style at anIndex in the fill styles list of * the parent Shape object. * @return this object. */ public ShapeStyle setFillStyle(final Integer anIndex) { fillStyle = anIndex; return this; } /** * Sets the index of the fill style that will be applied to any overlapping * area filled. May be set to zero if no style is selected or null if the ~ * line style remains unchanged. * * @param anIndex * selects the alternate fill style at anIndex in the fill styles * list of the parent Shape object. * @return this object. */ public ShapeStyle setAltFillStyle(final Integer anIndex) { altFillStyle = anIndex; return this; } /** * Sets the index of the line style that will be applied to any line drawn. * May be set to zero if no style is selected or null if the line style * remains unchanged. * * @param anIndex * selects the line style at anIndex in the line styles list of * the parent Shape object. * @return this object. */ public ShapeStyle setLineStyle(final Integer anIndex) { lineStyle = anIndex; return this; } /** * Sets the list of new line styles. May be set to null if no styles are * being defined. * * @param list * a list of LineStyle objects. Must not be null. * @return this object. */ public ShapeStyle setLineStyles(final List<LineStyle1> list) { if (list == null) { throw new IllegalArgumentException(); } lineStyles = list; return this; } /** * Sets the list of new fill styles. May be set to null if no styles are * being defined. * * @param list * a list of fill style objects. Must not be null. * @return this object. */ public ShapeStyle setFillStyles(final List<FillStyle> list) { if (list == null) { throw new IllegalArgumentException(); } fillStyles = list; return this; } /** {@inheritDoc} */ public ShapeStyle copy() { return new ShapeStyle(this); } @Override public String toString() { return String.format(FORMAT, moveX, moveY, fillStyle, altFillStyle, lineStyle, fillStyles, lineStyles); } /** {@inheritDoc} */ @SuppressWarnings({"PMD.NPathComplexity", "PMD.CyclomaticComplexity" }) public int prepareToEncode(final Context context) { // CHECKSTYLE:OFF hasLine = lineStyle != null; hasFill = fillStyle != null; hasAlt = altFillStyle != null; hasMove = (moveX != null) && (moveY != null); hasStyles = !lineStyles.isEmpty() || !fillStyles.isEmpty(); int numberOfBits = 6; if (hasMove) { final int fieldSize = Math.max(Coder.size(moveX), Coder .size(moveY)); numberOfBits += 5 + fieldSize * 2; } numberOfBits += hasFill ? context.get(Context.FILL_SIZE) : 0; numberOfBits += hasAlt ? context.get(Context.FILL_SIZE) : 0; numberOfBits += (hasLine) ? context.get(Context.LINE_SIZE) : 0; context.put(Context.SHAPE_SIZE, context.get(Context.SHAPE_SIZE) + numberOfBits); if (hasStyles) { int numberOfFillBits = Coder.unsignedSize(fillStyles.size()); int numberOfLineBits = Coder.unsignedSize(lineStyles.size()); if ((numberOfFillBits == 0) && context.contains(Context.POSTSCRIPT)) { numberOfFillBits = 1; } if ((numberOfLineBits == 0) && context.contains(Context.POSTSCRIPT)) { numberOfLineBits = 1; } final boolean countExtended = context .contains(Context.ARRAY_EXTENDED); int numberOfStyleBits = 0; final int flushBits = context.get(Context.SHAPE_SIZE); numberOfStyleBits += (flushBits % 8 > 0) ? 8 - (flushBits % 8) : 0; numberOfStyleBits += (countExtended && (fillStyles.size() >= EXTENDED)) ? 24 : 8; for (final FillStyle style : fillStyles) { numberOfStyleBits += style.prepareToEncode(context) << 3; } numberOfStyleBits += (countExtended && (lineStyles.size() >= EXTENDED)) ? 24 : 8; for (final LineStyle1 style : lineStyles) { numberOfStyleBits += style.prepareToEncode(context) << 3; } numberOfStyleBits += 8; context.put(Context.FILL_SIZE, numberOfFillBits); context.put(Context.LINE_SIZE, numberOfLineBits); context.put(Context.SHAPE_SIZE, context.get(Context.SHAPE_SIZE) + numberOfStyleBits); numberOfBits += numberOfStyleBits; } return numberOfBits; // CHECKSTYLE:ON } /** {@inheritDoc} */ @SuppressWarnings({"PMD.NPathComplexity", "PMD.CyclomaticComplexity" }) public void encode(final SWFEncoder coder, final Context context) throws IOException { coder.writeBits(0, 1); coder.writeBits(hasStyles ? 1 : 0, 1); coder.writeBits(hasLine ? 1 : 0, 1); coder.writeBits(hasAlt ? 1 : 0, 1); coder.writeBits(hasFill ? 1 : 0, 1); coder.writeBits(hasMove ? 1 : 0, 1); if (hasMove) { final int fieldSize = Math.max(Coder.size(moveX), Coder .size(moveY)); // CHECKSTYLE IGNORE MagicNumberCheck FOR NEXT 1 LINES coder.writeBits(fieldSize, 5); coder.writeBits(moveX, fieldSize); coder.writeBits(moveY, fieldSize); } if (hasFill) { coder.writeBits(fillStyle, context.get(Context.FILL_SIZE)); } if (hasAlt) { coder.writeBits(altFillStyle, context.get(Context.FILL_SIZE)); } if (hasLine) { coder.writeBits(lineStyle, context.get(Context.LINE_SIZE)); } if (hasStyles) { final boolean countExtended = context .contains(Context.ARRAY_EXTENDED); coder.alignToByte(); if (countExtended && (fillStyles.size() >= EXTENDED)) { coder.writeByte(EXTENDED); coder.writeShort(fillStyles.size()); } else { coder.writeByte(fillStyles.size()); } for (final FillStyle style : fillStyles) { style.encode(coder, context); } if (countExtended && (lineStyles.size() >= EXTENDED)) { coder.writeByte(EXTENDED); coder.writeShort(lineStyles.size()); } else { coder.writeByte(lineStyles.size()); } for (final LineStyle1 style : lineStyles) { style.encode(coder, context); } int numberOfFillBits = Coder.unsignedSize(fillStyles.size()); int numberOfLineBits = Coder.unsignedSize(lineStyles.size()); if (context.contains(Context.POSTSCRIPT)) { if (numberOfFillBits == 0) { numberOfFillBits = 1; } if (numberOfLineBits == 0) { numberOfLineBits = 1; } } coder.writeByte((numberOfFillBits << Coder.TO_UPPER_NIB) | numberOfLineBits); // Update the stream with the new numbers of line and fill bits context.put(Context.FILL_SIZE, numberOfFillBits); context.put(Context.LINE_SIZE, numberOfLineBits); } } }