/*
Copyright 2006,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.files;
using it.stefanochizzolini.clown.objects;
using it.stefanochizzolini.clown.util.collections.generic;
using System;
using System.Collections;
using System.Collections.Generic;
namespace it.stefanochizzolini.clown.documents{
/**
<summary>Document pages collection.</summary>
*/
public class Pages
: PdfObjectWrapper<PdfDictionary>,
IExtList<Page>,
IList<Page>
{
#region nested types
public class Enumerator
: IEnumerator<Page>
{
#region dynamic
#region fields
/**
<summary>Collection size.</summary>
*/
private int count;
/**
<summary>Index of the next item.</summary>
*/
private int index = 0;
/**
<summary>Current page.</summary>
*/
private Page current;
/**
<summary>Current level index.</summary>
*/
private int levelIndex = 0;
/**
<summary>Stacked level indexes.</summary>
*/
private Stack<int> levelIndexes = new Stack<int>();
/**
<summary>Current child tree nodes.</summary>
*/
private PdfArray kids;
/**
<summary>Current parent tree node.</summary>
*/
private PdfDictionary parent;
#endregion
#region constructors
internal Enumerator(
Pages pages
)
{
count = pages.Count;
parent = pages.BaseDataObject;
kids = (PdfArray)File.Resolve(parent[PdfName.Kids]);
}
#endregion
#region interface
#region public
#region IEnumerator<Page>
Page IEnumerator<Page>.Current
{get{return current;}}
#region IEnumerator
public object Current
{get{return ((IEnumerator<Page>)this).Current;}}
public bool MoveNext(
)
{
if(index == count)
return false;
current = GetNext();
return true;
}
public void Reset(
)
{throw new NotSupportedException();}
#endregion
#region IDisposable
public void Dispose(
)
{}
#endregion
#endregion
#endregion
#region private
private Page GetNext(
)
{
/*
NOTE: As stated in [PDF:1.6:3.6.2], to retrieve pages is a matter of diving
inside a B-tree.
This is a special adaptation of the get() algorithm necessary to keep
a low overhead throughout the page tree scan (using the get() method
would have implied a nonlinear computational cost).
*/
/*
NOTE: Algorithm:
1. [Vertical, down] We have to go downward the page tree till we reach
a page (leaf node).
2. [Horizontal] Then we iterate across the page collection it belongs to,
repeating step 1 whenever we find a subtree.
3. [Vertical, up] When leaf-nodes scan is complete, we go upward solving
parent nodes, repeating step 2.
*/
while(true)
{
// Did we complete current page-tree-branch level?
if(kids.Count == levelIndex) // Page subtree complete.
{
// 3. Go upward one level.
// Restore node index at the current level!
levelIndex = levelIndexes.Pop() + 1; // Next node (partially scanned level).
// Move upward!
parent = (PdfDictionary)File.Resolve(parent[PdfName.Parent]);
kids = (PdfArray)File.Resolve(parent[PdfName.Kids]);
}
else // Page subtree incomplete.
{
PdfReference kidReference = (PdfReference)kids[levelIndex];
PdfDictionary kid = (PdfDictionary)File.Resolve(kidReference);
// Is current kid a page object?
if(kid[PdfName.Type].Equals(PdfName.Page)) // Page object.
{
// 2. Page found.
index++; // Absolute page index.
levelIndex++; // Current level node index.
return new Page(kidReference);
}
else // Page tree node.
{
// 1. Go downward one level.
// Save node index at the current level!
levelIndexes.Push(levelIndex);
// Move downward!
parent = kid;
kids = (PdfArray)File.Resolve(parent[PdfName.Kids]);
levelIndex = 0; // First node (new level).
}
}
}
}
#endregion
#endregion
#endregion
}
#endregion
/*
TODO:IMPL A B-tree algorithm should be implemented to optimize the inner layout
of the page tree (better insertion/deletion performance). In this case, it would
be necessary to keep track of the modified tree nodes for incremental update.
*/
#region dynamic
#region constructors
internal Pages(
Document context
) : base(
context.File,
new PdfDictionary(
new PdfName[3]
{
PdfName.Type,
PdfName.Kids,
PdfName.Count
},
new PdfDirectObject[3]
{
PdfName.Pages,
new PdfArray(),
new PdfInteger(0)
}
)
)
{}
internal Pages(
PdfDirectObject baseObject
) : base(
baseObject,
null // NO container (page tree root node MUST be an indirect object [PDF:1.6:3.6.1]).
)
{}
#endregion
#region interface
#region public
#region PdfObjectWrapper<PdfDictionary>
public override object Clone(
Document context
)
{throw new NotImplementedException();}
#endregion
#region IExtList<Page>
public IList<Page> GetRange(
int index,
int count
)
{
return GetSlice(
index,
index + count
);
}
public IList<Page> GetSlice(
int fromIndex,
int toIndex
)
{
List<Page> pages = new List<Page>(toIndex - fromIndex);
int i = fromIndex;
while(i < toIndex)
{pages.Add(this[i++]);}
return pages;
}
public void InsertAll<TVar>(
int index,
ICollection<TVar> pages
)
where TVar : Page
{CommonAddAll(index,pages);}
#region IExtCollection<Page>
public void AddAll<TVar>(
ICollection<TVar> pages
)
where TVar : Page
{CommonAddAll(-1,pages);}
public void RemoveAll<TVar>(
ICollection<TVar> pages
)
where TVar : Page
{
/*
NOTE: The interface contract doesn't prescribe any relation among the removing-collection's
items, so we cannot adopt the optimized approach of the add*(...) methods family,
where adding-collection's items are explicitly ordered.
*/
foreach(Page page in pages)
{Remove(page);}
}
public int RemoveAll(
Predicate<Page> match
)
{
/*
NOTE: Removal is indirectly fulfilled through an intermediate collection
in order not to interfere with the enumerator execution.
*/
List<Page> removingPages = new List<Page>();
foreach(Page page in this)
{
if(match(page))
{removingPages.Add(page);}
}
RemoveAll(removingPages);
return removingPages.Count;
}
#endregion
#endregion
#region IList<Page>
public int IndexOf(
Page page
)
{return page.Index;}
public void Insert(
int index,
Page page
)
{CommonAddAll(index,(ICollection<Page>)new Page[]{page});}
public void RemoveAt(
int index
)
{Remove(this[index]);}
public Page this[
int index
]
{
get
{
/*
NOTE: As stated in [PDF:1.6:3.6.2], to retrieve pages is a matter of diving
inside a B-tree. To keep it as efficient as possible, this implementation
does NOT adopt recursion to deepen its search, opting for an iterative
strategy instead.
*/
int pageOffset = 0;
PdfDictionary parent = BaseDataObject;
PdfArray kids = (PdfArray)File.Resolve(parent[PdfName.Kids]);
for(
int i = 0;
i < kids.Count;
i++
)
{
PdfReference kidReference = (PdfReference)kids[i];
PdfDictionary kid = (PdfDictionary)File.Resolve(kidReference);
// Is current kid a page object?
if(kid[PdfName.Type].Equals(PdfName.Page)) // Page object.
{
// Did we reach the searched position?
if(pageOffset == index) // Vertical scan (we finished).
// We got it!
return new Page(kidReference);
else // Horizontal scan (go past).
// Cumulate current page object count!
pageOffset++;
}
else // Page tree node.
{
// Does the current subtree contain the searched page?
if(((PdfInteger)kid[PdfName.Count]).RawValue + pageOffset > index) // Vertical scan (deepen the search).
{
// Go down one level!
parent = kid;
kids = (PdfArray)File.Resolve(parent[PdfName.Kids]);
i = -1;
}
else // Horizontal scan (go past).
{
// Cumulate current subtree count!
pageOffset += ((PdfInteger)kid[PdfName.Count]).RawValue;
}
}
}
return null;
}
set
{
RemoveAt(index);
Insert(index,value);
}
}
#region ICollection<Page>
public void Add(
Page page
)
{CommonAddAll(-1,(ICollection<Page>)new Page[]{page});}
public void Clear(
)
{throw new NotImplementedException();}
public bool Contains(
Page page
)
{throw new NotImplementedException();}
public void CopyTo(
Page[] pages,
int index
)
{throw new NotImplementedException();}
public int Count
{get{return ((PdfInteger)BaseDataObject[PdfName.Count]).RawValue;}}
public bool IsReadOnly
{get{return false;}}
public bool Remove(
Page page
)
{
PdfDictionary pageData = page.BaseDataObject;
// Get the parent tree node!
PdfDirectObject parent = pageData[PdfName.Parent];
PdfDictionary parentData = (PdfDictionary)File.Resolve(parent);
// Get the parent's page collection!
PdfDirectObject kids = parentData[PdfName.Kids];
PdfArray kidsData = (PdfArray)File.Resolve(kids);
// Remove the page!
kidsData.Remove(page.BaseObject);
bool updateParent = !File.Update(kids); // Try to update the page collection.
// Unbind the page from its parent!
pageData[PdfName.Parent] = null;
page.Update();
// Decrementing the pages counters...
do
{
// Get the page collection counter!
PdfDirectObject count = parentData[PdfName.Count];
IPdfNumber countData = (IPdfNumber)File.Resolve(count);
// Decrement the counter at the current level!
countData.RawValue--;
updateParent |= !File.Update(count); // Try to update the counter.
// Is the parent tree node to be updated?
/*
NOTE: It avoids to update the parent tree node if its modified fields are all
indirect objects which perform independent updates.
*/
if(updateParent)
{
File.Update(parent);
updateParent = false; // Reset.
}
// Iterate upward!
parent = parentData[PdfName.Parent];
parentData = (PdfDictionary)File.Resolve(parent);
} while(parent != null);
return true;
}
#region IEnumerable<Page>
public IEnumerator<Page> GetEnumerator(
)
{return new Enumerator(this);}
#region IEnumerable
IEnumerator IEnumerable.GetEnumerator(
)
{return this.GetEnumerator();}
#endregion
#endregion
#endregion
#endregion
#endregion
#region private
/**
Add a collection of pages at the specified position.
<param name="index">Addition position. To append, use value -1.</param>
<param name="pages">Collection of pages to add.</param>
*/
private void CommonAddAll<TPage>(
int index,
ICollection<TPage> pages
)
where TPage : Page
{
PdfDirectObject parent;
PdfDictionary parentData;
PdfDirectObject kids;
PdfArray kidsData;
int offset;
// Append operation?
if(index == -1) // Append operation.
{
// Get the parent tree node!
parent = BaseObject;
parentData = BaseDataObject;
// Get the parent's page collection!
kids = parentData[PdfName.Kids];
kidsData = (PdfArray)File.Resolve(kids);
offset = 0; // Not used.
}
else // Insert operation.
{
// Get the page currently at the specified position!
Page pivotPage = this[index];
// Get the parent tree node!
parent = pivotPage.BaseDataObject[PdfName.Parent];
parentData = (PdfDictionary)File.Resolve(parent);
// Get the parent's page collection!
kids = parentData[PdfName.Kids];
kidsData = (PdfArray)File.Resolve(kids);
// Get the insertion's relative position within the parent's page collection!
offset = kidsData.IndexOf(pivotPage.BaseObject);
}
// Adding the pages...
foreach(Page page in pages)
{
// Append?
if(index == -1) // Append.
{
// Append the page to the collection!
kidsData.Add(page.BaseObject);
}
else // Insert.
{
// Insert the page into the collection!
kidsData.Insert(
offset++,
page.BaseObject
);
}
// Bind the page to the collection!
page.BaseDataObject[PdfName.Parent] = parent;
page.Update();
}
bool updateParent = !File.Update(kids); // Try to update the page collection.
// Incrementing the pages counters...
do
{
// Get the page collection counter!
PdfDirectObject count = parentData[PdfName.Count];
IPdfNumber countData = (IPdfNumber)File.Resolve(count);
// Increment the counter at the current level!
countData.RawValue += pages.Count;
updateParent |= !File.Update(count); // Try to update the page counter.
// Is the parent tree node to be updated?
/*
NOTE: It avoids to update the parent tree node if its modified fields are all
indirect objects which perform independent updates.
*/
if(updateParent)
{
File.Update(parent);
updateParent = false; // Reset.
}
// Iterate upward!
parent = parentData[PdfName.Parent];
parentData = (PdfDictionary)File.Resolve(parent);
} while(parent != null);
}
#endregion
#endregion
#endregion
}
}
|