Package com.scriptographer.ai

Source Code of com.scriptographer.ai.Item

/*
* 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 02.12.2004.
*/

package com.scriptographer.ai;

import java.awt.Graphics2D;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Map;
import java.util.Stack;

import com.scratchdisk.list.Lists;
import com.scratchdisk.list.ReadOnlyList;
import com.scratchdisk.script.ChangeReceiver;
import com.scratchdisk.util.ArrayList;
import com.scratchdisk.util.IntegerEnumUtils;
import com.scratchdisk.util.SoftIntMap;
import com.scriptographer.CommitManager;
import com.scriptographer.ScriptographerEngine;
import com.scriptographer.ScriptographerException;
import com.scriptographer.adm.Image;

/**
* The Item type allows you to access and modify the artwork items in
* Illustrator documents. Its functionality is inherited by different document
* item types such as {@link Path}, {@link CompoundPath}, {@link Group},
* {@link Layer} and {@link Raster}. They each add a layer of functionality that
* is unique to their type, but share the underlying properties and functions
* that they inherit from Item.
*
* @author lehni
*
* @jsreference {@type field} {@name document} {@reference Item#document} {@after data}
*/
public class Item extends DocumentObject implements Style, ChangeReceiver {

  /**
   * The internal version. this is used for internally reflected data, such as
   * segmentList, pathStyle, and so on. Every time an object gets modified,
   * ScriptographerEngine.selectionChanged() gets fired that increases the
   * version of all involved items. update-commit related code needs to check
   * against this variable
   */
  protected int version = 0;

  /**
   * Handle history, to keep track of version / handle pairs in a stack.
   */
  protected Stack<HandleHistoryEntry> handleHistory = null;

  /**
   * The history version at the time of the creation of this item. This is
   * used for isValid checks. If historyVersion is below creationVersion, the
   * item does not exist anymore and is invalid (happens through undo's). Set
   * to -1 if creation version is unknown, for existing items that are
   * wrapped, rather than newly created. These version values include both
   * branch and level information. See Document.
   */
  protected long creationVersion;

  /**
   * The history version at which this item was removed. Set to an invalid
   * value as long as it is not removed. This is used in isValid checks.
   */
  protected long deletionVersion = Long.MAX_VALUE;

  /**
   * Then history version of the last modification of this item. Refetching is
   * needed for any item for which the current historyVersion is between
   * between creationVersion and modificationVersion. These version values
   * include both branch and level information. See Document.
   */
  protected long modificationVersion;

  /**
   * The handle for the dictionary that contains this item, if any
   */
  protected int dictionaryHandle = 0;

  /**
   * The key under which this item was inserted into the dictionary
   */
  protected int dictionaryKey = 0;

  /**
   * The art item's dictionary
   */
  private Dictionary data = null;

  /**
   * Internal hash map that keeps track of already wrapped objects. defined
   * as soft.
   */
  private static SoftIntMap<Item> items = new SoftIntMap<Item>();

  private PathStyle style = null;

  /**
   * For Document#currentStyleItem
   */
  protected final static int HANDLE_CURRENT_STYLE = -1;

  // from AIArt.h

  // AIArtType
  protected final static short
    // The special type kAnyArt is never returned as an item type, but
    // is used as a parameter to the Matching Item suite function
    // GetMatchingArt.
    TYPE_ANY = -1,

    // The type kUnknownArt is reserved for objects that are not supported
    // in the plug-in interface. You should anticipate unknown items
    // and ignore them gracefully. For example graph objects return
    // kUnkownType.
    //
    // If a plug-in written for an earlier version of the plug-in API calls
    // GetArt- Type with an item of a type unknown in its version,
    // this function will map the art type to either an appropriate type or
    // to kUnknownArt.
    TYPE_UNKNOWN = 0,
    TYPE_GROUP = 1,
    TYPE_PATH = 2,
    TYPE_COMPOUNDPATH = 3,

    // Pre-AI11 text art type. No longer supported but remains as a place
    // holder so that the segmentValues for other art types remain the same.
    TYPE_TEXT = 4,

    // Pre-AI11 text art type. No longer supported but remains as a place
    // holder so that the segmentValues for other art types remain the same.
    TYPE_TEXTPATH = 5,

    // Pre-AI11 text art type. No longer supported but remains as a place
    // holder so that the segmentValues for other art types remain the same.
    TYPE_TEXTRUN = 6,
    TYPE_PLACED = 7,

    // The special type kMysteryPathArt is never returned as an item
    // type, it is an obsolete parameter to GetMatchingArt. It used to match
    // paths inside text objects without matching the text objects
    // themselves. In AI11 and later the kMatchTextPaths flag is used to
    // indicate that text paths should be returned.
    TYPE_MYSTERYPATH = 8,
    TYPE_RASTER = 9,
    TYPE_PLUGIN = 10,
    TYPE_MESH = 11,
    TYPE_TEXTFRAME = 12,
    TYPE_SYMBOL = 13,

    // A foreign object is a "black box" containing drawing commands.
    // Construct using AIForeignObjectSuite::New(... rather than
    // AIArtSuite::NewArt(.... See AIForeignObjectSuite.
    TYPE_FOREIGN = 14,

    // A text object read from a legacy file (AI10, AI9, AI8 ....
    TYPE_LEGACYTEXT = 15,

    // Lehni: self defined type for layer groups:
    TYPE_LAYER = 100,
    TYPE_TRACING = 101;

  private static native int nativeCreate(short type);

  /**
   * Creates a wrapper for a AIArtHandle. Make sure the right constructor is
   * used (Path, Raster). Use wrapArtHandle instead of directly calling this
   * constructor (it is called from the anchestor's constructors).
   *
   * @param handle
   */
  protected Item(int handle, Document doc, boolean created,
      boolean unversioned) {
    super(handle, doc);
    if (document == null)
      throw new ScriptographerException(
          "Unable to create item. There is no open document.");
    if (unversioned) {
      creationVersion = 0;
    } else if (created) {
      // Set the creation level to the current level for this newly
      // created item, so that isValid works right away. After the history
      // cycle, these values are set one higher, since this items really
      // belongs to the document.historyVersion + 1 cycle.
      creationVersion = document.historyVersion;
      // This item's versions need to be updated after the history cycle
      // is finished.
      document.addCreatedItem(this);
    } else {
      // This is an existing item of which the creation level is unknown.
      // set levels to -1
      creationVersion = -1;
      // Since creationLevel for this item is not known, add it to
      // the items to check on each undo.
      document.checkItems.add(new SoftReference<Item>(this));
    }
    // Use the current history level for the modification level, to force
    // updates bellow this level, since we do not know when exactly
    // the item was created or last modified.
    modificationVersion = document.historyVersion;
    // Keep track of this object from now on, see wrapArtHandle
    if (handle != 0)
      items.put(handle, this);

    // Collect new items. Used for effects
    if (created && createdItems != null)
      createdItems.add(this);
  }

  protected Item(int handle, int docHandle, boolean created) {
    this(handle, Document.wrapHandle(docHandle), created, false);
  }

