/*
* MorphLineStyle2.java
* Transform
*
* Copyright (c) 2009-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.linestyle;
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.datatype.Color;
import com.flagstone.transform.exception.IllegalArgumentRangeException;
import com.flagstone.transform.fillstyle.FillStyle;
/**
* MorphLineStyle2 extends MorphLineStyle by supporting different styles for
* line joins and line ends, a fill style for the stroke and whether the stroke
* thickness is scaled if an object is resized.
*/
@SuppressWarnings("PMD.CyclomaticComplexity")
public final class MorphLineStyle2 implements LineStyle {
/** Format string used in toString() method. */
private static final String FORMAT = "MorphLineStyle2: {"
+ " startWidth=%d; endWidth=%d; startColor=%s; endColor=%s;"
+ " fillStyle=%s; startCap=%s; endCap=%s; joinStyle=%s;"
+ " scaledHorizontally=%b; scaledVertically=%b;"
+ " pixelAligned=%b; lineClosed=%b; miterLimit=%d}";
/** Width of the line at the start of the morph. */
private int startWidth;
/** Width of the line at the end of the morph. */
private int endWidth;
/** Color of the line at the start of the morph. */
private Color startColor;
/** Color of the line at the end of the morph. */
private Color endColor;
/** Code for the cap style used for the start of the line. */
private int startCap;
/** Code for the cap style used for the end of the line. */
private int endCap;
/** Code for the style used to join two line together. */
private int joinStyle;
/** Fill style used to draw the stroke. */
private FillStyle fillStyle;
/** Does the line allow scaling horizontally. */
private boolean horizontal;
/** Does the line allow scaling vertically. */
private boolean vertical;
/** Is the line drawn along pixel boundaries. */
private boolean pixelAligned;
/** Should the line be closed if the start and end points coincide. */
private boolean lineClosed;
/** Parameter controlling the mitering when joining two lines. */
private int miterLimit;
/** Indicates the style contains a fill style. */
private transient boolean hasFillStyle;
/** Indicates the style contains a mitering limit. */
private transient boolean hasMiter;
/**
* Creates and initialises a MorphLineStyle2 object using values encoded
* in the Flash binary format.
*
* @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 MorphLineStyle2(final SWFDecoder coder, final Context context)
throws IOException {
startWidth = coder.readUnsignedShort();
endWidth = coder.readUnsignedShort();
int bits = coder.readByte();
if ((bits & Coder.BIT6) > 0) {
startCap = 1;
} else if ((bits & Coder.BIT7) > 0) {
startCap = 2;
} else {
startCap = 0;
}
if ((bits & Coder.BIT4) > 0) {
joinStyle = 1;
hasMiter = false;
} else if ((bits & Coder.BIT5) > 0) {
joinStyle = 2;
hasMiter = true;
} else {
joinStyle = 0;
hasMiter = false;
}
hasFillStyle = (bits & Coder.BIT3) != 0;
horizontal = (bits & Coder.BIT2) == 0;
vertical = (bits & Coder.BIT1) == 0;
pixelAligned = (bits & Coder.BIT0) != 0;
bits = coder.readByte();
lineClosed = (bits & Coder.BIT2) == 0;
endCap = bits & Coder.PAIR0;
if (hasMiter) {
coder.readUnsignedShort();
}
if (hasFillStyle) {
final SWFFactory<FillStyle> decoder = context.getRegistry()
.getMorphFillStyleDecoder();
final List<FillStyle> styles = new ArrayList<FillStyle>();
decoder.getObject(styles, coder, context);
fillStyle = styles.get(0);
} else {
startColor = new Color(coder, context);
endColor = new Color(coder, context);
}
}
/**
* Create a new MorphLineStyle2 object with the stroke thickness and color
* for the start and end of the morphing process.
* @param initialWidth the width of the line at the start of the process.
* @param finalWidth the width of the line at the end of the process.
* @param initialColor the colour used to draw the line at the start of
* the process.
* @param finalColor the colour used to draw the line at the end of
* the process.
*/
public MorphLineStyle2(final int initialWidth, final int finalWidth,
final Color initialColor, final Color finalColor) {
super();
setStartWidth(initialWidth);
setEndWidth(finalWidth);
setStartColor(initialColor);
setEndColor(finalColor);
vertical = true;
vertical = true;
lineClosed = true;
}
/**
* Create a new MorphLineStyle2 object with the stroke thickness and fill
* style for the start and end of the morphing process.
* @param initialWidth the width of the line at the start of the process.
* @param finalWidth the width of the line at the end of the process.
* @param style a FillStyle (morph fill styles only) that describes the
* fill used to draw the line at the start and end of the process.
*/
public MorphLineStyle2(final int initialWidth, final int finalWidth,
final FillStyle style) {
super();
setStartWidth(initialWidth);
setEndWidth(finalWidth);
setFillStyle(style);
vertical = true;
vertical = true;
lineClosed = true;
}
/**
* Creates and initialises a MorphLineStyle2 object using the values copied
* from another MorphLineStyle2 object.
*
* @param object
* a MorphLineStyle2 object from which the values will be
* copied.
*/
public MorphLineStyle2(final MorphLineStyle2 object) {
startWidth = object.startWidth;
endWidth = object.endWidth;
startColor = object.startColor;
endColor = object.endColor;
if (object.fillStyle != null) {
fillStyle = object.fillStyle.copy();
}
startCap = object.startCap;
endCap = object.endCap;
joinStyle = object.joinStyle;
horizontal = object.horizontal;
vertical = object.vertical;
pixelAligned = object.pixelAligned;
lineClosed = object.lineClosed;
miterLimit = object.miterLimit;
}
/**
* Get the width of the line at the start of the morphing process.
*
* @return the starting stroke width.
*/
public int getStartWidth() {
return startWidth;
}
/**
* Get the width of the line at the end of the morphing process.
*
* @return the final stroke width.
*/
public int getEndWidth() {
return endWidth;
}
/**
* Get the colour of the line at the start of the morphing process.
*
* @return the starting stroke colour.
*/
public Color getStartColor() {
return startColor;
}
/**
* Returns the colour of the line at the end of the morphing process.
*
* @return the final stroke colour.
*/
public Color getEndColor() {
return endColor;
}
/**
* Sets the width of the line at the start of the morphing process.
*
* @param aNumber
* the starting width of the line. Must be in the range 0..65535.
*/
public void setStartWidth(final int aNumber) {
if ((aNumber < 0) || (aNumber > Coder.USHORT_MAX)) {
throw new IllegalArgumentRangeException(
0, Coder.USHORT_MAX, aNumber);
}
startWidth = aNumber;
}
/**
* Sets the width of the line at the end of the morphing process.
*
* @param aNumber
* the ending width of the line. Must be in the range 0..65535.
*/
public void setEndWidth(final int aNumber) {
if ((aNumber < 0) || (aNumber > Coder.USHORT_MAX)) {
throw new IllegalArgumentRangeException(
0, Coder.USHORT_MAX, aNumber);
}
endWidth = aNumber;
}
/**
* Returns the colour of the line at the start of the morphing process.
*
* @param aColor
* the starting colour of the line. Must not be null.
*/
public void setStartColor(final Color aColor) {
if (aColor == null) {
throw new IllegalArgumentException();
}
startColor = aColor;
}
/**
* Sets the colour of the line at the end of the morphing process.
*
* @param aColor
* the ending colour of the line. Must not be null.
*/
public void setEndColor(final Color aColor) {
if (aColor == null) {
throw new IllegalArgumentException();
}
endColor = aColor;
}
/**
* Get the CapStyle used for the start of the line.
* @return the CapStyle that specifies how the start of the line is drawn.
*/
public CapStyle getStartCap() {
CapStyle style;
if (startCap == 1) {
style = CapStyle.NONE;
} else if (startCap == 2) {
style = CapStyle.SQUARE;
} else {
style = CapStyle.ROUND;
}
return style;
}
/**
* Set the CapStyle used for the start of the line.
* @param style the CapStyle that specifies how the start of the line
* is drawn.
*/
public void setStartCap(final CapStyle style) {
switch (style) {
case NONE:
startCap = 1;
break;
case SQUARE:
startCap = 2;
break;
default:
startCap = 0;
break;
}
}
/**
* Get the CapStyle used for the end of the line.
* @return the CapStyle that specifies how the end of the line is drawn.
*/
public CapStyle getEndCap() {
CapStyle style;
if (endCap == 1) {
style = CapStyle.NONE;
} else if (endCap == 2) {
style = CapStyle.SQUARE;
} else {
style = CapStyle.ROUND;
}
return style;
}
/**
* Set the CapStyle used for the end of the line.
* @param style the CapStyle that specifies how the end of the line
* is drawn.
*/
public void setEndCap(final CapStyle style) {
switch (style) {
case NONE:
endCap = 1;
break;
case SQUARE:
endCap = 2;
break;
default:
endCap = 0;
break;
}
}
/**
* Get the JoinStyle used when joining with another line or curve.
* @return the JoinStyle used to connect with another line or curve.
*/
public JoinStyle getJoinStyle() {
JoinStyle style;
if (endCap == 1) {
style = JoinStyle.BEVEL;
} else if (endCap == 2) {
style = JoinStyle.MITER;
} else {
style = JoinStyle.ROUND;
}
return style;
}
/**
* Set the JoinStyle used when joining with another line or curve.
* @param style the JoinStyle used to connect with another line or curve.
*/
public void setJoinStyle(final JoinStyle style) {
switch (style) {
case BEVEL:
joinStyle = 1;
break;
case MITER:
joinStyle = 2;
break;
default:
joinStyle = 0;
break;
}
}
/**
* Is the stroke scaled horizontally if the shape is redrawn.
* @return true if the stroke is scaled horizontally, false if the stroke
* thickness does not change.
*/
public boolean isHorizontal() {
return horizontal;
}
/**
* Indicates whether the stroke is scaled horizontally if the shape is
* redrawn.
* @param scale true if the stroke is scaled horizontally, false if the
* stroke thickness does not change.
*/
public void setHorizontal(final boolean scale) {
horizontal = scale;
}
/**
* Is the stroke scaled vertically if the shape is redrawn.
* @return true if the stroke is scaled vertically, false if the stroke
* thickness does not change.
*/
public boolean isVertical() {
return vertical;
}
/**
* Indicates whether the stroke is scaled vertically if the shape is
* redrawn.
* @param scale true if the stroke is scaled vertically, false if the
* stroke thickness does not change.
*/
public void setVertical(final boolean scale) {
vertical = scale;
}
/**
* Are the end points of the line aligned to pixel boundaries.
* @return true if the end points are aligned to full pixels, false
* otherwise.
*/
public boolean isPixelAligned() {
return pixelAligned;
}
/**
* Indicates whether the end points of the line aligned to pixel boundaries.
* @param align true if the end points are aligned to full pixels, false
* otherwise.
*/
public void setPixelAligned(final boolean align) {
pixelAligned = align;
}
/**
* Is the path closed if the end point matches the starting point. If true
* then the line will be joined, otherwise an end cap is drawn.
* @return true if the line will be closed, false if the path remains open.
*/
public boolean isLineClosed() {
return lineClosed;
}
/**
* Indicates whether the path closed if the end point matches the starting
* point. If true then the line will be joined, otherwise an end cap is
* drawn.
* @param close true if the line will be closed, false if the path remains
* open.
*/
public void setLineClosed(final boolean close) {
lineClosed = close;
}
/**
* Get the limit for drawing miter joins.
* @return the value controlling how miter joins are drawn.
*/
public int getMiterLimit() {
return miterLimit;
}
/**
* Set the limit for drawing miter joins.
* @param limit the value controlling how miter joins are drawn.
*/
public void setMiterLimit(final int limit) {
if ((limit < 0) || (limit > Coder.USHORT_MAX)) {
throw new IllegalArgumentRangeException(
0, Coder.USHORT_MAX, limit);
}
miterLimit = limit;
}
/**
* Get the FillStyle used for the line stroke.
* @return the FillStyle used to draw the line.
*/
public FillStyle getFillStyle() {
return fillStyle;
}
/**
* Set the FillStyle (morphing fill styles only) used for the line stroke.
* @param style the FillStyle used to draw the line.
*/
public void setFillStyle(final FillStyle style) {
fillStyle = style;
}
/** {@inheritDoc} */
public MorphLineStyle2 copy() {
return new MorphLineStyle2(this);
}
@Override
public String toString() {
return String.format(FORMAT, startWidth, endWidth, startColor,
endColor, fillStyle, startCap, endCap, joinStyle,
horizontal, vertical, pixelAligned, lineClosed,
miterLimit);
}
/** {@inheritDoc} */
public int prepareToEncode(final Context context) {
// CHECKSTYLE:OFF
hasFillStyle = fillStyle != null;
hasMiter = joinStyle == 2;
int length = 6;
if (hasMiter) {
length += 2;
}
if (hasFillStyle) {
length += fillStyle.prepareToEncode(context);
} else {
length += 4;
length += 4;
}
if (horizontal || vertical) {
context.put(Context.SCALING_STROKE, 1);
}
return length;
// CHECKSTYLE:ON
}
/** {@inheritDoc} */
@SuppressWarnings({"PMD.NPathComplexity", "PMD.CyclomaticComplexity" })
public void encode(final SWFEncoder coder, final Context context)
throws IOException {
coder.writeShort(startWidth);
coder.writeShort(endWidth);
int value = 0;
if (startCap == 1) {
value |= Coder.BIT6;
} else if (startCap == 2) {
value |= Coder.BIT7;
}
if (joinStyle == 1) {
value |= Coder.BIT4;
} else if (joinStyle == 2) {
value |= Coder.BIT5;
}
value |= fillStyle == null ? 0 : Coder.BIT3;
value |= horizontal ? 0 : Coder.BIT2;
value |= vertical ? 0 : Coder.BIT1;
value |= pixelAligned ? Coder.BIT0 : 0;
coder.writeByte(value);
value = lineClosed ? 0 : Coder.BIT2;
value |= endCap;
coder.writeByte(value);
if (hasMiter) {
coder.writeShort(miterLimit);
}
if (hasFillStyle) {
fillStyle.encode(coder, context);
} else {
startColor.encode(coder, context);
endColor.encode(coder, context);
}
}
}