git-svn-id: https://pdfsharp.svn.codeplex.com/svn@39620 56d0cb2f-6006-4f69-a5a2-94740...
[pdfsharp:pdfsharp.git] / PDFsharp / code / PdfSharp / PdfSharp.Pdf / PdfPages.cs
1 #region PDFsharp - A .NET library for processing PDF\r
2 //\r
3 // Authors:\r
4 //   Stefan Lange (mailto:Stefan.Lange@pdfsharp.com)\r
5 //\r
6 // Copyright (c) 2005-2009 empira Software GmbH, Cologne (Germany)\r
7 //\r
8 // http://www.pdfsharp.com\r
9 // http://sourceforge.net/projects/pdfsharp\r
10 //\r
11 // Permission is hereby granted, free of charge, to any person obtaining a\r
12 // copy of this software and associated documentation files (the "Software"),\r
13 // to deal in the Software without restriction, including without limitation\r
14 // the rights to use, copy, modify, merge, publish, distribute, sublicense,\r
15 // and/or sell copies of the Software, and to permit persons to whom the\r
16 // Software is furnished to do so, subject to the following conditions:\r
17 //\r
18 // The above copyright notice and this permission notice shall be included\r
19 // in all copies or substantial portions of the Software.\r
20 //\r
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\r
24 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
26 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER \r
27 // DEALINGS IN THE SOFTWARE.\r
28 #endregion\r
29 \r
30 using System;\r
31 using System.Collections.Generic;\r
32 using System.Diagnostics;\r
33 using System.Collections;\r
34 using System.Text;\r
35 using System.IO;\r
36 using PdfSharp.Internal;\r
37 using PdfSharp.Pdf.IO;\r
38 using PdfSharp.Pdf;\r
39 using PdfSharp.Pdf.Advanced;\r
40 using PdfSharp.Pdf.Annotations;\r
41 \r
42 namespace PdfSharp.Pdf\r
43 {\r
44   /// <summary>\r
45   /// Represents the pages of the document.\r
46   /// </summary>\r
47   [DebuggerDisplay("(PageCount={Count})")]\r
48   public sealed class PdfPages : PdfDictionary, IEnumerable\r
49   {\r
50     internal PdfPages(PdfDocument document)\r
51       : base(document)\r
52     {\r
53       Elements.SetName(Keys.Type, "/Pages");\r
54       Elements[Keys.Count] = new PdfInteger(0);\r
55     }\r
56 \r
57     internal PdfPages(PdfDictionary dictionary)\r
58       : base(dictionary)\r
59     {\r
60     }\r
61 \r
62     /// <summary>\r
63     /// Gets the number of pages.\r
64     /// </summary>\r
65     public int Count\r
66     {\r
67       get { return PagesArray.Elements.Count; }\r
68     }\r
69 \r
70     /// <summary>\r
71     /// Gets the page with the specified index.\r
72     /// </summary>\r
73     public PdfPage this[int index]\r
74     {\r
75       get\r
76       {\r
77         if (index < 0 || index >= Count)\r
78           throw new ArgumentOutOfRangeException("index", index, PSSR.PageIndexOutOfRange);\r
79 \r
80         PdfDictionary dict = (PdfDictionary)((PdfReference)PagesArray.Elements[index]).Value;\r
81         if (!(dict is PdfPage))\r
82           dict = new PdfPage(dict);\r
83         return (PdfPage)dict;\r
84       }\r
85       //set\r
86       //{\r
87       //  if (index < 0 || index >= this.pages.Count)\r
88       //    throw new ArgumentOutOfRangeException("index", index, PSSR.PageIndexOutOfRange);\r
89       //  this.pages[index] = value;\r
90       //}\r
91     }\r
92 \r
93     /// <summary>\r
94     /// Creates a new PdfPage, adds it to this document, and returns it.\r
95     /// </summary>\r
96     public PdfPage Add()\r
97     {\r
98       PdfPage page = new PdfPage();\r
99       Insert(Count, page);\r
100       return page;\r
101     }\r
102 \r
103     /// <summary>\r
104     /// Adds the specified PdfPage to this document and maybe returns a new PdfPage object.\r
105     /// The value returned is a new object if the added page comes from a foreign document.\r
106     /// </summary>\r
107     public PdfPage Add(PdfPage page)\r
108     {\r
109       return Insert(Count, page);\r
110     }\r
111 \r
112     /// <summary>\r
113     /// Creates a new PdfPage, inserts it at the specified position into this document, and returns it.\r
114     /// </summary>\r
115     public PdfPage Insert(int index)\r
116     {\r
117       PdfPage page = new PdfPage();\r
118       Insert(index, page);\r
119       return page;\r
120     }\r
121 \r
122     /// <summary>\r
123     /// Inserts the specified PdfPage at the specified position to this document and maybe returns a new PdfPage object.\r
124     /// The value returned is a new object if the inserted page comes from a foreign document.\r
125     /// </summary>\r
126     public PdfPage Insert(int index, PdfPage page)\r
127     {\r
128       if (page == null)\r
129         throw new ArgumentNullException("page");\r
130 \r
131       // Is the page already owned by this document\r
132       if (page.Owner == Owner)\r
133       {\r
134         // Case: Page is removed and than inserted a another position.\r
135         int count = Count;\r
136         for (int idx = 0; idx < count; idx++)\r
137         {\r
138           if (ReferenceEquals(this[idx], page))\r
139             throw new InvalidOperationException(PSSR.MultiplePageInsert);\r
140         }\r
141         // TODO: check this case\r
142         Owner.irefTable.Add(page);\r
143         PagesArray.Elements.Insert(index, page.Reference);\r
144         Elements.SetInteger(PdfPages.Keys.Count, PagesArray.Elements.Count);\r
145         return page;\r
146       }\r
147 \r
148       // All page insertions come here\r
149       if (page.Owner == null)\r
150       {\r
151         // Case: New page was created and inserted now.\r
152         page.Document = Owner;\r
153 \r
154         Owner.irefTable.Add(page);\r
155         PagesArray.Elements.Insert(index, page.Reference);\r
156         Elements.SetInteger(PdfPages.Keys.Count, PagesArray.Elements.Count);\r
157       }\r
158       else\r
159       {\r
160         // Case: Page is from an external document -> import it.\r
161         page = ImportExternalPage(page);\r
162         Owner.irefTable.Add(page);\r
163         PagesArray.Elements.Insert(index, page.Reference);\r
164         Elements.SetInteger(PdfPages.Keys.Count, PagesArray.Elements.Count);\r
165         PdfAnnotations.FixImportedAnnotation(page);\r
166       }\r
167       if (Owner.Settings.TrimMargins.AreSet)\r
168         page.TrimMargins = Owner.Settings.TrimMargins;\r
169       return page;\r
170     }\r
171 \r
172     /// <summary>\r
173     /// Removes the specified page from the document.\r
174     /// </summary>\r
175     public void Remove(PdfPage page)\r
176     {\r
177       PagesArray.Elements.Remove(page.Reference);\r
178       Elements.SetInteger(PdfPages.Keys.Count, PagesArray.Elements.Count);\r
179     }\r
180 \r
181     /// <summary>\r
182     /// Removes the specified page from the document.\r
183     /// </summary>\r
184     public void RemoveAt(int index)\r
185     {\r
186       PagesArray.Elements.RemoveAt(index);\r
187       Elements.SetInteger(PdfPages.Keys.Count, PagesArray.Elements.Count);\r
188     }\r
189 \r
190     /// <summary>\r
191     /// Moves a page within the page sequence.\r
192     /// </summary>\r
193     /// <param name="oldIndex">The page index before this operation.</param>\r
194     /// <param name="newIndex">The page index after this operation.</param>\r
195     public void MovePage(int oldIndex, int newIndex)\r
196     {\r
197       if (oldIndex < 0 || oldIndex >= Count)\r
198         throw new ArgumentOutOfRangeException("oldIndex");\r
199       if (newIndex < 0 || newIndex >= Count)\r
200         throw new ArgumentOutOfRangeException("newIndex");\r
201       if (oldIndex == newIndex)\r
202         return;\r
203 \r
204       //PdfPage page = (PdfPage)pagesArray.Elements[oldIndex];\r
205       PdfReference page = (PdfReference)pagesArray.Elements[oldIndex];\r
206       pagesArray.Elements.RemoveAt(oldIndex);\r
207       pagesArray.Elements.Insert(newIndex, page);\r
208     }\r
209 \r
210     /// <summary>\r
211     /// Imports an external page. The elements of the imported page are cloned and added to this document.\r
212     /// Important: In contrast to PdfFormXObject adding an external page always make a deep copy\r
213     /// of their transitive closure. Any reuse of already imported objects is not intended because\r
214     /// any modification of an imported page must not change another page.\r
215     /// </summary>\r
216     PdfPage ImportExternalPage(PdfPage importPage)\r
217     {\r
218       if (importPage.Owner.openMode != PdfDocumentOpenMode.Import)\r
219         throw new InvalidOperationException("A PDF document must be opened with PdfDocumentOpenMode.Import to import pages from it.");\r
220 \r
221       PdfPage page = new PdfPage(this.document);\r
222 \r
223       CloneElement(page, importPage, PdfPage.Keys.Resources, false);\r
224       CloneElement(page, importPage, PdfPage.Keys.Contents, false);\r
225       CloneElement(page, importPage, PdfPage.Keys.MediaBox, true);\r
226       CloneElement(page, importPage, PdfPage.Keys.CropBox, true);\r
227       CloneElement(page, importPage, PdfPage.Keys.Rotate, true);\r
228       CloneElement(page, importPage, PdfPage.Keys.BleedBox, true);\r
229       CloneElement(page, importPage, PdfPage.Keys.TrimBox, true);\r
230       CloneElement(page, importPage, PdfPage.Keys.ArtBox, true);\r
231 #if true\r
232       // Do not deep copy annotations.\r
233       CloneElement(page, importPage, PdfPage.Keys.Annots, false);\r
234 #else\r
235       // Deep copy annotations.\r
236       CloneElement(page, importPage, PdfPage.Keys.Annots, true);\r
237 #endif\r
238       // TODO more elements?\r
239       return page;\r
240     }\r
241 \r
242     /// <summary>\r
243     /// Helper function for ImportExternalPage.\r
244     /// </summary>\r
245     void CloneElement(PdfPage page, PdfPage importPage, string key, bool deepcopy)\r
246     {\r
247       Debug.Assert(page.Owner == this.document);\r
248       Debug.Assert(importPage.Owner != null);\r
249       Debug.Assert(importPage.Owner != this.document);\r
250 \r
251       PdfItem item = importPage.Elements[key];\r
252       if (item != null)\r
253       {\r
254         PdfImportedObjectTable importedObjectTable = null;\r
255         if (!deepcopy)\r
256           importedObjectTable = Owner.FormTable.GetImportedObjectTable(importPage);\r
257 \r
258         // The item can be indirect. If so, replace it by its value.\r
259         if (item is PdfReference)\r
260           item = ((PdfReference)item).Value;\r
261         if (item is PdfObject)\r
262         {\r
263           PdfObject root = (PdfObject)item;\r
264           if (deepcopy)\r
265           {\r
266             Debug.Assert(root.Owner != null, "See 'else' case for details");\r
267             root = PdfObject.DeepCopyClosure(this.document, root);\r
268           }\r
269           else\r
270           {\r
271             // The owner can be null if the item is not a reference\r
272             if (root.Owner == null)\r
273               root.Document = importPage.Owner;\r
274             root = PdfObject.ImportClosure(importedObjectTable, page.Owner, root);\r
275           }\r
276 \r
277           if (root.Reference == null)\r
278             page.Elements[key] = root;\r
279           else\r
280             page.Elements[key] = root.Reference;\r
281         }\r
282         else\r
283         {\r
284           // Simple items are just cloned.\r
285           page.Elements[key] = item.Clone();\r
286         }\r
287       }\r
288     }\r
289 \r
290     /// <summary>\r
291     /// Gets a PdfArray containing all pages of this document. The array must not be modified.\r
292     /// </summary>\r
293     public PdfArray PagesArray\r
294     {\r
295       get\r
296       {\r
297         if (this.pagesArray == null)\r
298           this.pagesArray = (PdfArray)Elements.GetValue(Keys.Kids, VCF.Create);\r
299         return this.pagesArray;\r
300       }\r
301     }\r
302     PdfArray pagesArray;\r
303 \r
304     /// <summary>\r
305     /// Replaces the page tree by a flat array of indirect references to the pages objects.\r
306     /// </summary>\r
307     internal void FlattenPageTree()\r
308     {\r
309       // Acrobat creates a balanced tree if the number of pages is rougly more than ten. This is\r
310       // not difficult but obviously also not necessary. I created a document with 50000 pages with\r
311       // PDF4NET and Acrobat opened it in less than 2 seconds.\r
312 \r
313       //PdfReference xrefRoot = this.Document.Catalog.Elements[PdfCatalog.Keys.Pages] as PdfReference;\r
314       //PdfDictionary[] pages = GetKids(xrefRoot, null);\r
315 \r
316       // Promote inheritable values down the page tree\r
317       PdfPage.InheritedValues values = new PdfPage.InheritedValues();\r
318       PdfPage.InheritValues(this, ref values);\r
319       PdfDictionary[] pages = GetKids(Reference, values, null);\r
320 \r
321       // Replace /Pages in catalog by this object\r
322       // xrefRoot.Value = this;\r
323 \r
324       PdfArray array = new PdfArray(Owner);\r
325       foreach (PdfDictionary page in pages)\r
326       {\r
327         // Fix the parent\r
328         page.Elements[PdfPage.Keys.Parent] = Reference;\r
329         array.Elements.Add(page.Reference);\r
330       }\r
331 \r
332       Elements.SetName(Keys.Type, "/Pages");\r
333 #if true\r
334       // direct array\r
335       Elements.SetValue(Keys.Kids, array);\r
336 #else\r
337       // incdirect array\r
338       this.Document.xrefTable.Add(array);\r
339       Elements.SetValue(Keys.Kids, array.XRef);\r
340 #endif\r
341       Elements.SetInteger(Keys.Count, array.Elements.Count);\r
342     }\r
343 \r
344     /// <summary>\r
345     /// Recursively converts the page tree into a flat array.\r
346     /// </summary>\r
347     PdfDictionary[] GetKids(PdfReference iref, PdfPage.InheritedValues values, PdfDictionary parent)\r
348     {\r
349       // TODO: inherit inheritable keys...\r
350       PdfDictionary kid = (PdfDictionary)iref.Value;\r
351 \r
352       if (kid.Elements.GetName(Keys.Type) == "/Page")\r
353       {\r
354         PdfPage.InheritValues(kid, values);\r
355         return new PdfDictionary[] { kid };\r
356       }\r
357       else\r
358       {\r
359         Debug.Assert(kid.Elements.GetName(Keys.Type) == "/Pages");\r
360         PdfPage.InheritValues(kid, ref values);\r
361         List<PdfDictionary> list = new List<PdfDictionary>();\r
362         PdfArray kids = kid.Elements["/Kids"] as PdfArray;\r
363         //newTHHO 15.10.2007 begin\r
364         if (kids == null)\r
365         {\r
366           PdfReference xref3 = kid.Elements["/Kids"] as PdfReference;\r
367           kids = xref3.Value as PdfArray;\r
368         }\r
369         //newTHHO 15.10.2007 end\r
370         foreach (PdfReference xref2 in kids)\r
371           list.AddRange(GetKids(xref2, values, kid));\r
372         int count = list.Count;\r
373         Debug.Assert(count == kid.Elements.GetInteger("/Count"));\r
374         //return (PdfDictionary[])list.ToArray(typeof(PdfDictionary));\r
375         return list.ToArray();\r
376       }\r
377     }\r
378 \r
379     /// <summary>\r
380     /// Prepares the document for saving.\r
381     /// </summary>\r
382     internal override void PrepareForSave()\r
383     {\r
384       // TODO: Close all open content streams\r
385 \r
386       // TODO: Create the page tree.\r
387       // Arrays have a limit of 8192 entries, but I successfully tested documents\r
388       // with 50000 pages and no page tree.\r
389       // ==> wait for bug report.\r
390       int count = this.pagesArray.Elements.Count;\r
391       for (int idx = 0; idx < count; idx++)\r
392       {\r
393 \r
394         PdfPage page = this[idx];\r
395         page.PrepareForSave();\r
396       }\r
397     }\r
398 \r
399     /// <summary>\r
400     /// Gets the enumerator.\r
401     /// </summary>\r
402     public new IEnumerator GetEnumerator()\r
403     {\r
404       return new PdfPagesEnumerator(this);\r
405     }\r
406 \r
407     private class PdfPagesEnumerator : IEnumerator\r
408     {\r
409       private PdfPage currentElement;\r
410       private int index;\r
411       private PdfPages list;\r
412 \r
413       internal PdfPagesEnumerator(PdfPages list)\r
414       {\r
415         this.list = list;\r
416         this.index = -1;\r
417       }\r
418 \r
419       public bool MoveNext()\r
420       {\r
421         if (this.index < this.list.Count - 1)\r
422         {\r
423           this.index++;\r
424           this.currentElement = this.list[this.index];\r
425           return true;\r
426         }\r
427         this.index = this.list.Count;\r
428         return false;\r
429       }\r
430 \r
431       public void Reset()\r
432       {\r
433         this.currentElement = null;\r
434         this.index = -1;\r
435       }\r
436 \r
437       object IEnumerator.Current\r
438       {\r
439         get { return Current; }\r
440       }\r
441 \r
442       public PdfPage Current\r
443       {\r
444         get\r
445         {\r
446           if (this.index == -1 || this.index >= this.list.Count)\r
447             throw new InvalidOperationException(PSSR.ListEnumCurrentOutOfRange);\r
448           return this.currentElement;\r
449         }\r
450       }\r
451     }\r
452 \r
453     /// <summary>\r
454     /// Predefined keys of this dictionary.\r
455     /// </summary>\r
456     internal sealed class Keys : PdfPage.InheritablePageKeys\r
457     {\r
458       /// <summary>\r
459       /// (Required) The type of PDF object that this dictionary describes; \r
460       /// must be Pages for a page tree node.\r
461       /// </summary>\r
462       [KeyInfo(KeyType.Name | KeyType.Required, FixedValue = "Pages")]\r
463       public const string Type = "/Type";\r
464 \r
465       /// <summary>\r
466       /// (Required except in root node; must be an indirect reference)\r
467       /// The page tree node that is the immediate parent of this one.\r
468       /// </summary>\r
469       [KeyInfo(KeyType.Dictionary | KeyType.Required)]\r
470       public const string Parent = "/Parent";\r
471 \r
472       /// <summary>\r
473       /// (Required) An array of indirect references to the immediate children of this node.\r
474       /// The children may be page objects or other page tree nodes.\r
475       /// </summary>\r
476       [KeyInfo(KeyType.Array | KeyType.Required)]\r
477       public const string Kids = "/Kids";\r
478 \r
479       /// <summary>\r
480       /// (Required) The number of leaf nodes (page objects) that are descendants of this node \r
481       /// within the page tree.\r
482       /// </summary>\r
483       [KeyInfo(KeyType.Integer | KeyType.Required)]\r
484       public const string Count = "/Count";\r
485 \r
486       /// <summary>\r
487       /// Gets the KeysMeta for these keys.\r
488       /// </summary>\r
489       public static DictionaryMeta Meta\r
490       {\r
491         get\r
492         {\r
493           if (Keys.meta == null)\r
494             Keys.meta = CreateMeta(typeof(Keys));\r
495           return Keys.meta;\r
496         }\r
497       }\r
498       static DictionaryMeta meta;\r
499     }\r
500 \r
501     /// <summary>\r
502     /// Gets the KeysMeta of this dictionary type.\r
503     /// </summary>\r
504     internal override DictionaryMeta Meta\r
505     {\r
506       get { return Keys.Meta; }\r
507     }\r
508   }\r
509 }\r