/*
* 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 23.10.2005.
*/
package com.scriptographer.ai;
import com.scratchdisk.list.ReadOnlyList;
import com.scratchdisk.util.IntegerEnumUtils;
import com.scriptographer.CommitManager;
/**
* The TextItem type allows you to access and modify the text items in
* Illustrator documents. Its functionality is inherited by different text item
* types such as {@link PointText}, {@link PathText} and {@link AreaText}. They
* each add a layer of functionality that is unique to their type, but share the
* underlying properties and functions that they inherit from TextItem.
*
* @author lehni
*/
public abstract class TextItem extends Item implements TextStoryProvider {
// AITextType
protected static final short
TEXTTYPE_UNKNOWN = -1,
TEXTTYPE_POINT = 0,
TEXTTYPE_AREA = 1,
TEXTTYPE_PATH = 2;
TextRange range = null;
TextRange visibleRange = null;
protected TextItem(int handle, int docHandle, boolean created) {
super(handle, docHandle, created);
}
protected TextItem(int handle) {
super(handle);
}
protected boolean commitAndInvalidate(boolean invalidate) {
// Commit changes to anything depending on the item's story as well.
CommitManager.commit(getStory());
return super.commitAndInvalidate(invalidate);
}
private native int nativeGetOrientation();
private native void nativeSetOrientation(int orientation);
/**
* The orientation of the text in the text item.
*/
public TextOrientation getOrientation() {
return IntegerEnumUtils.get(TextOrientation.class,
nativeGetOrientation());
}
public void setOrientation(TextOrientation orientation) {
nativeSetOrientation(orientation.value);
}
/**
* Specifies whether to use optical alignment within the text item. Optical
* alignment hangs punctuation outside the edges of a text item.
*
* @return {@true if the text item uses optical alignment}
*/
public native boolean getOpticalAlignment();
public native void setOpticalAlignment(boolean active);
// TODO:
// AIAPI AIErr (*DoTextFrameHit) ( const AIHitRef hitRef, TextRangeRef* textRange );
private native Item nativeCreateOutline();
/**
* Converts the text in the text item to outlines. Unlike the Illustrator
* 'Create Outlines' action, this won't remove the text item.
*
* @return a {@link Group} item containing the outlined text as {@link Path}
* and {@link CompoundPath} items.
*/
public Item createOutline() {
// Apply changes and reflow the layout before creating outlines
// All styles regarding this story need to be committed, as
// CharacterStyle uses Story as the commit key.
CommitManager.commit(this.getStory());
// This should not be needed since TextRange takes care of it
// when committing already:
// document.reflowText();
return nativeCreateOutline();
}
/**
* {@grouptitle Text Item Linking}
*
* Links the supplied text item to this one.
*
* @param next The text item that will be linked
* @return {@true if the text item was linked}
*/
public native boolean link(TextItem next);
/**
* Returns {@true if the text item is linked}
*/
public native boolean isLinked();
private native boolean nativeUnlink(boolean before, boolean after);
/**
* Unlinks the text item from its current story.
*
* @return {@true if the operation as successful}
*/
public boolean unlink() {
return nativeUnlink(true, true);
}
/**
* Unlinks the text item from its current story and breaks up the story
* into two parts before the text item.
*
* @return {@true if the operation as successful}
*/
public boolean unlinkBefore() {
return nativeUnlink(true, false);
}
/**
* Unlinks the text item from its current story and breaks up the story
* into two parts after the text item.
*
* @return {@true if the operation as successful}
*/
public boolean unlinkAfter() {
return nativeUnlink(false, true);
}
/**
* {@grouptitle Hierarchy}
*
* Returns the index of the text item in its {@link TextItem#getStory()}.
*/
public native int getIndex();
/**
* Returns this text item's story's index in the document's stories array.
*/
private native int getStoryIndex();
/*
* @see TextStoryProvider.
*/
public native int getStoryHandle();
/**
* Returns the story that the text item belongs to.
*/
public TextStory getStory() {
// don't wrap directly. always go through StoryList
// to make sure we're not getting more than one reference
// to the same Story, so things can be cached there:
int index = getStoryIndex();
TextStoryList list = document.getStories(this, true);
if (index >= 0 && index < list.size())
return list.get(index);
return null;
}
private TextItem getTextItem(int index) {
TextStory story = getStory();
if (story != null) {
ReadOnlyList<TextItem> list = story.getTextItems();
if (index >= 0 && index < list.size())
return list.get(index);
}
return null;
}
/**
* Returns the next text item in a story of various linked text items.
*/
public TextItem getNextTextItem() {
return getTextItem(getIndex() + 1);
}
/**
* Returns the previous text item in a story of various linked text items.
*/
public TextItem getPreviousTextItem() {
return getTextItem(getIndex() - 1);
}
// ATE
/**
* {@grouptitle Range Properties}
*
* The text contents of the text item.
*/
public String getContent() {
return getRange().getContent();
}
public void setContent(String text) {
getRange().setContent(text);
}
/**
* @jshide
*/
public native int nativeGetRange(boolean includeOverflow);
/**
* Returns a text range for all the characters, even the invisible ones
* outside the container.
*/
public TextRange getRange() {
// once a range object is created, always return the same reference
// and swap handles instead. like this references in JS remain...
if (range == null) {
range = new TextRange(nativeGetRange(true), document);
} else if (range.version != CommitManager.version) {
range.changeHandle(nativeGetRange(true));
}
return range;
}
/**
* In case there's an overflow in the text, this only returns a range over
* the visible characters, while {@link TextItem#getRange()} returns one
* over the whole text.
*/
public TextRange getVisibleRange() {
// once a range object is created, always return the same reference
// and swap handles instead. like this references in JS remain...
if (visibleRange == null) {
visibleRange = new TextRange(nativeGetRange(false), document);
} else if (visibleRange.version != CommitManager.version) {
visibleRange.changeHandle(nativeGetRange(false));
}
return visibleRange;
}
/**
* Returns the selected text of the text item as a text range.
*
* @jshide
*/
public native TextRange getSelectedRange();
/**
* Returns the index of the first visible character of the text item. (this
* is the equivalent of calling TextItem.visibleRange.start)
*/
public int getStart() {
return getVisibleRange().getStart();
}
/**
* Returns the index of the last visible character of the text item. (this
* is the equivalent of calling TextItem.visibleRange.end)
*/
public int getEnd() {
return getVisibleRange().getEnd();
}
/**
* {@grouptitle Sub Ranges}
*
* The text ranges of the words contained within the text item. Note that
* the returned text range includes the trailing whitespace characters of
* the words.
*
* Sample code:
* <code>
* var text = new PointText(new Point(0,0));
* text.content = 'The contents of the point text.';
* var word = text.words[1];
* print(word.content) // 'contents ' - note the space after 'contents';
* </code>
*/
public ReadOnlyList<TextRange> getWords() {
return getRange().getWords();
}
/**
* The text ranges of the paragraphs contained within the text item. Note
* that the returned text range includes the trailing paragraph (\r)
* characters of the paragraphs.
*
* Sample code:
* <code>
* var text = new PointText(new Point(0,0));
*
* // ('\r' is the escaped character that specifies a new paragraph)
* text.content = 'First paragraph\rSecond paragraph';
* var paragraph = text.paragraphs[1];
* print(paragraph.content) //returns 'Second paragraph';
* </code>
*/
public ReadOnlyList<TextRange> getParagraphs() {
return getRange().getParagraphs();
}
/**
* The text ranges of the characters contained within the text item.
*
* Sample code:
* <code>
* var text = new PointText(new Point(0,0));
* text.content = 'abc';
* var character = text.characters[1];
* print(character.content) //returns 'b';
* </code>
*/
public ReadOnlyList<TextRange> getCharacters() {
return getRange().getCharacters();
}
/**
* {@grouptitle Style Properties}
*
* The character style of the text item.
*/
public CharacterStyle getCharacterStyle() {
return getRange().getCharacterStyle();
}
public void setCharacterStyle(CharacterStyle style) {
getRange().setCharacterStyle(style);
}
/**
* The paragraph style of the text item.
*/
public ParagraphStyle getParagraphStyle() {
return getRange().getParagraphStyle();
}
public void setParagraphStyle(ParagraphStyle style) {
getRange().setParagraphStyle(style);
}
public native boolean equals(Object obj);
// TODO:
// ATEErr (*GetTextLinesIterator) ( TextFrameRef textframe, TextLinesIteratorRef* ret);
// ATEErr (*GetLineOrientation) ( TextFrameRef textframe, LineOrientation* ret);
// ATEErr (*SetLineOrientation) ( TextFrameRef textframe, LineOrientation lineOrientation);
/* Check if this frame is selected. To set the selection, you have to use application specific
API for that. In Illustrator case, you can use AIArtSuite to set the selection.
*/
}