Files
PDFPatcher/App/Processor/ContentProcessors/PageDimensionProcessor.cs
T

339 lines
11 KiB
C#

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);
}
if (b == null) {
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(new float[]{
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 == false && (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;
if (odd && (filter & PageFilterFlag.Even) == PageFilterFlag.Even
|| (odd == false && (filter & PageFilterFlag.Odd) == PageFilterFlag.Odd)
) {
return false;
}
return true;
}
/// <summary>
/// 无损拉伸平移页面。
/// </summary>
/// <param name="pdf">PDF 文档。</param>
/// <param name="pageNumber">页码。</param>
/// <param name="ct">拉伸及平移参数。</param>
internal static byte[] ScaleContent(PdfReader pdf, int pageNumber, CoordinateTranslationSettings ct) {
var newContent = Encoding.ASCII.GetBytes(String.Join(" ", new string[] {
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(new double[] { 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) {
var an = PdfReader.GetPdfObject(item) as PdfDictionary;
if (an != null) {
var rect = an.GetAsArray(PdfName.RECT);
if (rect != null && rect.Size == 4) {
rect[0] = new PdfNumber((rect[0] as PdfNumber).FloatValue * ct.XScale + ct.XTranslation);
rect[1] = new PdfNumber((rect[1] as PdfNumber).FloatValue * ct.YScale + ct.YTranslation);
rect[2] = new PdfNumber((rect[2] as PdfNumber).FloatValue * ct.XScale + ct.XTranslation);
rect[3] = new PdfNumber((rect[3] as PdfNumber).FloatValue * 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(new float[] {
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.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) == false) {
return false;
}
if (_pageRanges != null && _pageRanges.IsInRange(context.PageNumber) == false) {
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;
ct = null;
}
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
}
}