Add copyright headers to all .cs files
[pdfmod:pdfmod.git] / src / PdfMod / Pdf / Document.cs
1 // Copyright (C) 2009 Novell, Inc.
2 // Copyright (C) 2009 Julien Rebetez
3 // Copyright (C) 2009 Michael McKinley
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18
19 using System;
20 using System.Linq;
21 using System.Collections.Generic;
22
23 using Mono.Unix;
24
25 using Hyena;
26 using PdfSharp.Pdf;
27 using PdfSharp.Pdf.IO;
28
29 namespace PdfMod.Pdf
30 {
31     public class Document : IDisposable
32     {
33         private PdfDocument pdf_document;
34         private List<Page> pages = new List<Page> ();
35         private string tmp_path;
36         private string tmp_uri;
37         private PageLabels page_labels;
38         internal string CurrentStateUri { get { return tmp_uri ?? Uri; } }
39
40         public PageLabels Labels { get { return page_labels; } }
41         public string SuggestedSavePath { get; set; }
42         public string Uri { get; private set; }
43         public string Path { get; private set; }
44         public PdfDocument Pdf { get { return pdf_document; } }
45         public int Count { get { return pages.Count; } }
46
47         public IEnumerable<Page> Pages {
48             get {
49                 foreach (var page in pages)
50                     yield return page;
51             }
52         }
53
54         public Page this [int index] {
55             get { return pages[index]; }
56         }
57
58         public string Title {
59             get {
60                 var title = Pdf.Info.Title;
61                 return String.IsNullOrEmpty (title) ? null : title;
62             }
63             set {
64                 if (value.Trim () == "")
65                     value = null;
66
67                 if (value == Title)
68                     return;
69
70                 Pdf.Info.Title = value;
71                 StartSaveTempTimeout ();
72             }
73         }
74
75         public string Author {
76             get { return Pdf.Info.Author; }
77             set {
78                 if (value.Trim () == "")
79                     value = null;
80
81                 if (value == Author)
82                     return;
83
84                 Pdf.Info.Author = value;
85                 StartSaveTempTimeout ();
86             }
87         }
88
89         public string Keywords {
90             get { return Pdf.Info.Keywords; }
91             set {
92                 if (value.Trim () == "")
93                     value = null;
94
95                 if (value == Keywords)
96                     return;
97
98                 Pdf.Info.Keywords = value;
99                 StartSaveTempTimeout ();
100             }
101         }
102
103         public string Subject {
104             get { return Pdf.Info.Subject; }
105             set {
106                 if (value.Trim () == "")
107                     value = null;
108
109                 if (value == Subject)
110                     return;
111
112                 Pdf.Info.Subject = value;
113                 StartSaveTempTimeout ();
114             }
115         }
116
117         public string Filename {
118             get { return System.IO.Path.GetFileName (SuggestedSavePath); }
119         }
120
121         public string Password { get; set; }
122
123         public event System.Action Changed;
124         public event System.Action PagesMoved;
125         public event Action<Page []> PagesRemoved;
126         public event Action<int, Page []> PagesAdded;
127         public event Action<Page []> PagesChanged;
128
129         public Document ()
130         {
131         }
132
133         public void Load (string uri, PdfPasswordProvider passwordProvider, bool isAlreadyTmp)
134         {
135             if (isAlreadyTmp) {
136                 tmp_uri = new Uri (uri).AbsoluteUri;
137                 tmp_path = new Uri (uri).LocalPath;
138             }
139
140             var uri_obj = new Uri (uri);
141             Uri = uri_obj.AbsoluteUri;
142             SuggestedSavePath = Path = uri_obj.LocalPath;
143
144             pdf_document = PdfSharp.Pdf.IO.PdfReader.Open (Path, PdfDocumentOpenMode.Modify | PdfDocumentOpenMode.Import, passwordProvider);
145             for (int i = 0; i < pdf_document.PageCount; i++) {
146                 var page = new Page (pdf_document.Pages[i]) {
147                     Document = this,
148                     Index = i
149                 };
150                 pages.Add (page);
151             }
152
153             page_labels = new PageLabels (pdf_document);
154             ExpireThumbnails (pages);
155             OnChanged ();
156         }
157
158         public bool HasUnsavedChanged {
159             get { return tmp_uri != null || save_timeout_id != 0; }
160         }
161
162         public long FileSize {
163             get { return new System.IO.FileInfo (tmp_path ?? Path).Length; }
164         }
165
166         public void Dispose ()
167         {
168             if (pdf_document != null) {
169                 pdf_document.Dispose ();
170                 pdf_document = null;
171             }
172
173             if (tmp_path != null) {
174                 System.IO.File.Delete (tmp_path);
175                 tmp_path = tmp_uri = null;
176             }
177         }
178
179         public IEnumerable<Page> FindPagesMatching (string text)
180         {
181             using (var doc = Poppler.Document.NewFromFile (tmp_uri ?? Uri, Password ?? "")) {
182                 for (int i = 0; i < doc.NPages; i++) {
183                     using (var page = doc.GetPage (i)) {
184                         var list = page.FindText (text);
185                         if (list != null && list.Count > 0) {
186                             yield return pages[i];
187                             list.Dispose ();
188                         }
189                     }
190                 }
191             }
192         }
193
194         public void Move (int to_index, Page [] move_pages, int [] new_indexes)
195         {
196             // Remove all the pages
197             foreach (var page in move_pages) {
198                 pages.Remove (page);
199             }
200
201             if (new_indexes == null) {
202                 new_indexes = move_pages.Select (p => to_index++).ToArray ();
203             }
204
205             // Add back at the right index
206             for (int i = 0; i < move_pages.Length; i++) {
207                 pages.Insert (new_indexes[i], move_pages[i]);
208             }
209
210             // Do the actual move in the document
211             foreach (var page in move_pages) {
212                 pdf_document.Pages.MovePage (page.Index, IndexOf (page));
213             }
214
215             Reindex ();
216
217             SaveTemp ();
218
219             var handler = PagesMoved;
220             if (handler != null) {
221                 handler ();
222             }
223         }
224
225         public int IndexOf (Page page)
226         {
227             return pages.IndexOf (page);
228         }
229
230         public void Remove (params Page [] remove_pages)
231         {
232             foreach (var page in remove_pages) {
233                 pdf_document.Pages.Remove (page.Pdf);
234                 pages.Remove (page);
235             }
236
237             Reindex ();
238             SaveTemp ();
239
240             var handler = PagesRemoved;
241             if (handler != null) {
242                 handler (remove_pages);
243             }
244         }
245
246         public void Rotate (Page [] rotate_pages, int rotate_by)
247         {
248             foreach (var page in rotate_pages) {
249                 page.Pdf.Rotate += rotate_by;
250             }
251
252             OnPagesChanged (rotate_pages);
253         }
254
255         public void Save (string uri)
256         {
257             Pdf.Save (uri);
258             Log.DebugFormat ("Saved to {0}", uri);
259             Uri = uri;
260
261             if (tmp_uri != null) {
262                 try {
263                     System.IO.File.Delete (tmp_path);
264                 } catch (Exception e) {
265                     Log.Exception ("Couldn't delete tmp file after saving", e);
266                 } finally {
267                     tmp_uri = tmp_path = null;
268                 }
269             }
270         }
271
272         public void AddFromUri (Uri uri)
273         {
274             AddFromUri (uri, 0);
275         }
276
277         public void AddFromUri (Uri uri, int to_index)
278         {
279             AddFromUri (uri, to_index, null);
280         }
281
282         public void AddFromUri (Uri uri, int to_index, int [] pages_to_import)
283         {
284             Log.DebugFormat ("Inserting pages from {0} to index {1}", uri, to_index);
285             using (var doc = PdfSharp.Pdf.IO.PdfReader.Open (uri.LocalPath, null, PdfSharp.Pdf.IO.PdfDocumentOpenMode.Import)) {
286                 var pages = new List<Page> ();
287                 for (int i = 0; i < doc.PageCount; i++) {
288                     if (pages_to_import == null || pages_to_import.Contains (i)) {
289                         pages.Add (new Page (doc.Pages [i]));
290                     }
291                 }
292                 Add (to_index, pages.ToArray ());
293                 to_index += pages.Count;
294             }
295         }
296
297         public void Add (int to_index, params Page [] add_pages)
298         {
299             int i = to_index;
300             foreach (var page in add_pages) {
301                 var pdf = pdf_document.Pages.Insert (i, page.Pdf);
302                 page.Pdf = pdf;
303                 page.Document = this;
304                 pages.Insert (i, page);
305                 i++;
306             }
307
308             Reindex ();
309             SaveTemp ();
310             ExpireThumbnails (add_pages);
311
312             var handler = PagesAdded;
313             if (handler != null) {
314                 handler (to_index, add_pages);
315             }
316         }
317
318         private Poppler.Document poppler_doc;
319         private Poppler.Document PopplerDoc {
320             get {
321                 if (poppler_doc == null) {
322                     poppler_doc = Poppler.Document.NewFromFile (tmp_uri ?? Uri, Password ?? "");
323                 }
324                 return poppler_doc;
325             }
326         }
327
328         private void ExpireThumbnails (IEnumerable<Page> update_pages)
329         {
330             if (poppler_doc != null) {
331                 poppler_doc.Dispose ();
332                 poppler_doc = null;
333             }
334
335             foreach (var page in update_pages) {
336                 page.SurfaceDirty = true;
337             }
338         }
339
340         public PageThumbnail GetSurface (Page page, int w, int h, int min_width)
341         {
342             if (w < min_width || h < min_width) {
343                 return null;
344             }
345
346             using (var ppage = PopplerDoc.GetPage (page.Index)) {
347                 double pw, ph;
348                 ppage.GetSize (out pw, out ph);
349                 double scale = Math.Min (w / pw, h / ph);
350
351                 var surface = new Cairo.ImageSurface (Cairo.Format.Argb32, (int)(scale * pw), (int)(scale * ph));
352                 var cr = new Cairo.Context (surface);
353                 cr.Scale (scale, scale);
354                 ppage.Render (cr);
355                 page.SurfaceDirty = false;
356                 return new PageThumbnail () { Surface = surface, Context = cr };
357             }
358         }
359
360         private void Reindex ()
361         {
362             for (int i = 0; i < pages.Count; i++) {
363                 pages[i].Index = i;
364             }
365         }
366
367         private uint save_timeout_id = 0;
368         private void StartSaveTempTimeout ()
369         {
370             if (save_timeout_id != 0) {
371                 GLib.Source.Remove (save_timeout_id);
372             }
373             
374             save_timeout_id = GLib.Timeout.Add (100, OnSaveTempTimeout);
375         }
376
377         private bool OnSaveTempTimeout ()
378         {
379             save_timeout_id = 0;
380             SaveTemp ();
381             return false;
382         }
383
384         private void SaveTemp ()
385         {
386             try {
387                 if (tmp_path == null) {
388                     tmp_path = Core.Client.GetTmpFilename ();
389                     if (System.IO.File.Exists (tmp_path)) {
390                         System.IO.File.Delete (tmp_path);
391                     }
392                     tmp_uri = new Uri (tmp_path).AbsoluteUri;
393                 }
394
395                 pdf_document.Save (tmp_path);
396                 Log.DebugFormat ("Saved tmp file to {0}", tmp_path);
397                 OnChanged ();
398             } catch (Exception e) {
399                 Log.Exception ("Failed to save tmp document", e);
400                 // TODO tell user, shutdown
401             }
402         }
403
404         private void OnPagesChanged (Page [] changed_pages)
405         {
406             Reindex ();
407             SaveTemp ();
408             ExpireThumbnails (changed_pages);
409
410             var handler = PagesChanged;
411             if (handler != null) {
412                 handler (changed_pages);
413             }
414             OnChanged ();
415         }
416
417         private void OnChanged ()
418         {
419             var handler = Changed;
420             if (handler != null) {
421                 handler ();
422             }
423         }
424
425         // Return a simple, nice string describing the selected pages
426         //   e.g.  Page 1, or Page 3 - 6, or Page 2, 4, 6
427         public static string GetPageSummary (List<Page> pages, int maxListed)
428         {
429             string pages_summary = null;
430             if (pages.Count == 1) {
431                 // Translators: {0} is the number of pages (always 1), and {1} is the page number, eg Page 1, or Page 5
432                 pages_summary = String.Format (Catalog.GetPluralString ("Page {1}", "Page {1}", pages.Count), pages.Count, pages[0].Index + 1);
433             } else if (pages[0].Index + pages.Count - 1 == pages[pages.Count - 1].Index) {
434                 // Translators: {0} is the number of pages, and {1} is the first page, {2} is the last page,
435                 // eg Pages 3 - 7
436                 pages_summary = String.Format (Catalog.GetPluralString ("Pages {1} - {2}", "Pages {1} - {2}", pages.Count),
437                     pages.Count, pages[0].Index + 1, pages[pages.Count - 1].Index + 1);
438             } else if (pages.Count < maxListed) {
439                 string page_nums = String.Join (", ", pages.Select (p => (p.Index + 1).ToString ()).ToArray ());
440                 // Translators: {0} is the number of pages, {1} is a comma separated list of page numbers, eg Pages 1, 4, 9
441                 pages_summary = String.Format (Catalog.GetPluralString ("Pages {1}", "Pages {1}", pages.Count), pages.Count, page_nums);
442             } else {
443                 // Translators: {0} is the number of pages, eg 12 Pages
444                 pages_summary = String.Format (Catalog.GetPluralString ("{0} Page", "{0} Pages", pages.Count), pages.Count);
445             }
446             return pages_summary;
447         }
448
449     }
450 }