  /**
   * Creates a new AIArtHandle of the specified type in the active document
   * and wraps it in a item.
   *
   * @param type Item.TYPE_
   */
  protected Item(short type) {
    // Create with false handle, to get document pointer and have time to
    // activate with forCreation = true, to make sure currentStyle gets
    // committed, etc.
    this(0, null, true, false);
    document.activate(false, true);
    // Now set the handle
    handle = nativeCreate(type);
    // Keep track of this object from now on, see wrapArtHandle
    items.put(handle, this);
  }

  /**
   * Creates a wrapper for newly created items in the active document.
   * Do not use to wrap existing ones, since it passes created = true!
   */
  protected Item(int handle) {
    this(handle, null, true, false);
  }

  /**
   * Wraps an AIArtHandle of given type (determined by
   * sAIArt->GetType(artHandle)) by the correct Item ancestor class:
   *
   * @param artHandle
   * @param type
   * @return the wrapped item
   */
  protected static Item wrapHandle(int artHandle, short type, int textType,
      int docHandle, boolean wrapped, boolean created) {
    // ScriptographerEngine.logConsole("wrapHandle: @" + Integer.toHexString(artHandle));
    // First see whether the object was already wrapped before:
    Item item = null;
    // Only try to use the previous wrapper for this address if the object
    // was marked wrapped otherwise we might get wrong wrappers for objects
    // that reuse a previous address
    if (wrapped) {
      item = items.get(artHandle);
      // Make sure this item is still valid. It might be a reused art
      // handle too...
      if (item != null && !item.isValid())
        item = null;
    }
    // If it wasn't wrapped yet, do it now:
    // TODO: Don't forget to add all types also to the native
    // Item_getType function in com_scriptographer_ai_Item.cpp!
    if (item == null) {
      switch (type) {
      case TYPE_PATH:
        item = new Path(artHandle, docHandle, created);
        break;
      case TYPE_GROUP:
        item = new Group(artHandle, docHandle, created);
        break;
      case TYPE_RASTER:
        item = new Raster(artHandle, docHandle, created);
        break;
      case TYPE_PLACED:
        item = new PlacedFile(artHandle, docHandle, created);
        break;
      case TYPE_LAYER:
        item = new Layer(artHandle, docHandle, created);
        break;
      case TYPE_COMPOUNDPATH:
        item = new CompoundPath(artHandle, docHandle, created);
        break;
      case TYPE_TEXTFRAME:
        switch (textType) {
        case TextItem.TEXTTYPE_POINT:
          item = new PointText(artHandle, docHandle, created);
          break;
        case TextItem.TEXTTYPE_AREA:
          item = new AreaText(artHandle, docHandle, created);
          break;
        case TextItem.TEXTTYPE_PATH:
          item = new PathText(artHandle, docHandle, created);
          break;
        }
        break;
      case TYPE_TRACING:
        item = new Tracing(artHandle, docHandle, created);
        break;
      case TYPE_SYMBOL:
        item = new PlacedSymbol(artHandle, docHandle, created);
      }
    }
    return item;
  }

  protected static native Item wrapHandle(int artHandle, int docHandle,
      boolean created, boolean checkWrapped);

  /**
   * Returns the wrapper, if the object has one
   *
   * @param artHandle
   * @return the wrapper for the artHandle
   */
  protected static Item getIfWrapped(int artHandle) {
    Item item = items.get(artHandle);
    // Make sure this item is still valid. It might be a reused art handle
    // too...
    if (item != null && !item.isValid()) {
      // Remove invalid ones so they dont come back, as their handle was
      // now reused in the meantime by a new item. We can do this safely
      // as getIfWrapped is only used for now valid items, so there is no
      // way we could go back to the previous item through undoing, as
      // Illustrator would not have given us the handle otherwise.
      items.remove(artHandle);
      item = null;
    }
    return item;
  }

  /**
   * Increases the version of the items associated with artHandles, if
   * there are any. It does not wrap the artHandles if they weren't already.
   *
   * Called from the native environment.
   */
  protected static void updateIfWrapped(int[] artHandles) {
    // Reuse item objects for lookups, instead of creating a new one
    // for every artHandle
    for (int i = 0; i < artHandles.length; i+=2) {
      // artHandles contains two entries for every item:
      // The current handle, and the initial handle that was stored
      // in the item's dictionary when it was wrapped.
      // See the native side for more explanations
      // (ScriptographerEngine::wrapArtHandle,
      // ScriptographerEngine::onSelectionChanged)
      int curHandle = artHandles[i];
      int prevHandle = artHandles[i + 1];
      Item item = null;
      // Only change the handle if it has changed.
      // Items where the handle has not changed are also included,
      // since their version numbers needs to increase.
      if (prevHandle != 0 && prevHandle != curHandle) {
        // In case there was already an item with the initial handle
        // before, change its handle now:
        item = items.get(prevHandle);
        if (item != null)
          item.changeHandle(curHandle, 0, false);
      } else {
        item = items.get(curHandle);
        // Now update it if it was found
        if (item != null)
          item.version++;
      }
    }
  }

  /**
   * @jshide
   */
  public static void removeIfWrapped(int[] artHandles, boolean removeHandles) {
    for (int i = 0; i < artHandles.length; i++) {
      Item item = items.get(artHandles[i]);
      if (item != null)
        item.remove(removeHandles);
    }
  }

  /**
   * Simple helper class for handle history entries, to keep track of
   * version / handle pairs in a stack.
   */
  static class HandleHistoryEntry {
    long version;
    int handle;

    HandleHistoryEntry(long version, int handle) {
      this.version = version;
      this.handle = handle;
    }
  }
 
  protected void changeHandle(int newHandle, int docHandle,
      boolean clearDictionary) {
    // Remove the object at the old handle
    if (handle != newHandle) {
      // If there was no handle history yet, add the creation version to
      // the history as a first element.
      if (handleHistory == null) {
        handleHistory = new Stack<HandleHistoryEntry>();
        handleHistory.push(
            new HandleHistoryEntry(creationVersion, handle));
      }
      items.remove(handle);
      // Change the handles...
      handle = newHandle;
      // ...and insert it again
      items.put(newHandle, this);
      // Now add the new handle to the history.
      handleHistory.push(
          new HandleHistoryEntry(document.historyVersion, newHandle));
      // Mark this item as modified. This will update modificationVersion
      // after the cycle through #updateModified(), which will also adjust
      // the version of the last HistoryEntry.
      setModified();
    }
    if (docHandle != 0)
      document = Document.wrapHandle(docHandle);
    if (clearDictionary) {
      dictionaryHandle = 0;
      dictionaryKey = 0;
    }
    // Update
    version++;
  }

  /**
   * Call to update the item's modification level
   */
  protected void setModified() {
    modificationVersion = document.historyVersion;
    // This item's modification date needs updating after the cycle.
    document.addModifiedItem(this);
  }

