using System; using System.Text; using iTextSharp.text; using iTextSharp.text.pdf; using PDFPatcher.Common; using PDFPatcher.Model; namespace PDFPatcher.Processor { sealed class PageDimensionProcessor : IPageProcessor { CoordinateTranslationSettings[] _cts; bool _resizePages, _adjustMargins; PageRangeCollection _pageRanges; PaperSize _refPaperSize; public PageBoxSettings Settings { get; set; } internal static CoordinateTranslationSettings ResizePage(PdfDictionary page, PageBoxSettings settings, PaperSize refPaperSize) { var size = refPaperSize ?? settings.PaperSize.Clone(); var hAlign = settings.HorizontalAlign; var vAlign = settings.VerticalAlign; var pb = page.GetAsArray(PdfName.CROPBOX); var b = pb != null ? PdfReader.GetNormalizedRectangle(pb) : null; pb = page.GetAsArray(PdfName.MEDIABOX); if (pb == null) { throw new PdfException("页面缺少 MediaBox。"); } var mb = PdfReader.GetNormalizedRectangle(pb); var n = PdfHelper.GetPageRotation(page); if (n == 90 || n == 270) { size = new PaperSize(size.PaperName, size.Height, size.Width); } // 自动旋转页面适应原页面的方向 if (settings.AutoRotation && size.SpecialSize == SpecialPaperSize.None && (size.Width > size.Height) ^ (mb.Width > mb.Height)) { size = new PaperSize(size.PaperName, size.Height, size.Width); } b ??= new Rectangle(mb); float d, z = 1, zx = 1, zy = 1; float dx = 0, dy = 0; float sw = b.Width, sh = b.Height; // resized width and height if (size.SpecialSize == SpecialPaperSize.FixedWidthAutoHeight || size.SpecialSize == SpecialPaperSize.AsWidestPage || size.SpecialSize == SpecialPaperSize.AsNarrowestPage) { size.Height = b.Height * size.Width / b.Width; } if (settings.ScaleContent) { zx = size.Width / b.Width; zy = size.Height / b.Height; z = zx < zy ? zx : zy; sw *= z; sh *= z; b.Left *= z; b.Bottom *= z; b.Top *= z; b.Right *= z; } if (b.Width != size.Width) { d = size.Width - sw; dx = hAlign == HorizontalAlignment.Left ? 0 : hAlign == HorizontalAlignment.Right ? d : d / 2; b.Left -= dx; b.Right = b.Left + size.Width; } if (b.Height != size.Height) { d = size.Height - sh; dy = vAlign == VerticalAlignment.Bottom ? d : vAlign == VerticalAlignment.Top ? 0 : d / 2; b.Top += dy; b.Bottom = b.Top - size.Height; } var a = new float[] { b.Left, b.Bottom, b.Right, b.Top }; page.Put(PdfName.CROPBOX, new PdfArray(a)); ResizeBox(page, mb, b); if (page.GetAsArray(PdfName.BLEEDBOX) != null) { ResizeBox(page, PdfReader.GetNormalizedRectangle(page.GetAsArray(PdfName.BLEEDBOX)), b); } if (page.GetAsArray(PdfName.TRIMBOX) != null) { ResizeBox(page, PdfReader.GetNormalizedRectangle(page.GetAsArray(PdfName.TRIMBOX)), b); } if (page.GetAsArray(PdfName.ARTBOX) != null) { ResizeBox(page, PdfReader.GetNormalizedRectangle(page.GetAsArray(PdfName.ARTBOX)), b); } //if (p.Contains (PdfName.BLEEDBOX)) { // p.Put (PdfName.BLEEDBOX, pr); //} //if (p.Contains(PdfName.TRIMBOX)) { // p.Put (PdfName.TRIMBOX, pr); //} //if (p.Contains(PdfName.ARTBOX)) { // p.Put (PdfName.ARTBOX, pr); //} var ct = new CoordinateTranslationSettings(); if (settings.ScaleContent) { ct.XScale = ct.YScale = z; } else { ct.XTranslation = -dx; ct.YTranslation = -dy; } return ct; } static void ResizeBox(PdfDictionary page, Rectangle box, Rectangle refBox) { page.Put(PdfName.MEDIABOX, new PdfArray([ box.Left < refBox.Left ? box.Left : refBox.Left, box.Bottom < refBox.Bottom ? box.Bottom : refBox.Bottom, box.Right > refBox.Right ? box.Right : refBox.Right, box.Top > refBox.Top ? box.Top : refBox.Top ])); } static bool RotatePage(PdfDictionary page, PageBoxSettings settings) { if (settings.Rotation == 0) { return false; } var mb = page.GetPageVisibleRectangle(); if (mb == null) { throw new PdfException("页面缺少 MediaBox。"); } var ls = mb.Width > mb.Height; // Landscape if (ls && (settings.Filter & PageFilterFlag.Portrait) == PageFilterFlag.Portrait || !ls && (settings.Filter & PageFilterFlag.Landscape) == PageFilterFlag.Landscape) { return false; } var n = (PdfHelper.GetPageRotation(page) + settings.Rotation) % 360; if (n != 0) { page.Put(PdfName.ROTATE, n); } else { page.Remove(PdfName.ROTATE); } return true; } static bool FilterPageNumber(int pageNumber, PageFilterFlag filter) { var odd = (pageNumber & 1) > 0; return (!odd || (filter & PageFilterFlag.Even) != PageFilterFlag.Even) && (odd || (filter & PageFilterFlag.Odd) != PageFilterFlag.Odd); } /// /// 无损拉伸平移页面。 /// /// PDF 文档。 /// 页码。 /// 拉伸及平移参数。 internal static byte[] ScaleContent(PdfReader pdf, int pageNumber, CoordinateTranslationSettings ct) { var newContent = Encoding.ASCII.GetBytes(String.Join(" ", ct.XScale.ToText (), "0", "0", ct.YScale.ToText (), ct.XTranslation.ToText (), ct.YTranslation.ToText (), "cm " )); var cb = pdf.GetPageContent(pageNumber); Array.Resize(ref newContent, cb.Length + newContent.Length); cb.CopyTo(newContent, newContent.Length - cb.Length); pdf.SafeSetPageContent(pageNumber, newContent); var page = pdf.GetPageN(pageNumber); RewriteAnnotationCoordinates(ct, page); return newContent; } static void ScaleContent(PageProcessorContext context, CoordinateTranslationSettings ct) { var cmds = context.PageCommands.Commands; if (cmds.Count > 0 && cmds[0].Type == PdfPageCommandType.Matrix) { var c = cmds[0] as MatrixCommand; if (c.Name.ToString() == "cm") { c.Multiply([ct.XScale, 0, 0, ct.YScale, ct.XTranslation, ct.YTranslation]); } } else { cmds.Insert(0, new MatrixCommand(MatrixCommand.CM, ct.XScale, 0, 0, ct.YScale, ct.XTranslation, ct.YTranslation)); } RewriteAnnotationCoordinates(ct, context.Page); } static void RewriteAnnotationCoordinates(CoordinateTranslationSettings ct, PdfDictionary page) { var ann = page.GetAsArray(PdfName.ANNOTS); if (ann == null) { return; } foreach (var item in ann.ArrayList) { if (PdfReader.GetPdfObject(item) is PdfDictionary an) { var rect = an.GetAsArray(PdfName.RECT); if (rect != null && rect.Size == 4) { rect[0] = new PdfNumber((rect.GetAsNumber(0)?.FloatValue ?? 0) * ct.XScale + ct.XTranslation); rect[1] = new PdfNumber((rect.GetAsNumber(1)?.FloatValue ?? 0) * ct.YScale + ct.YTranslation); rect[2] = new PdfNumber((rect.GetAsNumber(2)?.FloatValue ?? 0) * ct.XScale + ct.XTranslation); rect[3] = new PdfNumber((rect.GetAsNumber(3)?.FloatValue ?? 0) * ct.YScale + ct.YTranslation); } } } } internal static void AdjustMargins(PdfDictionary page, Margins margins) { if (margins.IsRelative) { var box = page.GetAsArray(PdfName.CROPBOX) ?? page.GetAsArray(PdfName.MEDIABOX); var r = PdfReader.GetNormalizedRectangle(box); margins = new Margins(margins.Left * r.Width, margins.Top * r.Height, margins.Right * r.Width, margins.Bottom * r.Height); } AdjustBoxDimension(page, margins, PdfName.CROPBOX); AdjustBoxDimension(page, margins, PdfName.MEDIABOX); AdjustBoxDimension(page, margins, PdfName.BLEEDBOX); AdjustBoxDimension(page, margins, PdfName.TRIMBOX); AdjustBoxDimension(page, margins, PdfName.ARTBOX); } static void AdjustBoxDimension(PdfDictionary page, Margins margins, PdfName boxName) { var b = page.GetAsArray(boxName); if (b == null) { return; } var r = PdfReader.GetNormalizedRectangle(b); page.Put(boxName, new PdfArray([ r.Left - margins.Left, r.Bottom - margins.Bottom, r.Right + margins.Right, r.Top + margins.Top ])); } #region IPageProcessor 成员 public int EstimateWorkload(PdfReader pdf) { return pdf.NumberOfPages; } public void BeginProcess(DocProcessorContext context) { _resizePages = Settings.NeedResize; if (_resizePages) { context.ExtraData[DocProcessorContext.CoordinateTransition] = _cts = new CoordinateTranslationSettings[context.Pdf.NumberOfPages + 1]; } _adjustMargins = Settings.NeedAdjustMargins; _pageRanges = String.IsNullOrEmpty(Settings.PageRanges) ? null : PageRangeCollection.Parse(Settings.PageRanges, 1, context.Pdf.NumberOfPages, true); //todo 为新增加的适应拉伸模式设置参考尺寸 switch (Settings.PaperSize.SpecialSize) { case SpecialPaperSize.AsSpecificPage: break; case SpecialPaperSize.AsFirstPage: var r = context.Pdf.GetPageSizeWithRotation(1); _refPaperSize = new PaperSize(Settings.PaperSize.PaperName, r.Width, r.Height); break; case SpecialPaperSize.AsWidestPage: case SpecialPaperSize.AsNarrowestPage: case SpecialPaperSize.AsLargestPage: case SpecialPaperSize.AsSmallestPage: _refPaperSize = GetRefPaperSize(context); if (_refPaperSize == null) { throw new InvalidOperationException("无法匹配与指定尺寸及页码范围相符的页面。"); } break; } } private PaperSize GetRefPaperSize(DocProcessorContext context) { Rectangle refRectangle = null; var specialSize = Settings.PaperSize.SpecialSize; foreach (var range in _pageRanges ?? PageRangeCollection.CreateSingle(1, context.Pdf.NumberOfPages)) { foreach (var page in range) { var r = context.Pdf.GetPageSizeWithRotation(page); if (refRectangle == null) { refRectangle = r; continue; } switch (specialSize) { case SpecialPaperSize.AsWidestPage: if (r.Width > refRectangle.Width) { refRectangle = r; } break; case SpecialPaperSize.AsNarrowestPage: if (r.Width < refRectangle.Width) { refRectangle = r; } break; case SpecialPaperSize.AsLargestPage: if (r.Width * r.Height > refRectangle.Width * refRectangle.Height) { refRectangle = r; } break; case SpecialPaperSize.AsSmallestPage: if (r.Width * r.Height < refRectangle.Width * refRectangle.Height) { refRectangle = r; } break; } } } return refRectangle != null ? new PaperSize(Settings.PaperSize.PaperName, refRectangle.Width, specialSize != SpecialPaperSize.AsNarrowestPage && specialSize != SpecialPaperSize.AsWidestPage ? refRectangle.Height : 0) : null; } public bool Process(PageProcessorContext context) { var f = Settings.Filter; if (!FilterPageNumber(context.PageNumber, f) || _pageRanges != null && !_pageRanges.IsInRange(context.PageNumber)) { return false; } context.Pdf.ResetReleasePage(); if (_resizePages) { var ct = ResizePage(context.Page, Settings, _refPaperSize); if (Settings.ScaleContent) { ScaleContent(context, ct); context.IsPageContentModified = true; } _cts[context.PageNumber] = ct; } if (_adjustMargins) { AdjustMargins(context.Page, Settings.Margins); } if (Settings.Rotation != 0) { RotatePage(context.Page, Settings); } context.Pdf.ResetReleasePage(); return true; } public bool EndProcess(PdfReader pdf) { return false; } #endregion #region IProcessor 成员 public string Name => "修改页面尺寸"; #endregion } }