[pdfmod] Show custom page labels (eg ii or A-10) if set



commit b97700ff60aa5930877d53fa83cc29f24e9cd69e
Author: Michael McKinley <m mckinley gmail com>
Date:   Fri Aug 7 11:03:57 2009 -0700

    Show custom page labels (eg ii or A-10) if set
    
    Signed-off-by: Gabriel Burt <gabriel burt gmail com>

 src/PdfMod/Makefile.am            |    1 +
 src/PdfMod/PdfMod.mdp             |    1 +
 src/PdfMod/PdfMod/Document.cs     |    3 +
 src/PdfMod/PdfMod/PageLabels.cs   |  274 +++++++++++++++++++++++++++++++++++++
 src/PdfMod/PdfMod/PdfListStore.cs |   17 ++-
 5 files changed, 289 insertions(+), 7 deletions(-)
---
diff --git a/src/PdfMod/Makefile.am b/src/PdfMod/Makefile.am
index be15cb2..0544d26 100644
--- a/src/PdfMod/Makefile.am
+++ b/src/PdfMod/Makefile.am
@@ -90,6 +90,7 @@ FILES =  \
 	PdfMod/GlobalActions.cs \
 	PdfMod/MetadataEditorBox.cs \
 	PdfMod/Page.cs \
+	PdfMod/PageLabels.cs \
 	PdfMod/PageThumbnail.cs \
 	PdfMod/PdfIconView.cs \
 	PdfMod/PdfListStore.cs \
diff --git a/src/PdfMod/PdfMod.mdp b/src/PdfMod/PdfMod.mdp
index b13736d..e020204 100644
--- a/src/PdfMod/PdfMod.mdp
+++ b/src/PdfMod/PdfMod.mdp
@@ -37,6 +37,7 @@
     <File name="PdfMod.Actions/MoveAction.cs" subtype="Code" buildaction="Compile" />
     <File name="PdfMod/MetadataEditorBox.cs" subtype="Code" buildaction="Compile" />
     <File name="PdfMod/PageThumbnail.cs" subtype="Code" buildaction="Compile" />
+    <File name="PdfMod/PageLabels.cs" subtype="Code" buildaction="Compile" />
   </Contents>
   <References>
     <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
diff --git a/src/PdfMod/PdfMod/Document.cs b/src/PdfMod/PdfMod/Document.cs
index 26343de..0d46740 100644
--- a/src/PdfMod/PdfMod/Document.cs
+++ b/src/PdfMod/PdfMod/Document.cs
@@ -16,8 +16,10 @@ namespace PdfMod
         private List<Page> pages = new List<Page> ();
         private string tmp_path;
         private string tmp_uri;
+        private PageLabels page_labels;
         internal string CurrentStateUri { get { return tmp_uri ?? Uri; } }
 
+        public PageLabels Labels { get { return page_labels; } }
         public string SuggestedSavePath { get; set; }
         public string Uri { get; private set; }
         public string Path { get; private set; }
@@ -130,6 +132,7 @@ namespace PdfMod
                 pages.Add (page);
             }
 
+            page_labels = new PageLabels (pdf_document);
             ExpireThumbnails (pages);
             OnChanged ();
         }