  /**
   * Called by document to update items in document.modifiedItems after cycle.
   */
  protected void updateModified(long version) {
    // Adjust last HistoryEntry as well if it was created in the current
    // cycle.
    if (handleHistory != null) {
      HandleHistoryEntry last = handleHistory.peek();
      if (last.version == modificationVersion) {
        if (Document.reportUndoHistory)
          ScriptographerEngine.logConsole("Updating handleHistory version "
              + Integer.toString(last.handle, 16) + ", " + last.version);
        last.version = version;
      }
    }
    modificationVersion = version;
  }

  /**
   * Checks a cached version number against the internal version and decides
   * if the cached data needs an update. Also takes history levels into
   * account.
   */
  protected boolean needsUpdate(int version) {
    // Before checking if this item is valid or needs updates, check handle
    // history, and swap back to previous handles if version went back
    // bellow the last entry on the stack.
    // TODO: Should this be moved to isValid() instead?
    if (handleHistory != null) {
      HandleHistoryEntry last = handleHistory.peek();
      if (document.historyVersion < last.version) {
        handleHistory.pop();
        HandleHistoryEntry previous = handleHistory.peek();
        // Just like in #changeHandle(), remove old handle...
        items.remove(handle);
        if (Document.reportUndoHistory)
          ScriptographerEngine.logConsole("Switching back to handleHistory version for handle "
              + Integer.toString(handle, 16) + ": "
              + Integer.toString(previous.handle, 16) + ", " + previous.version);
        // ...change the handle to the previous one...
        handle = previous.handle;
        // ...and add it again.
        items.put(handle, this);
        if (handleHistory.size() <= 1) {
          // We've reached the end of the history,
          // switch back to simple mode.
          handleHistory = null;
        }
      }
    }
    return isValid() && (version != this.version
        || document.historyVersion < modificationVersion);
  }

  /**
   * Returns true if this item needs an update regardless of the cached
   * version, due to history changes.
   */
  protected boolean needsUpdate() {
    return this.needsUpdate(version);
  }

  /**
   * Called by native methods through commitIfWrapped if all cached changes
   * need to be committed before the objects are modified.
   *
   * The version is then increased to invalidate the cached values, as they
   * were just changed.
   */
  protected boolean commitAndInvalidate(boolean invalidate) {
    boolean committed = CommitManager.commit(this);
    // Increasing version by one causes refetching of cached data:
    if (invalidate)
      version++;
    return committed;
  }

  /**
   * Called by native methods if all cached changes need to be committed
   * before the objects are modified.
   */
  protected static boolean commitIfWrapped(int handle, boolean invalidate) {
    Item item = getIfWrapped(handle);
    if (item != null)
      return item.commitAndInvalidate(invalidate);
    return false;
  }

  private static native boolean nativeRemove(int handle, int docHandle,
      int dictionaryHandle);

  protected boolean remove(boolean removeHandle) {
    if (handle != 0 && (!removeHandle || isValid()
        && nativeRemove(handle, document.handle, dictionaryHandle))) {
      // Do not remove from items since undoing can bring them back, and
      // we don't want to loose the undo history tracking ionformation for
      // them: items.remove(handle);
      deletionVersion = document.historyVersion;
      // This item's versions need to be updated after the history cycle
      // is finished.
      document.addRemovedItem(this);
      return true;
    }
    return false;
  }

  /**
   * Removes the item from the document. If the item has children,
   * they are also removed.
   *
   * @return {@true if the item was removed}
   */
  public boolean remove() {
    return remove(true);
  }
 
  /**
   * Removes all the children items contained within the item.
   *
   * @return {@true if removing was successful}
   */
  public boolean removeChildren() {
    Item child = getFirstChild();
    boolean removed = false;
    while (child != null) {
      Item next = child.getNextSibling();
      child.remove();
      child = next;
      removed = true;
    }
    return removed;
  }

  protected native void finalize();

  /**
   * Copies the item to another document, or duplicates it within the
   * same document.
   *
   * @param document the document to copy the item to
   * @return the new copy of the item
   */
  public native Item copyTo(Document document);

  /**
   * Copies the item into the specified item.
   *
   * @param item
   */
  public native Item copyTo(Item item);

  /**
   * Clones the item within the same document.
   *
   * @return the newly cloned item
   */
  public Object clone() {
    Item parent = getParent();
    // TODO: parent == null: dictionary item -> return valid parent?
    Item clone = parent != null ? copyTo(parent) : copyTo(document);
    clone.moveAbove(this);
    return clone;
  }

  /**
   * The name of the item as it appears in the layers palette.
   *
   * Sample code:
   * <code>
   * var layer = new Layer(); // a layer is an item
   * print(layer.name); // null
   * layer.name = 'A nice name';
   * print(layer.name); // 'A nice name'
   * </code>
   */
  public native String getName();

  public native void setName(String name);

  /**
   * The item's position within the art board. This is the
   * {@link Rectangle#getCenter()} of the {@link Item#getBounds()} rectangle.
   *
   * Sample code:
   * <code>
   * // Create a circle at position { x: 10, y: 10 }
   * var circle = new Path.Circle(new Point(10, 10), 10);
   *
   * // Move the circle to { x: 20, y: 20 }
   * circle.position = new Point(20, 20);
   *
   * // Move the circle 10 points to the right
   * circle.position += new Point(10, 0);
   * print(circle.position); // { x: 30, y: 20 }
   * </code>
   */
  public native Point getPosition();

  public void setPosition(Point pt) {
    translate(pt.subtract(getPosition()));
  }

  /**
   * @jshide
   */
  public void setPosition(double x, double y) {
    setPosition(new Point(x, y));
  }
 
  /**
   * The path style of the item.
   *
   * Sample code:
   * <code>
   * var circle = new Path.Circle(new Point(10, 10), 10);
   * circle.style = {
   *   fillColor: new RGBColor(1, 0, 0),
   *   strokeColor: new RGBColor(0, 1, 0),
   *   strokeWidth: 5
   * };
   * </code>
   */
  public PathStyle getStyle() {
    if (style == null) {
      style = new PathStyle(this);
    } else {
      style.update();
    }
    return style;
  }

  public void setStyle(PathStyle style) {
    // Make sure it's created and fetched
    getStyle();
    this.style.init(style);
    this.style.markDirty();
  }

  /**
   * A boolean value that specifies whether the center point of the item is
   * visible.
   *
   * @jshide
   */
  public native boolean isCenterVisible();

  /**
   * @jshide
   */
  public native void setCenterVisible(boolean centerVisible);

  private native int nativeGetAttributes(int attributes);

  private native void nativeSetAttributes(int attributes, int values);

  /*
   * This can be used to retrieve all user attributes and restore them again
   * after an operation that would change them.
   */
  protected int getAttributes() {
    return nativeGetAttributes(0xffffffff);
  }

  protected void setAttributes(int attributes) {
    // Setting all attributes at once does not seem to work, so loop
    // through attributes and set separately.
    //  nativeSetAttributes(0xffffffff, attributes);
    for (ItemAttribute attribute : ItemAttribute.values())
      nativeSetAttributes(attribute.value, attributes & attribute.value);
  }

