// =========================================================================== //  ©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. // =========================================================================== // NB In ABCpdf Version 10 and later, much of the functionality here is // provided by the ReduceSizeOperation class. using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.Diagnostics; using System.Globalization; using System.IO; using System.Xml; using WebSupergoo.ABCpdf14; using WebSupergoo.ABCpdf14.Atoms; namespace WebSupergoo.FontUnembedment {   /// <summary>IFontOptions contains the options for font processing.</summary>   public interface IFontOptions {     /// <summary>Font name.</summary>     string Name { get; }     /// <summary>Font language. This specifies the internal PDF encoding.</summary>     LanguageType Language { get; }     /// <summary>Whether the font is embedded.</summary>     bool Embedded { get; }     /// <summary>Whether to (embed and) subset the font.</summary>     bool IsSubset { get; }     /// <summary>Makes the font referenced and not embedded.     /// It throws an exception if the font uses the Unicode language     /// and is thus always embedded.</returns>     void MakeReferenced();     /// <summary>Makes the font embedded.</summary>     /// <param name="subset">Whether to subset the font.</param>     void MakeEmbedded(bool subset);   }   /// <summary>   /// FontUnembedder unembeds fonts. Always manually check the output for problems.   /// It does not process annotation appearances.   /// </summary>   public sealed class FontUnembedder {     /// <summary>Information about source font.</summary>     public sealed class FontObjectInfo {       private int _id;       private string _name;       private bool _encodingIsIdentical;       private int _characterCount;       public FontObjectInfo(int id, string name, bool encodingIsIdentical) {         _id = id;         _name = name;         _encodingIsIdentical = encodingIsIdentical;       }       /// <summary>Object ID.</summary>       public int Id { get { return _id; } }       /// <summary>Font name (including the prefix for font subset).</summary>       public string Name { get { return _name; } }       /// <summary>Whether the encoding is identical to that of the replacement font.</summary>       public bool EncodingIsIdentical { get { return _encodingIsIdentical; } }       /// <summary>Number of characters processed.</summary>       public int CharacterCount {         get { return _characterCount; }         set { _characterCount = value; }       }     }     /// <summary>Information about destination font.</summary>     public sealed class ProcessingInfo {       private int _fontId;       private string _fontName;       private LanguageType _language;       private int _characterCount;       private List<FontObjectInfo> _sourceFonts;       public ProcessingInfo(int fontId, string fontName, LanguageType langauge) {         _fontId = fontId;         _fontName = fontName;         _language = langauge;         _sourceFonts = new List<FontObjectInfo>();       }       /// <summary>Object ID.</summary>       public int FontId { get { return _fontId; } }       /// <summary>Font name.</summary>       public string FontName { get { return _fontName; } }       /// <summary>Language.</summary>       public LanguageType Language { get { return _language; } }       /// <summary>Number of characters processed, the sum of CharacterCount in SourceFonts.</summary>       public int CharacterCount {         get { return _characterCount; }         set { _characterCount = value; }       }       /// <summary>The list of source fonts replaced by this font.</summary>       public List<FontObjectInfo> SourceFonts { get { return _sourceFonts; } }     }     private sealed class TargetFont: IFontOptions {       public string Name;       public LanguageType Language;       public bool Embed;       public bool Subset;       public TargetFont(string name, LanguageType language) {         Name = name;         Language = language;         if(GetEmbed(language)) {           Embed = true;           Subset = true;         }       }       public static bool GetEmbed(LanguageType language) {         return language==LanguageType.Unicode;       }       string IFontOptions.Name { get { return Name; } }       LanguageType IFontOptions.Language { get { return Language; } }       bool IFontOptions.Embedded { get { return Embed; } }       bool IFontOptions.IsSubset { get { return Subset; } }       void IFontOptions.MakeReferenced() {         if(GetEmbed(Language))           throw new NotSupportedException("Font in the Unicode language cannot be referenced without being embedded.");         Embed = false;         Subset = false;       }       void IFontOptions.MakeEmbedded(bool subset) {         Embed = true;         Subset = subset;       }     }     private sealed class FontIdentity: IEquatable<FontIdentity> {       public string Name;       public bool Vertical;       public FontIdentity(string name, bool vertical) {         Name = name;         Vertical = vertical;       }       public bool Equals(FontIdentity obj) {         return Name==obj.Name && Vertical==obj.Vertical;       }       public override bool Equals(object obj) {         FontIdentity v = obj as FontIdentity;         return v!=null && Equals(v);       }       public override int GetHashCode() {         int v = Name.GetHashCode();         if(Vertical)           v = ~v;         return v;       }     }     private sealed class ReplacementFont {       public FontIdentity Identity;       public LanguageType Language;       public int FontId;       public bool Subset;       public int HelperFontId;       public string NonSpaceCode;       public string NonSpaceChar;       public int NonSpaceCharPreference;       public List<FontObjectInfo> SourceFonts;       public ReplacementFont(FontIdentity identity, LanguageType language, int fontId, bool subset) {         Identity = identity;         Language = language;         FontId = fontId;         Subset = subset;       }       public void SetNonSpaceChar(string s) {         const int maxPreference = 9;         if(NonSpaceCharPreference>=maxPreference || s=="")           return;         UnicodeCategory preferenceCategory = default(UnicodeCategory);         int preference = 0;         int preferenceIndex = 0;         for(int i = 0; i<s.Length; ++i) {           UnicodeCategory category = char.GetUnicodeCategory(s, i);           int pref = GetUnicodePreference(category);           if(pref>preference) {             if(category!=UnicodeCategory.Surrogate               || char.IsHighSurrogate(s, i)               && i<s.Length-1 && char.IsLowSurrogate(s, i+1))             {               preferenceCategory = category;               preference = pref;               preferenceIndex = i;               if(pref>=maxPreference)                 break;             }           }         }         if(preference<=NonSpaceCharPreference)           return;         NonSpaceCharPreference = preference;         NonSpaceChar = s.Substring(preferenceIndex,           preferenceCategory!=UnicodeCategory.Surrogate? 1: 2);       }       private static int GetUnicodePreference(UnicodeCategory category) {         switch(category) {         case UnicodeCategory.SpaceSeparator:         case UnicodeCategory.LineSeparator:         case UnicodeCategory.ParagraphSeparator:         case UnicodeCategory.Control:         case UnicodeCategory.Format:           return 0;         case UnicodeCategory.PrivateUse:           return 1;         case UnicodeCategory.Surrogate:           return 2;         case UnicodeCategory.SpacingCombiningMark:           return 3;         case UnicodeCategory.NonSpacingMark:         case UnicodeCategory.EnclosingMark:           return 4;         case UnicodeCategory.ModifierSymbol:           return 5;         case UnicodeCategory.ModifierLetter:           return 6;         case UnicodeCategory.OtherPunctuation:         case UnicodeCategory.OtherSymbol:           return 7;         case UnicodeCategory.TitlecaseLetter:         case UnicodeCategory.OtherLetter:         case UnicodeCategory.LetterNumber:         case UnicodeCategory.OtherNumber:           return 8;         case UnicodeCategory.UppercaseLetter:         case UnicodeCategory.LowercaseLetter:         case UnicodeCategory.DecimalDigitNumber:         case UnicodeCategory.ConnectorPunctuation:         case UnicodeCategory.DashPunctuation:         case UnicodeCategory.OpenPunctuation:         case UnicodeCategory.ClosePunctuation:         case UnicodeCategory.InitialQuotePunctuation:         case UnicodeCategory.FinalQuotePunctuation:         case UnicodeCategory.MathSymbol:         case UnicodeCategory.CurrencySymbol:           return 9;         }         return 1;       }     }     [StructLayout(LayoutKind.Auto)]     private struct FontResource {       public FontObjectInfo SourceFont;       public ReplacementFont Font;       public FontResource(FontObjectInfo sourceFont, ReplacementFont font) {         SourceFont = sourceFont;         Font = font;       }     }     [StructLayout(LayoutKind.Auto)]     private struct Replacement {       public FontObjectInfo SourceFont;       public ReplacementFont Font;       public int Offset;       public int Length;       public List<string> Text;     }     [StructLayout(LayoutKind.Auto)]     private struct Reference {       public int PageId;       public int Id;       public string Path;       public Reference(int pageId, int id, string path) {         PageId = pageId;         Id = id;         Path = path;       }     }     private sealed class FontReference {       public ReplacementFont Font;       public List<Reference> References;       public FontReference(ReplacementFont font) {         Font = font;         References = new List<Reference>();       }     }     [StructLayout(LayoutKind.Auto)]     private struct SourceFontReference {       public FontObjectInfo SourceFont;       public FontReference FontRef;       public SourceFontReference(FontObjectInfo sourceFont,         FontReference fontRef)       {         SourceFont = sourceFont;         FontRef = fontRef;       }     }     private readonly char[] _stringStart;     private LanguageType? _defaultLanguage;     private Dictionary<string, TargetFont> _fonts;     private Dictionary<string, TargetFont> _fontVariants;     private bool _forceDefaultLanguage;     public FontUnembedder() {       _stringStart = new char[] { '(', '<' };       _fonts = new Dictionary<string, TargetFont>(StringComparer.OrdinalIgnoreCase);       _fontVariants = new Dictionary<string, TargetFont>(StringComparer.OrdinalIgnoreCase);     }     /// <summary>The default language. If the property is not null, all fonts are processed.</summary>     public LanguageType? DefaultLanguage {       get { return _defaultLanguage; }       set { _defaultLanguage = value; }     }     /// <summary>Whether to throw an exception when it fails to process fonts using DefaultLanguage.</summary>     public bool ForceDefaultLanguage {       get { return _forceDefaultLanguage; }       set { _forceDefaultLanguage = value; }     }     /// <summary>Adds font for processing. An exception is thrown in the Process method     /// if it fails to process an added font.</summary>     /// <param name="family">Font family name.</param>     /// <param name="language">Language. If it is Unicode, the destination font     /// is embedded and subsetted by default.</param>     /// <returns>Font options.</returns>     public IFontOptions AddFont(string family, LanguageType language) {       return AddFont(family, family, language);     }     /// <summary>Adds font for processing. An exception is thrown in the Process method     /// if it fails to process an added font.</summary>     /// <param name="family">Source font family name.</param>     /// <param name="targetFamily">Destination font family name.</param>     /// <param name="language">Language. If it is Unicode, the destination font     /// is embedded and subsetted by default.</param>     /// <returns>Font options.</returns>     public IFontOptions AddFont(string family, string targetFamily, LanguageType language) {       TargetFont target = new TargetFont(targetFamily, language);       _fonts.Add(family, target);       if(family.IndexOf(' ')<0)         AddFontNames(_fontVariants, family, target, false);       else {         AddFontNames(_fontVariants, family.Replace(" ", ""), target, true);         AddFontNames(_fontVariants, family.Replace(' ', '+'), target, true);         AddFontNames(_fontVariants, family.Replace(' ', '_'), target, true);         AddFontNames(_fontVariants, family.Replace(' ', '-'), target, true);       }       return target;     }     private static void AddFontNames(Dictionary<string, TargetFont> fontVariants,       string name, TargetFont target, bool addBaseName)     {       if(addBaseName && !fontVariants.ContainsKey(name))         fontVariants.Add(name, target);       string s = name+"PS";       if(!fontVariants.ContainsKey(s))         fontVariants.Add(s, target);       s = name+"MT";       if(!fontVariants.ContainsKey(s))         fontVariants.Add(s, target);       s = name+"PSMT";       if(!fontVariants.ContainsKey(s))         fontVariants.Add(s, target);     }     private bool GetTargetFont(string name, out TargetFont outTarget,       out string outTargetName)     {       if(_fonts.TryGetValue(name, out outTarget)         || _fontVariants.TryGetValue(name, out outTarget))       {         outTargetName = outTarget.Name;         return true;       }       char[] anyOf = new char[] { '-', ',' };       string name0 = name;       int i;       while((i = name0.LastIndexOfAny(anyOf))>=0) {         name0 = name0.Substring(0, i);         if(_fonts.TryGetValue(name0, out outTarget)           || _fontVariants.TryGetValue(name0, out outTarget))         {           int removeCount = 0;           if(name.EndsWith("PSMT", StringComparison.Ordinal))             removeCount = 4;           else if(name.EndsWith("MT", StringComparison.Ordinal)             || name.EndsWith("PS", StringComparison.Ordinal))             removeCount = 2;           outTargetName = outTarget.Name+name.Substring(             i, name.Length-i-removeCount);           return true;         }       }       outTargetName = null;       return false;     }     public void Process(Doc doc, ICollection<ProcessingInfo> infos) {       if(_fonts.Count<=0 && !_defaultLanguage.HasValue)         return;       SortedDictionary<int, bool> objectMap = new SortedDictionary<int, bool>();       SortedDictionary<int, SourceFontReference> fontMap         = new SortedDictionary<int, SourceFontReference>();  // int:source font id       Dictionary<FontIdentity, FontReference> uniqueFontMap         = new Dictionary<FontIdentity, FontReference>();       SortedDictionary<int, SortedDictionary<int, SortedDictionary<int, Replacement>>> replaceMap         = new SortedDictionary<int, SortedDictionary<int, SortedDictionary<int, Replacement>>>();  // int:stream id, int:page id, int:stream offset       int resourceStreamId = 0, resourcePageId = 0;       Dictionary<string, FontResource> fontResources         = new Dictionary<string, FontResource>();       int oldPageId = doc.Page;       try {         for(int i = 0, count = doc.PageCount; i<count; ++i) {           doc.PageNumber = i+1;           AddSourceFonts(doc, infos!=null, doc.Page,             fontMap, uniqueFontMap, objectMap);           doc.Flatten();           string content = doc.GetText("SVG+");           XmlDocument xml;           using(StringReader reader = new StringReader(content)) {             xml = new XmlDocument();             xml.Load(reader);           }           XmlNode svg = xml["svg"];           XmlNode node = svg==null? null: svg.FirstChild;           while(node!=svg) {             if(node.Name=="text")               ProcessText(doc, infos!=null, node, fontMap, uniqueFontMap,                 replaceMap, ref resourceStreamId, ref resourcePageId,                 fontResources);             if(node.FirstChild!=null)               node = node.FirstChild;             else if(node.NextSibling!=null)               node = node.NextSibling;             else {               do {                 node = node.ParentNode;               } while(node!=svg && node.NextSibling==null);               if(node!=svg)                 node = node.NextSibling;             }           }           resourceStreamId = 0;           resourcePageId = 0;           fontResources.Clear();         }       } finally {         doc.Page = oldPageId;       }       SortedDictionary<int, SortedDictionary<int, int>> streamRemap         = new SortedDictionary<int, SortedDictionary<int, int>>();  // int:page id, int stream id       // replace text operands in content streams       int pageId = 0;       int oldFontId = 0;       double oldFontSize = 0;       XColor oldColor = null;       double oldRectLeft = 0, oldRectBottm = 0, oldRectRight = 0, oldRectTop = 0;       XTextStyle.KerningType oldKerning = XTextStyle.KerningType.None;       Doc helperDoc = null;       StringBuilder builder = new StringBuilder();       try {         foreach(KeyValuePair<int, SortedDictionary<int, SortedDictionary<int, Replacement>>> streamPair in replaceMap) {           doc.GetInfo(streamPair.Key, "Decompress");           string content = doc.GetInfo(streamPair.Key, "Stream");           bool hasUnchanged = false;           int pageCount = streamPair.Value.Count;           foreach(KeyValuePair<int, SortedDictionary<int, Replacement>> pagePair in streamPair.Value) {             --pageCount;             SortedDictionary<int, int> remap = null;             int iContent = 0;             foreach(Replacement replace in pagePair.Value.Values) {               if(replace.SourceFont.EncodingIsIdentical && !replace.Font.Subset)                 continue;               int iEnd = replace.Offset;               int opEnd = replace.Offset+replace.Length;               for(int j = 0; j<replace.Text.Count; ++j) {                 int i = content.IndexOfAny(_stringStart, iEnd, opEnd-iEnd);                 if(i<0)                   throw new ApplicationException("Unable to find text operand.");                 iEnd = GetStringEnd(content, i, opEnd);                 if(iEnd<0)                   throw new ApplicationException("Unable to find text operand end.");                 string s = replace.Text[j];                 if(s!="") {                   if(replace.SourceFont!=null)                     replace.SourceFont.CharacterCount += s.Length;                   int extraCount;                   int id;                   if(replace.Font.Subset) {                     if(pageId==0) {                       oldFontId = doc.Font;                       oldFontSize = doc.TextStyle.Size;                       if(oldColor==null)                         oldColor = new XColor();                       oldColor.SetColor(doc.Color);                       oldRectLeft = doc.Rect.Left;                       oldRectBottm = doc.Rect.Bottom;                       oldRectRight = doc.Rect.Right;                       oldRectTop = doc.Rect.Top;                       oldKerning = doc.TextStyle.Kerning;                       // Use a detached page to avoid changes to the page tree                       doc.Page = pageId = doc.AddObject("<</Type /Page /MediaBox ["                         + doc.MediaBox.String + "] >>");                       doc.FontSize = 8;                       doc.Color.Alpha = 255;                       doc.TextStyle.Kerning = XTextStyle.KerningType.None;                     }                     doc.Font = replace.Font.FontId;                     doc.Rect.SetSides(0, 0, 10000, 10000);  // make sure it is big enough and reset Pos                     id = AddText(doc, replace.Font, s, out extraCount);                     if(replace.SourceFont.EncodingIsIdentical)                       continue;                     s = doc.GetInfo(id, "Stream");                     doc.Flatten();  // transfer used characters to the font                     int newId = doc.GetInfoInt(doc.Page, "/Contents*[-1]:Ref");                     if(newId!=0)                       doc.ObjectSoup.RemoveAt(newId);  // avoid "Object ID unrealistically large"                     doc.SetInfo(doc.Page, "/Contents*[-1]:Del", "");                     // remove id last. order is important                     if(id!=0)                       doc.ObjectSoup.RemoveAt(id);  // avoid "Object ID unrealistically large"                   } else {                     if(helperDoc==null) {                       helperDoc = new Doc();                       helperDoc.TextStyle.Kerning = XTextStyle.KerningType.None;                     }                     if(replace.Font.HelperFontId==0) {                       bool embed = TargetFont.GetEmbed(replace.Font.Language);                       replace.Font.HelperFontId = AddDocFont(helperDoc,                         replace.Font.Identity, replace.Font.Language,                         embed, embed);                       if(replace.Font.HelperFontId==0)                         throw new ApplicationException(string.Format(                           "Unable to add font \"{0}\" in language {1}.",                           replace.Font.Identity, replace.Font.Language));                     }                     helperDoc.Font = replace.Font.HelperFontId;                     helperDoc.Rect.SetSides(0, 0, 10000, 10000);  // make sure it is big enough and reset Pos                     id = AddText(helperDoc, replace.Font, s, out extraCount);                     s = helperDoc.GetInfo(id, "Stream");                     helperDoc.SetInfo(helperDoc.Page, "/Contents*[-1]:Del", "");                     if(id!=0)                       helperDoc.ObjectSoup.RemoveAt(id);  // avoid "Object ID unrealistically large"                   }                   if(s.Length<=0)  // this can happen - e.g. a zwsp char                     s = "()";                   int k = s.IndexOfAny(_stringStart);                   if(k<0)                     throw new ApplicationException("Unable to find text operand.");                   int kEnd = GetStringEnd(s, k, s.Length);                   if(kEnd<0)                     throw new ApplicationException("Unable to find text operand end.");                   builder.Append(content, iContent, i-iContent);                   if(extraCount<=0)                     builder.Append(s, k, kEnd-k);                   else {                     builder.Append(s, k, kEnd-k-extraCount-1);                     builder.Append(s, kEnd-1, 1);                   }                   iContent = iEnd;                 }               }             }             if(iContent<=0)               hasUnchanged = true;             else {               builder.Append(content, iContent, content.Length-iContent);               if(pageCount<=0 && !hasUnchanged)                 doc.SetInfo(streamPair.Key, "Stream", builder.ToString());               else {                 Debug.Assert(pagePair.Key>0);                 DictAtom dict = (DictAtom)doc.ObjectSoup[streamPair.Key].Atom;                 int id = doc.AddObject(dict.ToString()+"stream\nendstream\n");                 doc.SetInfo(id, "Stream", builder.ToString());                 doc.GetInfo(id, "Compress");                 if(remap==null && !streamRemap.TryGetValue(pagePair.Key, out remap)) {                   remap = new SortedDictionary<int, int>();                   streamRemap.Add(pagePair.Key, remap);                 }                 remap.Add(streamPair.Key, id);               }               builder.Length = 0;             }           }           doc.GetInfo(streamPair.Key, "Compress");         }       } finally {         if(helperDoc!=null)           helperDoc.Dispose();         if(pageId!=0) {           doc.Page = oldPageId;           doc.Delete(pageId);           doc.Font = oldFontId;           doc.TextStyle.Size = oldFontSize;           if(oldColor!=null)             doc.Color.SetColor(oldColor);           doc.Rect.SetSides(oldRectLeft, oldRectBottm, oldRectRight, oldRectTop);           doc.TextStyle.Kerning = oldKerning;         }       }       Dictionary<int, ProcessingInfo> infoMap = infos==null? null:         new Dictionary<int, ProcessingInfo>();       foreach(SourceFontReference sourceFontRef in fontMap.Values) {         List<Reference> list = sourceFontRef.FontRef.References;         for(int i = 0; i<list.Count; ++i) {  // update font references           int id = list[i].Id;           if(list[i].PageId>0) {             SortedDictionary<int, int> remap;             if(streamRemap.TryGetValue(list[i].PageId, out remap)) {               int remappedId;               if(remap.TryGetValue(id, out remappedId))                 id = remappedId;             }           }           doc.SetInfo(id, list[i].Path,             sourceFontRef.FontRef.Font.FontId);         }         if(infoMap!=null) {  // collect statistics           ReplacementFont font = sourceFontRef.FontRef.Font;           ProcessingInfo info;           if(!infoMap.TryGetValue(font.FontId, out info)) {             info = new ProcessingInfo(font.FontId, font.Identity.Name, font.Language);             infoMap.Add(font.FontId, info);           }           info.SourceFonts.Add(sourceFontRef.SourceFont);           info.CharacterCount += sourceFontRef.SourceFont.CharacterCount;         }       }       // remap stream references       foreach(KeyValuePair<int, SortedDictionary<int, int>> pagePair in streamRemap) {         objectMap.Clear();         RemapXObjects(doc, pagePair.Key, pagePair.Value, objectMap);         int count = doc.GetInfoInt(pagePair.Key, "/Contents*:Count");         if(count>0) {           bool remapped = false;           for(int i = 0; i<count; ++i) {             string path = "/Contents*["+i.ToString()+"]:Ref";             int id = doc.GetInfoInt(pagePair.Key, path);             if(pagePair.Value.TryGetValue(id, out id)) {               if(!remapped) {                 remapped = true;                 if(doc.GetInfoInt(pagePair.Key, "/Contents:Ref")!=0) {                   string arr = doc.GetInfo(pagePair.Key, "/Contents*");                   doc.SetInfo(pagePair.Key, "/Contents", arr);                 }               }               doc.SetInfo(pagePair.Key, path, id);             }           }         } else {           int id = doc.GetInfoInt(pagePair.Key, "/Contents:Ref");           if(pagePair.Value.TryGetValue(id, out id))             doc.SetInfo(pagePair.Key, "/Contents:Ref", id);         }       }       if(infoMap!=null) {  // return statistics         foreach(ProcessingInfo info in infoMap.Values)           infos.Add(info);       }     }     private static void RemapXObjects(Doc doc, int resourceObj,       SortedDictionary<int, int> remap, SortedDictionary<int, bool> objectMap)     {       string keys = doc.GetInfo(resourceObj, "/Resources*/XObject*:Keys");       if(keys=="")         return;       bool remapped = false;       int i = 0;       while(true) {         int j = keys.IndexOf(',', i);         string key = j<0? keys.Substring(i): keys.Substring(i, j-i);         string path = "/Resources*/XObject*/"+key+":Ref";         int id = doc.GetInfoInt(resourceObj, path);         if(remap.TryGetValue(id, out id)) {           if(!remapped) {             remapped = true;             string dict;             if(doc.GetInfoInt(resourceObj, "/Resources:Ref")!=0) {               dict = doc.GetInfo(resourceObj, "/Resources*");               doc.SetInfo(resourceObj, "/Resources", dict);             }             if(doc.GetInfoInt(resourceObj, "/Resources/XObject:Ref")!=0) {               dict = doc.GetInfo(resourceObj, "/Resources/XObject*");               doc.SetInfo(resourceObj, "/Resources/XObject", dict);             }           }           doc.SetInfo(resourceObj, path, id);         }         if(!objectMap.ContainsKey(id)) {           objectMap.Add(id, false);           RemapXObjects(doc, id, remap, objectMap);         }         if(j<0)           break;         i = j+1;       }     }     private void AddSourceFonts(Doc doc, bool report, int resourceObj,       SortedDictionary<int, SourceFontReference> fontMap,       Dictionary<FontIdentity, FontReference> uniqueFontMap,       SortedDictionary<int, bool> objectMap)     {  // fonts not used in Tj/TJ need this method to get processed       int i;       string keys = doc.GetInfo(resourceObj, "/Resources*/Font*:Keys");       if(keys!="") {         i = 0;         while(true) {           int j = keys.IndexOf(',', i);           string resourceName = j<0? keys.Substring(i): keys.Substring(i, j-i);           GetSourceFont(doc, report, 0, resourceObj, resourceName,             fontMap, uniqueFontMap);           if(j<0)             break;           i = j+1;         }       }       keys = doc.GetInfo(resourceObj, "/Resources*/XObject*:Keys");       if(keys=="")         return;       i = 0;       while(true) {         int j = keys.IndexOf(',', i);         string key = j<0? keys.Substring(i): keys.Substring(i, j-i);         int id = doc.GetInfoInt(resourceObj, "/Resources*/XObject*/"+key+":Ref");         if(!objectMap.ContainsKey(id)) {           objectMap.Add(id, false);           AddSourceFonts(doc, report, id, fontMap, uniqueFontMap, objectMap);         }         if(j<0)           break;         i = j+1;       }     }     private SourceFontReference GetSourceFont(Doc doc, bool report,       int pageId, int resourceObj, string resourceName,       SortedDictionary<int, SourceFontReference> fontMap,       Dictionary<FontIdentity, FontReference> uniqueFontMap)     {       string path = "/Resources*/Font*/"+resourceName;       string fontIdPath = path+":Ref";       SourceFontReference sourceFontRef = new SourceFontReference();       FontIdentity identity = null;       string fontName = null;       string encoding = null;       string fontV = "";       int sourceFontId = doc.GetInfoInt(resourceObj, fontIdPath);       if(sourceFontId!=0) {         if(!fontMap.TryGetValue(sourceFontId, out sourceFontRef))           identity = NewFontIdentity(doc, sourceFontId, "",             out fontName, out encoding);       } else if((fontV = doc.GetInfo(resourceObj, path))!="")         identity = NewFontIdentity(doc, resourceObj, path+"*",           out fontName, out encoding);       if(sourceFontRef.FontRef==null) {         if(identity==null)           return sourceFontRef;         ReplacementFont font;         if(!uniqueFontMap.TryGetValue(identity, out sourceFontRef.FontRef)) {           font = null;           TargetFont target;           string targetName;           if(GetTargetFont(identity.Name, out target, out targetName))             font = AddReplaceFont(doc, new FontIdentity(targetName,               identity.Vertical), target.Language,               target.Embed, target.Subset);           else if(_defaultLanguage.HasValue) {             LanguageType language = _defaultLanguage.Value;             bool embed = TargetFont.GetEmbed(language);             font = AddReplaceFont(doc, identity, language, embed, embed);           }           if(font==null) {             const string format = "Unable to add font \"{0}\" in language {1}.";             if(targetName!=null)               throw new ApplicationException(string.Format(format,                 targetName, target.Language));             else if(_forceDefaultLanguage && _defaultLanguage.HasValue)               throw new ApplicationException(string.Format(format,                 identity.Name, _defaultLanguage.Value));             return sourceFontRef;           }           string fontName0, encoding0;           FontIdentity identity0 = NewFontIdentity(doc, font.FontId, "",             out fontName0, out encoding0);           if(identity0!=null) {             if(identity0.Name==identity.Name)               identity0 = null;             else {               FontReference fontRef;               if(uniqueFontMap.TryGetValue(identity0, out fontRef)) {                 if(font.FontId!=fontRef.Font.FontId)                   doc.Delete(font.FontId);                 sourceFontRef.FontRef = fontRef;               }             }           }           if(sourceFontRef.FontRef==null) {             if(report)               font.SourceFonts = new List<FontObjectInfo>();             sourceFontRef.FontRef = new FontReference(font);             if(identity0!=null)               uniqueFontMap.Add(identity0, sourceFontRef.FontRef);           }           uniqueFontMap.Add(identity, sourceFontRef.FontRef);         }         font = sourceFontRef.FontRef.Font;         if(fontV!="") {  // create indirect object           sourceFontId = doc.AddObject(fontV);           doc.SetInfo(resourceObj, fontIdPath, sourceFontId);           if(font.SourceFonts!=null) {             if(encoding==null)               encoding = doc.GetInfo(sourceFontId, "/Encoding*:Name");             sourceFontRef.SourceFont = new FontObjectInfo(0, fontName,               encoding!="" && !encoding.StartsWith("Identity-",               StringComparison.Ordinal)                 && encoding==doc.GetInfo(font.FontId, "/Encoding*:Name"));             font.SourceFonts.Add(sourceFontRef.SourceFont);           }         } else if(font.SourceFonts!=null) {           if(encoding==null)             encoding = doc.GetInfo(sourceFontId, "/Encoding*:Name");           sourceFontRef.SourceFont = new FontObjectInfo(sourceFontId, fontName,             encoding!="" && !encoding.StartsWith("Identity-",             StringComparison.Ordinal)               && encoding==doc.GetInfo(font.FontId, "/Encoding*:Name"));           font.SourceFonts.Add(sourceFontRef.SourceFont);         }         fontMap.Add(sourceFontId, sourceFontRef);       }       sourceFontRef.FontRef.References.Add(new Reference(pageId, resourceObj, fontIdPath));       return sourceFontRef;     }     private void ProcessText(Doc doc, bool report, XmlNode node,       SortedDictionary<int, SourceFontReference> fontMap,       Dictionary<FontIdentity, FontReference> uniqueFontMap,       SortedDictionary<int, SortedDictionary<int, SortedDictionary<int, Replacement>>> replaceMap,       ref int resourceStreamId, ref int resourcePageId,       Dictionary<string, FontResource> fontResources)     {       XmlAttribute attr = node.Attributes["pdf_Tf"];       if(attr==null)         return;       string resourceName = attr.Value;       int streamId;       if(!ParseInt32Attribute(node.Attributes["pdf_StreamID"], out streamId))         return;       if(streamId==0)         return;       int offset;       if(!ParseInt32Attribute(node.Attributes["pdf_StreamOffset"], out offset))         return;       int length;       if(!ParseInt32Attribute(node.Attributes["pdf_StreamLength"], out length))         return;       SortedDictionary<int, Replacement> streamReplaceMap = null;       SortedDictionary<int, SortedDictionary<int, Replacement>> fontResourceDictReplaceMap;       if(replaceMap.TryGetValue(streamId, out fontResourceDictReplaceMap)) {         if(fontResourceDictReplaceMap.TryGetValue(0, out streamReplaceMap)) {           if(streamReplaceMap.ContainsKey(offset))             return;         }       }       int pageId = 0;  // stream has its resource dictionary       if(streamReplaceMap==null) {         string resourceDict = doc.GetInfo(streamId, "/Resources*/Font");         if(resourceDict=="")           resourceDict = doc.GetInfo(streamId, "/Resources");         if(resourceDict=="") {           pageId = doc.Page;  // stream uses the page's resource dictionary           resourceDict = doc.GetInfo(pageId, "/Resources*/Font");           if(resourceDict=="")             resourceDict = doc.GetInfo(pageId, "/Resources");           if(resourceDict=="")             pageId = -1;  // failed         }         if(pageId!=0 && fontResourceDictReplaceMap!=null) {           if(fontResourceDictReplaceMap.TryGetValue(pageId, out streamReplaceMap))             if(streamReplaceMap.ContainsKey(offset))               return;         }       }       FontResource resource;       if(streamId!=resourceStreamId || pageId!=resourcePageId) {         resourceStreamId = streamId;         resourcePageId = pageId;         fontResources.Clear();         resource = new FontResource();       } else if(fontResources.TryGetValue(resourceName, out resource)) {         if(resource.Font==null)           return;       }       if(resource.Font==null) {         SourceFontReference sourceFontRef = GetSourceFont(doc, report, pageId,           pageId<=0? streamId: pageId, resourceName, fontMap, uniqueFontMap);         if(sourceFontRef.FontRef==null) {           fontResources.Add(resourceName, resource);           return;         }         resource.Font = sourceFontRef.FontRef.Font;         resource.SourceFont = sourceFontRef.SourceFont;         fontResources.Add(resourceName, resource);       }       Replacement replace = new Replacement();       replace.SourceFont = resource.SourceFont;       replace.Font = resource.Font;       replace.Offset = offset;       replace.Length = length;       string s;       if(node.ChildNodes.Count<=0 || node.ChildNodes[0].Name!="tspan") {         s = node.InnerText;         replace.Text = new List<string>(1);         replace.Text.Add(s);         replace.Font.SetNonSpaceChar(s);       } else {         replace.Text = new List<string>(node.ChildNodes.Count);         for(int i = 0; i<node.ChildNodes.Count; ++i) {           XmlNode child = node.ChildNodes[i];           if(child.Name=="tspan") {             s = child.InnerText;             replace.Text.Add(s);             replace.Font.SetNonSpaceChar(s);           }         }       }       if(streamReplaceMap==null) {         if(fontResourceDictReplaceMap==null) {           fontResourceDictReplaceMap = new SortedDictionary<int, SortedDictionary<int, Replacement>>();           replaceMap.Add(streamId, fontResourceDictReplaceMap);         }         streamReplaceMap = new SortedDictionary<int, Replacement>();         fontResourceDictReplaceMap.Add(pageId, streamReplaceMap);       }       streamReplaceMap.Add(offset, replace);     }     private static ReplacementFont AddReplaceFont(Doc doc, FontIdentity identity,       LanguageType language, bool embed, bool subset)     {       int fontId = AddDocFont(doc, identity, language, embed, subset);       return fontId==0? null: new ReplacementFont(identity,         language, fontId, subset);     }     private static int AddDocFont(Doc doc, FontIdentity identity,       LanguageType language, bool embed, bool subset)     {       Debug.Assert(embed || !subset && !TargetFont.GetEmbed(language));       return !embed? doc.AddFont(identity.Name, language, identity.Vertical):         doc.EmbedFont(identity.Name, language, identity.Vertical, subset);     }     private static FontIdentity NewFontIdentity(Doc doc, int id, string path,       out string outFontName, out string outEncoding)     {       string name = doc.GetInfo(id, path+"/DescendantFonts*[0]*/BaseFont*:Name");       if(name=="")         name = doc.GetInfo(id, path+"/BaseFont*:Name");       if(name=="") {         outFontName = null;         outEncoding = null;         return null;       }       outFontName = name;       if(HasEmbeddedSubsetPrefix(name))         name = name.Substring(7);       string encoding = doc.GetInfo(id, path+"/Encoding*:Name");       outEncoding = encoding;       bool vertical = encoding.Length>0? encoding[encoding.Length-1]=='V':         doc.GetInfo(id, path+"/Subtype*:Name")=="Type0"         && doc.GetInfoInt(id, path+"/Encoding*/WMode*:Num")==1;       return new FontIdentity(name, vertical);     }     private int AddText(Doc doc, ReplacementFont font, string text, out int outExtraCount) {       if(text[text.Length-1]!=' ') {         outExtraCount = 0;         return doc.AddText(text);       }       if(font.NonSpaceCode==null) {         if(font.NonSpaceChar==null)           font.NonSpaceChar = "0";         int id = doc.AddText(font.NonSpaceChar);         string s = doc.GetInfo(id, "Stream");         doc.Delete(id);         int i = s.IndexOfAny(_stringStart);         if(i<0)           throw new ApplicationException("Unable to find text operand.");         int iEnd = GetStringEnd(s, i, s.Length);         if(iEnd<0)           throw new ApplicationException("Unable to find text operand end.");         Debug.Assert(iEnd-i>2);         font.NonSpaceCode = s.Substring(i+1, iEnd-i-2);       }       outExtraCount = font.NonSpaceCode.Length;       return doc.AddText(text+font.NonSpaceChar);     }     private static bool ParseInt32Attribute(XmlAttribute attr, out int outV) {       if(attr==null) {         outV = 0;         return false;       }       return int.TryParse(attr.Value, NumberStyles.Integer,         NumberFormatInfo.InvariantInfo, out outV);     }     private static bool HasEmbeddedSubsetPrefix(string name) {       if(name.Length<7 || name[6]!='+')         return false;       for(int i = 0; i<6; ++i) {         if(name[i]<'A' || name[i]>'Z')           return false;       }       return true;     }     private static int GetStringEnd(string s, int i, int iEnd) {       if(s[i]=='<') {         ++i;         i = s.IndexOf('>', i, iEnd-i);         return i<0? i: i+1;       }       if(s[i]=='(') {         int level = 0;         bool escaped = false;         while(++i<iEnd) {           if(escaped)             escaped = false;           else {             switch(s[i]) {             case '\\': escaped = true; break;             case '(': ++level; break;             case ')':               if(level<=0)                 return i+1;               --level;               break;             }           }         }       }       return -1;     }   } }