using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using iTextSharp.text; using iTextSharp.text.pdf; using iTextSharp.text.pdf.fonts.cmaps; using PDFPatcher.Common; using PDFPatcher.Model; using FontId = System.Tuple; namespace PDFPatcher.Processor { sealed class ReplaceFontProcessor : IPageProcessor { static readonly string[] __LegacyFonts = ["宋体", "楷体_GB2312", "仿宋_GB2312", "黑体", "STSONG-LIGHT-GB-EUC-H", "STSONG-LIGHT-GBK-EUC-H"]; static readonly string[] __AlternativeFonts = ["宋体", "楷体", "仿宋", "微软雅黑", "宋体", "宋体"]; static readonly PdfName __GbkEncoding = new PdfName("GBK-EUC-H"); static readonly PdfName __GbEncoding = new PdfName("GB-EUC-H"); static readonly string[] __BuiltInEncodings = ["78-EUC-H", "78-EUC-V", "78-H", "78ms-RKSJ-H", "78ms-RKSJ-V", "78-RKSJ-H", "78-RKSJ-V", "78-V", "83pv-RKSJ-H", "90msp-RKSJ-H", "90msp-RKSJ-V", "90ms-RKSJ-H", "90ms-RKSJ-V", "90pv-RKSJ-H", "90pv-RKSJ-V", "Add-H", "Add-RKSJ-H", "Add-RKSJ-V", "Add-V", "Adobe-CNS1-0", "Adobe-CNS1-1", "Adobe-CNS1-2", "Adobe-CNS1-3", "Adobe-CNS1-4", "Adobe-CNS1-5", "Adobe-CNS1-6", "Adobe-GB1-0", "Adobe-GB1-1", "Adobe-GB1-2", "Adobe-GB1-3", "Adobe-GB1-4", "Adobe-GB1-5", "Adobe-Japan1-0", "Adobe-Japan1-1", "Adobe-Japan1-2", "Adobe-Japan1-3", "Adobe-Japan1-4", "Adobe-Japan1-5", "Adobe-Japan1-6", "Adobe-Korea1-0", "Adobe-Korea1-1", "Adobe-Korea1-2", "B5-H", "B5pc-H", "B5pc-V", "B5-V", "CNS1-H", "CNS1-V", "CNS2-H", "CNS2-V", "CNS-EUC-H", "CNS-EUC-V", "ETen-B5-H", "ETen-B5-V", "ETenms-B5-H", "ETenms-B5-V", "ETHK-B5-H", "ETHK-B5-V", "EUC-H", "EUC-V", "Ext-H", "Ext-RKSJ-H", "Ext-RKSJ-V", "Ext-V", "GB-EUC-H", "GB-EUC-V", "GB-H", "GBK2K-H", "GBK2K-V", "GBK-EUC-H", "GBK-EUC-V", "GBKp-EUC-H", "GBKp-EUC-V", "GBpc-EUC-H", "GBpc-EUC-V", "GBT-EUC-H", "GBT-EUC-V", "GBT-H", "GBTpc-EUC-H", "GBTpc-EUC-V", "GBT-V", "GB-V", "H", "Hankaku", "Hiragana", "HKdla-B5-H", "HKdla-B5-V", "HKdlb-B5-H", "HKdlb-B5-V", "HKgccs-B5-H", "HKgccs-B5-V", "HKm314-B5-H", "HKm314-B5-V", "HKm471-B5-H", "HKm471-B5-V", "HKscs-B5-H", "HKscs-B5-V", "Katakana", "KSC-EUC-H", "KSC-EUC-V", "KSC-H", "KSC-Johab-H", "KSC-Johab-V", "KSCms-UHC-H", "KSCms-UHC-HW-H", "KSCms-UHC-HW-V", "KSCms-UHC-V", "KSCpc-EUC-H", "KSCpc-EUC-V", "KSC-V", "NWP-H", "NWP-V", "RKSJ-H", "RKSJ-V", "Roman", "UniCNS-UCS2-H", "UniCNS-UCS2-V", "UniCNS-UTF16-H", "UniCNS-UTF16-V", "UniCNS-UTF32-H", "UniCNS-UTF32-V", "UniCNS-UTF8-H", "UniCNS-UTF8-V", "UniGB-UCS2-H", "UniGB-UCS2-V", "UniGB-UTF16-H", "UniGB-UTF16-V", "UniGB-UTF32-H", "UniGB-UTF32-V", "UniGB-UTF8-H", "UniGB-UTF8-V", "UniJIS2004-UTF16-H", "UniJIS2004-UTF16-V", "UniJIS2004-UTF32-H", "UniJIS2004-UTF32-V", "UniJIS2004-UTF8-H", "UniJIS2004-UTF8-V", "UniJISPro-UCS2-HW-V", "UniJISPro-UCS2-V", "UniJISPro-UTF8-V", "UniJIS-UCS2-H", "UniJIS-UCS2-HW-H", "UniJIS-UCS2-HW-V", "UniJIS-UCS2-V", "UniJIS-UTF16-H", "UniJIS-UTF16-V", "UniJIS-UTF32-H", "UniJIS-UTF32-V", "UniJIS-UTF8-H", "UniJIS-UTF8-V", "UniJISX02132004-UTF32-H", "UniJISX02132004-UTF32-V", "UniJISX0213-UTF32-H", "UniJISX0213-UTF32-V", "UniKS-UCS2-H", "UniKS-UCS2-V", "UniKS-UTF16-H", "UniKS-UTF16-V", "UniKS-UTF32-H", "UniKS-UTF32-V", "UniKS-UTF8-H", "UniKS-UTF8-V", "V", "WP-Symbol"]; static readonly string[] __GbEucEncodings = ["GBK-EUC-H", "GBK-EUC-V", "GBT-EUC-H", "GB-EUC-H", "GB-EUC-V"]; const string HalfWidthLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const string FullWidthLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const string HalfWidthNumbers = "0123456789"; const string FullWidthNumbers = "0123456789"; const string HorizontalPunctuations = ",、。:;!?〖〗…‥—–(){}〔〕【】《》〈〉「」『』[]"; const string VerticalPunctuations = "︐︑︒︓︔︕︖︗︘︙︰︱︲︵︶︷︸︹︺︻︼︽︾︿﹀﹁﹂﹃﹄﹇﹈"; readonly bool _embedLegacyFonts; readonly bool _trimTrailingWhiteSpace; NewFont _currentNewFont; FontInfo _currentFont; FontFactoryImp _fontFactory; Dictionary _newFonts; Dictionary _fontMap; Dictionary _fontNameIDMap; Dictionary _fontInfoMap; Dictionary _fontSubstitutions; Dictionary _fontRefIDMap; Dictionary> _fontDictMap; HashSet _bypassFonts, _processedForms; public ReplaceFontProcessor(bool embedLegacyFonts, bool trimTrailingWhiteSpace, Dictionary fontSubstitutions) { _embedLegacyFonts = embedLegacyFonts; _trimTrailingWhiteSpace = trimTrailingWhiteSpace; _fontSubstitutions = fontSubstitutions; } #region IPageProcessor 成员 public string Name => "嵌入汉字库"; public void BeginProcess(DocProcessorContext context) { _fontSubstitutions ??= []; var l = __LegacyFonts.Length + _fontSubstitutions.Count; _newFonts = new Dictionary(l); _fontMap = new Dictionary(l); _fontNameIDMap = []; _fontInfoMap = []; _fontFactory = new FontFactoryImp(); _fontRefIDMap = []; _fontDictMap = []; _bypassFonts = []; _processedForms = []; foreach (var item in FontHelper.GetInstalledFonts(true)) { try { _fontFactory.Register(item.Value, item.Key); } catch (Exception) { // ignore } } //_fontFactory.RegisterDirectory (Common.FontHelper.FontDirectory); } public bool EndProcess(PdfReader pdf) { // 用新的字体引用替代字体资源表的字体 foreach (var map in _fontDictMap) { var d = map.Key; foreach (var item in map.Value) { d.Put(item.Key, item.Value); } } SubSetFontData(pdf); return false; } public int EstimateWorkload(PdfReader pdf) { return pdf.NumberOfPages; } public bool Process(PageProcessorContext context) { Tracker.IncrementProgress(1); _currentFont = null; _currentNewFont = null; _fontNameIDMap.Clear(); _fontMap.Clear(); bool modified = false; if (ProcessPageContents(context)) { context.IsPageContentModified = modified = true; } ProcessResourceContents(context, context.Page.Locate(PdfName.RESOURCES, PdfName.XOBJECT), ref modified); var annots = context.Page.GetAsArray(PdfName.ANNOTS); if (annots != null && annots.Size != 0) { foreach (var item in annots) { if (PdfReader.GetPdfObjectRelease(item) is PdfDictionary annot) { ProcessResourceContents(context, annot, ref modified); } } } return modified; } bool ProcessPageContents(PageProcessorContext context) { var fonts = context.Page.Locate(PdfName.RESOURCES, PdfName.FONT); if (fonts == null) { return false; } LoadFonts(context, fonts); if (_fontMap.Count == 0) { return false; } return ProcessCommands(context.PageCommands.Commands); } void ProcessResourceContents(PageProcessorContext context, PdfDictionary container, ref bool modified) { if (container == null) { return; } foreach (var item in GetForms(container)) { if (_processedForms.Add(item.Ref.Number) == false || !(item.Resource is PRStream s)) { continue; } var resources = item.Resource.GetAsDict(PdfName.RESOURCES); if (resources == null || resources.Size == 0) { continue; } var fonts = resources.GetAsDict(PdfName.FONT); if (fonts == null || fonts.Size == 0) { continue; } LoadFonts(context, fonts); var cp = new PdfPageCommandProcessor(); cp.ProcessContent(PdfReader.GetStreamBytes(s), resources); if (ProcessCommands(cp.Commands)) { using (var ms = new MemoryStream()) { cp.WritePdfCommands(ms); ms.Flush(); s.SetData(ms.ToArray(), ms.Length > 100, 9); } modified = true; } } } #endregion bool ProcessCommands(IList parent) { var r = false; EnclosingCommand ec; var l = parent.Count; for (int i = 0; i < l; i++) { ec = parent[i] as EnclosingCommand; if (ec == null) { continue; } var n = ec.Name.ToString(); if (n == "BT") { foreach (var item in ec.Commands) { if (item.Type == PdfPageCommandType.Enclosure) { foreach (var sc in ((item as EnclosingCommand).Commands)) { ProcessTextCommand(sc); } } else if (ProcessTextCommand(item) == false) { continue; } } r = true; } else if (n == "BDC") { r |= ProcessCommands(ec.Commands); } else { var cnf = _currentNewFont; var cf = _currentFont; r |= ProcessCommands(ec.Commands); _currentNewFont = cnf; _currentFont = cf; } } return r; } bool ProcessTextCommand(PdfPageCommand item) { var cn = item.Name.ToString(); if (cn == "Tf") { var cf = item.Operands[0] as PdfName; if (_fontMap.TryGetValue(cf, out _currentNewFont) == false) { //Tracker.TraceMessage ("找不到字体:" + cf.ToString ()); _currentNewFont = null; } if (_fontNameIDMap.TryGetValue(cf, out int ni) == false || _fontInfoMap.TryGetValue(ni, out _currentFont) == false) { _currentFont = null; } } else if (item.Type == PdfPageCommandType.Text) { if (_currentNewFont == null || _currentFont == null) { return false; } RewriteTextOut(_currentNewFont, _currentFont, item); } return true; } void RewriteTextOut(NewFont ef, FontInfo fontInfo, PdfPageCommand cmd) { var ops = cmd.Operands; var f = ef.Font.BaseFont as TrueTypeFontUnicode; if (ops.Length == 0) { return; } var op = ops[0]; string t; if (op.Type == PdfObject.STRING) { t = fontInfo.DecodeText(op as PdfString); if (_trimTrailingWhiteSpace) { t = t.TrimEnd(); } ops[0] = RewriteText(ef, f, t); } else if (op.Type == PdfObject.ARRAY) { var a = op as PdfArray; var l = a.Size; for (int i = 0; i < l; i++) { op = a[i]; if (op.Type == PdfObject.STRING) { t = fontInfo.DecodeText(op as PdfString); if (_trimTrailingWhiteSpace/* && i == l - 1*/) { t = t.TrimEnd(); } a[i] = RewriteText(ef, f, t); } } } } static PdfString RewriteText(NewFont newFont, TrueTypeFontUnicode ttf, string text) { var cs = newFont.CharSubstitutions.Count > 0; var widths = newFont.GlyphWidths; var unicodeMap = newFont.UnicodeCidMap; using (var bb = new ByteBuffer()) { foreach (var ch in text) { if (cs == false || newFont.CharSubstitutions.TryGetValue(ch, out char c) == false) { c = ch; } if (unicodeMap.TryGetValue(c, out int cid) == false) { var tt = ttf.GetMetricsTT(c); if (tt == null) { newFont.AbsentChars.Add(c); continue; } cid = tt[0]; unicodeMap[c] = cid; if (widths.ContainsKey(cid) == false) { widths[cid] = tt[1]; } } bb.Append((byte)(cid >> 8)); bb.Append((byte)cid); } return new PdfString(bb.ToByteArray()); } } static bool DetectLegacyCjkFont(PdfDictionary font) { var en = font.GetAsName(PdfName.ENCODING); if (en == null || (PdfName.WIN_ANSI_ENCODING.Equals(en) || __GbkEncoding.Equals(en) || __GbEncoding.Equals(en)) == false ) { return false; } return PdfDocumentFont.HasEmbeddedFont(font) == false; } static IEnumerable GetForms(PdfDictionary container) { var visitedRefs = new HashSet(); return PdfModelHelper.GetReferencedResources(container, o => PdfName.FORM.Equals(o.GetAsName(PdfName.SUBTYPE)), visitedRefs); } void LoadFonts(PageProcessorContext context, PdfDictionary fonts) { var r = new Dictionary(fonts.Length); // 替代的字体 foreach (var item in fonts) { string sn; // 替换字体名称 string n; // 字体名称 bool v; // 是否竖排文字 if (item.Value is not PdfIndirectReference fr || _bypassFonts.Contains(fr.Number)) { continue; } if (_fontRefIDMap.TryGetValue(fr.Number, out NewFont nf) == false) { var f = fr.CastAs(); if (f == null) { goto BYPASSFONT; } var fn = f.GetAsName(PdfName.BASEFONT); if (fn == null) { goto BYPASSFONT; } n = PdfDocumentFont.RemoveSubsetPrefix(PdfHelper.GetPdfNameString(fn)); // 字体名称 var p = -1; FontSubstitution fs; if (_fontSubstitutions.TryGetValue(n, out fs)) { sn = fs.Substitution; } else { if (_embedLegacyFonts == false || DetectLegacyCjkFont(f) == false) { goto BYPASSFONT; } p = Array.IndexOf(__LegacyFonts, n.ToUpperInvariant()); if (p == -1) { goto BYPASSFONT; } sn = null; } v = f.GetAsName(PdfName.ENCODING)?.ToString().EndsWith("-V") ?? false; if (_newFonts.TryGetValue(new FontId(sn ?? n, v), out nf) == false) { try { Tracker.TraceMessage($"加载字体:{(v ? "@" : String.Empty)}{(sn != null ? $"{sn}(替换 {n})" : n)}"); if (sn != null) { n = sn; } string sf = null; foreach (var font in FontUtility.InstalledFonts) { if (font.DisplayName == n) { sf = font.OriginalName; break; } } nf = new NewFont { Font = _fontFactory.GetFont(sf ?? n, v ? BaseFont.IDENTITY_V : BaseFont.IDENTITY_H), FontRef = context.Pdf.AddPdfObject(new PdfDictionary()), DescendantFontRef = context.Pdf.AddPdfObject(new PdfArray()), Vertical = v, }; var fd = f.Locate(PdfName.DESCENDANTFONTS, 0, PdfName.FONTDESCRIPTOR); if (fd != null) { var num = fd.GetAsNumber(PdfName.ITALICANGLE)?.DoubleValue ?? 0d; if (num != 0) { nf.ItalicAngle = num; } } if (fs != null) { SetupFontSubstitutionMaps(nf, fs); } if (sn == null && p != -1 && nf.Font.BaseFont == null) { nf.Font = _fontFactory.GetFont(__AlternativeFonts[p], v ? BaseFont.IDENTITY_V : BaseFont.IDENTITY_H); } if (nf.Font.BaseFont == null) { throw new FileNotFoundException("无法加载字体:" + n); } _newFonts.Add(new FontId(n, v), nf); } catch (Exception) { Tracker.TraceMessage(Tracker.Category.Error, "无法加载字体"); throw; } } r[item.Key] = nf.FontRef; if (_fontInfoMap.ContainsKey(fr.Number) == false) { var fi = new FontInfo(f, fr.Number); _fontInfoMap.Add(fr.Number, fi); try { ReadSingleByteFontWidths(f, fi, nf); ReadCidFontWidths(f, fi, nf); } catch (NullReferenceException) { Tracker.TraceMessage(Tracker.Category.ImportantMessage, $"字体“{n}”的 CID 宽度表错误。"); } } _fontRefIDMap[nf.FontRef.Number] = nf; } _fontMap[item.Key] = nf; _fontNameIDMap[item.Key] = fr.Number; continue; BYPASSFONT: _bypassFonts.Add(fr.Number); } if (r.Count > 0) { _fontDictMap[fonts] = r; } } static void SetupFontSubstitutionMaps(NewFont nf, FontSubstitution fs) { if (fs.TraditionalChineseConversion != 0) { if (fs.TraditionalChineseConversion > 0) { Map(nf.CharSubstitutions, Constants.Chinese.Simplified, Constants.Chinese.Traditional); } else { Map(nf.CharSubstitutions, Constants.Chinese.Traditional, Constants.Chinese.Simplified); } } if (fs.NumericWidthConversion != 0) { if (fs.NumericWidthConversion > 0) { Map(nf.CharSubstitutions, HalfWidthNumbers, FullWidthNumbers); } else { Map(nf.CharSubstitutions, FullWidthNumbers, HalfWidthNumbers); } } if (fs.AlphabeticWidthConversion != 0) { if (fs.AlphabeticWidthConversion > 0) { Map(nf.CharSubstitutions, HalfWidthLetters, FullWidthLetters); } else { Map(nf.CharSubstitutions, FullWidthLetters, HalfWidthLetters); } } if (fs.PunctuationWidthConversion != 0) { if (fs.PunctuationWidthConversion > 0) { Map(nf.CharSubstitutions, SetCaseProcessor.HalfWidthPunctuations, SetCaseProcessor.FullWidthPunctuations); } else { Map(nf.CharSubstitutions, SetCaseProcessor.FullWidthPunctuations, SetCaseProcessor.HalfWidthPunctuations); } } if (fs.OriginalCharacters != null && fs.SubstituteCharacters != null) { var sl = fs.SubstituteCharacters.Length; for (int i = 0; i < fs.OriginalCharacters.Length; i++) { if (i >= sl) { break; } nf.CharSubstitutions[fs.OriginalCharacters[i]] = fs.SubstituteCharacters[i]; } } #region HACK: 将 iText 转码的横向标点转回竖向标点 if (nf.Vertical) { Map(nf.CharSubstitutions, HorizontalPunctuations, VerticalPunctuations); } #endregion } static void Map(Dictionary map, string from, string to) { var i = 0; foreach (var item in from) { map[item] = to[i++]; } } static void ReadSingleByteFontWidths(PdfDictionary font, FontInfo fontInfo, NewFont newFont) { var wl = font.GetAsArray(PdfName.WIDTHS); if (wl == null) { return; } var fc = font.TryGetInt32(PdfName.FIRSTCHAR, 0); var widths = newFont.GlyphWidths; foreach (PdfNumber item in wl) { if (item == null) { continue; } var s = fontInfo.Decode([(byte)fc], 0, 1); if (s.Length == 0) { continue; } int w; if (widths.TryGetValue(s[0], out w) == false || w == 0) { widths[s[0]] = item.IntValue; } ++fc; } } static void ReadCidFontWidths(PdfDictionary font, FontInfo fontInfo, NewFont newfont) { var w = newfont.Vertical ? PdfName.W2 : PdfName.W; var fw = font.GetAsArray(w); if (fw == null) { fw = font.Locate(PdfName.DESCENDANTFONTS, 0, w); if (fw == null) { return; } } var encoding = font.GetAsName(PdfName.ENCODING)?.ToString().Substring(1); CMapCidUni cMap = encoding != null && Array.BinarySearch(__BuiltInEncodings, encoding) >= 0 ? CMapCache.GetCachedCMapCidUni(encoding) : null; if (Array.IndexOf(__GbEucEncodings, encoding) != -1) { cMap = new GbkCidUni(cMap); } var l = fw.Size; PdfObject cw; int cid; var widths = newfont.GlyphWidths; for (int i = 0; i < l; i++) { cid = (fw[i] as PdfNumber).IntValue; if (++i >= l) { break; } cw = fw[i]; if (cw.Type == PdfObject.ARRAY) { foreach (var width in cw as PdfArray) { var u = fontInfo.DecodeCidToUnicode(cMap, cid); if (u == 0 && cid != 0) { Debug.WriteLine(cid.ToText() + "-无法解码CID"); continue; } widths[u] = (width as PdfNumber).IntValue; Debug.WriteLine(String.Join(" ", [cid.ToText(), u.ToText("X"), ((char)u).ToString(), widths[u].ToText()])); ++cid; } } else if (cw.Type == PdfObject.NUMBER) { var cid2 = (cw as PdfNumber).IntValue + 1; var width = (fw[++i] as PdfNumber).IntValue; do { var u = fontInfo.DecodeCidToUnicode(cMap, cid); if (u == 0 && cid != 0) { Debug.WriteLine(cid.ToText() + "-无法解码CID"); continue; } widths[u] = width; Debug.WriteLine(String.Join(" ", [cid.ToText(), u.ToText("X"), ((char)u).ToString(), width.ToText()])); } while (++cid < cid2); } } } static void ChangeLegacyFontDictionary(PdfReader pdf, NewFont font) { var f = PdfReader.GetPdfObject(font.FontRef) as PdfDictionary; f.Put(PdfName.TYPE, PdfName.FONT); f.Put(PdfName.SUBTYPE, PdfName.TYPE0); f.Put(PdfName.BASEFONT, new PdfName(font.FontName)); f.Put(PdfName.ENCODING, new PdfName(font.Vertical ? BaseFont.IDENTITY_V : BaseFont.IDENTITY_H)); f.Put(PdfName.DESCENDANTFONTS, font.DescendantFontRef); var metrics = new int[font.UnicodeCidMap.Count][]; var i = -1; foreach (var m in font.UnicodeCidMap) { metrics[++i] = [m.Value, 0, m.Key]; } var ttf = font.Font.BaseFont as TrueTypeFontUnicode; Array.Sort(metrics, ttf); var u = pdf.AddPdfObject(ttf.GetToUnicode(metrics)); f.Put(PdfName.TOUNICODE, u); } static void WriteCidWidths(NewFont font, PdfDictionary fontDictionary) { var l = font.GlyphWidths.Count; if (l == 0) { return; } var widths = new CharacterWidth[l]; var i = -1; int width; foreach (var item in font.GlyphWidths) { if (item.Value == FontInfo.DefaultDefaultWidth || font.UnicodeCidMap.TryGetValue(item.Key, out var cid) == false) { continue; } widths[++i] = new CharacterWidth(cid, item.Value); } l = ++i; Array.Resize(ref widths, l); Array.Sort(widths, CharacterWidth.Compare); var w = new PdfArray(); int id, id2; CharacterWidth cw; for (i = 0; i < l; i++) { id = widths[i].ID; w.Add(new PdfNumber(id)); width = widths[i].Width; var i2 = i; id2 = id; var wl = new PdfArray { new PdfNumber(width) }; while (++i2 < l && (cw = widths[i2]).ID == ++id2 && cw.Width != width) { wl.Add(new PdfNumber(cw.Width)); } if (wl.Size > 1) { w.Add(wl); i = i2 - 1; continue; } id2 = id; for (i2 = i + 1; i2 < l; i2++) { cw = widths[i2]; if (++id2 != cw.ID || cw.Width != width) { i2--; id2--; break; } } if (i2 > i) { w.Add(new PdfNumber(id2)); w.Add(new PdfNumber(width)); i = i2; } else { w.Add(wl); } } if (w.Size > 0) { fontDictionary.Put(font.Vertical ? PdfName.W2 : PdfName.W, w); } } void SubSetFontData(PdfReader pdf) { foreach (var font in _newFonts) { var newFont = font.Value; Tracker.TraceMessage($"嵌入字体:{newFont.Font.Familyname}({newFont.UnicodeCidMap.Count}字)"); if (newFont.AbsentChars.Count > 0) { Tracker.TraceMessage(Tracker.Category.ImportantMessage, $"丢失{newFont.AbsentChars.Count}字:{String.Concat(newFont.AbsentChars)}"); } ChangeLegacyFontDictionary(pdf, newFont); var ttf = newFont.Font.BaseFont as TrueTypeFontUnicode; var fa = PdfReader.GetPdfObject(newFont.DescendantFontRef) as PdfArray; var df = new PdfDictionary(); fa.Add(df); df.Put(PdfName.TYPE, PdfName.FONT); df.Put(PdfName.SUBTYPE, ttf.Cff ? PdfName.CIDFONTTYPE0 : PdfName.CIDFONTTYPE2); df.Put(PdfName.BASEFONT, new PdfName(newFont.FontName)); WriteCidWidths(newFont, df); var fs = pdf.AddPdfObject(SubsetFont(newFont, ttf)); var fd = ttf.GetFontDescriptor(fs, newFont.SubsetPrefix, null); if (newFont.ItalicAngle != 0) { fd.Put(PdfName.ITALICANGLE, newFont.ItalicAngle); fd.Put(PdfName.FLAGS, 1 << 6); } df.Put(PdfName.FONTDESCRIPTOR, pdf.AddPdfObject(fd)); var csi = new PdfDictionary(); csi.Put(PdfName.REGISTRY, new PdfString("Adobe")); csi.Put(PdfName.ORDERING, new PdfString("Identity")); csi.Put(PdfName.SUPPLEMENT, new PdfNumber(0)); df.Put(PdfName.CIDSYSTEMINFO, csi); } } static PdfStream SubsetFont(NewFont ef, TrueTypeFontUnicode ttf) { byte[] b; if (ttf.Cff) { int[] metricsTT; var d = new Dictionary(ef.UnicodeCidMap.Count); foreach (var item in ef.UnicodeCidMap) { metricsTT = ttf.GetMetricsTT(item.Key); d.Add(item.Value, new int[] { metricsTT[0], metricsTT[1], item.Key }); } var f = new CFFFontSubset(new RandomAccessFileOrArray(ttf.ReadCffFont()), d); b = f.Process(f.GetNames()[0]); } else { var r = new int[ef.UnicodeCidMap.Count]; ef.UnicodeCidMap.Values.CopyTo(r, 0); b = new TrueTypeFontSubSet(ttf.FileName, new RandomAccessFileOrArray(ttf.FileName), new System.util.collections.HashSet2(r), ttf.DirectoryOffset, true, true).Process(); } var s = new PdfStream(b); if (ttf.Cff) { s.Put(PdfName.SUBTYPE, new PdfName("CIDFontType0C")); } s.FlateCompress(); s.Put(PdfName.LENGTH1, new PdfNumber(b.Length)); return s; } [DebuggerDisplay("{ID},{Width}")] struct CharacterWidth(int id, int width) { public int ID = id, Width = width; public static int Compare(CharacterWidth x, CharacterWidth y) { return x.ID.CompareTo(y.ID); } } [DebuggerDisplay("{FontName}")] sealed class NewFont { Font _Font; public Dictionary FontDictionaries { get; } = []; public PRIndirectReference FontRef { get; set; } public PdfIndirectReference DescendantFontRef { get; set; } /// /// 字体 Unicode 到宽度的映射表。 /// public Dictionary GlyphWidths { get; } = []; /// /// 字体 Unicode 和 CID 的映射表。 /// public Dictionary UnicodeCidMap { get; } = []; public string SubsetPrefix { get; private set; } public string FontName => SubsetPrefix + _Font.Familyname; public HashSet AbsentChars { get; } = []; public Dictionary CharSubstitutions { get; } = []; public bool Vertical { get; set; } public double ItalicAngle { get; set; } public Font Font { get => _Font; set { _Font = value; SubsetPrefix = BaseFont.CreateSubsetPrefix(); } } } sealed class GbkCidUni(CMapCidUni baseCMap) : CMapCidUni { readonly CMapCidUni _BaseCMap = baseCMap; public override int Lookup(int character) { return character > 813 && character < 908 ? character - (814 - 0x21) : character == 7716 ? ' ' : _BaseCMap.Lookup(character); } } } }