  /**
   * @jshide
   */
  public boolean getAttribute(ItemAttribute attribute) {
    if (attribute != null)
      return (nativeGetAttributes(attribute.value) & attribute.value) != 0;
    return false;
  }

  /**
   * @jshide
   */
  public void setAttribute(ItemAttribute attribute, boolean value) {
    if (attribute != null) {
      if (attribute == ItemAttribute.SELECTED
          || attribute == ItemAttribute.FULLY_SELECTED)
        document.commitCurrentStyle();
      nativeSetAttributes(attribute.value, value ? attribute.value : 0);
    }
  }

  /**
   * Specifies whether an item is selected.
   *
   * Sample code:
   * <code>
   * print(document.selectedItems.length); // 0
   * var path = new Path.Circle(new Size(50, 50), 25);
   * path.selected = true; // Select the path
   * print(document.selectedItems.length) // 1
   * </code>
   *
   * @return {true if the item is selected or partially selected (groups with
   * some selected items/partially selected paths)}
   */
  public boolean isSelected() {
    return getAttribute(ItemAttribute.SELECTED);
  }

  public void setSelected(boolean selected) {
    setAttribute(ItemAttribute.SELECTED, selected);
  }

  /**
   * Specifies whether the item is fully selected. For paths this means that
   * all segments are selected, for container items (groups/layers) all
   * children are selected.
   *
   * @return {@true if the item is fully selected}
   */
  public boolean isFullySelected() {
    return getAttribute(ItemAttribute.FULLY_SELECTED);
  }

  public void setFullySelected(boolean selected) {
    setAttribute(ItemAttribute.FULLY_SELECTED, selected);
  }

  /**
   * Specifies whether the item is locked.
   *
   * Sample code:
   * <code>
   * var path = new Path();
   * print(path.locked) // false
   * path.locked = true; // Locks the path
   * </code>
   *
   * @return {@true if the item is locked}
   */
  public boolean isLocked() {
    return getAttribute(ItemAttribute.LOCKED);
  }

  public void setLocked(boolean locked) {
    setAttribute(ItemAttribute.LOCKED, locked);
  }

  /**
   * Specifies whether the item is visible.
   *
   * Sample code:
   * <code>
   * var path = new Path();
   * print(path.visible) // true
   * path.visible = false; // Hides the path
   * </code>
   *
   * @return {@true if the item is visible}
   */
  public boolean isVisible() {
    return !getAttribute(ItemAttribute.HIDDEN);
  }

  public void setVisible(boolean visible) {
    setAttribute(ItemAttribute.HIDDEN, !visible);
  }

  /**
   * Specifies whether the item is hidden.
   *
   * Sample code:
   * <code>
   * var path = new Path();
   * print(path.hidden); // false
   * path.hidden = true; // Hides the path
   * </code>
   *
   * @return {@true if the item is hidden}
   *
   * @jshide
   */
  public final boolean isHidden() {
    return !isVisible();
  }

  /**
   * @jshide
   */
  public final void setHidden(boolean hidden) {
    setVisible(!hidden);
  }

  /**
   * Specifies whether the item defines a clip mask. This can only be set on
   * paths, compound paths, and text frame objects, and only if the item is
   * already contained within a clipping group.
   *
   * Sample code:
   * <code>
   * var group = new Group();
   * group.appendChild(path);
   * group.clipped = true;
   * path.clipMask = true;
   * </code>
   *
   * @return {@true if the item defines a clip mask}
   */
  public boolean isClipMask() {
    return getAttribute(ItemAttribute.CLIP_MASK);
  }

  @SuppressWarnings("deprecation")
  public void setClipMask(boolean clipMask) {
    setAttribute(ItemAttribute.CLIP_MASK, clipMask);
    if (clipMask) {
      PathStyle style = getStyle();
      style.setFillColor(Color.NONE);
      style.setStrokeColor(Color.NONE);
      // We need to reflect the clip mask status in the deprectted clip
      // property too, otherwise the delayed commmit() through the
      // CommitManager would override whatever we set here.
      style.clip = clipMask;
    }
  }

  /**
   * Specifies whether the item is targeted.
   *
   * Sample code:
   * <code>
   * var path = new Path();
   * print(path.targeted) // false
   * path.targeted = true; // Marks the the path as targeted
   * </code>
   *
   * @return {@true if the item is locked}
   */
  public boolean isTargeted() {
    return getAttribute(ItemAttribute.TARGETED);
  }

  public void setTargeted(boolean targeted) {
    setAttribute(ItemAttribute.TARGETED, targeted);
  }

  private native int nativeGetBlendMode();

  private native void nativeSetBlendMode(int mode);

  /**
   * The blend mode of the item.
   *
   * Sample code:
   * <code>
   * var circle = new Path.Circle(new Point(50, 50), 10);
   * print(circle.blendMode); // normal
   *
   * // Change the blend mode of the path item:
   * circle.blendMode = 'multiply';
   * </code>
   */
  public BlendMode getBlendMode() {
    return IntegerEnumUtils.get(BlendMode.class, nativeGetBlendMode());
  }

  public void setBlendMode(BlendMode blend) {
    nativeSetBlendMode(blend.value);
  }

  /**
   * The opacity of the item.
   *
   * Sample code:
   * <code>
   * // Create a circle at position { x: 10, y: 10 }
   * var circle = new Path.Circle(new Point(10, 10), 10);
   *
   * // Change the opacity of the circle to 50%:
   * circle.opacity = 0.5;
   * </code>
   *
   * @return the opacity of the item as a value between 0 and 1.
   */
  public native float getOpacity();

  public native void setOpacity(float opacity);

  public native boolean getIsolated();

  public native void setIsolated(boolean isolated);

  private native int nativeGetKnockout(boolean inherited);

  private native void nativeSetKnockout(int knockout);

  public Knockout getKnockout(boolean inherited) {
    return IntegerEnumUtils.get(Knockout.class,
        nativeGetKnockout(inherited));
  }

  public Knockout getKnockout() {
    return getKnockout(false);
  }

  public void setKnockout(Knockout knockout) {
    nativeSetKnockout(knockout.value);
  }

  public native boolean getAlphaIsShape();

  public native void setAlphaIsShape(boolean isShape);
 
  private native int nativeGetData();

  /**
   * @jshide
   */
  public LiveEffectPosition getEffectPosition(LiveEffect effect,
      Map<String, Object> parameters) {
    if (effect != null)
      return IntegerEnumUtils.get(LiveEffectPosition.class,
          nativeGetEffectPosition(effect, parameters));
    return null;
  }

  private native int nativeGetEffectPosition(LiveEffect effect,
      Map<String, Object> parameters);

  /**
   * @jshide
   */
  public boolean hasEffect(LiveEffect effect, Map<String, Object> parameters) {
    return getEffectPosition(effect, parameters) != null;
  }