diff --git a/src/PdfMod/PdfMod/PageLabels.cs b/src/PdfMod/PdfMod/PageLabels.cs
new file mode 100644
index 0000000..8a3f492
--- /dev/null
+++ b/src/PdfMod/PdfMod/PageLabels.cs
@@ -0,0 +1,274 @@
+//Author: Michael McKinley (m mckinley gmail com)
+
+using System;
+using System.Linq;
+using System.Text;
+using System.Collections.Generic;
+
+using PdfSharp;
+using PdfSharp.Pdf;
+using PdfSharp.Pdf.Advanced;
+using System;
+
+namespace PdfMod
+{
+    struct PageLabelFormat
+    {
+        public string number_style;
+        public string prefix;
+        public int first_number;
+    }
+    
+    public class PageLabels
+    {
+        private const string name_labels = "/PageLabels";
+        private const string name_numtree = "/Nums";
+        
+        //Keys (PdfNames) for formatting attributes
+        private const string name_fmt = "/S";
+        private const string name_start_at = "/St";
+        private const string name_prefix = "/P";
+        
+        //Possible values for the numbering style
+        private const string alpha_upper = "/A"; 
+        private const string alpha_lower = "/a"; 
+        private const string roman_upper = "/R";
+        private const string roman_lower = "/r"; 
+        private const string arabic = "/D";
+        
+        private SortedDictionary<int, PageLabelFormat> page_labels;
+        private PdfDictionary.DictionaryElements pdf_elements;
+        private PdfDocument pdf_document;
+        private bool edited;
+
+        public string this[Page page] { get { return this[page.Index]; } }
+        
+        public string this[int index]
+        {
+            get
+            {
+                if (index < 0 || index > pdf_document.PageCount) {
+                    throw new IndexOutOfRangeException();
+                }
+
+                if (page_labels.Count == 0) {
+                    return null;
+                }
+    
+                int range_base = GetFormat (index);        
+                try {
+                    PageLabelFormat cur_format = page_labels[range_base];
+                    string label = cur_format.prefix;
+                    
+                    //Restart numbering for each range of pages
+                    int vindex = index + cur_format.first_number - range_base;
+                    
+                    if (cur_format.number_style == roman_upper || cur_format.number_style == alpha_upper) {
+                        label += RenderVal (vindex, cur_format.number_style).ToUpper ();
+                    } else {
+                        label += RenderVal (vindex, cur_format.number_style).ToLower ();
+                    }
+                    return label;
+                } catch (KeyNotFoundException e) {
+                    return (1 + index).ToString ();
+                }
+                return (1 + index).ToString ();
+            }
+        }
+        
+        internal PageLabels (PdfDocument document)
+        {
+            page_labels = new SortedDictionary<int, PageLabelFormat>();
+            pdf_elements = document.Internals.Catalog.Elements;
+            pdf_document = document;
+            edited = false;
+            
+            //Ignore documents that don't have labelling stuff defined
+            if (!pdf_elements.Contains (name_labels)) {
+                return;
+            }
+            
+            //Ignore documents that don't have a properly-defined PageLabelFmt section
+            PdfDictionary my_labels = pdf_elements.GetDictionary (name_labels);
+            if (!my_labels.Elements.Contains (name_numtree)) {
+                return;
+            }        
+
+            /* The number tree (not my term) is a PdfArray arranged as follows: [##, dict, ##, dict, ##, dict ...]
+             * ## represents the starting index of the page (0-based) and the following dict is a PdfDictionary
+             * containing formatting information regarding the range
+             */
+            
+            PdfArray number_tree = my_labels.Elements.GetArray (name_numtree);
+            
+            for (int i = 0; i < number_tree.Elements.Count / 2; ++i) {    
+                Console.WriteLine ("Range # {0}", i);
+                PageLabelFormat temp_label = new PageLabelFormat ();
+                
+                int range_start = number_tree.Elements.GetInteger (i * 2);
+                PdfDictionary label_data = number_tree.Elements.GetDictionary (i * 2 + 1);
+                
+                //Set the prefix, default to ""
+                if (label_data.Elements.Contains (name_prefix)) {
+                    temp_label.prefix = label_data.Elements.GetString (name_prefix);
+                } else {
+                    temp_label.prefix = "";
+                }        
+                
+                //Set the start number, default to 1
+                if (label_data.Elements.Contains (name_start_at)) {
+                    temp_label.first_number = label_data.Elements.GetInteger (name_start_at);
+                } else {
+                    temp_label.first_number = 1;
+                }
+                
+                //Set the format type, default to no numbering (only show the prefix)
+                if (label_data.Elements.Contains (name_fmt)) {
+                    temp_label.number_style = label_data.Elements.GetString (name_fmt);
+                } else {
+                    temp_label.number_style = "";
+                }
+
+                page_labels.Add (range_start, temp_label);
+            }
+        }
+        
+        //Determine which formatting rules apply to page index.  Returns the start of the formatting range
+        private int GetFormat (int index)
+        {
+            //Todo: find the correct range using a binary search
+            SortedDictionary<int, PageLabelFormat>.KeyCollection ranges = page_labels.Keys;
+            
+            int last = -1;
+            foreach (int range_start in ranges) {
+                if (range_start > index) break;
+                last = range_start;
+            }
+            return last;
+        }
+        
+        //Render the value index in the proper format (case-agnostic)
+        private string RenderVal (int index, string fmt)
+        {
+            if (arabic == fmt) {
+                return index.ToString ();
+            } else if (roman_upper == fmt || roman_lower == fmt) {
+                return ToRoman (index);
+            } else if (alpha_lower == fmt || alpha_upper == fmt) {
+                return ToAlpha (index);
+            } else {
+                return "";
+            }        
+        }
+    
+        //Convert val into Roman numerals
+        private string ToRoman (int val)
+        {
+            StringBuilder roman_val = new StringBuilder ();
+            //TODO: see if there's a more elegant conversion
+            
+            if (val >= 1000) {
+                roman_val.Append ('M', val / 1000);
+                val -= (1000 * (val / 1000));
+            }            
+            if (val >= 900) {
+                roman_val.Append ("CM");
+                val -= 900;
+            }
+            if (val >= 500) {
+                roman_val.Append ('D', val / 500);
+                val -= (500 * (val / 500));
+            }            
+            if (val >= 400) {
+                roman_val.Append ("CD");
+                val -= 400;
+            }
+            if (val >= 100) {
+                roman_val.Append ('C', val / 100);
+                val -= (100 * (val / 100));
+            }            
+            if (val >= 90) {
+                roman_val.Append ("XC");
+                val -= 90;
+            }    
+            if (val >= 50) {
+                roman_val.Append ('L', val / 50);
+                val -= (50 * (val / 50));
+            }            
+            if (val >= 40) {
+                roman_val.Append ("XL");
+                val -= 40;
+            }            
+            if (val >= 10) {
+                roman_val.Append ('X', val / 10);
+                val -= (10 * (val / 10));
+            }            
+            if (val >= 9) {
+                roman_val.Append ("IX");
+                val -= 9;
+            }    
+            if (val >= 5) {
+                roman_val.Append ('V', val / 5);
+                val -= (5 * (val / 5));
+            }            
+            if (val >= 4) {
+                roman_val.Append ("IV");
+                val -= 4;
+            }
+            roman_val.Append ('I', val);    
+            return roman_val.ToString ();
+        }
+        
+        //Convert val into the alpha representation. 1 -> a, 2 -> b, ... 26 -> z, 27 -> aa, 28 -> bb, etc.
+        private string ToAlpha (int val)
+        {
+            char letter = (char)((val - 1) % 26 + 'a');        
+            int rep_count = (val - 1)/26 + 1;
+            StringBuilder s = new StringBuilder (rep_count);
+            s.Append (letter, rep_count);
+            return s.ToString ();
+        }
+        
+        //Write labels to the PDF
+        internal void WriteLabels ()
+        {
+            if (!edited) { 
+                return;
+            }
+            
+            //Grab the labels element, creating it if necessary
+            PdfDictionary labels_dict;
+            if (!pdf_elements.Contains (name_labels)) {
+                labels_dict = new PdfDictionary (pdf_document);
+                pdf_elements.Add (new PdfName (name_labels), labels_dict);
+            } else {
+                labels_dict = pdf_elements.GetDictionary (name_labels);
+            }
+            labels_dict.Elements.Clear ();
+
+            //Create the number tree
+            PdfArray number_tree = new PdfArray (pdf_document);
+
+            //Add the range-start, attrib-dict pairs
+            foreach (int range_start in page_labels.Keys)
+            {
+                number_tree.Elements.Add (new PdfInteger (range_start));
+                PageLabelFormat label_format = page_labels[range_start];    
+                PdfDictionary r_attribs = new PdfDictionary (pdf_document);
+
+                if (label_format.number_style.Length > 0) {
+                    r_attribs.Elements.Add (new PdfName (name_fmt), new PdfName (label_format.number_style));        
+                }             
+                if (label_format.first_number > 1) {
+                    r_attribs.Elements.Add (new PdfName (name_start_at), new PdfInteger (label_format.first_number));    
+                }
+                if (label_format.prefix.Length > 0) {
+                    r_attribs.Elements.Add (new PdfName (name_prefix), new PdfString (label_format.prefix));
+                    
+                }
+                number_tree.Elements.Add (r_attribs);
+            }
+            labels_dict.Elements.Add (new PdfName (name_numtree), number_tree);
+        }
+    }
+}
diff --git a/src/PdfMod/PdfMod/PdfListStore.cs b/src/PdfMod/PdfMod/PdfListStore.cs
index 145e7aa..8800211 100644
--- a/src/PdfMod/PdfMod/PdfListStore.cs
+++ b/src/PdfMod/PdfMod/PdfListStore.cs
@@ -48,21 +48,24 @@ namespace PdfMod
                 }
             }
         }
-
+        
+        private string GetPageTooltip (Page page)
+        {
+            var label = page.Document.Labels[page];
+            string page_no = Catalog.GetString (String.Format ("Page {0}", page.Index + 1));
+            return ((null == label) ? page_no : String.Format ("{0} ({1})", label, page_no));
+        }
+        
         public void UpdateForPage (TreeIter iter, Page page)
         {
             SetValue (iter, SortColumn, page.Index);
-            SetValue (iter, TooltipColumn, String.Format (Catalog.GetString ("Page {0}"), page.Index + 1));
+            SetValue (iter, TooltipColumn, GetPageTooltip(page));
             SetValue (iter, PageColumn, page);
         }
 
         internal object [] GetValuesForPage (Page page)
         {
-            return new object[] {
-                page.Index,
-                String.Format (Catalog.GetString ("Page {0}"), page.Index + 1),
-                page
-            };
+            return new object[] { page.Index, GetPageTooltip(page), page };
         }
     }
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]