mirror of
https://github.com/wmjordan/PDFPatcher.git
synced 2026-10-18 21:52:34 +08:00
339 lines
11 KiB
C#
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
|
|
}
|
|
}
|