/*
Copyright 2007-2010 Stefano Chizzolini. http://www.pdfclown.org
Contributors:
* Stefano Chizzolini (original code developer, http://www.stefanochizzolini.it)
This file should be part of the source code distribution of "PDF Clown library"
(the Program): see the accompanying README files for more info.
This Program is free software; you can redistribute it and/or modify it under the terms
of the GNU Lesser General Public License as published by the Free Software Foundation;
either version 3 of the License, or (at your option) any later version.
This Program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY,
either expressed or implied; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
You should have received a copy of the GNU Lesser General Public License along with this
Program (see README files); if not, go to the GNU website (http://www.gnu.org/licenses/).
Redistribution and use, with or without modification, are permitted provided that such
redistributions retain the above copyright notice, license and disclaimer, along with
this list of conditions.
*/
package org.pdfclown.documents.contents;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.pdfclown.documents.contents.colorSpaces.Color;
import org.pdfclown.documents.contents.colorSpaces.ColorSpace;
import org.pdfclown.documents.contents.colorSpaces.DeviceGrayColor;
import org.pdfclown.documents.contents.colorSpaces.DeviceGrayColorSpace;
import org.pdfclown.documents.contents.fonts.Font;
import org.pdfclown.documents.contents.objects.CompositeObject;
import org.pdfclown.documents.contents.objects.ContainerObject;
import org.pdfclown.documents.contents.objects.ContentObject;
import org.pdfclown.documents.contents.objects.InlineImage;
import org.pdfclown.documents.contents.objects.ShowText;
import org.pdfclown.documents.contents.objects.Text;
import org.pdfclown.documents.contents.objects.XObject;
import org.pdfclown.objects.PdfName;
import org.pdfclown.util.NotImplementedException;
import org.pdfclown.util.math.geom.Dimension;
/**
Content objects scanner.
<p>It wraps a {@link Contents content objects collection} to scan its graphics state
through an oriented cursor.</p>
<p>Scanning is performed at an arbitrary depth, according to the content objects nesting:
each depth level corresponds to a scan level so that at any time it's possible
to seamlessly navigate across the levels (see {@link #getParentLevel()},
{@link #getChildLevel()}).</p>
<p>Scanning can be either "dry" (simulation of graphics state stacking without actual rendering)
or "wet" ({@link #render(Graphics2D,Dimension2D) rendering over a given device context}).
@author Stefano Chizzolini (http://www.stefanochizzolini.it)
@since 0.0.4
@version 0.1.0
*/
public final class ContentScanner
{
// <class>
// <classes>
/**
Graphics state [PDF:1.6:4.3].
*/
public static final class GraphicsState
implements Cloneable
{
// <class>
// <dynamic>
// <fields>
private float charSpace;
private AffineTransform ctm;
private Color<?> fillColor;
private ColorSpace<?> fillColorSpace;
private Font font;
private float fontSize;
private float lead;
private LineCapEnum lineCap;
private LineDash lineDash;
private LineJoinEnum lineJoin;
private float lineWidth;
private float miterLimit;
private TextRenderModeEnum renderMode;
private float rise;
private float scale;
private Color<?> strokeColor;
private ColorSpace<?> strokeColorSpace;
private AffineTransform tlm;
private AffineTransform tm;
private float wordSpace;
private ContentScanner scanner;
// </fields>
// <constructors>
private GraphicsState(
ContentScanner scanner
)
{
this.scanner = scanner;
initialize();
}
// </constructors>
// <interface>
// <public>
/**
Gets a deep copy of the graphics state object.
*/
@Override
public GraphicsState clone(
)
{
GraphicsState clone;
{
// Shallow copy.
try
{clone = (GraphicsState)super.clone();}
catch(CloneNotSupportedException e)
{throw new RuntimeException(e); /* NOTE: It should never happen. */}
// Deep copy.
/* NOTE: Mutable objects are to be cloned. */
clone.ctm = (AffineTransform)ctm.clone();
clone.tlm = (AffineTransform)tlm.clone();
clone.tm = (AffineTransform)tm.clone();
}
return clone;
}
/**
Copies this graphics state into the specified one.
@param state Target graphics state object.
*/
public void copyTo(
GraphicsState state
)
{
state.charSpace = charSpace;
state.ctm = (AffineTransform)ctm.clone();
state.fillColor = fillColor;
state.fillColorSpace = fillColorSpace;
state.font = font;
state.fontSize = fontSize;
state.lead = lead;
state.lineCap = lineCap;
state.lineDash = lineDash;
state.lineJoin = lineJoin;
state.lineWidth = lineWidth;
state.miterLimit = miterLimit;
state.renderMode = renderMode;
state.rise = rise;
state.scale = scale;
state.strokeColor = strokeColor;
state.strokeColorSpace = strokeColorSpace;
//TODO:temporary hack (define TextState for textual parameters!)...
if(state.scanner.getParent() instanceof Text)
{
state.tlm = (AffineTransform)tlm.clone();
state.tm = (AffineTransform)tm.clone();
}
else
{
state.tlm = new AffineTransform();
state.tm = new AffineTransform();
}
state.wordSpace = wordSpace;
}
/**
Gets the current character spacing [PDF:1.6:5.2.1].
*/
public float getCharSpace(
)
{return charSpace;}
/**
Gets the current transformation matrix.
*/
public AffineTransform getCtm(
)
{return ctm;}
/**
Gets the current color for nonstroking operations [PDF:1.6:4.5.1].
*/
public Color<?> getFillColor(
)
{return fillColor;}
/**
Gets the current color space for nonstroking operations [PDF:1.6:4.5.1].
*/
public ColorSpace<?> getFillColorSpace(
)
{return fillColorSpace;}
/**
Gets the current font [PDF:1.6:5.2].
*/
public Font getFont(
)
{return font;}
/**
Gets the current font size [PDF:1.6:5.2].
*/
public float getFontSize(
)
{return fontSize;}
/**
Gets the initial current transformation matrix.
@since 0.1.0
*/
public AffineTransform getInitialCtm(
)
{
AffineTransform initialCtm;
if(getScanner().getRenderContext() == null) // Device-independent.
{
initialCtm = new AffineTransform(); // Identity.
}
else // Device-dependent.
{
IContentContext contentContext = getScanner().getContentContext();
Dimension2D canvasSize = getScanner().getCanvasSize();
// Axes orientation.
RotationEnum rotation = contentContext.getRotation();
switch(rotation)
{
case Downward:
initialCtm = new AffineTransform(1, 0, 0, -1, 0, canvasSize.getHeight());
break;
case Leftward:
initialCtm = new AffineTransform(0, 1, 1, 0, 0, 0);
break;
case Upward:
initialCtm = new AffineTransform(-1, 0, 0, 1, canvasSize.getWidth(), 0);
break;
case Rightward:
initialCtm = new AffineTransform(0, -1, -1, 0, canvasSize.getWidth(), canvasSize.getHeight());
break;
default:
throw new NotImplementedException();
}
// Scaling.
Rectangle2D contentBox = contentContext.getBox();
Dimension2D rotatedCanvasSize = rotation.transform(canvasSize);
initialCtm.scale(
rotatedCanvasSize.getWidth() / contentBox.getWidth(),
rotatedCanvasSize.getHeight() / contentBox.getHeight()
);
// Origin alignment.
initialCtm.translate(-contentBox.getMinX(), -contentBox.getMinY());
}
return initialCtm;
}
/**
Gets the current leading [PDF:1.6:5.2.4].
*/
public float getLead(
)
{return lead;}
/**
Gets the current line cap style [PDF:1.6:4.3.2].
*/
public LineCapEnum getLineCap(
)
{return lineCap;}
/**
Gets the current line dash pattern [PDF:1.6:4.3.2].
*/
public LineDash getLineDash(
)
{return lineDash;}
/**
Gets the current line join style [PDF:1.6:4.3.2].
*/
public LineJoinEnum getLineJoin(
)
{return lineJoin;}
/**
Gets the current line width [PDF:1.6:4.3.2].
*/
public float getLineWidth(
)
{return lineWidth;}
/**
Gets the current miter limit [PDF:1.6:4.3.2].
*/
public float getMiterLimit(
)
{return miterLimit;}
/**
Gets the current text rendering mode [PDF:1.6:5.2.5].
*/
public TextRenderModeEnum getRenderMode(
)
{return renderMode;}
/**
Gets the current text rise [PDF:1.6:5.2.6].
*/
public float getRise(
)
{return rise;}
/**
Gets the current horizontal scaling [PDF:1.6:5.2.3].
*/
public float getScale(
)
{return scale;}
/**
Gets the scanner associated to this state.
*/
public ContentScanner getScanner(
)
{return scanner;}
/**
Gets the current color for stroking operations [PDF:1.6:4.5.1].
*/
public Color<?> getStrokeColor(
)
{return strokeColor;}
/**
Gets the current color space for stroking operations [PDF:1.6:4.5.1].
*/
public ColorSpace<?> getStrokeColorSpace(
)
{return strokeColorSpace;}
/**
Gets the current text line matrix [PDF:1.6:5.3].
*/
public AffineTransform getTlm(
)
{return tlm;}
/**
Gets the current text matrix [PDF:1.6:5.3].
*/
public AffineTransform getTm(
)
{return tm;}
/**
Gets the current word spacing [PDF:1.6:5.2.2].
*/
public float getWordSpace(
)
{return wordSpace;}
/**
@see #getCharSpace()
*/
public void setCharSpace(
float value
)
{charSpace = value;}
/**
@see #getCtm()
*/
public void setCtm(
AffineTransform value
)
{ctm = value;}
/**
@see #getFillColor()
*/
public void setFillColor(
Color<?> value
)
{fillColor = value;}
/**
@see #getFillColorSpace()
*/
public void setFillColorSpace(
ColorSpace<?> value
)
{fillColorSpace = value;}
/**
@see #getFont()
*/
public void setFont(
Font value
)
{font = value;}
/**
@see #getFontSize()
*/
public void setFontSize(
float value
)
{fontSize = value;}
/**
@see #getLead()
*/
public void setLead(
float value
)
{lead = value;}
/**
@see #getLineCap()
*/
public void setLineCap(
LineCapEnum value
)
{lineCap = value;}
/**
@see #getLineDash()
*/
public void setLineDash(
LineDash value
)
{lineDash = value;}
/**
@see #getLineJoin()
*/
public void setLineJoin(
LineJoinEnum value
)
{lineJoin = value;}
/**
@see #getLineWidth()
*/
public void setLineWidth(
float value
)
{lineWidth = value;}
/**
@see #getMiterLimit()
*/
public void setMiterLimit(
float value
)
{miterLimit = value;}
/**
@see #getRenderMode()
*/
public void setRenderMode(
TextRenderModeEnum value
)
{renderMode = value;}
/**
@see #getRise()
*/
public void setRise(
float value
)
{rise = value;}
/**
@see #getScale()
*/
public void setScale(
float value
)
{scale = value;}
/**
@see #getStrokeColor()
*/
public void setStrokeColor(
Color<?> value
)
{strokeColor = value;}
/**
@see #getStrokeColorSpace()
*/
public void setStrokeColorSpace(
ColorSpace<?> value
)
{strokeColorSpace = value;}
/**
@see #getTlm()
*/
public void setTlm(
AffineTransform value
)
{tlm = value;}
/**
@see #getTm()
*/
public void setTm(
AffineTransform value
)
{tm = value;}
/**
@see #getWordSpace()
*/
public void setWordSpace(
float value
)
{wordSpace = value;}
/**
Resolves the given text-space point to its equivalent device-space one [PDF:1.6:5.3.3],
expressed in standard PDF coordinate system (lower-left origin).
@param point Point to transform.
*/
public Point2D textToDeviceSpace(
Point2D point
)
{return textToDeviceSpace(point, false);}
/**
Resolves the given text-space point to its equivalent device-space one [PDF:1.6:5.3.3].
@param point Point to transform.
@param topDown Whether the y-axis orientation has to be adjusted to common top-down orientation
rather than standard PDF coordinate system (bottom-up).
*/
public Point2D textToDeviceSpace(
Point2D point,
boolean topDown
)
{
/*
NOTE: The text rendering matrix (trm) is obtained from the concatenation
of the current transformation matrix (ctm) and the text matrix (tm).
*/
AffineTransform trm = topDown
? new AffineTransform(1, 0, 0, -1, 0, scanner.getCanvasSize().getHeight())
: new AffineTransform();
trm.concatenate(ctm);
trm.concatenate(tm);
return trm.transform(point, null);
}
/**
Resolves the given user-space point to its equivalent device-space one [PDF:1.6:4.2.3],
expressed in standard PDF coordinate system (lower-left origin).
@param point Point to transform.
*/
public Point2D userToDeviceSpace(
Point2D point
)
{return ctm.transform(point, null);}
// </public>
// <private>
private GraphicsState clone(
ContentScanner scanner
)
{
GraphicsState state = clone();
state.scanner = scanner;
return state;
}
private void initialize(
)
{
// State parameters initialization.
charSpace = 0;
ctm = getInitialCtm();
fillColor = DeviceGrayColor.Default;
fillColorSpace = DeviceGrayColorSpace.Default;
font = null;
fontSize = 0;
lead = 0;
lineCap = LineCapEnum.Butt;
lineDash = new LineDash();
lineJoin = LineJoinEnum.Miter;
lineWidth = 1;
miterLimit = 10;
renderMode = TextRenderModeEnum.Fill;
rise = 0;
scale = 100;
strokeColor = DeviceGrayColor.Default;
strokeColorSpace = DeviceGrayColorSpace.Default;
tlm = new AffineTransform();
tm = new AffineTransform();
wordSpace = 0;
// Rendering context initialization.
Graphics2D renderContext = getScanner().getRenderContext();
if(renderContext != null)
{renderContext.setTransform(ctm);}
}
// </private>
// </interface>
// </dynamic>
// </class>
}
/**
Object information.
<h3>Remarks</h3>
<p>This class provides derivative (higher-level) information
about the currently scanned object.</p>
*/
public static abstract class GraphicsObjectWrapper<TDataObject extends ContentObject>
{
// <static>
private static GraphicsObjectWrapper<?> get(
ContentScanner scanner
)
{
ContentObject object = scanner.getCurrent();
if(object instanceof ShowText)
return new TextStringWrapper(scanner);
else if(object instanceof Text)
return new TextWrapper(scanner);
else if(object instanceof XObject)
return new XObjectWrapper(scanner);
else if(object instanceof InlineImage)
return new InlineImageWrapper(scanner);
else
return null;
}
// </static>
// <dynamic>
// <fields>
protected Rectangle2D box;
private TDataObject baseDataObject;
// </fields>
// <constructors>
protected GraphicsObjectWrapper(
TDataObject baseDataObject
)
{this.baseDataObject = baseDataObject;}
// </constructors>
// <interface>
// <public>
/**
Gets the underlying data object.
*/
public TDataObject getBaseDataObject(
)
{return baseDataObject;}
/**
Gets the object's bounding box.
*/
public Rectangle2D getBox(
)
{return box;}
// </public>
// </interface>
// </dynamic>
}
/**
Inline image information.
*/
public static final class InlineImageWrapper
extends GraphicsObjectWrapper<InlineImage>
{
private InlineImageWrapper(
ContentScanner scanner
)
{
super((InlineImage)scanner.getCurrent());
AffineTransform ctm = scanner.getState().getCtm();
this.box = new Rectangle2D.Double(
ctm.getTranslateX(),
scanner.getContentContext().getBox().getHeight() - ctm.getTranslateY(),
ctm.getScaleX(),
Math.abs(ctm.getScaleY())
);
}
/**
Gets the inline image.
*/
public InlineImage getInlineImage(
)
{return getBaseDataObject();}
}
/**
Text information.
*/
public static final class TextWrapper
extends GraphicsObjectWrapper<Text>
{
private List<TextStringWrapper> textStrings;
private TextWrapper(
ContentScanner scanner
)
{
super((Text)scanner.getCurrent());
textStrings = new ArrayList<TextStringWrapper>();
extract(scanner.getChildLevel());
}
@Override
public Rectangle2D getBox(
)
{
if(box == null)
{
for(TextStringWrapper textString : textStrings)
{
if(box == null)
{box = (Rectangle2D)textString.getBox().clone();}
else
{box.add(textString.getBox());}
}
}
return box;
}
/**
Gets the text strings.
*/
public List<TextStringWrapper> getTextStrings(
)
{return textStrings;}
private void extract(
ContentScanner level
)
{
if(level == null)
return;
while(level.moveNext())
{
ContentObject content = level.getCurrent();
if(content instanceof ShowText)
{textStrings.add((TextStringWrapper)level.getCurrentWrapper());}
else if(content instanceof ContainerObject)
{extract(level.getChildLevel());}
}
}
}
/**
Text string information.
*/
public static final class TextStringWrapper
extends GraphicsObjectWrapper<ShowText>
implements ITextString
{
private TextStyle style;
private List<TextChar> textChars;
TextStringWrapper(
ContentScanner scanner
)
{
super((ShowText)scanner.getCurrent());
textChars = new ArrayList<TextChar>();
{
GraphicsState state = scanner.getState();
style = new TextStyle(
state.getFont(),
(float)(state.getFontSize() * state.getTm().getScaleY()),
state.getRenderMode(),
state.getStrokeColor(),
state.getStrokeColorSpace(),
state.getFillColor(),
state.getFillColorSpace()
);
getBaseDataObject().scan(
state,
new ShowText.IScanner()
{
@Override
public void scanChar(
char textChar,
Rectangle2D textCharBox
)
{
textChars.add(
new TextChar(
textChar,
textCharBox,
style,
false
)
);
}
}
);
}
}
@Override
public Rectangle2D getBox(
)
{
if(box == null)
{
for(TextChar textChar : textChars)
{
if(box == null)
{box = (Rectangle2D)textChar.getBox().clone();}
else
{box.add(textChar.getBox());}
}
}
return box;
}
/**
Gets the text style.
*/
public TextStyle getStyle(
)
{return style;}
@Override
public String getText(
)
{
StringBuilder textBuilder = new StringBuilder();
for(TextChar textChar : textChars)
{textBuilder.append(textChar);}
return textBuilder.toString();
}
@Override
public List<TextChar> getTextChars(
)
{return textChars;}
}
/**
External object information.
*/
public static final class XObjectWrapper
extends GraphicsObjectWrapper<XObject>
{
private PdfName name;
private org.pdfclown.documents.contents.xObjects.XObject xObject;
private XObjectWrapper(
ContentScanner scanner
)
{
super((XObject)scanner.getCurrent());
IContentContext context = scanner.getContentContext();
AffineTransform ctm = scanner.getState().getCtm();
this.box = new Rectangle2D.Double(
ctm.getTranslateX(),
context.getBox().getHeight() - ctm.getTranslateY(),
ctm.getScaleX(),
Math.abs(ctm.getScaleY())
);
this.name = getBaseDataObject().getName();
this.xObject = getBaseDataObject().getResource(context);
}
/**
Gets the corresponding resource key.
*/
public PdfName getName(
)
{return name;}
/**
Gets the external object.
*/
public org.pdfclown.documents.contents.xObjects.XObject getXObject(
)
{return xObject;}
}
// </classes>
// <static>
// <fields>
private static final int StartIndex = -1;
// </fields>
// </static>
// <dynamic>
// <fields>
/**
Child level.
*/
private ContentScanner childLevel;
/**
Content objects collection.
*/
private Contents contents;
/**
Current object index at this level.
*/
private int index;
/**
Object collection at this level.
*/
private List<ContentObject> objects;
/**
Parent level.
*/
private ContentScanner parentLevel;
/**
Current graphics state.
*/
private GraphicsState state;
/**
Rendering context.
*/
private Graphics2D renderContext;
/**
Rendering object.
*/
private Shape renderObject;
/**
Device-space size of the rendering canvas.
*/
private Dimension2D renderSize;
// </fields>
// <constructors>
/**
@param contents Content objects collection to scan.
*/
public ContentScanner(
Contents contents
)
{
this.parentLevel = null;
this.objects = this.contents = contents;
moveStart();
}
/**
@param contentContext Content context containing the content objects collection to scan.
*/
public ContentScanner(
IContentContext contentContext
)
{this(contentContext.getContents());}
/**
@param parentLevel Parent scan level.
*/
private ContentScanner(
ContentScanner parentLevel
)
{
this.parentLevel = parentLevel;
this.contents = parentLevel.contents;
this.objects = ((CompositeObject)parentLevel.getCurrent()).getObjects();
moveStart();
}
// </constructors>
// <interface>
// <public>
/**
Gets the size of the current imageable area.
<p>It can be either the <i>user-space area</i> (dry scanning)
or the <i>device-space area</i> (wet scanning).</p>
*/
public Dimension2D getCanvasSize(
)
{
return renderSize == null
? Dimension.get(getContentContext().getBox()) // Device-independent (user-space) area.
: renderSize; // Device-dependent (device-space) area.
}
/**
Gets the current child scan level.
@see #getParentLevel()
@see #getRootLevel()
*/
public ContentScanner getChildLevel(
)
{return childLevel;}
/**
Gets the content context associated to the content objects collection.
*/
public IContentContext getContentContext(
)
{return contents.getContentContext();}
/**
Gets the content objects collection this scanner is inspecting.
*/
public Contents getContents(
)
{return contents;}
/**
Gets the current content object.
@see #getIndex()
@see #getParent()
*/
public ContentObject getCurrent(
)
{
try
{return objects.get(index);}
catch(Exception e)
{return null;}
}
/**
Gets the current content object's information.
@see #getCurrent()
*/
public GraphicsObjectWrapper<?> getCurrentWrapper(
)
{return GraphicsObjectWrapper.get(this);}
/**
Gets the current position.
@see #getCurrent()
*/
public int getIndex(
)
{return index;}
/**
Gets the current parent object.
@see #getCurrent()
*/
public CompositeObject getParent(
)
{return (parentLevel == null ? null : (CompositeObject)parentLevel.getCurrent());}
/**
Gets the parent scan level.
@see #getChildLevel()
@see #getRootLevel()
*/
public ContentScanner getParentLevel(
)
{return parentLevel;}
/**
Gets the rendering context.
@return <code>null</code> in case of dry scanning.
*/
public Graphics2D getRenderContext(
)
{return renderContext;}
/**
Gets the rendering object.
@return <code>null</code> in case of scanning outside a shape.
*/
public Shape getRenderObject(
)
{return renderObject;}
/**
Gets the root scan level.
@see #getChildLevel()
@see #getParentLevel()
*/
public ContentScanner getRootLevel(
)
{
ContentScanner level = this;
while(true)
{
ContentScanner parentLevel = level.getParentLevel();
if(parentLevel == null)
return level;
level = parentLevel;
}
}
/**
Gets the graphics state applied to the current content object.
*/
public GraphicsState getState(
)
{return state;}
/**
Inserts a content object at the current position.
*/
public void insert(
ContentObject object
)
{
if(index == -1)
{index = 0;}
objects.add(index,object);
refresh();
}
/**
Inserts content objects at the current position.
<p>After the insertion is complete, the lastly-inserted content object is at the current position.</p>
*/
public void insert(
Collection<? extends ContentObject> objects
)
{
int index = 0;
int size = objects.size();
for(ContentObject object : objects)
{
insert(object);
if(++index < size)
{moveNext();}
}
}
/**
Gets whether this level is the root of the hierarchy.
*/
public boolean isRootLevel(
)
{return parentLevel == null;}
/**
Moves to the object at the given position.
@param index New position.
@return Whether the object was successfully reached.
*/
public boolean move(
int index
)
{
if(this.index > index)
{moveStart();}
while(this.index < index
&& moveNext());
return getCurrent() != null;
}
/**
Moves after the last object.
*/
public void moveEnd(
)
{moveLast(); moveNext();}
/**
Moves to the first object.
@return Whether the first object was successfully reached.
*/
public boolean moveFirst(
)
{moveStart(); return moveNext();}
/**
Moves to the last object.
@return Whether the last object was successfully reached.
*/
public boolean moveLast(
)
{
int lastIndex = objects.size()-1;
while(index < lastIndex)
{moveNext();}
return getCurrent() != null;
}
/**
Moves to the next object.
@return Whether the next object was successfully reached.
*/
public boolean moveNext(
)
{
// Scanning the current graphics object...
ContentObject currentObject = getCurrent();
if(currentObject != null)
{currentObject.scan(state);}
// Moving to the next object...
if(index < objects.size())
{index++; refresh();}
return getCurrent() != null;
}
/**
Moves before the first object.
*/
public void moveStart(
)
{
index = StartIndex;
if(state == null)
{
if(parentLevel == null)
{state = new GraphicsState(this);}
else
{state = parentLevel.state.clone(this);}
}
else
{
if(parentLevel == null)
{state.initialize();}
else
{parentLevel.state.copyTo(state);}
}
refresh();
}
/**
Removes the content object at the current position.
@return Removed object.
*/
public ContentObject remove(
)
{
ContentObject removedObject = objects.remove(index);
refresh();
return removedObject;
}
/**
Renders the contents into the specified context.
@param renderContext Rendering context.
@param renderSize Rendering canvas size.
@since 0.1.0
*/
public void render(
Graphics2D renderContext,
Dimension2D renderSize
)
{render(renderContext, renderSize, null);}
/**
Renders the contents into the specified object.
@param renderContext Rendering context.
@param renderSize Rendering canvas size.
@param renderObject Rendering object.
@since 0.1.0
*/
public void render(
Graphics2D renderContext,
Dimension2D renderSize,
Shape renderObject
)
{
if(isRootLevel())
{
// Initialize the context!
renderContext.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
renderContext.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC
);
// Paint the canvas background!
renderContext.setColor(java.awt.Color.WHITE);
renderContext.fillRect(0,0,(int)renderSize.getWidth(),(int)renderSize.getHeight());
}
try
{
this.renderContext = renderContext;
this.renderSize = renderSize;
this.renderObject = renderObject;
// Scan this level for rendering!
moveStart(); while(moveNext());
}
catch(Exception e)
{throw new RuntimeException("Rendering failed.", e);}
finally
{
this.renderContext = null;
this.renderSize = null;
this.renderObject = null;
}
}
/**
Replaces the content object at the current position.
@return Replaced object.
*/
public ContentObject setCurrent(
ContentObject value
)
{
ContentObject replacedObject = objects.set(index,value);
refresh();
return replacedObject;
}
// </public>
// <private>
/**
Synchronizes the scanner state.
*/
private void refresh(
)
{
if(getCurrent() instanceof CompositeObject)
{childLevel = new ContentScanner(this);}
else
{childLevel = null;}
}
// </private>
// </interface>
// </dynamic>
// </class>
}