  /**
   * @jshide
   */
  public boolean addEffect(LiveEffect effect, Map<String, Object> parameters,
      LiveEffectPosition position) {
    if (effect != null) {
      LiveEffectParameters params = parameters != null
      ? parameters instanceof LiveEffectParameters
          ? (LiveEffectParameters) parameters
          : new LiveEffectParameters(parameters)
        : new LiveEffectParameters();
      // The moment we pass params to nativeAddEffect, Illustrator
      // will take care of the releasing, so set release to false.
      params.release = false;
      return nativeAddEffect(effect, params, position != null
          ? position.value : effect.getPosition().value);
    }
    return false;
  }

  /**
   * @jshide
   */
  public boolean addEffect(LiveEffect effect, Map<String, Object> parameters) {
    return addEffect(effect, parameters, effect.getPosition());
  }

  /**
   * @jshide
   */
  public boolean addEffect(LiveEffect effect) {
    return addEffect(effect, null);
  }

  private native boolean nativeAddEffect(LiveEffect effect,
      LiveEffectParameters data, int position);

  /**
   * @jshide
   */
  public native boolean editEffect(LiveEffect effect,
      Map<String, Object> parameters);

  /**
   * @jshide
   */
  public native boolean removeEffect(LiveEffect effect,
      Map<String, Object> parameters);

  /**
   * An object contained within the item which can be used to store data.
   * The values in this object can be accessed even after the file has been
   * closed and opened again. Since these values are stored in a native
   * structure, only a limited amount of value types are supported: Number,
   * String, Boolean, Item, Point, Matrix.
   *
   * Sample code:
   * <code>
   * var path = new Path.Circle(new Point(50, 50), 50);
   * path.data.point = new Point(50, 50);
   * print(path.data.point); // {x: 50, y: 50}
   * </code>
   */
  public Dictionary getData() {
    // We need to check if existing data references are still valid,
    // as Dictionary.releaseAll() is invalidating them after each
    // history cycle. See Dictionary.releaseAll() for more explanations
    if (data == null || !data.isValid())
      data = Dictionary.wrapHandle(nativeGetData(), document, this);
    return data; 
  }

  public void setData(Map<String, Object> map) {
    Dictionary data = getData();
    if (map != data) {
      data.clear();
      data.putAll(map);
    }
  }

  /**
   * {@grouptitle Document Hierarchy}
   *
   * The document that the item belongs to.
   */
  public Document getDocument() {
    // This is only here for the API document.
    // It does exactly the same as the definition in DocumentObject
    return document;
  }

  /**
   * The item's parent layer, if any.
   */
  public native Layer getLayer();
 
  /**
   * The item that this item is contained within.
   *
   * Sample code:
   * <code>
   * var path = new Path();
   * print(path.parent) // Layer (Layer 1)
   *
   * var group = new Group();
   * group.appendTop(path);
   * print(path.parent); // Group (@31fbbe00)
   * </code>
   */
  public native Item getParent();

  /**
   * The children items contained within this item.
   *
   * Sample code:
   * <code>
   * var group = new Group();
   *
   * // the group doesn't have any children yet
   * print(group.children.length); // 0
   *
   * var path = new Path();
   * path.name = 'pathName';
   *
   * // append the path in the group
   * group.appendTop(path);
   *
   * print(group.children.length); // 1
   *
   * // access children by index:
   * print(group.children[0]); // Path (pathName)
   *
   * // access children by name:
   * print(group.children['pathName']); // Path (pathName)
   * </code>
   */
  public ItemList getChildren() {
    // Don't implement this in native as the number of items is not known
    // in advance and like this, a java ArrayList can be used:
    // TODO: Cache the result. Invalidate cached version when version
    // changes, or when appendChild / moveAbove / bellow affects this
    // children list.
    ItemList list = new ItemList();
    Item child = getFirstChild();
    while (child != null) {
      list.add(child);
      child = child.getNextSibling();
    }
    return list;
  }

  public void setChildren(ReadOnlyList<Item> children) {
    removeChildren();
    for (Item child : children)
      appendBottom(child);
  }

  public void setChildren(Item[] children) {
    setChildren(Lists.asList(children));
  }

  /**
   * Reverses the order of this item's children
   */
  public boolean reverseChildren() {
    boolean changed = false;
    for (Item child : getChildren())
      changed = appendTop(child) | changed;
    return changed;
  }

  /**
   * The first item contained within this item.
   */
  public native Item getFirstChild();

  /**
   * The last item contained within this item.
   */
  public native Item getLastChild();
 
  /**
   * The next item on the same level as this item.
   */
  public native Item getNextSibling();

  /**
   * The previous item on the same level as this item.
   */
  public native Item getPreviousSibling();

  /**
   * The index of this item within the list of it's parent's children.
   */
  public int getIndex() {
    Item item = getPreviousSibling();
    int i = 0;
    while (item != null) {
      item = item.getPreviousSibling();
      i++;
    }
    return i;
  }
 
  /**
   * {@grouptitle Bounding Rectangles}
   *
   * The bounding rectangle of the item excluding stroke width.
   */
  public native Rectangle getBounds();

  /**
   * @jshide
   */
  public void setBounds(double x, double y, double width, double height) {
    Rectangle rect = getBounds();
    Matrix matrix = new Matrix();
    // Read this from bottom to top:
    // Translate to new center:
    matrix.translate(
        x + width * 0.5f,
        y + height * 0.5f);
    // Scale to new Size, if size changes and avoid divisions by 0:
    if (width != rect.width || height != rect.height)
      matrix.scale(
          rect.width != 0 ? width / rect.width : 1,
          rect.height != 0 ? height / rect.height : 1);
    // Translate to center:
    matrix.translate(
        -(rect.x + rect.width * 0.5f),
        -(rect.y + rect.height * 0.5f));
    // Now execute the transformation:
    transform(matrix);
  }

  public void setBounds(Rectangle rect) {
    setBounds(rect.x, rect.y, rect.width, rect.height);
  }

  /**
   * The bounding rectangle of the item including stroke width.
   */
  public native Rectangle getStrokeBounds();

  /**
   * The bounding rectangle of the item including stroke width and controls.
   */
  public native Rectangle getControlBounds();

  /*
   * Stroke Styles
   */

  /**
   * @copy PathStyle#getStrokeColor()
   */
  public Color getStrokeColor() {
    return getStyle().getStrokeColor();
  }

  public void setStrokeColor(Color color) {
    getStyle().setStrokeColor(color);
  }

  public void setStrokeColor(java.awt.Color color) {
    getStyle().setStrokeColor(color);
  }

  /**
   * @copy PathStyle#getStrokeWidth()
   */
  public Float getStrokeWidth() {
    return getStyle().getStrokeWidth();
  }

  public void setStrokeWidth(Float width) {
    getStyle().setStrokeWidth(width);
  }

  /**
   * @copy PathStyle#getStrokeCap()
   */
  public StrokeCap getStrokeCap() {
    return getStyle().getStrokeCap();
  }

  public void setStrokeCap(StrokeCap cap) {
    getStyle().setStrokeCap(cap);
  }

  /**
   * @copy PathStyle#getStrokeJoin()
   */
  public StrokeJoin getStrokeJoin() {
    return getStyle().getStrokeJoin();
  }

