510 lines
20 KiB
C#
510 lines
20 KiB
C#
#region PDFsharp - A .NET library for processing PDF
|
||
//
|
||
// Authors:
|
||
// Stefan Lange
|
||
//
|
||
// Copyright (c) 2005-2017 empira Software GmbH, Cologne Area (Germany)
|
||
//
|
||
// http://www.pdfsharp.com
|
||
// http://sourceforge.net/projects/pdfsharp
|
||
//
|
||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||
// copy of this software and associated documentation files (the "Software"),
|
||
// to deal in the Software without restriction, including without limitation
|
||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||
// and/or sell copies of the Software, and to permit persons to whom the
|
||
// Software is furnished to do so, subject to the following conditions:
|
||
//
|
||
// The above copyright notice and this permission notice shall be included
|
||
// in all copies or substantial portions of the Software.
|
||
//
|
||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||
// DEALINGS IN THE SOFTWARE.
|
||
#endregion
|
||
|
||
using System;
|
||
using System.Diagnostics;
|
||
#if GDI
|
||
using System.Drawing;
|
||
using System.Drawing.Imaging;
|
||
#endif
|
||
#if WPF
|
||
using System.Windows.Media;
|
||
#endif
|
||
using PdfSharp.Drawing;
|
||
|
||
namespace PdfSharp.Pdf.Advanced
|
||
{
|
||
/// <summary>
|
||
/// Represents an external form object (e.g. an imported page).
|
||
/// </summary>
|
||
public sealed class PdfFormXObject : PdfXObject, IContentStream
|
||
{
|
||
internal PdfFormXObject(PdfDocument thisDocument)
|
||
: base(thisDocument)
|
||
{
|
||
Elements.SetName(Keys.Type, "/XObject");
|
||
Elements.SetName(Keys.Subtype, "/Form");
|
||
}
|
||
|
||
internal PdfFormXObject(PdfDocument thisDocument, XForm form)
|
||
: base(thisDocument)
|
||
{
|
||
// BUG: form is not used
|
||
Elements.SetName(Keys.Type, "/XObject");
|
||
Elements.SetName(Keys.Subtype, "/Form");
|
||
|
||
//if (form.IsTemplate)
|
||
//{ }
|
||
}
|
||
|
||
internal double DpiX
|
||
{
|
||
get { return _dpiX; }
|
||
set { _dpiX = value; }
|
||
}
|
||
double _dpiX = 72;
|
||
|
||
internal double DpiY
|
||
{
|
||
get { return _dpiY; }
|
||
set { _dpiY = value; }
|
||
}
|
||
double _dpiY = 72;
|
||
|
||
internal PdfFormXObject(PdfDocument thisDocument, PdfImportedObjectTable importedObjectTable, XPdfForm form)
|
||
: base(thisDocument)
|
||
{
|
||
Debug.Assert(importedObjectTable != null);
|
||
Debug.Assert(ReferenceEquals(thisDocument, importedObjectTable.Owner));
|
||
Elements.SetName(Keys.Type, "/XObject");
|
||
Elements.SetName(Keys.Subtype, "/Form");
|
||
|
||
if (form.IsTemplate)
|
||
{
|
||
Debug.Assert(importedObjectTable == null);
|
||
// TODO more initialization here???
|
||
return;
|
||
}
|
||
|
||
XPdfForm pdfForm = form;
|
||
// Get import page
|
||
PdfPages importPages = importedObjectTable.ExternalDocument.Pages;
|
||
if (pdfForm.PageNumber < 1 || pdfForm.PageNumber > importPages.Count)
|
||
PSSR.ImportPageNumberOutOfRange(pdfForm.PageNumber, importPages.Count, form._path);
|
||
PdfPage importPage = importPages[pdfForm.PageNumber - 1];
|
||
|
||
// Import resources
|
||
PdfItem res = importPage.Elements["/Resources"];
|
||
if (res != null) // unlikely but possible
|
||
{
|
||
#if true
|
||
// Get root object
|
||
PdfObject root;
|
||
if (res is PdfReference)
|
||
root = ((PdfReference)res).Value;
|
||
else
|
||
root = (PdfDictionary)res;
|
||
|
||
root = ImportClosure(importedObjectTable, thisDocument, root);
|
||
// If the root was a direct object, make it indirect.
|
||
if (root.Reference == null)
|
||
thisDocument._irefTable.Add(root);
|
||
|
||
Debug.Assert(root.Reference != null);
|
||
Elements["/Resources"] = root.Reference;
|
||
#else
|
||
// Get transitive closure
|
||
PdfObject[] resources = importPage.Owner.Internals.GetClosure(resourcesRoot);
|
||
int count = resources.Length;
|
||
#if DEBUG_
|
||
for (int idx = 0; idx < count; idx++)
|
||
{
|
||
Debug.Assert(resources[idx].XRef != null);
|
||
Debug.Assert(resources[idx].XRef.Document != null);
|
||
Debug.Assert(resources[idx].Document != null);
|
||
if (resources[idx].ObjectID.ObjectNumber == 12)
|
||
GetType();
|
||
}
|
||
#endif
|
||
// 1st step. Already imported objects are reused and new ones are cloned.
|
||
for (int idx = 0; idx < count; idx++)
|
||
{
|
||
PdfObject obj = resources[idx];
|
||
if (importedObjectTable.Contains(obj.ObjectID))
|
||
{
|
||
// external object was already imported
|
||
PdfReference iref = importedObjectTable[obj.ObjectID];
|
||
Debug.Assert(iref != null);
|
||
Debug.Assert(iref.Value != null);
|
||
Debug.Assert(iref.Document == Owner);
|
||
// replace external object by the already clone counterpart
|
||
resources[idx] = iref.Value;
|
||
}
|
||
else
|
||
{
|
||
// External object was not imported earlier and must be cloned
|
||
PdfObject clone = obj.Clone();
|
||
Debug.Assert(clone.Reference == null);
|
||
clone.Document = Owner;
|
||
if (obj.Reference != null)
|
||
{
|
||
// add it to this (the importer) document
|
||
Owner.irefTable.Add(clone);
|
||
Debug.Assert(clone.Reference != null);
|
||
// save old object identifier
|
||
importedObjectTable.Add(obj.ObjectID, clone.Reference);
|
||
//Debug.WriteLine("Cloned: " + obj.ObjectID.ToString());
|
||
}
|
||
else
|
||
{
|
||
// The root object (the /Resources value) is not an indirect object
|
||
Debug.Assert(idx == 0);
|
||
// add it to this (the importer) document
|
||
Owner.irefTable.Add(clone);
|
||
Debug.Assert(clone.Reference != null);
|
||
}
|
||
// replace external object by its clone
|
||
resources[idx] = clone;
|
||
}
|
||
}
|
||
#if DEBUG_
|
||
for (int idx = 0; idx < count; idx++)
|
||
{
|
||
Debug.Assert(resources[idx].XRef != null);
|
||
Debug.Assert(resources[idx].XRef.Document != null);
|
||
Debug.Assert(resources[idx].Document != null);
|
||
if (resources[idx].ObjectID.ObjectNumber == 12)
|
||
GetType();
|
||
}
|
||
#endif
|
||
|
||
// 2nd step. Fix up indirect references that still refers to the import document.
|
||
for (int idx = 0; idx < count; idx++)
|
||
{
|
||
PdfObject obj = resources[idx];
|
||
Debug.Assert(obj.Owner != null);
|
||
FixUpObject(importedObjectTable, importedObjectTable.Owner, obj);
|
||
}
|
||
|
||
// Set resources key to the root of the clones
|
||
Elements["/Resources"] = resources[0].Reference;
|
||
#endif
|
||
}
|
||
|
||
// Take /Rotate into account.
|
||
PdfRectangle rect = importPage.Elements.GetRectangle(PdfPage.Keys.MediaBox);
|
||
// Reduce rotation to 0, 90, 180, or 270.
|
||
int rotate = (importPage.Elements.GetInteger(PdfPage.Keys.Rotate) % 360 + 360) % 360;
|
||
//rotate = 0;
|
||
if (rotate == 0)
|
||
{
|
||
// Set bounding box to media box.
|
||
Elements["/BBox"] = rect;
|
||
}
|
||
else
|
||
{
|
||
// TODO: Have to adjust bounding box? (I think not, but I'm not sure -> wait for problem)
|
||
Elements["/BBox"] = rect;
|
||
|
||
// Rotate the image such that it is upright.
|
||
XMatrix matrix = new XMatrix();
|
||
double width = rect.Width;
|
||
double height = rect.Height;
|
||
matrix.RotateAtPrepend(-rotate, new XPoint(width / 2, height / 2));
|
||
|
||
// Translate the image such that its center lies on the center of the rotated bounding box.
|
||
double offset = (height - width) / 2;
|
||
if (rotate == 90)
|
||
{
|
||
// TODO It seems we can simplify this as the sign of offset changes too.
|
||
if (height > width)
|
||
matrix.TranslatePrepend(offset, offset); // Tested.
|
||
else
|
||
matrix.TranslatePrepend(offset, offset); // TODO Test case.
|
||
}
|
||
else if (rotate == 270)
|
||
{
|
||
// TODO It seems we can simplify this as the sign of offset changes too.
|
||
if (height > width)
|
||
matrix.TranslatePrepend(-offset, -offset); // Tested.
|
||
else
|
||
matrix.TranslatePrepend(-offset, -offset); // Tested.
|
||
}
|
||
|
||
//string item = "[" + PdfEncoders.ToString(matrix) + "]";
|
||
//Elements[Keys.Matrix] = new PdfLiteral(item);
|
||
Elements.SetMatrix(Keys.Matrix, matrix);
|
||
}
|
||
|
||
// Preserve filter because the content keeps unmodified.
|
||
PdfContent content = importPage.Contents.CreateSingleContent();
|
||
#if !DEBUG
|
||
content.Compressed = true;
|
||
#endif
|
||
PdfItem filter = content.Elements["/Filter"];
|
||
if (filter != null)
|
||
Elements["/Filter"] = filter.Clone();
|
||
|
||
// (no cloning needed because the bytes keep untouched)
|
||
Stream = content.Stream; // new PdfStream(bytes, this);
|
||
Elements.SetInteger("/Length", content.Stream.Value.Length);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the PdfResources object of this form.
|
||
/// </summary>
|
||
public PdfResources Resources
|
||
{
|
||
get
|
||
{
|
||
if (_resources == null)
|
||
_resources = (PdfResources)Elements.GetValue(Keys.Resources, VCF.Create);
|
||
return _resources;
|
||
}
|
||
}
|
||
PdfResources _resources;
|
||
|
||
PdfResources IContentStream.Resources
|
||
{
|
||
get { return Resources; }
|
||
}
|
||
|
||
internal string GetFontName(XFont font, out PdfFont pdfFont)
|
||
{
|
||
pdfFont = _document.FontTable.GetFont(font);
|
||
Debug.Assert(pdfFont != null);
|
||
string name = Resources.AddFont(pdfFont);
|
||
return name;
|
||
}
|
||
|
||
string IContentStream.GetFontName(XFont font, out PdfFont pdfFont)
|
||
{
|
||
return GetFontName(font, out pdfFont);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the resource name of the specified font data within this form XObject.
|
||
/// </summary>
|
||
internal string GetFontName(string idName, byte[] fontData, out PdfFont pdfFont)
|
||
{
|
||
pdfFont = _document.FontTable.GetFont(idName, fontData);
|
||
Debug.Assert(pdfFont != null);
|
||
string name = Resources.AddFont(pdfFont);
|
||
return name;
|
||
}
|
||
|
||
string IContentStream.GetFontName(string idName, byte[] fontData, out PdfFont pdfFont)
|
||
{
|
||
return GetFontName(idName, fontData, out pdfFont);
|
||
}
|
||
|
||
string IContentStream.GetImageName(XImage image)
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
string IContentStream.GetFormName(XForm form)
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
#if keep_code_some_time_as_reference
|
||
/// <summary>
|
||
/// Replace all indirect references to external objects by their cloned counterparts
|
||
/// owned by the importer document.
|
||
/// </summary>
|
||
void FixUpObject_old(PdfImportedObjectTable iot, PdfObject value)
|
||
{
|
||
// TODO: merge with PdfXObject.FixUpObject
|
||
PdfDictionary dict;
|
||
PdfArray array;
|
||
if ((dict = value as PdfDictionary) != null)
|
||
{
|
||
// Set document for cloned direct objects
|
||
if (dict.Owner == null)
|
||
dict.Document = Owner;
|
||
else
|
||
Debug.Assert(dict.Owner == Owner);
|
||
|
||
// Search for indirect references in all keys
|
||
PdfName[] names = dict.Elements.KeyNames;
|
||
foreach (PdfName name in names)
|
||
{
|
||
PdfItem item = dict.Elements[name];
|
||
// Is item an iref?
|
||
PdfReference iref = item as PdfReference;
|
||
if (iref != null)
|
||
{
|
||
// Does the iref already belong to this document?
|
||
if (iref.Document == Owner)
|
||
{
|
||
// Yes: fine
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
Debug.Assert(iref.Document == iot.ExternalDocument);
|
||
// No: replace with iref of cloned object
|
||
PdfReference newXRef = iot[iref.ObjectID];
|
||
Debug.Assert(newXRef != null);
|
||
Debug.Assert(newXRef.Document == Owner);
|
||
dict.Elements[name] = newXRef;
|
||
}
|
||
}
|
||
else if (item is PdfObject)
|
||
{
|
||
// Fix up inner objects
|
||
FixUpObject_old(iot, (PdfObject)item);
|
||
}
|
||
}
|
||
}
|
||
else if ((array = value as PdfArray) != null)
|
||
{
|
||
// Set document for cloned direct objects
|
||
if (array.Owner == null)
|
||
array.Document = Owner;
|
||
else
|
||
Debug.Assert(array.Owner == Owner);
|
||
|
||
// Search for indirect references in all array elements
|
||
int count = array.Elements.Count;
|
||
for (int idx = 0; idx < count; idx++)
|
||
{
|
||
PdfItem item = array.Elements[idx];
|
||
// Is item an iref?
|
||
PdfReference iref = item as PdfReference;
|
||
if (iref != null)
|
||
{
|
||
// Does the iref belongs to this document?
|
||
if (iref.Document == Owner)
|
||
{
|
||
// Yes: fine
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
Debug.Assert(iref.Document == iot.ExternalDocument);
|
||
// No: replace with iref of cloned object
|
||
PdfReference newXRef = iot[iref.ObjectID];
|
||
Debug.Assert(newXRef != null);
|
||
Debug.Assert(newXRef.Document == Owner);
|
||
array.Elements[idx] = newXRef;
|
||
}
|
||
}
|
||
else if (item is PdfObject)
|
||
{
|
||
// Fix up inner objects
|
||
FixUpObject_old(iot, (PdfObject)item);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
// /// <summary>
|
||
// /// Returns ???
|
||
// /// </summary>
|
||
// public override string ToString()
|
||
// {
|
||
// return "Form";
|
||
// }
|
||
|
||
/// <summary>
|
||
/// Predefined keys of this dictionary.
|
||
/// </summary>
|
||
public sealed new class Keys : PdfXObject.Keys
|
||
{
|
||
/// <summary>
|
||
/// (Optional) The type of PDF object that this dictionary describes; if present,
|
||
/// must be XObject for a form XObject.
|
||
/// </summary>
|
||
[KeyInfo(KeyType.Name | KeyType.Optional)]
|
||
public const string Type = "/Type";
|
||
|
||
/// <summary>
|
||
/// (Required) The type of XObject that this dictionary describes; must be Form
|
||
/// for a form XObject.
|
||
/// </summary>
|
||
[KeyInfo(KeyType.Name | KeyType.Required)]
|
||
public const string Subtype = "/Subtype";
|
||
|
||
/// <summary>
|
||
/// (Optional) A code identifying the type of form XObject that this dictionary
|
||
/// describes. The only valid value defined at the time of publication is 1.
|
||
/// Default value: 1.
|
||
/// </summary>
|
||
[KeyInfo(KeyType.Integer | KeyType.Optional)]
|
||
public const string FormType = "/FormType";
|
||
|
||
/// <summary>
|
||
/// (Required) An array of four numbers in the form coordinate system, giving the
|
||
/// coordinates of the left, bottom, right, and top edges, respectively, of the
|
||
/// form XObject<63>s bounding box. These boundaries are used to clip the form XObject
|
||
/// and to determine its size for caching.
|
||
/// </summary>
|
||
[KeyInfo(KeyType.Rectangle | KeyType.Required)]
|
||
public const string BBox = "/BBox";
|
||
|
||
/// <summary>
|
||
/// (Optional) An array of six numbers specifying the form matrix, which maps
|
||
/// form space into user space.
|
||
/// Default value: the identity matrix [1 0 0 1 0 0].
|
||
/// </summary>
|
||
[KeyInfo(KeyType.Array | KeyType.Optional)]
|
||
public const string Matrix = "/Matrix";
|
||
|
||
/// <summary>
|
||
/// (Optional but strongly recommended; PDF 1.2) A dictionary specifying any
|
||
/// resources (such as fonts and images) required by the form XObject.
|
||
/// </summary>
|
||
[KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(PdfResources))]
|
||
public const string Resources = "/Resources";
|
||
|
||
/// <summary>
|
||
/// (Optional; PDF 1.4) A group attributes dictionary indicating that the contents
|
||
/// of the form XObject are to be treated as a group and specifying the attributes
|
||
/// of that group (see Section 4.9.2, <20>Group XObjects<74>).
|
||
/// Note: If a Ref entry (see below) is present, the group attributes also apply to the
|
||
/// external page imported by that entry, which allows such an imported page to be
|
||
/// treated as a group without further modification.
|
||
/// </summary>
|
||
[KeyInfo(KeyType.Dictionary | KeyType.Optional)]
|
||
public const string Group = "/Group";
|
||
|
||
// further keys:
|
||
//Ref
|
||
//Metadata
|
||
//PieceInfo
|
||
//LastModified
|
||
//StructParent
|
||
//StructParents
|
||
//OPI
|
||
//OC
|
||
//Name
|
||
|
||
/// <summary>
|
||
/// Gets the KeysMeta for these keys.
|
||
/// </summary>
|
||
internal static DictionaryMeta Meta
|
||
{
|
||
get { return _meta ?? (_meta = CreateMeta(typeof(Keys))); }
|
||
}
|
||
static DictionaryMeta _meta;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the KeysMeta of this dictionary type.
|
||
/// </summary>
|
||
internal override DictionaryMeta Meta
|
||
{
|
||
get { return Keys.Meta; }
|
||
}
|
||
}
|
||
}
|