mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
Merge branch 'master' of https://github.com/UglyToad/PdfPig
This commit is contained in:
43
examples/ExtractImages.cs
Normal file
43
examples/ExtractImages.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
namespace UglyToad.Examples
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using PdfPig;
|
||||
using PdfPig.Content;
|
||||
using PdfPig.XObjects;
|
||||
|
||||
internal static class ExtractImages
|
||||
{
|
||||
public static void Run(string filePath)
|
||||
{
|
||||
using (var document = PdfDocument.Open(filePath))
|
||||
{
|
||||
foreach (var page in document.GetPages())
|
||||
{
|
||||
foreach (var image in page.GetImages())
|
||||
{
|
||||
switch (image)
|
||||
{
|
||||
case XObjectImage ximg:
|
||||
byte[] b;
|
||||
try
|
||||
{
|
||||
b = ximg.Bytes.ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
b = ximg.RawBytes.ToArray();
|
||||
}
|
||||
|
||||
Console.WriteLine($"Image with {b.Length} bytes and dictionary {ximg.ImageDictionary}.");
|
||||
break;
|
||||
case InlineImage inline:
|
||||
Console.WriteLine($"Inline image: {inline.RawBytes.Count} bytes.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
examples/OpenDocumentAndExtractWords.cs
Normal file
55
examples/OpenDocumentAndExtractWords.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace UglyToad.Examples
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using PdfPig;
|
||||
using PdfPig.Content;
|
||||
|
||||
public static class OpenDocumentAndExtractWords
|
||||
{
|
||||
public static void Run(string filePath)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
using (var document = PdfDocument.Open(filePath))
|
||||
{
|
||||
Word previous = null;
|
||||
foreach (var page in document.GetPages())
|
||||
{
|
||||
foreach (var word in page.GetWords())
|
||||
{
|
||||
if (previous != null)
|
||||
{
|
||||
var hasInsertedWhitespace = false;
|
||||
var bothNonEmpty = previous.Letters.Count > 0 && word.Letters.Count > 0;
|
||||
if (bothNonEmpty)
|
||||
{
|
||||
var prevLetter1 = previous.Letters[0];
|
||||
var currentLetter1 = word.Letters[0];
|
||||
|
||||
var baselineGap = Math.Abs(prevLetter1.StartBaseLine.Y - currentLetter1.StartBaseLine.Y);
|
||||
|
||||
if (baselineGap > 3)
|
||||
{
|
||||
hasInsertedWhitespace = true;
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasInsertedWhitespace)
|
||||
{
|
||||
sb.Append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append(word.Text);
|
||||
|
||||
previous = word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
55
examples/Program.cs
Normal file
55
examples/Program.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace UglyToad.Examples
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
Console.WriteLine("Welcome to the PdfPig examples gallery!");
|
||||
|
||||
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
var filesDirectory = Path.Combine(baseDirectory, "..", "..", "..", "..", "src", "UglyToad.PdfPig.Tests", "Integration", "Documents");
|
||||
|
||||
var examples = new Dictionary<int, (string name, Action action)>
|
||||
{
|
||||
{1,
|
||||
("Extract Words with newline detection",
|
||||
() => OpenDocumentAndExtractWords.Run(Path.Combine(filesDirectory, "Two Page Text Only - from libre office.pdf")))
|
||||
},
|
||||
{2,
|
||||
("Extract images",
|
||||
() => ExtractImages.Run(Path.Combine(filesDirectory, "2006_Swedish_Touring_Car_Championship.pdf")))
|
||||
}
|
||||
};
|
||||
|
||||
var choices = string.Join(Environment.NewLine, examples.Select(x => $"{x.Key}: {x.Value.name}"));
|
||||
|
||||
Console.WriteLine(choices);
|
||||
Console.WriteLine();
|
||||
|
||||
do
|
||||
{
|
||||
Console.Write("Enter a number to pick an example (enter 'q' to exit):");
|
||||
|
||||
var val = Console.ReadLine();
|
||||
|
||||
if (!int.TryParse(val, out var opt) || !examples.TryGetValue(opt, out var act))
|
||||
{
|
||||
if (string.Equals(val, "q", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"No option with value: {val}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
act.action.Invoke();
|
||||
} while (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
examples/UglyToad.Examples.csproj
Normal file
11
examples/UglyToad.Examples.csproj
Normal file
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\src\UglyToad.PdfPig\UglyToad.PdfPig.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
55
examples/UglyToad.Examples.sln
Normal file
55
examples/UglyToad.Examples.sln
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.28307.1022
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.Examples", "UglyToad.Examples.csproj", "{F72DA3EE-FBED-4271-88CC-05D883FD4DAD}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.Core", "..\src\UglyToad.PdfPig.Core\UglyToad.PdfPig.Core.csproj", "{B12C9CFF-879B-4C70-8C7B-7DBF953608C5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.Fonts", "..\src\UglyToad.PdfPig.Fonts\UglyToad.PdfPig.Fonts.csproj", "{371FB56D-E9BE-40A2-8419-5F5F6F8FE0C0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.Tokenization", "..\src\UglyToad.PdfPig.Tokenization\UglyToad.PdfPig.Tokenization.csproj", "{E5BD532A-B3D0-4975-80CD-8B0B6D70FD17}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.Tokens", "..\src\UglyToad.PdfPig.Tokens\UglyToad.PdfPig.Tokens.csproj", "{50308C34-4074-4D36-AA93-57CFC812D831}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig", "..\src\UglyToad.PdfPig\UglyToad.PdfPig.csproj", "{75ED54D6-308F-44AD-B85E-C027F3AA80AE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F72DA3EE-FBED-4271-88CC-05D883FD4DAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F72DA3EE-FBED-4271-88CC-05D883FD4DAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F72DA3EE-FBED-4271-88CC-05D883FD4DAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F72DA3EE-FBED-4271-88CC-05D883FD4DAD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B12C9CFF-879B-4C70-8C7B-7DBF953608C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B12C9CFF-879B-4C70-8C7B-7DBF953608C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B12C9CFF-879B-4C70-8C7B-7DBF953608C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B12C9CFF-879B-4C70-8C7B-7DBF953608C5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{371FB56D-E9BE-40A2-8419-5F5F6F8FE0C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{371FB56D-E9BE-40A2-8419-5F5F6F8FE0C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{371FB56D-E9BE-40A2-8419-5F5F6F8FE0C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{371FB56D-E9BE-40A2-8419-5F5F6F8FE0C0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E5BD532A-B3D0-4975-80CD-8B0B6D70FD17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E5BD532A-B3D0-4975-80CD-8B0B6D70FD17}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E5BD532A-B3D0-4975-80CD-8B0B6D70FD17}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E5BD532A-B3D0-4975-80CD-8B0B6D70FD17}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{50308C34-4074-4D36-AA93-57CFC812D831}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{50308C34-4074-4D36-AA93-57CFC812D831}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{50308C34-4074-4D36-AA93-57CFC812D831}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{50308C34-4074-4D36-AA93-57CFC812D831}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{75ED54D6-308F-44AD-B85E-C027F3AA80AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{75ED54D6-308F-44AD-B85E-C027F3AA80AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{75ED54D6-308F-44AD-B85E-C027F3AA80AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{75ED54D6-308F-44AD-B85E-C027F3AA80AE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {03F738BD-74E0-4DE7-8063-0ACE16E931F1}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Fonts.AdobeFontMetrics
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UglyToad.PdfPig.Fonts.AdobeFontMetrics
|
||||
{
|
||||
using System;
|
||||
using System.Globalization;
|
||||
@@ -11,6 +13,9 @@
|
||||
/// </summary>
|
||||
public static class AdobeFontMetricsParser
|
||||
{
|
||||
private static readonly object Locker = new object();
|
||||
private static readonly Dictionary<string, string> CharacterNames = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// This is a comment in a AFM file.
|
||||
/// </summary>
|
||||
@@ -603,7 +608,18 @@
|
||||
}
|
||||
case CharmetricsN:
|
||||
{
|
||||
metric.Name = parts[1];
|
||||
lock (Locker)
|
||||
{
|
||||
var name = parts[1];
|
||||
|
||||
if (!CharacterNames.TryGetValue(name, out var cached))
|
||||
{
|
||||
cached = name;
|
||||
CharacterNames[name] = cached;
|
||||
}
|
||||
|
||||
metric.Name = cached;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CharmetricsB:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// <summary>
|
||||
/// The meaning of the metric sets field.
|
||||
/// </summary>
|
||||
public enum AdobeFontMetricsWritingDirections
|
||||
public enum AdobeFontMetricsWritingDirections : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Writing direction 0 only.
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
{
|
||||
private static readonly HashSet<string> Standard14Names = new HashSet<string>();
|
||||
private static readonly Dictionary<string, string> Standard14Mapping = new Dictionary<string, string>(34);
|
||||
private static readonly Dictionary<string, AdobeFontMetrics> Standard14AfmMap = new Dictionary<string, AdobeFontMetrics>(34);
|
||||
private static readonly Dictionary<Standard14Font, AdobeFontMetrics> Standard14AfmTypeMap = new Dictionary<Standard14Font, AdobeFontMetrics>(14);
|
||||
private static readonly Dictionary<Standard14Font, string> BuilderTypesToNames = new Dictionary<Standard14Font, string>(14);
|
||||
private static readonly Dictionary<string, AdobeFontMetrics> Standard14Cache = new Dictionary<string, AdobeFontMetrics>(34);
|
||||
|
||||
static Standard14()
|
||||
{
|
||||
@@ -88,9 +88,14 @@
|
||||
Standard14Names.Add(fontName);
|
||||
Standard14Mapping.Add(fontName, afmName);
|
||||
|
||||
if (Standard14AfmMap.TryGetValue(afmName, out var metrics))
|
||||
if (type.HasValue)
|
||||
{
|
||||
Standard14AfmMap[fontName] = metrics;
|
||||
BuilderTypesToNames[type.Value] = afmName;
|
||||
}
|
||||
|
||||
if (Standard14Cache.TryGetValue(afmName, out var metrics))
|
||||
{
|
||||
Standard14Cache[fontName] = metrics;
|
||||
}
|
||||
|
||||
try
|
||||
@@ -112,11 +117,7 @@
|
||||
bytes = new ByteArrayInputBytes(memory.ToArray());
|
||||
}
|
||||
|
||||
Standard14AfmMap[fontName] = AdobeFontMetricsParser.Parse(bytes, true);
|
||||
if (type.HasValue)
|
||||
{
|
||||
Standard14AfmTypeMap[type.Value] = Standard14AfmMap[fontName];
|
||||
}
|
||||
Standard14Cache[fontName] = AdobeFontMetricsParser.Parse(bytes, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -130,7 +131,7 @@
|
||||
/// </summary>
|
||||
public static AdobeFontMetrics GetAdobeFontMetrics(string baseName)
|
||||
{
|
||||
Standard14AfmMap.TryGetValue(baseName, out var metrics);
|
||||
Standard14Cache.TryGetValue(baseName, out var metrics);
|
||||
|
||||
return metrics;
|
||||
}
|
||||
@@ -140,7 +141,7 @@
|
||||
/// </summary>
|
||||
public static AdobeFontMetrics GetAdobeFontMetrics(Standard14Font fontType)
|
||||
{
|
||||
return Standard14AfmTypeMap[fontType];
|
||||
return Standard14Cache[BuilderTypesToNames[fontType]];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
@@ -16,7 +16,10 @@
|
||||
private const string SingleInkscapePage = "Single Page Simple - from inkscape";
|
||||
private const string MotorInsuranceClaim = "Motor Insurance claim form";
|
||||
private const string PigProduction = "Pig Production Handbook";
|
||||
|
||||
private const string SinglePage90ClockwiseRotation = "SinglePage90ClockwiseRotation - from PdfPig";
|
||||
private const string SinglePage180ClockwiseRotation = "SinglePage180ClockwiseRotation - from PdfPig";
|
||||
private const string SinglePage270ClockwiseRotation = "SinglePage270ClockwiseRotation - from PdfPig";
|
||||
|
||||
private static string GetFilename(string name)
|
||||
{
|
||||
var documentFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents"));
|
||||
@@ -95,6 +98,24 @@
|
||||
Run(ByzantineGenerals, 702);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SinglePage90ClockwiseRotationFromPdfPig()
|
||||
{
|
||||
Run(SinglePage90ClockwiseRotation, 595);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SinglePage180ClockwiseRotationFromPdfPig()
|
||||
{
|
||||
Run(SinglePage180ClockwiseRotation, 842);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SinglePage270ClockwiseRotationFromPdfPig()
|
||||
{
|
||||
Run(SinglePage270ClockwiseRotation, 595);
|
||||
}
|
||||
|
||||
private static void Run(string file, int imageHeight = 792)
|
||||
{
|
||||
var pdfFileName = GetFilename(file);
|
||||
@@ -105,14 +126,14 @@
|
||||
var page = document.GetPage(1);
|
||||
|
||||
var violetPen = new Pen(Color.BlueViolet, 1);
|
||||
var greenPen = new Pen(Color.Crimson, 1);
|
||||
var redPen = new Pen(Color.Crimson, 1);
|
||||
|
||||
using (var bitmap = new Bitmap(image))
|
||||
using (var graphics = Graphics.FromImage(bitmap))
|
||||
{
|
||||
foreach (var word in page.GetWords())
|
||||
{
|
||||
DrawRectangle(word.BoundingBox, graphics, greenPen, imageHeight);
|
||||
DrawRectangle(word.BoundingBox, graphics, redPen, imageHeight);
|
||||
}
|
||||
|
||||
foreach (var letter in page.Letters)
|
||||
|
||||
@@ -494,40 +494,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePdfA1BFile()
|
||||
[Theory]
|
||||
[InlineData(PdfAStandard.A1B)]
|
||||
[InlineData(PdfAStandard.A1A)]
|
||||
[InlineData(PdfAStandard.A2B)]
|
||||
[InlineData(PdfAStandard.A2A)]
|
||||
public void CanGeneratePdfAFile(PdfAStandard standard)
|
||||
{
|
||||
var builder = new PdfDocumentBuilder
|
||||
{
|
||||
ArchiveStandard = PdfAStandard.A1B
|
||||
};
|
||||
|
||||
var page = builder.AddPage(PageSize.A4);
|
||||
|
||||
var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf"));
|
||||
|
||||
page.AddText("Howdy!", 12, new PdfPoint(25, 670), font);
|
||||
|
||||
var bytes = builder.Build();
|
||||
|
||||
WriteFile(nameof(CanGeneratePdfA1BFile), bytes);
|
||||
|
||||
using (var pdf = PdfDocument.Open(bytes, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(1, pdf.NumberOfPages);
|
||||
|
||||
Assert.True(pdf.TryGetXmpMetadata(out var xmp));
|
||||
|
||||
Assert.NotNull(xmp.GetXDocument());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePdfA1AFile()
|
||||
{
|
||||
var builder = new PdfDocumentBuilder
|
||||
{
|
||||
ArchiveStandard = PdfAStandard.A1A
|
||||
ArchiveStandard = standard
|
||||
};
|
||||
|
||||
var page = builder.AddPage(PageSize.A4);
|
||||
@@ -537,39 +513,11 @@
|
||||
|
||||
var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf"));
|
||||
|
||||
page.AddText("Howdy PDF/A-1A!", 10, new PdfPoint(25, 700), font);
|
||||
page.AddText($"Howdy PDF/{standard}!", 10, new PdfPoint(25, 700), font);
|
||||
|
||||
var bytes = builder.Build();
|
||||
|
||||
WriteFile(nameof(CanGeneratePdfA1AFile), bytes);
|
||||
|
||||
using (var pdf = PdfDocument.Open(bytes, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(1, pdf.NumberOfPages);
|
||||
|
||||
Assert.True(pdf.TryGetXmpMetadata(out var xmp));
|
||||
|
||||
Assert.NotNull(xmp.GetXDocument());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePdfA2BFile()
|
||||
{
|
||||
var builder = new PdfDocumentBuilder
|
||||
{
|
||||
ArchiveStandard = PdfAStandard.A2B
|
||||
};
|
||||
|
||||
var page = builder.AddPage(PageSize.A4);
|
||||
|
||||
var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf"));
|
||||
|
||||
page.AddText("Howdy PDF/A-2B and welcome!", 10, new PdfPoint(25, 700), font);
|
||||
|
||||
var bytes = builder.Build();
|
||||
|
||||
WriteFile(nameof(CanGeneratePdfA2BFile), bytes);
|
||||
WriteFile(nameof(CanGeneratePdfAFile) + standard, bytes);
|
||||
|
||||
using (var pdf = PdfDocument.Open(bytes, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
|
||||
@@ -93,5 +93,45 @@
|
||||
"Expected object count to be lower than 24");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMergeWithObjectStream()
|
||||
{
|
||||
var first = IntegrationHelpers.GetDocumentPath("Single Page Simple - from google drive.pdf");
|
||||
var second = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
|
||||
|
||||
var result = PdfMerger.Merge(first, second);
|
||||
|
||||
WriteFile(nameof(CanMergeWithObjectStream), result);
|
||||
|
||||
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(7, document.NumberOfPages);
|
||||
|
||||
foreach (var page in document.GetPages())
|
||||
{
|
||||
Assert.NotNull(page.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteFile(string name, byte[] bytes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists("Merger"))
|
||||
{
|
||||
Directory.CreateDirectory("Merger");
|
||||
}
|
||||
|
||||
var output = Path.Combine("Merger", $"{name}.pdf");
|
||||
|
||||
File.WriteAllBytes(output, bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +57,13 @@
|
||||
|
||||
switch (str)
|
||||
{
|
||||
case "-1":
|
||||
token = NumericToken.MinusOne;
|
||||
return true;
|
||||
case "-":
|
||||
case ".":
|
||||
case "0":
|
||||
case "0000":
|
||||
token = NumericToken.Zero;
|
||||
return true;
|
||||
case "1":
|
||||
@@ -92,9 +96,42 @@
|
||||
case "10":
|
||||
token = NumericToken.Ten;
|
||||
return true;
|
||||
case "11":
|
||||
token = NumericToken.Eleven;
|
||||
return true;
|
||||
case "12":
|
||||
token = NumericToken.Twelve;
|
||||
return true;
|
||||
case "13":
|
||||
token = NumericToken.Thirteen;
|
||||
return true;
|
||||
case "14":
|
||||
token = NumericToken.Fourteen;
|
||||
return true;
|
||||
case "15":
|
||||
token = NumericToken.Fifteen;
|
||||
return true;
|
||||
case "16":
|
||||
token = NumericToken.Sixteen;
|
||||
return true;
|
||||
case "17":
|
||||
token = NumericToken.Seventeen;
|
||||
return true;
|
||||
case "18":
|
||||
token = NumericToken.Eighteen;
|
||||
return true;
|
||||
case "19":
|
||||
token = NumericToken.Nineteen;
|
||||
return true;
|
||||
case "20":
|
||||
token = NumericToken.Twenty;
|
||||
return true;
|
||||
case "100":
|
||||
token = NumericToken.OneHundred;
|
||||
return true;
|
||||
case "500":
|
||||
token = NumericToken.FiveHundred;
|
||||
return true;
|
||||
case "1000":
|
||||
token = NumericToken.OneThousand;
|
||||
return true;
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
/// </summary>
|
||||
public class NumericToken : IDataToken<decimal>
|
||||
{
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for -1.
|
||||
/// </summary>
|
||||
public static readonly NumericToken MinusOne = new NumericToken(-1);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 0.
|
||||
/// </summary>
|
||||
@@ -64,11 +69,66 @@
|
||||
/// </summary>
|
||||
public static readonly NumericToken Ten = new NumericToken(10);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 11.
|
||||
/// </summary>
|
||||
public static readonly NumericToken Eleven = new NumericToken(11);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 12.
|
||||
/// </summary>
|
||||
public static readonly NumericToken Twelve = new NumericToken(12);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 13.
|
||||
/// </summary>
|
||||
public static readonly NumericToken Thirteen = new NumericToken(13);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 14.
|
||||
/// </summary>
|
||||
public static readonly NumericToken Fourteen = new NumericToken(14);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 15.
|
||||
/// </summary>
|
||||
public static readonly NumericToken Fifteen = new NumericToken(15);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 16.
|
||||
/// </summary>
|
||||
public static readonly NumericToken Sixteen = new NumericToken(16);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 17.
|
||||
/// </summary>
|
||||
public static readonly NumericToken Seventeen = new NumericToken(17);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 18.
|
||||
/// </summary>
|
||||
public static readonly NumericToken Eighteen = new NumericToken(18);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 19.
|
||||
/// </summary>
|
||||
public static readonly NumericToken Nineteen = new NumericToken(19);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 20.
|
||||
/// </summary>
|
||||
public static readonly NumericToken Twenty = new NumericToken(20);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 100.
|
||||
/// </summary>
|
||||
public static readonly NumericToken OneHundred = new NumericToken(100);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 500.
|
||||
/// </summary>
|
||||
public static readonly NumericToken FiveHundred = new NumericToken(500);
|
||||
|
||||
/// <summary>
|
||||
/// Single instance of numeric token for 1000.
|
||||
/// </summary>
|
||||
|
||||
@@ -3,21 +3,23 @@
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using Core;
|
||||
using Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the rotation of a page in a PDF document defined by the page dictionary in degrees clockwise.
|
||||
/// </summary>
|
||||
public struct PageRotationDegrees : IEquatable<PageRotationDegrees>
|
||||
{
|
||||
private static readonly TransformationMatrix Rotate90 = TransformationMatrix.FromValues(0, -1, 1, 0);
|
||||
private static readonly TransformationMatrix Rotate180 = TransformationMatrix.FromValues(-1, 0, 0, -1);
|
||||
private static readonly TransformationMatrix Rotate270 = TransformationMatrix.FromValues(0, 1, -1, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The rotation of the page in degrees clockwise.
|
||||
/// </summary>
|
||||
public int Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the rotation flips the x and y axes.
|
||||
/// </summary>
|
||||
public bool SwapsAxis => (Value == 90) || (Value == 270);
|
||||
|
||||
/// <summary>
|
||||
/// Get the rotation expressed in radians (anti-clockwise).
|
||||
/// </summary>
|
||||
@@ -66,21 +68,60 @@
|
||||
}
|
||||
|
||||
[Pure]
|
||||
internal TransformationMatrix Rotate(TransformationMatrix matrix)
|
||||
internal PdfRectangle Rotate(PdfRectangle rectangle, PdfVector pageSize)
|
||||
{
|
||||
// TODO: this is a bit of a hack because I don't understand matrices
|
||||
/* There should be a single Affine Transform we can apply to any point resulting
|
||||
* from a content stream operation which will rotate the point and translate it back to
|
||||
* a point where the origin is in the page's lower left corner.
|
||||
*
|
||||
* For example this matrix represents a (clockwise) rotation and translation:
|
||||
* [ cos sin tx ]
|
||||
* [ -sin cos ty ]
|
||||
* [ 0 0 1 ]
|
||||
*
|
||||
* The values of tx and ty are those required to move the origin back to the expected origin (lower-left).
|
||||
* The corresponding values should be:
|
||||
* Rotation: 0 90 180 270
|
||||
* tx: 0 0 w w
|
||||
* ty: 0 h h 0
|
||||
*
|
||||
* Where w and h are the page width and height after rotation.
|
||||
*/
|
||||
double cos, sin;
|
||||
double dx = 0, dy = 0;
|
||||
switch (Value)
|
||||
{
|
||||
case 0:
|
||||
return matrix;
|
||||
return rectangle;
|
||||
case 90:
|
||||
return Rotate90.Multiply(matrix);
|
||||
cos = 0;
|
||||
sin = 1;
|
||||
dy = pageSize.Y;
|
||||
break;
|
||||
case 180:
|
||||
return Rotate180.Multiply(matrix);
|
||||
cos = -1;
|
||||
sin = 0;
|
||||
dx = pageSize.X;
|
||||
dy = pageSize.Y;
|
||||
break;
|
||||
case 270:
|
||||
return Rotate270.Multiply(matrix);
|
||||
cos = 0;
|
||||
sin = -1;
|
||||
dx = pageSize.X;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid value for rotation: {Value}.");
|
||||
}
|
||||
|
||||
PdfPoint Multiply(PdfPoint pt)
|
||||
{
|
||||
return new PdfPoint((pt.X * cos) + (pt.Y * sin) + dx,
|
||||
(pt.X * -sin) + (pt.Y * cos) + dy);
|
||||
}
|
||||
|
||||
return new PdfRectangle(Multiply(rectangle.TopLeft), Multiply(rectangle.TopRight),
|
||||
Multiply(rectangle.BottomLeft), Multiply(rectangle.BottomRight));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -3,31 +3,31 @@
|
||||
/// <summary>
|
||||
/// Direction of the text.
|
||||
/// </summary>
|
||||
public enum TextDirection
|
||||
public enum TextDirection : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Other text direction.
|
||||
/// </summary>
|
||||
Other,
|
||||
Other = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Usual text direction (Left to Right).
|
||||
/// </summary>
|
||||
Horizontal,
|
||||
Horizontal = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Horizontal text, upside down.
|
||||
/// </summary>
|
||||
Rotate180,
|
||||
Rotate180 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Rotated text going down.
|
||||
/// </summary>
|
||||
Rotate90,
|
||||
Rotate90 = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Rotated text going up.
|
||||
/// </summary>
|
||||
Rotate270
|
||||
Rotate270 = 4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,71 +81,93 @@
|
||||
return new PdfRectangle(polygon[0], polygon[1]);
|
||||
}
|
||||
|
||||
PdfPoint[] MBR = new PdfPoint[0];
|
||||
double[] MBR = new double[8];
|
||||
|
||||
double Amin = double.PositiveInfinity;
|
||||
int j = 1;
|
||||
int k = 0;
|
||||
|
||||
PdfPoint Q = new PdfPoint();
|
||||
PdfPoint R0 = new PdfPoint();
|
||||
PdfPoint R1 = new PdfPoint();
|
||||
PdfPoint u = new PdfPoint();
|
||||
double QX = double.NaN;
|
||||
double QY = double.NaN;
|
||||
double R0X = double.NaN;
|
||||
double R0Y = double.NaN;
|
||||
double R1X = double.NaN;
|
||||
double R1Y = double.NaN;
|
||||
|
||||
while (true)
|
||||
{
|
||||
PdfPoint Pk = polygon[k];
|
||||
PdfPoint v = polygon[j].Subtract(Pk);
|
||||
double r = 1.0 / v.DotProduct(v);
|
||||
PdfPoint Pj = polygon[j];
|
||||
|
||||
double vX = Pj.X - Pk.X;
|
||||
double vY = Pj.Y - Pk.Y;
|
||||
double r = 1.0 / (vX * vX + vY * vY);
|
||||
|
||||
double tmin = 1;
|
||||
double tmax = 0;
|
||||
double smax = 0;
|
||||
int l = -1;
|
||||
double uX;
|
||||
double uY;
|
||||
|
||||
for (j = 0; j < polygon.Count; j++)
|
||||
{
|
||||
PdfPoint Pj = polygon[j];
|
||||
u = Pj.Subtract(Pk);
|
||||
double t = u.DotProduct(v) * r;
|
||||
PdfPoint Pt = new PdfPoint(t * v.X + Pk.X, t * v.Y + Pk.Y);
|
||||
u = Pt.Subtract(Pj);
|
||||
double s = u.DotProduct(u);
|
||||
Pj = polygon[j];
|
||||
uX = Pj.X - Pk.X;
|
||||
uY = Pj.Y - Pk.Y;
|
||||
double t = (uX * vX + uY * vY) * r;
|
||||
|
||||
double PtX = t * vX + Pk.X;
|
||||
double PtY = t * vY + Pk.Y;
|
||||
uX = PtX - Pj.X;
|
||||
uY = PtY - Pj.Y;
|
||||
|
||||
double s = uX * uX + uY * uY;
|
||||
|
||||
if (t < tmin)
|
||||
{
|
||||
tmin = t;
|
||||
R0 = Pt;
|
||||
R0X = PtX;
|
||||
R0Y = PtY;
|
||||
}
|
||||
|
||||
if (t > tmax)
|
||||
{
|
||||
tmax = t;
|
||||
R1 = Pt;
|
||||
R1X = PtX;
|
||||
R1Y = PtY;
|
||||
}
|
||||
|
||||
if (s > smax)
|
||||
{
|
||||
smax = s;
|
||||
Q = Pt;
|
||||
QX = PtX;
|
||||
QY = PtY;
|
||||
l = j;
|
||||
}
|
||||
}
|
||||
|
||||
if (l != -1)
|
||||
{
|
||||
PdfPoint PlMinusQ = polygon[l].Subtract(Q);
|
||||
PdfPoint R2 = R1.Add(PlMinusQ);
|
||||
PdfPoint R3 = R0.Add(PlMinusQ);
|
||||
PdfPoint Pl = polygon[l];
|
||||
double PlMinusQX = Pl.X - QX;
|
||||
double PlMinusQY = Pl.Y - QY;
|
||||
|
||||
u = R1.Subtract(R0);
|
||||
double R2X = R1X + PlMinusQX;
|
||||
double R2Y = R1Y + PlMinusQY;
|
||||
|
||||
double A = u.DotProduct(u) * smax;
|
||||
double R3X = R0X + PlMinusQX;
|
||||
double R3Y = R0Y + PlMinusQY;
|
||||
|
||||
uX = R1X - R0X;
|
||||
uY = R1Y - R0Y;
|
||||
|
||||
double A = (uX * uX + uY * uY) * smax;
|
||||
|
||||
if (A < Amin)
|
||||
{
|
||||
Amin = A;
|
||||
MBR = new[] { R0, R1, R2, R3 };
|
||||
MBR = new[] { R0X, R0Y, R1X, R1Y, R2X, R2Y, R3X, R3Y };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +178,10 @@
|
||||
if (k == polygon.Count) break;
|
||||
}
|
||||
|
||||
return new PdfRectangle(MBR[2], MBR[3], MBR[1], MBR[0]);
|
||||
return new PdfRectangle(new PdfPoint(MBR[4], MBR[5]),
|
||||
new PdfPoint(MBR[6], MBR[7]),
|
||||
new PdfPoint(MBR[2], MBR[3]),
|
||||
new PdfPoint(MBR[0], MBR[1]));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
using Tokenization.Scanner;
|
||||
using Tokens;
|
||||
using XObjects;
|
||||
using static UglyToad.PdfPig.Core.PdfSubpath;
|
||||
using static PdfPig.Core.PdfSubpath;
|
||||
|
||||
internal class ContentStreamProcessor : IOperationContext
|
||||
{
|
||||
@@ -49,6 +49,7 @@
|
||||
private readonly IFilterProvider filterProvider;
|
||||
private readonly ILog log;
|
||||
private readonly bool clipPaths;
|
||||
private readonly PdfVector pageSize;
|
||||
private readonly MarkedContentStack markedContentStack = new MarkedContentStack();
|
||||
|
||||
private Stack<CurrentGraphicsState> graphicsStack = new Stack<CurrentGraphicsState>();
|
||||
@@ -88,7 +89,8 @@
|
||||
IPageContentParser pageContentParser,
|
||||
IFilterProvider filterProvider,
|
||||
ILog log,
|
||||
bool clipPaths)
|
||||
bool clipPaths,
|
||||
PdfVector pageSize)
|
||||
{
|
||||
this.resourceStore = resourceStore;
|
||||
this.userSpaceUnit = userSpaceUnit;
|
||||
@@ -98,6 +100,7 @@
|
||||
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
|
||||
this.log = log;
|
||||
this.clipPaths = clipPaths;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
// initiate CurrentClippingPath to cropBox
|
||||
var clippingSubpath = new PdfSubpath();
|
||||
@@ -175,7 +178,7 @@
|
||||
|
||||
// TODO: this does not seem correct, produces the correct result for now but we need to revisit.
|
||||
// see: https://stackoverflow.com/questions/48010235/pdf-specification-get-font-size-in-points
|
||||
var pointSize = Math.Round(rotation.Rotate(transformationMatrix).Multiply(TextMatrices.TextMatrix).Multiply(fontSize).A, 2);
|
||||
var pointSize = Math.Round(transformationMatrix.Multiply(TextMatrices.TextMatrix).Multiply(fontSize).A, 2);
|
||||
|
||||
if (pointSize < 0)
|
||||
{
|
||||
@@ -216,16 +219,18 @@
|
||||
}
|
||||
|
||||
var boundingBox = font.GetBoundingBox(code);
|
||||
|
||||
var transformedGlyphBounds = PerformantRectangleTransformer
|
||||
.Transform(renderingMatrix, textMatrix, transformationMatrix, boundingBox.GlyphBounds);
|
||||
|
||||
var transformedGlyphBounds = rotation.Rotate(transformationMatrix)
|
||||
.Transform(textMatrix
|
||||
.Transform(renderingMatrix
|
||||
.Transform(boundingBox.GlyphBounds)));
|
||||
var transformedPdfBounds = PerformantRectangleTransformer
|
||||
.Transform(renderingMatrix, textMatrix, transformationMatrix, new PdfRectangle(0, 0, boundingBox.Width, 0));
|
||||
|
||||
var transformedPdfBounds = rotation.Rotate(transformationMatrix)
|
||||
.Transform(textMatrix
|
||||
.Transform(renderingMatrix
|
||||
.Transform(new PdfRectangle(0, 0, boundingBox.Width, 0))));
|
||||
if (rotation.Value > 0)
|
||||
{
|
||||
transformedGlyphBounds = rotation.Rotate(transformedGlyphBounds, pageSize);
|
||||
transformedPdfBounds = rotation.Rotate(transformedPdfBounds, pageSize);
|
||||
}
|
||||
|
||||
// If the text rendering mode calls for filling, the current nonstroking color in the graphics state is used;
|
||||
// if it calls for stroking, the current stroking color is used.
|
||||
@@ -540,7 +545,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var currentState = this.GetCurrentState();
|
||||
var currentState = GetCurrentState();
|
||||
if (CurrentPath.IsStroked)
|
||||
{
|
||||
CurrentPath.LineDashPattern = currentState.LineDashPattern;
|
||||
@@ -572,6 +577,7 @@
|
||||
|
||||
CurrentPath = null;
|
||||
}
|
||||
|
||||
public void ModifyClippingIntersect(FillingRule clippingRule)
|
||||
{
|
||||
if (CurrentPath == null)
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
namespace UglyToad.PdfPig.Graphics
|
||||
{
|
||||
using PdfPig.Core;
|
||||
|
||||
internal static class PerformantRectangleTransformer
|
||||
{
|
||||
public static PdfRectangle Transform(TransformationMatrix first, TransformationMatrix second, TransformationMatrix third, PdfRectangle rectangle)
|
||||
{
|
||||
var tl = rectangle.TopLeft;
|
||||
var tr = rectangle.TopRight;
|
||||
var bl = rectangle.BottomLeft;
|
||||
var br = rectangle.BottomRight;
|
||||
|
||||
var topLeftX = tl.X;
|
||||
var topLeftY = tl.Y;
|
||||
|
||||
var topRightX = tr.X;
|
||||
var topRightY = tr.Y;
|
||||
|
||||
var bottomLeftX = bl.X;
|
||||
var bottomLeftY = bl.Y;
|
||||
|
||||
var bottomRightX = br.X;
|
||||
var bottomRightY = br.Y;
|
||||
|
||||
// First
|
||||
var x = first.A * topLeftX + first.C * topLeftY + first.E;
|
||||
var y = first.B * topLeftX + first.D * topLeftY + first.F;
|
||||
topLeftX = x;
|
||||
topLeftY = y;
|
||||
|
||||
x = first.A * topRightX + first.C * topRightY + first.E;
|
||||
y = first.B * topRightX + first.D * topRightY + first.F;
|
||||
topRightX = x;
|
||||
topRightY = y;
|
||||
|
||||
x = first.A * bottomLeftX + first.C * bottomLeftY + first.E;
|
||||
y = first.B * bottomLeftX + first.D * bottomLeftY + first.F;
|
||||
bottomLeftX = x;
|
||||
bottomLeftY = y;
|
||||
|
||||
x = first.A * bottomRightX + first.C * bottomRightY + first.E;
|
||||
y = first.B * bottomRightX + first.D * bottomRightY + first.F;
|
||||
bottomRightX = x;
|
||||
bottomRightY = y;
|
||||
|
||||
// Second
|
||||
x = second.A * topLeftX + second.C * topLeftY + second.E;
|
||||
y = second.B * topLeftX + second.D * topLeftY + second.F;
|
||||
topLeftX = x;
|
||||
topLeftY = y;
|
||||
|
||||
x = second.A * topRightX + second.C * topRightY + second.E;
|
||||
y = second.B * topRightX + second.D * topRightY + second.F;
|
||||
topRightX = x;
|
||||
topRightY = y;
|
||||
|
||||
x = second.A * bottomLeftX + second.C * bottomLeftY + second.E;
|
||||
y = second.B * bottomLeftX + second.D * bottomLeftY + second.F;
|
||||
bottomLeftX = x;
|
||||
bottomLeftY = y;
|
||||
|
||||
x = second.A * bottomRightX + second.C * bottomRightY + second.E;
|
||||
y = second.B * bottomRightX + second.D * bottomRightY + second.F;
|
||||
bottomRightX = x;
|
||||
bottomRightY = y;
|
||||
|
||||
// Third
|
||||
x = third.A * topLeftX + third.C * topLeftY + third.E;
|
||||
y = third.B * topLeftX + third.D * topLeftY + third.F;
|
||||
topLeftX = x;
|
||||
topLeftY = y;
|
||||
|
||||
x = third.A * topRightX + third.C * topRightY + third.E;
|
||||
y = third.B * topRightX + third.D * topRightY + third.F;
|
||||
topRightX = x;
|
||||
topRightY = y;
|
||||
|
||||
x = third.A * bottomLeftX + third.C * bottomLeftY + third.E;
|
||||
y = third.B * bottomLeftX + third.D * bottomLeftY + third.F;
|
||||
bottomLeftX = x;
|
||||
bottomLeftY = y;
|
||||
|
||||
x = third.A * bottomRightX + third.C * bottomRightY + third.E;
|
||||
y = third.B * bottomRightX + third.D * bottomRightY + third.F;
|
||||
bottomRightX = x;
|
||||
bottomRightY = y;
|
||||
|
||||
return new PdfRectangle(new PdfPoint(topLeftX, topLeftY), new PdfPoint(topRightX, topRightY),
|
||||
new PdfPoint(bottomLeftX, bottomLeftY), new PdfPoint(bottomRightX, bottomRightY));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,15 +47,15 @@
|
||||
log?.Error($"Page {number} had its type specified as {type} rather than 'Page'.");
|
||||
}
|
||||
|
||||
MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers);
|
||||
CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox);
|
||||
|
||||
var rotation = new PageRotationDegrees(pageTreeMembers.Rotation);
|
||||
if (dictionary.TryGet(NameToken.Rotate, pdfScanner, out NumericToken rotateToken))
|
||||
{
|
||||
rotation = new PageRotationDegrees(rotateToken.Int);
|
||||
}
|
||||
|
||||
MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers);
|
||||
CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox);
|
||||
|
||||
var stackDepth = 0;
|
||||
|
||||
while (pageTreeMembers.ParentResources.Count > 0)
|
||||
@@ -71,6 +71,19 @@
|
||||
resourceStore.LoadResourceDictionary(resources);
|
||||
stackDepth++;
|
||||
}
|
||||
|
||||
// Apply rotation.
|
||||
if (rotation.SwapsAxis)
|
||||
{
|
||||
mediaBox = new MediaBox(new PdfRectangle(mediaBox.Bounds.Bottom,
|
||||
mediaBox.Bounds.Left,
|
||||
mediaBox.Bounds.Top,
|
||||
mediaBox.Bounds.Right));
|
||||
cropBox = new CropBox(new PdfRectangle(cropBox.Bounds.Bottom,
|
||||
cropBox.Bounds.Left,
|
||||
cropBox.Bounds.Top,
|
||||
cropBox.Bounds.Right));
|
||||
}
|
||||
|
||||
UserSpaceUnit userSpaceUnit = GetUserSpaceUnits(dictionary);
|
||||
|
||||
@@ -108,7 +121,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths);
|
||||
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths, mediaBox);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -121,7 +134,7 @@
|
||||
|
||||
var bytes = contentStream.Decode(filterProvider);
|
||||
|
||||
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths);
|
||||
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths, mediaBox);
|
||||
}
|
||||
|
||||
var page = new Page(number, dictionary, mediaBox, cropBox, rotation, content,
|
||||
@@ -137,7 +150,7 @@
|
||||
}
|
||||
|
||||
private PageContent GetContent(int pageNumber, IReadOnlyList<byte> contentBytes, CropBox cropBox, UserSpaceUnit userSpaceUnit,
|
||||
PageRotationDegrees rotation, bool clipPaths)
|
||||
PageRotationDegrees rotation, bool clipPaths, MediaBox mediaBox)
|
||||
{
|
||||
var operations = pageContentParser.Parse(pageNumber, new ByteArrayInputBytes(contentBytes),
|
||||
log);
|
||||
@@ -146,7 +159,8 @@
|
||||
pageContentParser,
|
||||
filterProvider,
|
||||
log,
|
||||
clipPaths);
|
||||
clipPaths,
|
||||
new PdfVector(mediaBox.Bounds.Width, mediaBox.Bounds.Height));
|
||||
|
||||
return context.Process(pageNumber, operations);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
/// <summary>
|
||||
/// Compliance with PDF/A2-B. Level B (basic) conformance are standards necessary for the reliable reproduction of a document's visual appearance.
|
||||
/// </summary>
|
||||
A2B = 3
|
||||
A2B = 3,
|
||||
/// <summary>
|
||||
/// Compliance with PDF/A2-A. Level A (accessible) conformance are PDF/A2-B standards in addition to features intended to improve a document's accessibility.
|
||||
/// </summary>
|
||||
A2A = 4
|
||||
}
|
||||
}
|
||||
@@ -352,6 +352,9 @@ namespace UglyToad.PdfPig.Writer
|
||||
break;
|
||||
case PdfAStandard.A2B:
|
||||
break;
|
||||
case PdfAStandard.A2A:
|
||||
PdfA1ARuleBuilder.Obey(catalogDictionary);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,10 @@ namespace UglyToad.PdfPig.Writer.Xmp
|
||||
part = 2;
|
||||
conformance = "B";
|
||||
break;
|
||||
case PdfAStandard.A2A:
|
||||
part = 2;
|
||||
conformance = "A";
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(standard), standard, null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user