  public void setStrokeJoin(StrokeJoin join) {
    getStyle().setStrokeJoin(join);
  }

  /**
   * @copy PathStyle#getDashOffset()
   */
  public Float getDashOffset() {
    return getStyle().getDashOffset();
  }

  public void setDashOffset(Float offset) {
    getStyle().setDashOffset(offset);
  }

  /**
   * @copy PathStyle#getDashArray()
   */
  public float[] getDashArray() {
    return getStyle().getDashArray();
  }

  public void setDashArray(float[] array) {
    getStyle().setDashArray(array);
  }
 
  /**
   * @copy PathStyle#getMiterLimit()
   */
  public Float getMiterLimit() {
    return getStyle().getMiterLimit();
  }

  public void setMiterLimit(Float limit) {
    getStyle().setMiterLimit(limit);
  }

  /**
   * @copy PathStyle#getStrokeOverprint()
   */
  public Boolean getStrokeOverprint() {
    return getStyle().getStrokeOverprint();
  }

  public void setStrokeOverprint(Boolean overprint) {
    getStyle().setStrokeOverprint(overprint);
  }

  /*
   * Fill Style
   */

  /**
   * @copy PathStyle#getFillColor()
   */
  public Color getFillColor() {
    return getStyle().getFillColor();
  }

  public void setFillColor(Color color) {
    getStyle().setFillColor(color);
  }

  public void setFillColor(java.awt.Color color) {
    getStyle().setFillColor(color);
  }

  /**
   * @copy PathStyle#getFillOverprint()
   */
  public Boolean getFillOverprint() {
    return getStyle().getFillOverprint();
  }

  public void setFillOverprint(Boolean overprint) {
    getStyle().setFillOverprint(overprint);
  }

  /*
   * Path Style
   */
  /**
   * {@grouptitle Path Style}
   *
   * @copy PathStyle#getWindingRule()
   */
  public WindingRule getWindingRule() {
    return getStyle().getWindingRule();
  }

  public void setWindingRule(WindingRule rule) {
    getStyle().setWindingRule(rule);
  }

  /**
   * @copy PathStyle#getResolution()
   */
  public Float getResolution() {
    return getStyle().getResolution();
  }

  public void setResolution(Float resolution) {
    getStyle().setResolution(resolution);
  }

  /*
   * End of Style
   */

  public HitResult hitTest(Point point, HitRequest request, float tolerance) {
    return document.hitTest(point, request, tolerance, this);
  }

  public HitResult hitTest(Point point, HitRequest request) {
    return hitTest(point, request, HitResult.DEFAULT_TOLERANCE);
  }

  public HitResult hitTest(Point point) {
    return hitTest(point, HitRequest.ALL, HitResult.DEFAULT_TOLERANCE);
  }

  public HitResult hitTest(Point point, float tolerance) {
    return hitTest(point, HitRequest.ALL, tolerance);
  }

  private native Item nativeExpand(int flags, int steps);

  /**
   * Breaks artwork up into individual parts and works just like calling
   * "expand" from the Object menu in Illustrator.
   *
   * It outlines stroked lines, text objects, gradients, patterns, etc.
   *
   * The item itself is removed, and the newly created item containing the
   * expanded artwork is returned.
   *
   * @param flags
   * @param steps the amount of steps for gradient, when the
   *       {@code 'gradient-to-paths'} flag is passed
   * @return the newly created item containing the expanded artwork
   */
  public Item expand(EnumSet<ExpandFlag> flags, int steps) {
    return nativeExpand(IntegerEnumUtils.getFlags(flags), steps);
  }

  public Item expand(EnumSet<ExpandFlag> flags) {
    return expand(flags, 0);
  }

  public Item expand(ExpandFlag[] flags, int steps) {
    return expand(EnumSet.copyOf(Arrays.asList(flags)), steps);
  }

  public Item expand(ExpandFlag[] flags) {
    return expand(flags, 0);
  }

  private static int defaultExpandFlags =
    IntegerEnumUtils.getFlags(EnumSet.of(ExpandFlag.PLUGIN_ART,
        ExpandFlag.TEXT, ExpandFlag.STROKE, ExpandFlag.PATTERN,
        ExpandFlag.SYMBOL_INSTANCES));

  /**
   * Calls {@link #expand(int, int)} with these flags set:
   * ExpandFlag#PLUGIN_ART, ExpandFlag#TEXT, ExpandFlag#STROKE,
   * ExpandFlag#PATTERN, ExpandFlag#SYMBOL_INSTANCES
   *
   * @return the newly created item containing the expanded artwork
   */
  public Item expand() {
    return nativeExpand(defaultExpandFlags, 0);
  }

  private native Raster nativeRasterize(int type, float resolution,
      int antialiasing, float width, float height);

  /**
   * Rasterizes the item into a newly created Raster object. The item itself
   * is not removed after rasterization.
   *
   * @param type the color mode of the raster {@default same as document}
   * @param resolution the resolution of the raster in dpi {@default 72}
   * @param antialiasing the amount of anti-aliasing {@default 4}
   * @param width {@default automatic}
   * @param height {@default automatic}
   * @return the newly created Raster item
   */
  public Raster rasterize(ColorType type, float resolution, int antialiasing,
      float width, float height) {
    return nativeRasterize(type != null ? type.value : -1, resolution,
        antialiasing, width, height);
  }

  public Raster rasterize(ColorType type, float resolution, int antialiasing) {
    return rasterize(type, resolution, antialiasing, -1, -1);
  }

  public Raster rasterize(ColorType type, float resolution) {
    return rasterize(type, resolution, 4, -1, -1);
  }
 
  public Raster rasterize(ColorType type) {
    return rasterize(type, 72, 4, -1, -1);
  }
 
  public Raster rasterize() {
    return rasterize(null, 72, 4, -1, -1);
  }

  private static native Raster nativeRasterize(Item[] items, int type,
      float resolution, int antialiasing, float width, float height);

  /**
   * Rasterizes the passed items into a newly created Raster object. The items
   * are not removed after rasterization.
   *
   * @param type the color mode of the raster {@default same as document}
   * @param resolution the resolution of the raster in dpi {@default 72}
   * @param antialiasing the amount of anti-aliasing {@default 4}
   * @param width {@default automatic}
   * @param height {@default automatic}
   * @return the newly created Raster item
   */
  public static Raster rasterize(Item[] items, ColorType type,
      float resolution, int antialiasing, float width, float height) {
    return nativeRasterize(items, type != null ? type.value : -1,
        resolution, antialiasing, width, height);
  }

  public static Raster rasterize(Item[] items, ColorType type,
      float resolution, int antialiasing) {
    return rasterize(items, type, resolution, antialiasing, -1, -1);
  }
 
  public static Raster rasterize(Item[] items, ColorType type) {
    return rasterize(items, type, 0, 4, -1, -1);
  }
 
  public static Raster rasterize(Item[] items) {
    return rasterize(items, null, 0, 4, -1, -1);
  }

