/*
Copyright 2007,2008 Stefano Chizzolini. http://clown.stefanochizzolini.it
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 General Public License as published by the Free Software
Foundation; either version 2 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 General Public License along with this
Program (see README files); if not, go to the GNU website (http://www.gnu.org/).
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.
*/
using it.stefanochizzolini.clown.bytes;
using colorsit.stefanochizzolini.clown.documents.contents.colorSpaces;
using fontsit.stefanochizzolini.clown.documents.contents.fonts;
using it.stefanochizzolini.clown.documents.contents.objects;
using it.stefanochizzolini.clown.files;
using it.stefanochizzolini.clown.objects;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
namespace it.stefanochizzolini.clown.documents.contents{
/**
<summary>Content objects scanner.</summary>
<remarks>
<para>It wraps the content objects collection (Contents) to scan its graphics state
through a forward cursor.</para>
<para>Scanning is performed at an arbitrary deepness, according to the content objects nesting:
in fact, each deepness level corresponds to a scan level so that at any time it's possible to seamlessly navigate across the levels (see ParentLevel, ChildLevel, LeafLevel).</para>
</remarks>
*/
public sealed class ContentScanner
{
#region types
/**
<summary>Graphics state [PDF:1.6:4.3].</summary>
*/
public sealed class GraphicsState
: ICloneable
{
#region static
#region interface
#region public
/**
<summary>Concatenates the given coordinate matrices.</summary>
*/
public static double[] Concat(
double[] matrix1,
double[] matrix2
)
{
double[] result = new double[6];
result[0] = matrix1[0]*matrix2[0] + matrix1[1]*matrix2[2]; // a.
result[1] = matrix1[0]*matrix2[1] + matrix1[1]*matrix2[3]; // b.
result[2] = matrix1[2]*matrix2[0] + matrix1[3]*matrix2[2]; // c.
result[3] = matrix1[2]*matrix2[1] + matrix1[3]*matrix2[3]; // d.
result[4] = matrix1[4]*matrix2[0] + matrix1[5]*matrix2[2] + 1*matrix2[4]; // e.
result[5] = matrix1[4]*matrix2[1] + matrix1[5]*matrix2[3] + 1*matrix2[5]; // f.
return result;
}
#endregion
#endregion
#endregion
#region dynamic
#region fields
/* NOTE: Initialized using prescribed default values. */
internal double charSpace = 0;
internal double[] ctm = new double[]{1,0,0,1,0,0};
internal colors::Color fillColor = colors::DeviceGrayColor.Default;
internal colors::ColorSpace fillColorSpace = colors::DeviceGrayColorSpace.Default;
internal fonts::Font font = null;
internal double fontSize = 0;
internal double lead = 0;
internal LineCapEnum lineCap = LineCapEnum.Butt;
internal LineDash lineDash = new LineDash();
internal LineJoinEnum lineJoin = LineJoinEnum.Miter;
internal double lineWidth = 1;
internal double miterLimit = 10;
internal TextRenderModeEnum renderMode = TextRenderModeEnum.Fill;
internal double rise = 0;
internal double scale = 100;
internal colors::Color strokeColor = colors::DeviceGrayColor.Default;
internal colors::ColorSpace strokeColorSpace = colors::DeviceGrayColorSpace.Default;
internal double[] tm = new double[]{1,0,0,1,0,0};
internal double wordSpace = 0;
private ContentScanner scanner;
#endregion
#region constructors
internal GraphicsState(ContentScanner scanner)
{this.scanner = scanner;}
#endregion
#region interface
#region public
/**
<summary>Gets a deep copy of the graphics state object.</summary>
*/
public object Clone(
)
{
// Shallow copy.
GraphicsState clone = (GraphicsState)this.MemberwiseClone();
// Deep copy.
/* NOTE: Mutable objects are to be cloned. */
return clone;
}
/**
<summary>Gets the current character spacing [PDF:1.6:5.2.1].</summary>
*/
public double CharSpace
{get{return charSpace;}}
/**
<summary>Gets the current transformation matrix.</summary>
*/
public double[] CTM
{get{return ctm;}}
/**
<summary>Gets the current color for nonstroking operations [PDF:1.6:4.5.1].</summary>
*/
public colors::Color FillColor
{get{return fillColor;}}
/**
<summary>Gets the current color space for nonstroking operations [PDF:1.6:4.5.1].</summary>
*/
public colors::ColorSpace FillColorSpace
{get{return fillColorSpace;}}
/**
<summary>Gets the current font.</summary>
*/
public fonts::Font Font
{get{return font;}}
/**
<summary>Gets the current font size.</summary>
*/
public double FontSize
{get{return fontSize;}}
/**
<summary>Gets the current leading [PDF:1.6:5.2.4].</summary>
*/
public double Lead
{get{return lead;}}
/**
<summary>Gets the current line cap style [PDF:1.6:4.3.2].</summary>
*/
public LineCapEnum LineCap
{get{return lineCap;}}
/**
<summary>Gets the current line dash pattern [PDF:1.6:4.3.2].</summary>
*/
public LineDash LineDash
{get{return lineDash;}}
/**
<summary>Gets the current line join style [PDF:1.6:4.3.2].</summary>
*/
public LineJoinEnum LineJoin
{get{return lineJoin;}}
/**
<summary>Gets the current line width [PDF:1.6:4.3.2].</summary>
*/
public double LineWidth
{get{return lineWidth;}}
/**
<summary>Gets the current miter limit [PDF:1.6:4.3.2].</summary>
*/
public double MiterLimit
{get{return miterLimit;}}
/**
<summary>Gets the current text rendering mode [PDF:1.6:5.2.5].</summary>
*/
public TextRenderModeEnum RenderMode
{get{return renderMode;}}
/**
<summary>Gets the current text rise [PDF:1.6:5.2.6].</summary>
*/
public double Rise
{get{return rise;}}
/**
<summary>Gets the current horizontal scaling [PDF:1.6:5.2.3].</summary>
*/
public double Scale
{get{return scale;}}
/**
<summary>Gets the current color for stroking operations [PDF:1.6:4.5.1].</summary>
*/
public colors::Color StrokeColor
{get{return strokeColor;}}
/**
<summary>Gets the current color space for stroking operations [PDF:1.6:4.5.1].</summary>
*/
public colors::ColorSpace StrokeColorSpace
{get{return strokeColorSpace;}}
/**
<summary>Resolves the given text-space point to its equivalent device-space one [PDF:1.6:5.3.3].</summary>
*/
public PointF TextToDeviceSpace(
PointF point
)
{
//TODO:verify whether text parameters are relevant in this context!!!
/*
NOTE: The text rendering matrix (trm) is obtained from the concatenation
of the text parameters matrix, the text matrix (tm) and the CTM.
*/
double[] trm = Concat(tm,ctm);
return new PointF(
(float)(trm[0] * point.X + trm[2] * point.Y + trm[4]),
(float)(((IPdfNumber)scanner.ContentContext.Box[3]).RawValue - (trm[1] * point.X + trm[3] * point.Y + trm[5]))
);
}
/**
<summary>Gets the text matrix.</summary>
*/
public double[] TM
{get{return tm;}}
/**
<summary>Resolves the given user-space point to its equivalent device-space one [PDF:1.6:4.2.3].</summary>
*/
public PointF UserToDeviceSpace(
PointF point
)
{
return new PointF(
(float)(ctm[0] * point.X + ctm[2] * point.Y + ctm[4]),
(float)(ctm[1] * point.X + ctm[3] * point.Y + ctm[5])
);
}
/**
<summary>Gets the current word spacing [PDF:1.6:5.2.2].</summary>
*/
public double WordSpace
{get{return wordSpace;}}
#endregion
#endregion
#endregion
}
#endregion
#region dynamic
#region fields
/**
Content objects collection.
*/
private Contents contents;
/**
Current object index at this level.
*/
private int index = 0;
/**
Current object collection at this level.
*/
/*
NOTE: This wrapping class represents an ugly workaround to the lack of support to covariance
within C#. TODO: it should be temporary, to eliminate before the stable release.
Ideally, in the ContentScanner constructors the objects collection should have been assigned
directly for any type derived from ContentObject (see Java generics wildcards), this way:
this.objects = (List<? : ContentObject>)((CompositeObject<? : ContentObject>)parentLevel.Current).Objects;
Using ConvertAll method was not an option, because keeping a reference to the original collection
was necessary.
*/
private class GenericObjects
{
private object objects;
internal GenericObjects(
object objects
)
{this.objects = objects;}
internal GenericObjects(
ContentObject parent
)
{
if(parent is CompositeObject<Operation>)
{this.objects = ((CompositeObject<Operation>)parent).Objects;}
else
{this.objects = ((CompositeObject<ContentObject>)parent).Objects;}
}
public int Count
{
get
{
if(objects is IList<Operation>)
return ((IList<Operation>)objects).Count;
else
return ((IList<ContentObject>)objects).Count;
}
}
public ContentObject this[
int index
]
{
get
{
if(objects is IList<Operation>)
return ((IList<Operation>)objects)[index];
else
return ((IList<ContentObject>)objects)[index];
}
set
{
if(objects is IList<Operation>)
{((IList<Operation>)objects)[index] = (Operation)value;}
else
{((IList<ContentObject>)objects)[index] = value;}
}
}
public void Insert(
int index,
ContentObject obj
)
{
if(objects is IList<Operation>)
{((IList<Operation>)objects).Insert(index,(Operation)obj);}
else
{((IList<ContentObject>)objects).Insert(index,obj);}
}
public void RemoveAt(
int index
)
{
if(objects is IList<Operation>)
{((IList<Operation>)objects).RemoveAt(index);}
else
{((IList<ContentObject>)objects).RemoveAt(index);}
}
}
private GenericObjects objects;
/**
Current graphics state.
*/
private GraphicsState state;
/**
Child level.
*/
private ContentScanner childLevel;
/**
Parent level.
*/
private ContentScanner parentLevel;
/**
Operation level.
*/
private ContentScanner leafLevel;
#endregion
#region constructors
/**
<param name="contents">Content objects collection to scan.</param>
*/
public ContentScanner(
Contents contents
)
{
this.contents = contents;
this.objects = new GenericObjects(this.contents);
this.state = new GraphicsState(this);
Refresh();
}
/**
<param name="parentLevel">Parent scan level.</param>
*/
private ContentScanner(
ContentScanner parentLevel
)
{
this.parentLevel = parentLevel;
this.contents = parentLevel.contents;
this.objects = new GenericObjects(parentLevel.Current);
this.state = (GraphicsState)parentLevel.state.Clone();
Refresh();
}
#endregion
#region interface
#region public
/**
<summary>Gets the current child object.</summary>
*/
public ContentObject Child
{get{return childLevel.Current;}}
/**
<summary>Gets the child scan level.</summary>
*/
public ContentScanner ChildLevel
{get{return childLevel;}}
/**
<summary>Gets the content context associated to the content objects collection.</summary>
*/
public IContentContext ContentContext
{get{return contents.ContentContext;}}
/**
<summary>Gets the contents collection this scanner is inspecting.</summary>
*/
public Contents Contents
{get{return contents;}}
/**
<summary>Gets/Sets the current content object.</summary>
*/
public ContentObject Current
{
get
{
try{return (ContentObject)objects[index];}
catch{return null;}
}
set
{
objects[index] = value;
Refresh();
}
}
/**
<summary>Gets the current position.</summary>
*/
public int Index
{get{return index;}}
/**
<summary>Inserts a content object at the current position.</summary>
*/
public void Insert(
ContentObject obj
)
{
objects.Insert(index,obj);
Refresh();
}
/**
<summary>Inserts content objects at the current position.</summary>
<remarks>After insertion complete, lastly-inserted content object is at the current
position.</remarks>
*/
public void Insert<T>(
ICollection<T> objects
) where T : ContentObject
{
int index = 0;
int count = objects.Count;
foreach(ContentObject obj in objects)
{
Insert(obj);
if(++index < count)
{MoveNext();}
}
}
/**
<summary>Gets the current leaf object.</summary>
*/
public ContentObject Leaf
{get{return leafLevel.Current;}}
/**
<summary>Gets the leaf scan level.</summary>
*/
public ContentScanner LeafLevel
{get{return leafLevel;}}
/**
<summary>Moves to the first object.</summary>
<returns>Whether the first object was successfully reached.</returns>
*/
public bool MoveFirst(
)
{
index = 0;
if(parentLevel == null)
{this.state = new GraphicsState(this);}
else
{this.state = (GraphicsState)parentLevel.state.Clone();}
Refresh();
return (Current != null);
}
/**
<summary>Moves to the last object.</summary>
<returns>Whether the last object was successfully reached.</returns>
*/
public bool MoveLast(
)
{
int lastIndex = contents.Count-1;
bool success = (index == lastIndex);
while(index < lastIndex)
{success = MoveNext();}
return success;
}
/**
<summary>Moves to the next object.</summary>
<returns>Whether the next object was successfully reached.</returns>
*/
public bool MoveNext(
)
{
// Updating the current graphics state...
{
ContentObject currentObject = Current;
if(currentObject is Operation)
{
Operation currentOperation = (Operation)currentObject;
if(currentOperation is ModifyCTM)
{state.ctm = ((ModifyCTM)currentOperation).ApplyTo(state.CTM);}
else if(currentOperation is SetCharSpace)
{state.charSpace = ((SetCharSpace)currentOperation).Value;}
else if(currentOperation is SetLineCap)
{state.lineCap = ((SetLineCap)currentOperation).Value;}
else if(currentOperation is SetLineDash)
{state.lineDash = ((SetLineDash)currentOperation).Value;}
else if(currentOperation is SetLineJoin)
{state.lineJoin = ((SetLineJoin)currentOperation).Value;}
else if(currentOperation is SetLineWidth)
{state.lineWidth = ((SetLineWidth)currentOperation).Value;}
else if(currentOperation is SetMiterLimit)
{state.miterLimit = ((SetMiterLimit)currentOperation).Value;}
else if(currentOperation is SetTextLead)
{state.lead = ((SetTextLead)currentOperation).Value;}
else if(currentOperation is SetTextRise)
{state.rise = ((SetTextRise)currentOperation).Value;}
else if(currentOperation is SetTextScale)
{state.scale = ((SetTextScale)currentOperation).Value;}
else if(currentOperation is SetTextMatrix)
{state.tm = ((SetTextMatrix)currentOperation).GetMatrix();}
else if(currentOperation is SetTextRenderMode)
{state.renderMode = ((SetTextRenderMode)currentOperation).Value;}
else if(currentOperation is SetWordSpace)
{state.wordSpace = ((SetWordSpace)currentOperation).Value;}
else if(currentOperation is SetFont)
{
SetFont setFontOperation = (SetFont)currentOperation;
state.font = ContentContext.Resources.Fonts[setFontOperation.Name];
state.fontSize = setFontOperation.Size;
}
else if(currentOperation is SetStrokeColor)
{
state.strokeColor = state.strokeColorSpace.GetColor(
currentOperation.Operands.ToArray()
);
}
else if(currentOperation is SetFillColor)
{
state.fillColor = state.fillColorSpace.GetColor(
currentOperation.Operands.ToArray()
);
}
else if(currentOperation is SetStrokeColorSpace)
{
/*
NOTE: The names DeviceGray, DeviceRGB, DeviceCMYK, and Pattern always identify
the corresponding color spaces directly; they never refer to resources in the
ColorSpace subdictionary [PDF:1.6:4.5.7].
*/
PdfName name = (PdfName)currentOperation.Operands[0];
if(name.Equals(PdfName.DeviceGray))
{state.strokeColorSpace = colors::DeviceGrayColorSpace.Default;}
else if(name.Equals(PdfName.DeviceRGB))
{state.strokeColorSpace = colors::DeviceRGBColorSpace.Default;}
else if(name.Equals(PdfName.DeviceCMYK))
{state.strokeColorSpace = colors::DeviceCMYKColorSpace.Default;}
//TODO:special color spaces[PDF:1.6:4.5.5]!!!
// else if(name.equals(PdfName.Pattern))
// {state.strokeColorSpace = Pattern.Default;}
else
{state.strokeColorSpace = ContentContext.Resources.ColorSpaces[name];}
//TODO:eliminate when full support to color spaces!!!
if(state.strokeColorSpace != null)
{
/*
NOTE: The operation also sets the current stroking color
to its initial value, which depends on the color space [PDF:1.6:4.5.7].
*/
state.strokeColor = state.strokeColorSpace.GetDefaultColor();
}
}
else if(currentOperation is SetFillColorSpace)
{
/*
NOTE: The names DeviceGray, DeviceRGB, DeviceCMYK, and Pattern always identify
the corresponding color spaces directly; they never refer to resources in the
ColorSpace subdictionary [PDF:1.6:4.5.7].
*/
PdfName name = (PdfName)currentOperation.Operands[0];
if(name.Equals(PdfName.DeviceGray))
{state.fillColorSpace = colors::DeviceGrayColorSpace.Default;}
else if(name.Equals(PdfName.DeviceRGB))
{state.fillColorSpace = colors::DeviceRGBColorSpace.Default;}
else if(name.Equals(PdfName.DeviceCMYK))
{state.fillColorSpace = colors::DeviceCMYKColorSpace.Default;}
//TODO:special color spaces[PDF:1.6:4.5.5]!!!
// else if(name.equals(PdfName.Pattern))
// {state.fillColorSpace = Pattern.Default;}
else
{state.fillColorSpace = ContentContext.Resources.ColorSpaces[name];}
//TODO:eliminate when full support to color spaces!!!
if(state.fillColorSpace != null)
{
/*
NOTE: The operation also sets the current nonstroking color
to its initial value, which depends on the color space [PDF:1.6:4.5.7].
*/
state.fillColor = state.fillColorSpace.GetDefaultColor();
}
}
}
}
// Trying to move to the next object...
if(index < objects.Count)
{index++;}
Refresh();
return (Current != null);
}
/**
<summary>Gets the current parent object.</summary>
*/
public CompositeObject<ContentObject> Parent
{get{return (CompositeObject<ContentObject>)parentLevel.Current;}}
/**
<summary>Gets the parent scan level.</summary>
*/
public ContentScanner ParentLevel
{get{return parentLevel;}}
/**
<summary>Removes the content object at the current position.</summary>
<returns>Removed object.</returns>
*/
public ContentObject Remove(
)
{
ContentObject oldObject = Current;
objects.RemoveAt(index);
Refresh();
return oldObject;
}
/**
<summary>Gets the current graphics state applied to the current content object.</summary>
<remarks>The returned object of this method is fundamental for any content manipulation
as it represents the actual constraints that affect the current content object
rendering.</remarks>
*/
public GraphicsState State
{get{return state;}}
#endregion
#region private
/**
<summary>Synchronizes the scanner state.</summary>
*/
private void Refresh(
)
{
if(Current is CompositeObject)
{
childLevel = new ContentScanner(this);
leafLevel = childLevel.LeafLevel;
}
else
{
childLevel = null;
leafLevel = this;
}
}
#endregion
#endregion
#endregion
}
}
|