// =========================================================================== //  ©2013-2026 WebSupergoo. All rights reserved. // //  This source code is for use exclusively with the ABCpdf product with //  which it is distributed, under the terms of the license for that //  product. Details can be found at // //    http://www.websupergoo.com/ // //  This copyright notice must not be deleted and must be reproduced alongside //  any sections of code extracted from this module. // =========================================================================== using System.Collections.Generic; using System.IO; using System.Text; using WebSupergoo.ABCpdf14; using WebSupergoo.ABCpdf14.Objects; using WebSupergoo.ABCpdf14.Atoms; namespace Glue3DUtilities {   /// <summary>Static class containing utilities to extract specific data from a PDF file</summary>   public static class PdfUtilities   {     /// <summary>Helper struct to store a view and associate it with a name</summary>     public class ViewInfo     {       /// <summary>Construct an instance</summary>       public ViewInfo(string viewPath, string viewData)       {         Path = viewPath;         View = viewData;       }       /// <summary>Name of the stream (composed from page, annotation and type of the stream)</summary>       public string Path { get; private set; }       /// <summary>The string instance containing the view data</summary>       public string View { get; private set; }     }     /// <summary>Helper struct to store a stream and associate it with a name</summary>     public class StreamInfo     {       /// <summary>Construct an instance</summary>       public StreamInfo(string streamPath, byte[] streamData)       {         Path = streamPath;         Stream = streamData;         Views = new List<ViewInfo>();       }       /// <summary>Name of the stream (composed from page, annotation and type of the stream)</summary>       public string Path { get; private set; }       /// <summary>The actual stream containing the 3D data</summary>       public byte[] Stream { get; private set; }       /// <summary>The list of 3D views associated with this stream</summary>       public List<ViewInfo> Views { get; set; }     }     /// <summary>Settings to use when extracting 3D data from a PDF</summary>     public class ExtractionSettings     {       /// <summary>Construct an instance</summary>       public ExtractionSettings()       {         ProcessU3D = ProcessPRC = false;         ProcessViews = false;         MaxViewsPerStream = 0;       }       /// <summary>Whether U3D data should be extracted</summary>       public bool ProcessU3D { get; set; }       /// <summary>Whether PRC data should be extracted</summary>       public bool ProcessPRC { get; set; }       /// <summary>Whether 3D views should be extracted</summary>       public bool ProcessViews { get; set; }       /// <summary>If there are a lot of views available, limit the number of views to this number (0 means no limit)</summary>       public int MaxViewsPerStream { get; set; }     }     /// <summary>Struct containing information about the results of the extraction process</summary>     public class ExtractionResult     {       /// <summary>Construct an instance</summary>       public ExtractionResult()       {         Streams = new List<StreamInfo>();         StreamsProcessed = PRCDataProcessed = U3DDataProcessed = 0;         ViewsProcessed = 0;       }       /// <summary>The list of 3D streams</summary>       public List<StreamInfo> Streams { get; set; }       /// <summary>Counts the number of 3D streams that were encountered</summary>       public int StreamsProcessed { get; set; }       /// <summary>Counts the number of 3D streams that were PRC type</summary>       public int PRCDataProcessed { get; set; }       /// <summary>Counts the number 3D streams thate were U3D type</summary>       public int U3DDataProcessed { get; set; }       /// <summary>Counts the number of views that were processed</summary>       public int ViewsProcessed { get; set; }     }     /// <summary>Convenience function to extract all 3D data (prc, u3d and views) from a pdf file and write them to individual files</summary>     public static bool ExtractAll3DDataToFiles(string filepath)     {       ExtractionResult result = null;       if (!ExtractAll3DData(filepath, out result))         return false;       for( int i = 0; i < result.Streams.Count; ++i )       {         StreamInfo currStream = result.Streams[i];         File.WriteAllBytes(currStream.Path, currStream.Stream);         for (int j = 0; j < currStream.Views.Count; ++j)         {           File.WriteAllText(currStream.Views[j].Path, currStream.Views[j].View.ToString());         }       }       return true;     }     /// <summary>Convenience function to extract all 3D data (prc, u3d and views) from a pdf file</summary>     public static bool ExtractAll3DData(string filepath, out ExtractionResult result)     {       ExtractionSettings extractSettings = new ExtractionSettings();       extractSettings.MaxViewsPerStream = 0;       extractSettings.ProcessPRC = true;       extractSettings.ProcessU3D = true;       extractSettings.ProcessViews = true;       if (!ProcessPDF(filepath, extractSettings, out result))         return false;       return true;     }     /// <summary>Convenience function to extract all 3D views from a pdf file</summary>     public static bool ExtractAll3DViews(string filepath, out ExtractionResult result)     {       ExtractionSettings extractSettings = new ExtractionSettings();       extractSettings.MaxViewsPerStream = 0;       extractSettings.ProcessPRC = false;       extractSettings.ProcessU3D = false;       extractSettings.ProcessViews = true;       if (!ProcessPDF(filepath, extractSettings, out result))         return false;       return true;     }     /// <summary>Process a PDF file and extract 3D data and/or views from it</summary>     public static bool ProcessPDF(string filepath, ExtractionSettings extractSettings, out ExtractionResult outResult)     {       // create result object, and check if any 3d data needs to be written       outResult = new ExtractionResult();       // open and process the document       using (Doc theDoc = new Doc())       {         theDoc.Read(filepath);         // go through all the pages         for (int pageNum = 1; pageNum <= theDoc.PageCount; pageNum++)         {           theDoc.PageNumber = pageNum;           Page p = theDoc.ObjectSoup[theDoc.Page] as Page;           // get annotations of the page           Annotation[] annots = p.GetAnnotations();           foreach (Annotation annot in annots)           {             // we need a 3D annotation             if (annot.SubType != "3D")               continue;             DeUnicode(theDoc, theDoc.ObjectSoup[annot.ID].Atom);             DictAtom dict3D = theDoc.ObjectSoup[annot.ID].Atom as DictAtom;             if (dict3D == null)               continue;             // viewbox info, take 3DB if available, otherwise fall back to annotation rectangle (as per PDF spec 1.7)             string viewRectStr = "";             if (dict3D.Contains("3DB"))             {               ArrayAtom dict3DB = dict3D["3DB"] as ArrayAtom;               if (dict3DB == null)               {                 RefAtom ref3DB = dict3D["3DB"] as RefAtom;                 if (ref3DB == null)                   continue;                 dict3DB = theDoc.ObjectSoup[ref3DB.ID].Atom as ArrayAtom;                 if (dict3DB == null)                   continue;               }               viewRectStr = "3DB " + dict3DB.ToString();             }             else             {               viewRectStr = "3DB [0 0 " + annot.Rect.Width + " " + annot.Rect.Height + "]";             }             // find the 3d stream data (3DD)             StreamInfo curr = null;             int id = theDoc.GetInfoInt(annot.ID, "/3DD:Ref");             if (id != 0)             {               // get the stream object               D3DStream streamObj = theDoc.ObjectSoup[id] as D3DStream;               if (streamObj == null)                 continue;               // update numbers               outResult.StreamsProcessed++;               // check the subtype               DictAtom dict3DD = streamObj.Atom as DictAtom;               bool doWrite = true;               if (dict3DD != null)               {                 string objSubtype = dict3DD["Subtype"].ToString();                 if ((objSubtype == "/PRC" && extractSettings.ProcessPRC == false) ||                   (objSubtype == "/U3D" && extractSettings.ProcessU3D == false))                 {                   doWrite = false;                 }               }               // check whether to decompress               if( streamObj.Compressed )               {                 // try to decompress                 if (!streamObj.Decompress())                   continue;               }               // stream data               byte[] streamData = streamObj.GetData();               if (streamData.Length < 10)                 continue;               // add to result data               bool isU3D = streamData[0] == 'U';               string streamPath = filepath + "_p" + pageNum.ToString() + "_an" + annot.ID.ToString() + (isU3D ? ".u3d" : ".prc");               curr = new StreamInfo(streamPath, doWrite ? streamData : null);               outResult.Streams.Add(curr);               // update numbers               if (isU3D)                 outResult.U3DDataProcessed++;               else                 outResult.PRCDataProcessed++;               // extract 3D views from 3DD's view array (VA)               if (extractSettings.ProcessViews)               {                 DeUnicode(theDoc, theDoc.ObjectSoup[id].Atom);                 int va = theDoc.GetInfoInt(id, "/VA:Ref");                 if (va != 0)                   DeUnicode(theDoc, theDoc.ObjectSoup[va].Atom);                 int numViews = theDoc.GetInfoInt(id, "/VA*:Count");                 if (extractSettings.MaxViewsPerStream > 0 && numViews > extractSettings.MaxViewsPerStream)                   numViews = extractSettings.MaxViewsPerStream;                 StringBuilder viewData = new StringBuilder();                 for (int j = 0; j < numViews; j++)                 {                   string opath = "/VA[" + j.ToString() + "]*";                   string[] keys = theDoc.GetInfo(id, opath + ":Keys").Split(new char[] { ',' });                   foreach (string key in keys)                   {                     string val = theDoc.GetInfo(id, opath + "/" + key + "*");                     viewData.Append(key);                     viewData.Append("\t");                     viewData.AppendLine(val);                   }                   if (viewRectStr.Length > 0)                     viewData.AppendLine(viewRectStr);                   // write to result                   string viewPath = filepath + "_p" + pageNum.ToString() + "_an" + annot.ID.ToString() + "_view" + j.ToString() + ".txt";                   if( curr != null )                     curr.Views.Add(new ViewInfo(viewPath, viewData.ToString()));                                      outResult.ViewsProcessed++;                   viewData.Clear();                 }               }             }             // look for 3DV as well, if we extract views             if (extractSettings.ProcessViews)             {               if (dict3D.Contains("3DV"))               {                 DictAtom dict3DV = dict3D["3DV"] as DictAtom;                 if (dict3DV == null)                 {                   RefAtom ref3DV = dict3D["3DV"] as RefAtom;                   if (ref3DV == null)                     continue;                   dict3DV = theDoc.ObjectSoup[ref3DV.ID].Atom as DictAtom;                   if (dict3DV == null)                     continue;                 }                 string[] myKeys = dict3DV.GetKeys();                 StringBuilder viewData = new StringBuilder();                 foreach (string currKey in myKeys)                 {                   string currValue = "";                   RefAtom valRef = dict3DV[currKey] as RefAtom;                   if (valRef != null)                   {                     DictAtom refedDic = theDoc.ObjectSoup[valRef.ID].Atom as DictAtom;                     if (refedDic != null)                       currValue = refedDic.ToString();                     else                     {                       ArrayAtom arrAt = theDoc.ObjectSoup[valRef.ID].Atom as ArrayAtom;                       if (arrAt != null)                       {                         currValue = arrAt.ToString();                       }                     }                   }                   else                   {                     currValue = dict3DV[currKey].ToString();                   }                   viewData.Append(currKey);                   viewData.Append("\t");                   viewData.Append(currValue);                   viewData.Append("\n");                 }                 if (viewRectStr.Length > 0)                   viewData.AppendLine(viewRectStr);                 // create the view name/path and write to result                 string viewPath = filepath + "_p" + pageNum.ToString() + "_an" + annot.ID.ToString() + "_view_default.txt";                 outResult.ViewsProcessed++;                 if( curr != null )                   curr.Views.Add(new ViewInfo(viewPath, viewData.ToString()));               }             }           }         }       }       return true;     }     private static void DeUnicode(Doc doc, Atom atom)     {       if (atom == null)         return;       ArrayAtom aa = atom as ArrayAtom;       DictAtom da = atom as DictAtom;       int n = 0;       if (aa != null)         n = aa.Count;       if (da != null)         n = da.Keys.Count;       for (int i = 0; i < n; i++)         DeUnicode(doc, Atom.GetItem(atom, i));       if (atom is StringAtom)       {         string str = ((StringAtom)atom).Text;         int p = str.IndexOf('\0');         if (p != -1)           str = str.Substring(0, p);         ((StringAtom)atom).Text = str;       }     }   } }