/*
* Scriptographer
*
* This file is part of Scriptographer, a Scripting Plugin for Adobe Illustrator
* http://scriptographer.org/
*
* Copyright (c) 2002-2010, Juerg Lehni
* http://scratchdisk.com/
*
* All rights reserved. See LICENSE file for details.
*
* File created on 14.02.2005.
*/
package com.scriptographer.ai;
import com.scratchdisk.script.ChangeReceiver;
import com.scratchdisk.util.IntegerEnumUtils;
import com.scriptographer.CommitManager;
import com.scriptographer.Committable;
/*
* PathStyle, FillStyle and StrokeStyle are used for Item, CharacterAttributes,
* and others In some places, not all of the values may be defined.
* Setting any value to null means the value is not defined.
*
* Setting fillColor or StrokeColor to Color.NONE means no fill / stroke
* Setting it to null undefines the value, it doesn't have the same effect as
* seting it to Color.NONE
*
* Since Java 1.5 comes with auto boxing / unboxing, I don't think it's a big
* deal that we're not returning native values but boxed ones here (or null,
* in case the value isn't defined)
*
* PathStyle derives AIObject so CharacterStyle has a handle.
*/
/**
* PathStyle is used for changing the visual styles of items contained within an
* Illustrator document and is returned by {@link Item#getStyle()} and
* {@link Document#getCurrentStyle}.
*
* All properties of PathStyle are also reflected directly in {@link Item},
* i.e.: {@link Item#getFillColor()}.
*
* To set multiple style properties in one go, you can pass an object to
* {@link Item#getStyle()}. This is a convenient way to define a style once and
* apply it to a series of items:
*
* <code>
* var circleStyle = {
* fillColor: new RGBColor(1, 0, 0),
* strokeColor: new GrayColor(1),
* strokeWidth: 5
* };
*
* var path = new Path.Circle(new Point(50, 50), 50);
* path.style = circleStyle;
* </code>
*
* @author lehni
*/
public class PathStyle extends NativeObject implements Style, Committable,
ChangeReceiver {
protected FillStyle fill;
protected StrokeStyle stroke;
/**
* Whether or not to use this as a clipping path.
* @deprecated in Illustrator, but we still need to keep it around to
* reflect the state
*/
protected Boolean clip;
/**
* Whether or not to lock the clipping path.
* @deprecated in Illustrator, but we still need to keep it around to
* reflect the state
*/
protected Boolean lockClip;
// Whether or not to use the even-odd rule to determine path insideness
protected WindingRule windingRule;
// Path's resolution
protected Float resolution;
private Item item = null;
protected boolean dirty = false;
protected int version = -1;
// Don't fetch immediately. Only fetch once values are requested
protected boolean fetched = false;
/*
* for CharacterStyle
*/
protected PathStyle(int handle) {
super(handle);
fill = new FillStyle(this);
stroke = new StrokeStyle(this);
}
/*
* For Item#getStyle
*/
protected PathStyle(Item item) {
this(0); // PathStyle doesn't use the handle, but CharacterStyle does
this.item = item;
}
protected PathStyle(PathStyle style) {
this(0); // PathStyle doesn't use the handle, but CharacterStyle does
init(style);
}
public PathStyle() {
super();
this.fill = new FillStyle(this);
this.stroke = new StrokeStyle(this);
}
/**
* @jshide
*/
public PathStyle(FillStyle fill, StrokeStyle stroke) {
super();
this.fill = new FillStyle(fill, this);
this.stroke = new StrokeStyle(stroke, this);
}
public boolean equals(Object obj) {
if (obj instanceof PathStyle) {
// TODO: Implement!
}
return false;
}
public Object clone() {
return new PathStyle(this);
}
protected void update() {
// Only update if it didn't change in the meantime:
if (item != null && (!fetched || (!dirty && item.needsUpdate(version))))
fetch();
}
/*
* This is complicated: for undefined values:
* - color needs an additional boolean value
* - boolean values are passed as short: -1 = undefined, 0 = false,
* 1 = true
* - float are passed as float: < 0 = undefined, >= 0 = defined
*/
protected void init(
Color fillColor, boolean hasFillColor, short fillOverprint,
Color strokeColor, boolean hasStrokeColor, short strokeOverprint,
float strokeWidth,
short strokeCap, short strokeJoin, float miterLimit,
float dashOffset, float[] dashArray,
short clip, short lockClip, int windingRule, float resolution) {
// dashArray doesn't need the boolean, as it's {} when set but empty
fill.init(fillColor, hasFillColor, fillOverprint);
stroke.init(strokeColor, hasStrokeColor, strokeOverprint, strokeWidth,
strokeCap, strokeJoin, miterLimit,
dashOffset, dashArray);
this.clip = clip >= 0 ? new Boolean(clip != 0) : null;
this.lockClip = lockClip >= 0 ? new Boolean(lockClip != 0) : null;
this.windingRule = IntegerEnumUtils.get(WindingRule.class, windingRule);
this.resolution = resolution >= 0 ? new Float(resolution) : null;
}
protected void init(PathStyle style) {
FillStyle fillStyle = style.fill;
StrokeStyle strokeStyle = style.stroke;
fill.init(fillStyle.color, fillStyle.overprint);
stroke.init(strokeStyle.color, strokeStyle.overprint, strokeStyle.width,
strokeStyle.cap, strokeStyle.join, strokeStyle.miterLimit,
strokeStyle.dashOffset, strokeStyle.dashArray);
this.clip = style.clip;
this.lockClip = style.lockClip;
this.windingRule = style.windingRule;
this.resolution = style.resolution;
}
protected native void nativeGet(int handle, int docHandle);
protected native void nativeSet(int handle, int docHandle,
Color fillColor, boolean hasFillColor,
short fillOverprint,
Color strokeColor, boolean hasStrokeColor,
short strokeOverprint, float strokeWidth,
int strokeCap, int strokeJoin, float miterLimit,
float dashOffset, float[] dashArray,
short clip, short lockClip, int windingRule, float resolution);
// These would belong to FillStyle and StrokeStyle, but in order to safe 4
// new native files, they're here:
protected static native void nativeInitStrokeStyle(int handle, Color color,
boolean hasColor, short overprint, float width,
int strokeCap, int strokeJoin, float miterLimit,
float dashOffset, float[] dashArray);
protected static native void nativeInitFillStyle(int handle, Color color,
boolean hasColor, short overprint);
/**
* just a wrapper around nativeCommit, which can be used in CharacterStyle
* as well (CharacterStyle has an own implementation of nativeCommit, but
* the calling is the same...)
*/
protected void commit(int handle, int docHandle) {
nativeSet(handle, docHandle,
fill.color != null && fill.color != Color.NONE ? fill.color : null,
fill.color != null,
fill.overprint != null
? (short) (fill.overprint.booleanValue() ? 1 : 0) : -1,
stroke.color != null && stroke.color != Color.NONE
? stroke.color : null,
stroke.color != null,
stroke.overprint != null
? (short) (stroke.overprint.booleanValue() ? 1 : 0) : -1,
stroke.width != null ? stroke.width.floatValue() : -1,
stroke.cap != null ? stroke.cap.value : -1,
stroke.join != null ? stroke.join.value : -1,
stroke.miterLimit != null ? stroke.miterLimit.floatValue() : -1,
stroke.dashOffset != null ? stroke.dashOffset.floatValue() : -1,
stroke.dashArray,
clip != null ? (short) (clip.booleanValue() ? 1 : 0) : -1,
lockClip != null ? (short) (lockClip.booleanValue() ? 1 : 0) : -1,
windingRule != null ? windingRule.value() : -1,
resolution != null ? resolution.floatValue() : -1
);
}
protected void fetch() {
nativeGet(item.handle, item.document.handle);
version = item.version;
fetched = true;
}
public void commit(boolean endExecution) {
if (dirty && item != null && item.isValid()) {
commit(item.handle, item.document.handle);
version = item.version;
item.setModified();
dirty = false;
}
}
protected void markDirty() {
// Only mark it as dirty if it's attached to a path already:
if (!dirty && item != null) {
CommitManager.markDirty(item, this);
dirty = true;
}
}
/**
* @jshide
*/
public FillStyle getFill() {
return fill;
}
/**
* @jshide
*/
public void setFill(FillStyle fill) {
update();
this.fill = new FillStyle(fill, this);
markDirty();
}
protected FillStyle getFill(boolean create) {
if (fill == null && create)
fill = new FillStyle(this);
return fill;
}
/**
* @jshide
*/
public StrokeStyle getStroke() {
return stroke;
}
protected StrokeStyle getStroke(boolean create) {
if (stroke == null && create)
stroke = new StrokeStyle(this);
return stroke;
}
/**
* @jshide
*/
public void setStroke(StrokeStyle stroke) {
update();
this.stroke = new StrokeStyle(stroke, this);
markDirty();
}
/*
* Path Style
*/
public WindingRule getWindingRule() {
update();
return windingRule;
}
public void setWindingRule(WindingRule rule) {
update();
this.windingRule = rule;
markDirty();
}
/**
* The output resolution for the path.
*/
public Float getResolution() {
update();
return resolution;
}
public void setResolution(Float resolution) {
update();
this.resolution = resolution;
markDirty();
}
/*
* Stroke Styles
*/
/**
* {@grouptitle Stroke Style}
*
* The color of the stroke.
*
* Sample code:
* <code>
* // Create a circle shaped path at { x: 50, y: 50 } with a radius of 10:
* var circle = new Path.Circle(new Point(50, 50), 10);
*
* // Set the stroke color of the circle to CMYK red:
* circle.strokeColor = new CMYKColor(1, 1, 0, 0);
* </code>
*/
public Color getStrokeColor() {
// TODO: Return Color.NONE instead of null?
return stroke != null ? stroke.getColor() : null;
}
public void setStrokeColor(Color color) {
getStroke(true).setColor(color);
}
public void setStrokeColor(java.awt.Color color) {
getStroke(true).setColor(color);
}
/**
* The width of the stroke.
*
* Sample code:
* <code>
* // Create a circle shaped path at { x: 50, y: 50 } with a radius of 10:
* var circle = new Path.Circle(new Point(50, 50), 10);
*
* // Set the stroke width of the circle to 3pt:
* circle.strokeWidth = 3;
* </code>
*/
public Float getStrokeWidth() {
return stroke != null ? stroke.getWidth() : null;
}
public void setStrokeWidth(Float width) {
getStroke(true).setWidth(width);
}
/**
* The cap of the stroke.
*
* Sample code:
* <code>
* // Create a line from { x: 0, y: 50 } to { x: 50, y: 50 };
* var line = new Path.Line(new Point(0, 50), new Point(50, 50));
*
* // Set the stroke cap of the line to be round:
* line.strokeCap = 'round';
* </code>
*/
public StrokeCap getStrokeCap() {
return stroke != null ? stroke.getCap() : null;
}
public void setStrokeCap(StrokeCap cap) {
getStroke(true).setCap(cap);
}
/**
* The join of the stroke.
*/
public StrokeJoin getStrokeJoin() {
return stroke != null ? stroke.getJoin() : null;
}
public void setStrokeJoin(StrokeJoin join) {
getStroke(true).setJoin(join);
}
/**
* The dash offset of the stroke.
*/
public Float getDashOffset() {
return stroke != null ? stroke.getDashOffset() : null;
}
public void setDashOffset(Float offset) {
getStroke(true).setDashOffset(offset);
}
/**
* Specifies an array containing the dash and gap lengths of the stroke.
*
* Sample code:
*
* <code>
* // Create a line from { x: 0, y: 50 } to { x: 50, y: 50 };
* var line = new Path.Line(new Point(0, 50), new Point(50, 50));
*
* line.strokeWidth = 3;
*
* // Set the dashed stroke to [10pt dash, 5pt gap, 8pt dash, 10pt gap]:
* line.dashArray = [10, 5, 8, 10];
* </code>
*/
public float[] getDashArray() {
return stroke != null ? stroke.getDashArray() : null;
}
public void setDashArray(float[] array) {
getStroke(true).setDashArray(array);
}
/**
* The miter limit controls when the program switches from a mitered
* (pointed) join to a beveled (squared-off) join. The default miter limit
* is 4, which means that when the length of the point reaches four times
* the stroke weight, the program switches from a miter join to a bevel
* join. A miter limit of 0 results in a bevel join.
*
* @return the miter limit as a value between 0 and 500
*/
public Float getMiterLimit() {
return stroke != null ? stroke.getMiterLimit() : null;
}
public void setMiterLimit(Float limit) {
getStroke(true).setMiterLimit(limit);
}
/**
* Specifies whether to overprint the stroke. By default, when you print
* opaque, overlapping colors, the top color knocks out the area underneath.
* You can use overprinting to prevent knockout and make the topmost
* overlapping printing ink appear transparent in relation to the underlying
* ink.
*
* @return {@true if the stroke is overprinted}
*/
public Boolean getStrokeOverprint() {
return stroke != null ? stroke.getOverprint() : null;
}
public void setStrokeOverprint(Boolean overprint) {
getStroke(true).setOverprint(overprint);
}
/*
* Fill Style
*/
/**
* {@grouptitle Fill Style}
*
* The fill color of the path.
*
* Sample code:
* <code>
* // Create a circle shaped path at { x: 50, y: 50 } with a radius of 10:
* var circle = new Path.Circle(new Point(50, 50), 10);
*
* // Set the fill color of the circle to CMYK red:
* circle.fillColor = new CMYKColor(1, 1, 0, 0);
* </code>
*/
public Color getFillColor() {
// TODO: Return Color.NONE instead of null?
return fill != null ? fill.getColor() : null;
}
public void setFillColor(Color color) {
getFill(true).setColor(color);
}
public void setFillColor(java.awt.Color color) {
getFill(true).setColor(color);
}
/**
* Specifies whether to overprint the fill. By default, when you print
* opaque, overlapping colors, the top color knocks out the area underneath.
* You can use overprinting to prevent knockout and make the topmost
* overlapping printing ink appear transparent in relation to the underlying
* ink.
*
* @return {@true if the fill is overprinted}
*/
public Boolean getFillOverprint() {
return fill != null ? fill.getOverprint() : null;
}
public void setFillOverprint(Boolean overprint) {
getFill(true).setOverprint(overprint);
}
}