/*
Copyright 2006,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 it.stefanochizzolini.clown.documents.contents;
using it.stefanochizzolini.clown.documents.contents.composition;
using it.stefanochizzolini.clown.documents.contents.objects;
using xObjectsit.stefanochizzolini.clown.documents.contents.xObjects;
using it.stefanochizzolini.clown.documents.interaction.navigation.page;
using it.stefanochizzolini.clown.files;
using it.stefanochizzolini.clown.objects;
using System;
using System.Collections.Generic;
using System.Drawing;
namespace it.stefanochizzolini.clown.documents{
/**
<summary>Document page [PDF:1.6:3.6.2].</summary>
*/
public class Page
: PdfObjectWrapper<PdfDictionary>,
IContentContext
{
/*
NOTE: Inheritable attributes are NOT early-collected, as they are NOT part
of the explicit representation of a page. They are retrieved everytime
clients call.
*/
#region types
/**
<summary>Annotations tab order [PDF:1.6:3.6.2].</summary>
*/
public enum TabOrderEnum
{
/**
<summary>Row order.</summary>
*/
Row,
/**
<summary>Column order.</summary>
*/
Column,
/**
<summary>Structure order.</summary>
*/
Structure
};
#endregion
#region static
#region fields
private static readonly Dictionary<TabOrderEnum,PdfName> TabOrderEnumCodes;
#endregion
#region constructors
static Page()
{
TabOrderEnumCodes = new Dictionary<TabOrderEnum,PdfName>();
TabOrderEnumCodes[TabOrderEnum.Row] = PdfName.R;
TabOrderEnumCodes[TabOrderEnum.Column] = PdfName.C;
TabOrderEnumCodes[TabOrderEnum.Structure] = PdfName.S;
}
#endregion
#region interface
#region public
public static Page Wrap(
PdfReference reference
)
{return new Page(reference);}
#endregion
#region private
/**
<summary>Gets the code corresponding to the given value.</summary>
*/
private static PdfName ToCode(
TabOrderEnum value
)
{return TabOrderEnumCodes[value];}
/**
<summary>Gets the tab order corresponding to the given value.</summary>
*/
private static TabOrderEnum ToTabOrderEnum(
PdfName value
)
{
foreach(KeyValuePair<TabOrderEnum,PdfName> tabOrder in TabOrderEnumCodes)
{
if(tabOrder.Value.Equals(value))
return tabOrder.Key;
}
return TabOrderEnum.Row;
}
#endregion
#endregion
#endregion
#region dynamic
#region constructors
/**
<summary>Creates a new page within the given document context, using default resources.</summary>
*/
public Page(
Document context
) : base(
context.File,
new PdfDictionary(
new PdfName[2]
{
PdfName.Type,
PdfName.Contents
},
new PdfDirectObject[2]
{
PdfName.Page,
context.File.Register(
new PdfStream()
)
}
)
)
{}
/**
<summary>Creates a new page within the given document context, using custom resources.</summary>
*/
public Page(
Document context,
Size size,
Resources resources
) : base(
context.File,
new PdfDictionary(
new PdfName[4]
{
PdfName.Type,
PdfName.MediaBox,
PdfName.Contents,
PdfName.Resources
},
new PdfDirectObject[4]
{
PdfName.Page,
new PdfRectangle(0,0,size.Width,size.Height),
context.File.Register(
new PdfStream()
),
resources.BaseObject
}
)
)
{}
internal Page(
PdfDirectObject baseObject
) : base(
baseObject,
null // NO container. NOTE: this is a simplification (the spec [PDF:1.6] doesn't apparently prescribe the use of an indirect object for page dictionary, whilst the general practice is as such. If an exception occur, you'll need to specify the proper container).
)
{}
#endregion
#region interface
#region public
/**
<summary>Gets/Sets the page's behavior in response to trigger events.</summary>
*/
public PageActions Actions
{
get
{
PdfDirectObject actionsObject = BaseDataObject[PdfName.AA];
if(actionsObject == null)
return null;
return new PageActions(actionsObject,Container);
}
set
{BaseDataObject[PdfName.AA] = value.BaseObject;}
}
/**
<summary>Gets/Sets the annotations associated to the page.</summary>
*/
public PageAnnotations Annotations
{
get
{
PdfDirectObject annotationsObject = BaseDataObject[PdfName.Annots];
if(annotationsObject == null)
return null;
return new PageAnnotations(annotationsObject,Container,this);
}
set
{BaseDataObject[PdfName.Annots] = value.BaseObject;}
}
public override object Clone(
Document context
)
{
/*
NOTE: We cannot just delegate the cloning to the base object, as it would
involve some unwanted objects like those in 'Parent' and 'Annots' entries that may
cause infinite loops (due to circular references) and may include exceeding contents
(due to copy propagations to the whole page-tree which this page belongs to).
TODO: 'Annots' entry must be finely treated to include any non-circular reference.
*/
// TODO:IMPL deal with inheritable attributes!!!
File contextFile = context.File;
PdfDictionary clone = new PdfDictionary(BaseDataObject.Count);
foreach(
KeyValuePair<PdfName,PdfDirectObject> entry in BaseDataObject
)
{
PdfName key = entry.Key;
// Is the entry unwanted?
if(key.Equals(PdfName.Parent)
|| key.Equals(PdfName.Annots))
continue;
// Insert the clone of the entry into the clone of the page dictionary!
clone[key] = (PdfDirectObject)entry.Value.Clone(contextFile);
}
return new Page(
contextFile.IndirectObjects.Add(clone).Reference
);
}
/**
<summary>Gets/Sets the page's display duration.</summary>
<remarks>
<para>The page's display duration (also called its advance timing)
is the maximum length of time, in seconds, that the page is displayed
during presentations before the viewer application automatically advances
to the next page.</para>
<para>By default, the viewer does not advance automatically.</para>
</remarks>
*/
public double Duration
{
get
{
IPdfNumber durationObject = (IPdfNumber)BaseDataObject[PdfName.Dur];
if(durationObject == null)
return 0;
return durationObject.RawValue;
}
set
{BaseDataObject[PdfName.Dur] = new PdfReal(value);}
}
/**
<summary>Gets the index of the page.</summary>
<remarks>The page index is not an explicit datum, therefore it needs
to be inferred from the position of the page object inside the page tree,
requiring a significant amount of computation: invoke it sparingly!</remarks>
*/
public int Index
{
get
{
/*
NOTE: We'll scan sequentially each page-tree level above this page object
collecting page counts. At each level we'll scan the kids array from the
lower-indexed item to the ancestor of this page object at that level.
*/
PdfReference ancestorKidReference = (PdfReference)BaseObject;
PdfReference parentReference = (PdfReference)BaseDataObject[PdfName.Parent];
PdfDictionary parent = (PdfDictionary)File.Resolve(parentReference);
PdfArray kids = (PdfArray)File.Resolve(parent[PdfName.Kids]);
int index = 0;
for(
int i = 0;
true;
i++
)
{
PdfReference kidReference = (PdfReference)kids[i];
// Is the current-level counting complete?
// NOTE: It's complete when it reaches the ancestor at this level.
if(kidReference.Equals(ancestorKidReference)) // Ancestor node.
{
// Does the current level correspond to the page-tree root node?
if(!parent.ContainsKey(PdfName.Parent))
{
// We reached the top: counting's finished.
return index;
}
// Set the ancestor at the next level!
ancestorKidReference = parentReference;
// Move up one level!
parentReference = (PdfReference)parent[PdfName.Parent];
parent = (PdfDictionary)File.Resolve(parentReference);
kids = (PdfArray)File.Resolve(parent[PdfName.Kids]);
i = -1;
}
else // Intermediate node.
{
PdfDictionary kid = (PdfDictionary)File.Resolve(kidReference);
if(kid[PdfName.Type].Equals(PdfName.Page))
index++;
else
index += ((PdfInteger)kid[PdfName.Count]).RawValue;
}
}
}
}
/**
<summary>Gets/Sets the page size.</summary>
*/
public Size? Size
{
get
{
PdfArray box = (PdfArray)File.Resolve(
GetInheritableAttribute(PdfName.MediaBox)
);
if(box == null)
return null;
return new Size(
(int)((IPdfNumber)box[2]).RawValue,
(int)((IPdfNumber)box[3]).RawValue
);
}
set
{
/*
NOTE: When page size is about to be modified, we MUST ensure that the change will affect just
the mediaBox of this page; so, if such a mediaBox is implicit (inherited), it MUST be cloned
and explicitly assigned to this page in order to apply changes.
*/
PdfDictionary dictionary = BaseDataObject;
PdfDirectObject entry = dictionary[PdfName.MediaBox];
if(entry == null)
{
// Clone the inherited attribute in order to restrict its change to this page's scope only!
entry = (PdfDirectObject)GetInheritableAttribute(PdfName.MediaBox).Clone(File);
// Associate the cloned attribute to this page's dictionary!
dictionary[PdfName.MediaBox] = entry;
}
PdfArray box = (PdfArray)File.Resolve(entry);
((IPdfNumber)box[2]).RawValue = value.Value.Width;
((IPdfNumber)box[3]).RawValue = value.Value.Height;
}
}
/**
<summary>Gets/Sets the tab order to be used for annotations on the page.</summary>
*/
public TabOrderEnum TabOrder
{
get
{return ToTabOrderEnum((PdfName)BaseDataObject[PdfName.Tabs]);}
set
{BaseDataObject[PdfName.Tabs] = ToCode(value);}
}
/**
<summary>Gets the transition effect to be used
when displaying the page during presentations.</summary>
*/
public Transition Transition
{
get
{
PdfDirectObject transitionObject = BaseDataObject[PdfName.Trans];
if(transitionObject == null)
return null;
return new Transition(transitionObject,Container);
}
set
{BaseDataObject[PdfName.Trans] = value.BaseObject;}
}
#region IContentContext
public PdfArray Box
{get{return (PdfArray)File.Resolve(GetInheritableAttribute(PdfName.MediaBox));}}
public Contents Contents
{
get
{
return new Contents(
BaseDataObject[PdfName.Contents],
((PdfReference)BaseObject).IndirectObject,
this
);
}
}
public Resources Resources
{
get
{
return new Resources(
GetInheritableAttribute(PdfName.Resources),
((PdfReference)BaseObject).IndirectObject
);
}
}
#region IContentEntity
public ContentObject ToInlineObject(
PrimitiveFilter context
)
{throw new NotImplementedException();}
public xObjects::XObject ToXObject(
Document context
)
{
File contextFile = context.File;
xObjects::FormXObject form = new xObjects::FormXObject(context);
PdfStream formStream = form.BaseDataObject;
// Header.
{
PdfDictionary formHeader = formStream.Header;
// Bounding box.
formHeader[PdfName.BBox] = (PdfDirectObject)GetInheritableAttribute(PdfName.MediaBox).Clone(contextFile);
// Resources.
{
PdfDirectObject resourcesObject = GetInheritableAttribute(PdfName.Resources);
// Same document?
/* NOTE: Try to reuse the resource dictionary whenever possible. */
formHeader[PdfName.Resources] = (context.Equals(Document) ?
resourcesObject
: (PdfDirectObject)resourcesObject.Clone(contextFile));
}
}
// Body (contents).
{
IBuffer formBody = formStream.Body;
PdfDataObject contentsDataObject = File.Resolve(BaseDataObject[PdfName.Contents]);
if(contentsDataObject is PdfStream)
{formBody.Append(((PdfStream)contentsDataObject).Body);}
else
{
foreach(PdfDirectObject contentStreamObject in (PdfArray)contentsDataObject)
{formBody.Append(((PdfStream)File.Resolve(contentStreamObject)).Body);}
}
}
return form;
}
#endregion
#endregion
#endregion
#region protected
protected PdfDirectObject GetInheritableAttribute(
PdfName key
)
{
/*
NOTE: It moves upward until it finds the inherited attribute.
*/
PdfDictionary dictionary = BaseDataObject;
while(true)
{
PdfDirectObject entry = dictionary[key];
if(entry != null)
return entry;
dictionary = (PdfDictionary)File.Resolve(
dictionary[PdfName.Parent]
);
if(dictionary == null)
{
// Isn't the page attached to the page tree?
/* NOTE: This condition is illegal. */
if(BaseDataObject[PdfName.Parent] == null)
throw new Exception("Inheritable attributes unreachable: Page objects MUST be inserted into their document's Pages collection before being used.");
return null;
}
}
}
#endregion
#endregion
#endregion
}
}
|