  private native void nativeDraw(Image image, int width, int height);

  /**
   * @jshide
   */
  public void draw(Image image) {
    nativeDraw(image, image.getWidth(), image.getHeight());
  }
 
  /**
   * {@grouptitle Tests}
   *
   * Checks if the item contains any children items.
   *
   * @return {@true if it has one or more children}
   */
  public boolean hasChildren() {
    return getFirstChild() != null;
  }

  /**
   * Checks whether the item is editable.
   *
   * @return {@true when neither the item, nor it's parents are locked or
   * hidden}
   */
  public native boolean isEditable();
 
  /**
   * Checks whether the item is valid, i.e. it hasn't been removed.
   *
   * Sample code:
   * <code>
   * var path = new Path();
   * print(path.isValid()); // true
   * path.remove();
   * print(path.isValid()); // false
   * </code>
   *
   * @return {@true if the item is valid}
   */
  public boolean isValid() {
    if (!Document.trackUndoHistory)
      return Item.isValid(handle);
    boolean valid = handle != 0
        && document.isValidVersion(creationVersion)
        && !document.isValidVersion(deletionVersion);
    // Weird stacking of if statements, just so Eclipse does not warn about
    // dead code
    if (Document.reportUndoHistory)
      if (!valid)
        ScriptographerEngine.logConsole(getId() + " is invalid { branch: "
            + ((creationVersion >> 32) & 0xffffffffl) + ", level: "
            + (creationVersion & 0xffffffffl) + " }, isValid: "
            + Item.isValid(handle));
    return valid;
  }

  protected void checkValid() {
    if (!isValid())
      throw new ScriptographerException("The item is no longer valid, either due to deletion or undoing. Use isValid() checks to avoid this error.");
  }

  private static native boolean[] nativeCheckItems(int[] values,
      int length);

  protected static void checkItems(Document document, long version) {
    ArrayList<SoftReference<Item>> checkItems = document.checkItems;
    if (!checkItems.isEmpty()) {
      int[] values = new int[checkItems.size() * 3];
      // Check all these handles in one go, for increased performance
      // We need to pass dictionaryHandle and key as well, so these
      // art items can be checked for validity differently.
      int j = 0;
      for (int i = 0, l = checkItems.size(); i < l; i++) {
        Item item = checkItems.get(i).get();
        if (item != null) {
          values[j++] = item.handle;
          values[j++] = item.dictionaryHandle;
          values[j++] = item.dictionaryKey;
        }
      }
      boolean[] valid = nativeCheckItems(values, j);
      // Update historyVersion to one that is not valid anymore
      for (int i = valid.length - 1; i >= 0; i--) {
        if (!valid[i]) {
          // Retrieve the item to update through its soft reference.
          Item item = checkItems.get(i).get();
          // Check for null as the soft reference might have been
          // released
          if (item != null) {
            item.creationVersion = version;
            if (Document.reportUndoHistory)
              ScriptographerEngine.logConsole("Marking " + item
                  + " as invalid before version: " + version);
          }
          // Remove it from the list
          checkItems.remove(i);
        }
      }
    }
  }

  /**
   * {@grouptitle Hierarchy Operations}
   *
   * Inserts the specified item as a child of the item by appending it to the
   * list of children and moving it above all other children. You can use this
   * function for groups, compound paths and layers.
   *
   * Sample code:
   * <code>
   * var group = new Group();
   * var path = new Path();
   * group.appendTop(path);
   * print(path.isDescendant(group)); // true
   * </code>
   *
   * @param item The item that will be appended as a child
   */
  public native boolean appendTop(Item item);

  /*
  public boolean appendTop(Item... items) {
    boolean ok = true;
    for (Item item : items) {
      if (!appendTop(item))
        ok = false;
    }
    return ok;
  }
  */

  /**
   * Inserts the specified item as a child of this item by appending it to the
   * list of children and moving it below all other children. You can use this
   * function for groups, compound paths and layers.
   *
   * Sample code:
   * <code>
   * var group = new Group();
   * var path = new Path();
   * group.appendBottom(path);
   * print(path.isDescendant(group)); // true
   * </code>
   *
   * @param item The item that will be appended as a child
   */
  public native boolean appendBottom(Item item);

  /**
   * A link to {@link #appendTop}
   *
   * @deprecated use {@link #appendTop} or {@link #appendBottom} instead.
   */
  public boolean appendChild(Item item) {
    return appendTop(item);
  }

  /**
   * Moves this item above the specified item.
   *
   * Sample code:
   * <code>
   * var firstPath = new Path();
   * var secondPath = new Path();
   * print(firstPath.isAbove(secondPath)); // false
   * firstPath.moveAbove(secondPath);
   * print(firstPath.isAbove(secondPath)); // true
   * </code>
   *
   * @param item The item above which it should be moved
   * @return true if it was moved, false otherwise
   */
  public native boolean moveAbove(Item item);

  /**
   * Moves the item below the specified item.
   *
   * Sample code:
   * <code>
   * var firstPath = new Path();
   * var secondPath = new Path();
   * print(secondPath.isBelow(firstPath)); // false
   * secondPath.moveBelow(firstPath);
   * print(secondPath.isBelow(firstPath)); // true
   * </code>
   *
   * @param item the item below which it should be moved
   * @return true if it was moved, false otherwise
   */
  public native boolean moveBelow(Item item);

  /**
   * {@grouptitle Hierarchy Tests}
   *
   * Checks if this item is above the specified item in the stacking order of
   * the document.
   *
   * Sample code:
   * <code>
   * var firstPath = new Path();
   * var secondPath = new Path();
   * print(secondPath.isAbove(firstPath)); // true
   * </code>
   *
   * @param item The item to check against
   * @return {@true if it is above the specified item}
   */
  public native boolean isAbove(Item item);

  /**
   * Checks if the item is below the specified item in the stacking order of
   * the document.
   *
   * Sample code:
   * <code>
   * var firstPath = new Path();
   * var secondPath = new Path();
   * print(firstPath.isBelow(secondPath)); // true
   * </code>
   *
   * @param item The item to check against
   * @return {@true if it is below the specified item}
   */
  public native boolean isBelow(Item item);

  public boolean isParent(Item item) {
    return getParent() == item;
  }

  public boolean isChild(Item item) {
    return item != null && item.getParent() == this;
  }

  /**
   * Checks if the item is contained within the specified item.
   *
   * Sample code:
   * <code>
   * var group = new Group();
   * var path = new Path();
   * group.appendTop(path);
   * print(path.isDescendant(group)); // true
   * </code>
   *
   * @param item The item to check against
   * @return {@true if it is inside the specified item}
   */
  public native boolean isDescendant(Item item);

  /**
   * Checks if the item is an ancestor of the specified item.
   *
   * Sample code:
   * <code>
   * var group = new Group();
   * var path = new Path();
   * group.appendChild(path);
   * print(group.isAncestor(path)); // true
   * print(path.isAncestor(group)); // false
   * </code>
   *
   * @param item the item to check against
   * @return {@true if the item is an ancestor of the specified item}
   */
  public native boolean isAncestor(Item item);

