using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Xml; using iTextSharp.text.pdf; using PDFPatcher.Common; using PDFPatcher.Model; namespace PDFPatcher.Processor { internal sealed class DocInfoExporter(PdfReader reader, ExporterOptions options) { const string SimpleBookmarkPageNumLeader = " ………… "; const int OpenDocWorkload = 10; const int BookmarkWorkload = 30; readonly PdfReader _reader = reader; readonly ExporterOptions _options = options; readonly PdfContentExport _contentExport = new PdfContentExport(options); readonly PdfActionExporter _actionExport = new PdfActionExporter(options.UnitConverter); Dictionary _pageReferenceMapper; Dictionary _namedDestinations; public FilePath BinaryStreamPath { get => _contentExport.BinaryStreamPath; set => _contentExport.BinaryStreamPath = value; } Dictionary PageReferenceMapper => _pageReferenceMapper ??= _reader.GetPageRefMapper(); static internal XmlWriterSettings GetWriterSettings() { return new XmlWriterSettings() { Encoding = AppContext.Exporter.GetEncoding(), Indent = true, IndentChars = "\t", CheckCharacters = false }; } internal int EstimateWorkload() { var n = _reader.NumberOfPages; var load = OpenDocWorkload; if (_options.ExportCatalog) { load += n / 100; } if (_options.ExportBookmarks) { load += BookmarkWorkload; } if (_options.ExtractPageLinks) { load += n; } if (_options.ExtractPageContent) { load += PageRangeCollection.Parse(_options.ExtractPageRange, 1, n, true).TotalPages; } if (_options.ExtractPageSettings) { load += n; } return load; } internal static GeneralInfo RewriteDocInfoWithEncoding(PdfReader pdf, Encoding encoding) { try { return RewriteDocInfoWithEncoding(pdf.Trailer.GetAsDict(PdfName.INFO), encoding); } catch (IOException) { // 忽略错误的元数据 return new GeneralInfo(); } } internal static GeneralInfo RewriteDocInfoWithEncoding(PdfDictionary info, Encoding encoding) { var r = new GeneralInfo(); if (info == null || info.Length == 0) { return r; } var dump = new PdfDictionary(); string t; PdfName n; PdfString s; foreach (var item in info) { s = item.Value as PdfString; if (s == null) { continue; } n = item.Key; t = s.Decode(encoding); if (PdfName.TITLE.Equals(n)) { r.Title = t; } else if (PdfName.AUTHOR.Equals(n)) { r.Author = t; } else if (PdfName.SUBJECT.Equals(n)) { r.Subject = t; } else if (PdfName.KEYWORDS.Equals(n)) { r.Keywords = t; } else if (PdfName.CREATOR.Equals(n)) { r.Creator = t; } else if (PdfName.PRODUCER.Equals(n)) { r.Producer = t; } dump.Put(n, t); } if (encoding != null) { info.Merge(dump); } dump = null; return r; } internal static void WriteDocumentInfoAttributes(XmlWriter w, string sourcePath, int numberOfPages) { w.WriteAttributeString(Constants.Info.ProductName, System.Windows.Forms.Application.ProductName); w.WriteAttributeString(Constants.Info.ProductVersion, Constants.InfoDocVersion); w.WriteAttributeString(Constants.Info.ExportDate, DateTime.Now.ToString("yyyy年MM月dd日 HH:mm:ss")); //w.WriteAttributeString (Constants.Info.DocumentName, Path.GetFileNameWithoutExtension (sourceFile)); w.WriteAttributeString(Constants.Info.DocumentPath, sourcePath); w.WriteAttributeString(Constants.Info.PageNumber, numberOfPages.ToText()); } internal void ExportDocument(XmlWriter w) { Tracker.IncrementProgress(OpenDocWorkload); if (_options.UnitConverter.UnitFactor != 1) { w.WriteStartElement(Constants.Units.ThisName); w.WriteAttributeString(Constants.Units.Unit, _options.UnitConverter.Unit); w.WriteEndElement(); } if (_options.ExportDocProperties) { Tracker.TraceMessage("导出文档信息。"); RewriteDocInfoWithEncoding(_reader, AppContext.Encodings.DocInfoEncoding); ExportDocumentInfo(w); } if (_options.ExportViewerPreferences) { Tracker.TraceMessage("导出阅读器设置。"); ExportViewerPreferences(w); } if (_options.ExportBookmarks || _options.ExtractPageLinks) { if (_options.ConsolidateNamedDestinations) { _reader.ConsolidateNamedDestinations(); } if (_options.ExportBookmarks) { Tracker.TraceMessage("导出书签。"); w.WriteStartElement(Constants.DocumentBookmark); ExportBookmarks(OutlineManager.GetBookmark(_reader, _options.UnitConverter), w); w.WriteEndElement(); Tracker.IncrementProgress(BookmarkWorkload); } if (_options.ExtractPageLinks) { Tracker.TraceMessage("导出页面连接。"); ExtractPageLinks(w); } if (_options.ConsolidateNamedDestinations == false) { Tracker.TraceMessage("导出命名目标。"); ExportNamedDestinations(w); } } if (_options.ExtractPageSettings) { Tracker.TraceMessage("导出页面设置。"); w.WriteStartElement(Constants.Content.PageSettings.ThisName); ExtractPageSettings(w); w.WriteEndElement(); } if (_options.ExportCatalog) { _contentExport.ExportTrailer(w, _reader); } if (_options.ExtractPageContent) { _contentExport.ExportContents(w, _reader); } Tracker.TraceMessage("完成导出任务。"); } internal void ExportDocumentInfo(XmlWriter w) { var info = _reader.Catalog; w.WriteStartElement(Constants.Info.ThisName); if (info.Contains(PdfName.VERSION)) w.WriteAttributeString(Constants.Version, PdfHelper.GetPdfNameString(info.GetAsName(PdfName.VERSION))); info = _reader.Trailer.GetAsDict(PdfName.INFO); if (info?.Length > 0) { string key, val; foreach (var item in info) { key = PdfName.DecodeName(item.Key.ToString()); val = item.Value.IsString() ? ((PdfString)item.Value).Decode(null) : item.Value.ToString(); switch (key) { case "Title": key = Constants.Info.Title; break; case "Author": key = Constants.Info.Author; break; case "Subject": key = Constants.Info.Subject; break; case "Keywords": key = Constants.Info.Keywords; break; case "Creator": key = Constants.Info.Creator; break; case "Producer": key = Constants.Info.Producer; break; case "CreationDate": key = Constants.Info.CreationDate; goto case "//DecodeDate"; case "ModDate": key = Constants.Info.ModDate; goto case "//DecodeDate"; case "//DecodeDate": try { val = PdfDate.Decode(val).ToString("yyyy年MM月dd日 HH:mm:ss"); } catch (Exception) { continue; } break; } w.WriteAttributeString(XmlConvert.EncodeLocalName(key), PdfHelper.GetValidXmlString(val)); } if (_reader.Metadata?.Length > 0) { w.WriteStartElement(Constants.Info.MetaData); using (MemoryStream ms = new MemoryStream(_reader.Metadata)) { var d = new XmlDocument(); d.Load(ms); d.DocumentElement?.WriteContentTo(w); } w.WriteEndElement(); } } w.WriteEndElement(); } internal static void WriteDocumentInfoAttributes(TextWriter w, string sourcePath, int numberOfPages) { w.WriteLine("#版本=" + Constants.InfoDocVersion); w.WriteLine("#" + Constants.Info.DocumentPath + "=" + sourcePath); w.WriteLine("#页数=" + numberOfPages); w.WriteLine(); } internal void ExportDocument(TextWriter w) { var i = RewriteDocInfoWithEncoding(_reader, AppContext.Encodings.DocInfoEncoding); OutlineManager.WriteSimpleBookmarkInstruction(w, Constants.Info.Title, PdfHelper.GetValidXmlString(i.Title)); OutlineManager.WriteSimpleBookmarkInstruction(w, Constants.Info.Author, PdfHelper.GetValidXmlString(i.Author)); OutlineManager.WriteSimpleBookmarkInstruction(w, Constants.Info.Subject, PdfHelper.GetValidXmlString(i.Subject)); OutlineManager.WriteSimpleBookmarkInstruction(w, Constants.Info.Keywords, PdfHelper.GetValidXmlString(i.Keywords)); OutlineManager.WriteSimpleBookmarkInstruction(w, Constants.Info.Creator, PdfHelper.GetValidXmlString(i.Creator)); OutlineManager.WriteSimpleBookmarkInstruction(w, Constants.Info.Producer, PdfHelper.GetValidXmlString(i.Producer)); } internal void ExportViewerPreferences(XmlWriter w) { var catalog = _reader.Catalog; if (catalog.Contains(PdfName.VIEWERPREFERENCES) || catalog.Contains(PdfName.PAGELAYOUT) || catalog.Contains(PdfName.PAGEMODE)) { w.WriteStartElement(Constants.ViewerPreferences); if (catalog.Contains(PdfName.PAGELAYOUT)) { w.WriteAttributeString(Constants.PageLayout, ValueHelper.MapValue(catalog.GetAsName(PdfName.PAGELAYOUT), Constants.PageLayoutType.PdfNames, Constants.PageLayoutType.Names)); } if (catalog.Contains(PdfName.PAGEMODE)) { w.WriteAttributeString(Constants.PageMode, ValueHelper.MapValue(catalog.GetAsName(PdfName.PAGEMODE), Constants.PageModes.PdfNames, Constants.PageModes.Names)); } if (catalog.Contains(PdfName.VIEWERPREFERENCES)) { ExportViewerPreferences(catalog.GetAsDict(PdfName.VIEWERPREFERENCES), w); } w.WriteEndElement(); } if (!catalog.Contains(PdfName.PAGELABELS)) { return; } var labels = ExtractPageLabels(catalog.GetAsDict(PdfName.PAGELABELS)); if (labels.Count > 0) { w.WriteStartElement(Constants.PageLabels); foreach (var item in labels) { w.WriteStartElement(Constants.PageLabelsAttributes.Style); w.WriteAttributeString(Constants.PageLabelsAttributes.PageNumber, item.PageNumber.ToText()); if (item.StartPage != 0) { w.WriteAttributeString(Constants.PageLabelsAttributes.StartPage, item.StartPage.ToText()); } if (String.IsNullOrEmpty(item.Prefix) == false) { w.WriteAttributeString(Constants.PageLabelsAttributes.Prefix, item.Prefix); } if (String.IsNullOrEmpty(item.Style) == false) { w.WriteAttributeString(Constants.PageLabelsAttributes.Style, ValueHelper.MapValue(item.Style[0], Constants.PageLabelStyles.PdfValues, Constants.PageLabelStyles.Names, item.Style) ); } w.WriteEndElement(); } w.WriteEndElement(); } } private static List ExtractPageLabels(PdfDictionary labels) { var a = new List(); var ls = labels.GetAsArray(PdfName.NUMS); if (ls == null) { return new List(); } for (int i = 0; i < ls.Size; i++) { var l = new PageLabel { PageNumber = ls.GetAsNumber(i++).IntValue + 1 }; var label = ls.GetAsDict(i); if (label.Contains(PdfName.ST)) { l.StartPage = label.GetAsNumber(PdfName.ST).IntValue; } if (label.Contains(PdfName.P)) { l.Prefix = label.GetAsString(PdfName.P).ToUnicodeString(); } if (label.Contains(PdfName.S)) { l.Style = PdfHelper.GetPdfNameString(label.GetAsName(PdfName.S)); } a.Add(l); l = null; } return a; } internal void ExtractPageSettings(XmlWriter w) { int n = _reader.NumberOfPages; PageSettings active = null, current; int fromP = 1, toP; for (int i = 1; i <= n; i++) { Tracker.IncrementProgress(1); current = PageSettings.FromReader(_reader, i, _options.UnitConverter); if (PageSettings.HavingSameDimension(active, current) && i != n) { continue; } if (i == 1) { active = current; if (n > i) { continue; } } toP = i == n ? n : i - 1; active.PageRange = (fromP != toP) ? String.Concat(fromP.ToText(), '-', toP.ToText()) : toP.ToText(); w.WriteStartElement(Constants.Content.Page); active.WriteXml(w); w.WriteEndElement(); fromP = i; active = current; } } private static void ExportViewerPreferences(PdfDictionary preferences, XmlWriter w) { foreach (var item in preferences) { if (item.Key.Equals(PdfName.TYPE)) { continue; } var itemName = ValueHelper.MapValue(item.Key, Constants.ViewerPreferencesType.PdfNames, Constants.ViewerPreferencesType.Names, PdfName.DecodeName(item.Key.ToString())); if (item.Value is PdfName nv) { if (PdfName.DIRECTION.Equals(item.Key)) { w.WriteAttributeString(Constants.ViewerPreferencesType.Direction, ValueHelper.MapValue(nv, Constants.ViewerPreferencesType.DirectionType.PdfNames, Constants.ViewerPreferencesType.DirectionType.Names)); } else { w.WriteAttributeString(itemName, PdfHelper.GetPdfFriendlyName(nv)); } } else if (item.Value.IsBoolean()) { w.WriteAttributeString( itemName, ((PdfBoolean)item.Value).BooleanValue ? Constants.Boolean.True : Constants.Boolean.False ); } else { w.WriteAttributeString(itemName, item.Value.ToString()); } } } internal void ExportNamedDestinations(XmlWriter w) { var nds = LoadNamedDestinations(); if (nds != null && nds.Count > 0) { w.WriteStartElement(Constants.NamedDestination); foreach (var item in nds) { w.WriteStartElement("位置"); w.WriteAttributeString(Constants.DestinationAttributes.Name, StringHelper.ReplaceControlAndBomCharacters(item.Key)); _actionExport.ExportGotoAction(item.Value as PdfObject, nds, w, PageReferenceMapper); w.WriteEndElement(); } w.WriteEndElement(); } } Dictionary LoadNamedDestinations() { return _namedDestinations ??= _reader.GetNamedDestinations(); } internal void ExportBookmarks(XmlElement bookmarks, TextWriter w, int level, bool isOpen) { if (bookmarks == null || bookmarks.HasChildNodes == false) { return; } var childBookmarks = bookmarks.SelectNodes(Constants.Bookmark); if (childBookmarks == null || childBookmarks.Count == 0) { return; } string title, page; bool open; foreach (XmlElement item in childBookmarks) { title = item.GetAttribute(Constants.BookmarkAttributes.Title); page = item.GetAttribute(Constants.DestinationAttributes.Page); open = item.GetAttribute(Constants.BookmarkAttributes.Open) == Constants.Boolean.True; if (open != isOpen && item.HasChildNodes) { OutlineManager.WriteSimpleBookmarkInstruction(w, "打开书签", open ? "是" : "否"); isOpen = open; } if (String.IsNullOrEmpty(title) == false) { for (int i = 0; i < level; i++) { w.Write('\t'); } w.Write(title.Replace('\n', ' ').Replace('\r', ' ')); w.Write(SimpleBookmarkPageNumLeader); w.WriteLine(page); } if (childBookmarks != null) { level++; ExportBookmarks(item, w, level, isOpen); level--; } } } /// /// 导出 PDF 书签。 /// /// /// internal void ExportBookmarks(XmlElement bookmarks, XmlWriter w) { if (bookmarks == null) { return; } foreach (XmlElement child in bookmarks.ChildNodes) { if (child == null) { continue; } w.WriteStartElement(Constants.Bookmark); foreach (XmlAttribute entry in child.Attributes) { var key = entry.Name as string; var value = entry.Value ?? String.Empty; switch (key) { case Constants.Coordinates.Bottom: case Constants.Coordinates.Left: case Constants.Coordinates.Top: case Constants.Coordinates.Right: if (String.IsNullOrEmpty(value) || value == "null") { continue; } goto default; case Constants.Coordinates.ScaleFactor: if (String.IsNullOrEmpty(value)) { continue; } goto default; case Constants.BookmarkAttributes.Title: w.WriteAttributeString(key, PdfHelper.GetValidXmlString(value)); break; default: w.WriteAttributeString(key, value); break; } } if (child.ChildNodes.Count > 0) { ExportBookmarks(child, w); } w.WriteEndElement(); } } /// /// 导出 PDF 文档页内连接。 /// /// /// internal void ExtractPageLinks(XmlWriter w) { w.WriteStartElement(Constants.PageLink); int numPages = _reader.NumberOfPages; for (int i = 1; i <= numPages; i++) { var pageDic = _reader.GetPageNRelease(i); Tracker.IncrementProgress(1); var annots = (PdfArray)PdfReader.GetPdfObject(pageDic.Get(PdfName.ANNOTS)); if (annots == null) { continue; } foreach (PdfObject item in annots.ArrayList) { if (item.IsNull()) { continue; } var annot = (PdfDictionary)PdfReader.GetPdfObjectRelease(item); if (PdfName.LINK.Equals(annot.Get(PdfName.SUBTYPE))) { w.WriteStartElement(Constants.PageLinkAttributes.Link); w.WriteAttributeString(Constants.PageLinkAttributes.PageNumber, i.ToText()); var rect = annot.GetAsArray(PdfName.RECT); if (rect != null && rect.Size == 4) { var u = _options.UnitConverter; var p = new float[4]; var k = 0; foreach (PdfNumber ri in rect.ArrayList) { if (ri == null) { break; } p[k] = u.FromPoint(ri.FloatValue); k++; } if (k == 4) { w.WriteAttributeString(Constants.Coordinates.Left, p[0].ToText()); w.WriteAttributeString(Constants.Coordinates.Bottom, p[1].ToText()); w.WriteAttributeString(Constants.Coordinates.Right, p[2].ToText()); w.WriteAttributeString(Constants.Coordinates.Top, p[3].ToText()); } } if (annot.Contains(PdfName.BORDER)) { w.WriteAttributeString(Constants.PageLinkAttributes.Border, PdfHelper.GetNumericArrayString(annot.GetAsArray(PdfName.BORDER), 1)); } if (annot.Contains(PdfName.C)) { ExportColor(annot.GetAsArray(PdfName.C), w); } if (annot.Contains(PdfName.H)) { var style = PdfHelper.GetPdfNameString(annot.GetAsName(PdfName.H)); style = ValueHelper.MapValue(style, ["N", "I", "O", "P"], ["无", "取反内容", "取反边框", "按下"], style ); w.WriteAttributeString(Constants.PageLinkAttributes.Style, style); } if (annot.Contains(PdfName.QUADPOINTS)) { w.WriteAttributeString(Constants.PageLinkAttributes.QuadPoints, PdfHelper.GetNumericArrayString(annot.GetAsArray(PdfName.QUADPOINTS), _options.UnitConverter.UnitFactor)); } if (annot.Contains(PdfName.CONTENTS)) { w.WriteAttributeString(Constants.PageLinkAttributes.Contents, annot.GetAsString(PdfName.CONTENTS).ToUnicodeString()); } ExportLinkAction(annot, w); if (annot.Contains(PdfName.BS)) { w.WriteStartElement("边框样式"); var bs = annot.GetAsDict(PdfName.BS); if (bs.Contains(PdfName.W)) { w.WriteAttributeString("宽度", bs.GetAsNumber(PdfName.W).FloatValue.ToText()); } if (bs.Contains(PdfName.S)) { var style = PdfHelper.GetPdfNameString(bs.GetAsName(PdfName.S)); style = ValueHelper.MapValue(style, ["S", "U", "D", "B", "I"], ["方框", "下划线", "虚线", "凸起", "凹陷"], style ); w.WriteAttributeString("样式", style); if (PdfName.D.Equals(bs.GetAsName(PdfName.S)) && bs.Contains(PdfName.D)) { w.WriteAttributeString("线型", PdfHelper.GetArrayString(bs.GetAsArray(PdfName.D))); } } w.WriteEndElement(); } w.WriteEndElement(); } } } w.WriteEndElement(); } internal static void ExportColor(PdfArray color, XmlWriter target) { if (color == null) { return; } switch (color.Size) { case 0: target.WriteAttributeString(Constants.Color, Constants.Colors.Transparent); break; case 1: target.WriteAttributeString(Constants.Colors.Gray, color.GetAsNumber(0).FloatValue.ToText()); break; case 3: target.WriteAttributeString(Constants.Colors.Red, color.GetAsNumber(0).FloatValue.ToText()); target.WriteAttributeString(Constants.Colors.Green, color.GetAsNumber(1).FloatValue.ToText()); target.WriteAttributeString(Constants.Colors.Blue, color.GetAsNumber(2).FloatValue.ToText()); break; case 4: target.WriteAttributeString(Constants.Colors.Cyan, color.GetAsNumber(0).FloatValue.ToText()); target.WriteAttributeString(Constants.Colors.Magenta, color.GetAsNumber(1).FloatValue.ToText()); target.WriteAttributeString(Constants.Colors.Yellow, color.GetAsNumber(2).FloatValue.ToText()); target.WriteAttributeString(Constants.Colors.Black, color.GetAsNumber(3).FloatValue.ToText()); break; } } /// /// 导出 PDF 文档的单个连接信息。 /// /// /// void ExportLinkAction(PdfDictionary link, XmlWriter w) { var dest = PdfReader.GetPdfObjectRelease(link.Get(PdfName.DEST)); var names = LoadNamedDestinations(); w.WriteStartElement(Constants.PageLinkAttributes.LinkAction); if (dest != null) { _actionExport.ExportGotoAction(dest, names, w, PageReferenceMapper); } else { _actionExport.ExportAction((PdfDictionary)PdfReader.GetPdfObjectRelease(link.Get(PdfName.A)), names, PageReferenceMapper, w); } w.WriteEndElement(); } } }