  /**
   * Checks whether the item is grouped with the specified item.
   *
   * @param item
   * @return {@true if the items are grouped together}
   */
  public boolean isGroupedWith(Item item) {
    Item parent = getParent();
    while (parent != null) {
      // Find group parents
      if ((parent instanceof Group || parent instanceof CompoundPath)
          && item.isDescendant(parent))
        return true;
      // Keep walking up otherwise
      parent = parent.getParent();
    }
    return false;
  }
 
  /**
   * {@grouptitle Transform Functions}
   *
   * Scales the item by the given values from its center point, or optionally
   * by a supplied point.
   *
   * @param sx
   * @param sy
   * @param center {@default the center point of the item}
   *
   * @see Matrix#scale(double, double, Point center)
   */
  public void scale(double sx, double sy, Point center) {
    transform(new Matrix().scale(sx, sy, center));
  }

  public void scale(double sx, double sy) {
    scale(sx, sy, getPosition());
  }

  /**
   * Scales the item by the given value from its center point, or optionally
   * by a supplied point.
   *
   * Sample code:
   * <code>
   * // Create a circle at position { x: 10, y: 10 }
   * var circle = new Path.Circle(new Point(10, 10), 10);
   * print(circle.bounds.width); // 20
   *
   * // Scale the path by 200% around its center point
   * circle.scale(2);
   *
   * print(circle.bounds.width); // 40
   * </code>
   *
   * <code>
   * // Create a circle at position { x: 10, y: 10 }
   * var circle = new Path.Circle(new Point(10, 10), 10);
   *
   * // Scale the path 200% from its bottom left corner
   * circle.scale(2, circle.bounds.bottomLeft);
   * </code>
   *
   * @param scale the scale factor
   * @param center {@default the center point of the item}
   * @see Matrix#scale(double, Point center)
   */
  public void scale(double scale, Point center) {
    scale(scale, scale, center);
  }

  public void scale(double scale) {
    scale(scale, scale);
  }

  /**
   * Translates (moves) the item by the given offset point.
   *
   * Sample code:
   * <code>
   * // Create a circle at position { x: 10, y: 10 }
   * var circle = new Path.Circle(new Point(10, 10), 10);
   * circle.translate(new Point(5, 10));
   * print(circle.position); // {x: 15, y: 20}
   * </code>
   *
   * Alternatively you can also add to the {@link #getPosition()} of the item:
   * <code>
   * // Create a circle at position { x: 10, y: 10 }
   * var circle = new Path.Circle(new Point(10, 10), 10);
   * circle.position += new Point(5, 10);
   * print(circle.position); // {x: 15, y: 20}
   * </code>
   *
   * @param t
   */
  public void translate(Point t) {
    transform(new Matrix().translate(t));
  }

  /**
   * Rotates the item by a given angle around the given point.
   *
   * Angles are oriented clockwise and measured in degrees by default. Read
   * more about angle units and orientation in the description of the
   * {@link com.scriptographer.ai.Point#getAngle()} property.
   *
   * @param angle the rotation angle
   * @see Matrix#rotate(double, Point)
   */
  public void rotate(double angle, Point center) {
    transform(new Matrix().rotate(angle, center));
  }

  /**
   * Rotates the item by a given angle around its center point.
   *
   * Angles are oriented clockwise and measured in degrees by default. Read
   * more about angle units and orientation in the description of the
   * {@link com.scriptographer.ai.Point#getAngle()} property.
   *
   * @param angle the rotation angle
   */
  public void rotate(double angle) {
    rotate(angle, getPosition());
  }

  /**
   * Shears the item with a given amount around its center point.
   *
   * @param shx
   * @param shy
   * @see Matrix#shear(double, double)
   */
  public void shear(double shx, double shy) {
    Point pos = getPosition();
    Matrix matrix = new Matrix();
    matrix.translate(pos.x, pos.y);
    matrix.shear(shx, shy);
    matrix.translate(-pos.x, -pos.y);
    transform(matrix);
  }

  /* Matrix Transform */
 
  private native void nativeTransform(Matrix matrix, int flags);

  /**
   * @jshide
   */
  public void transform(Matrix matrix, EnumSet<TransformFlag> flags) {
    nativeTransform(matrix, IntegerEnumUtils.getFlags(flags));
  }

  /**
   * Transforms the item with custom flags to be set.
   *
   * @param matrix
   * @param flags
   */
  public void transform(Matrix matrix, TransformFlag[] flags) {
    transform(matrix, EnumSet.copyOf(Arrays.asList(flags)));
  }

  private static int defaultTransformFlags =
      IntegerEnumUtils.getFlags(EnumSet.of(TransformFlag.OBJECTS,
          TransformFlag.CHILDREN));

  /**
   * Transforms the item with the flags TransformFlag.OBJECTS, and
   * TransformFlag.CHILDREN set
   *
   * @param matrix
   */
  public void transform(Matrix matrix) {
    nativeTransform(matrix, defaultTransformFlags);
  }

  /* TODO:
  {"equals",      artEquals,        0},
  {"hasEqualPath",  artHasEqualPath,    1},
  {"hasFill",      artHasFill,        0},
  {"hasStroke",    artHasStroke,      0},
  {"isClipping",    artIsClipping,      0},
  */
 
  /**
   * @jshide
   */
  public native int getItemType();

  /**
   * @jshide
   */
  public static native int getItemType(Class cls);

  public String toString() {
    if (isValid()) {
      String name = getName();
      if (name != null)
        return getClass().getSimpleName() + " '" + name + "'";
    }
    return super.toString();
  }

  private static ItemList createdItems = null;
 
  /**
   * @jshide
   */
  public static void collectCreatedItems() {
    createdItems = new ItemList();
  }

  /**
   * @jshide
   */
  public static void clearCreatedItems() {
    // Clear right away
    createdItems = null;
  }

  /**
   * @jshide
   */
  public static boolean hasCreatedItems() {
    return createdItems.size() > 0;
  }

  /**
   * @jshide
   */
  public static ItemList retreiveCreatedItems() {
    ItemList items = createdItems;
    createdItems = null;
    for (int i = items.size() - 1; i >= 0; i--) {
      Item item = items.get(i);
      // Make sure we're not returning any invalid new items.
      if (!item.isValid())
        items.remove(i);
    }
    return items;
  }

  /**
   * @jshide
   */
  public static boolean debug(Item item) {
    return item.isValid();
  }

  /*
   * This is just here for debugging the undo history code. It can be removed
   * once that works well.
   */
  protected static native boolean isValid(int handle);

  /**
   * Draws the item's content into a Graphics2D object. Useful for
   * conversions.
   *
   * @jshide
   */
  public void paint(Graphics2D graphics) {
    ItemList children = getChildren();
    for (int i = children.size() - 1; i >= 0; i--) {
      Item child = children.get(i);
      if (child.isVisible())
        child.paint(graphics);
    }
  }
}
TOP

Related Classes of com.scriptographer.ai.Item

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.