Skip to content

Commit b422cc2

Browse files
authored
Merge pull request #6 from kevbite/issue/5
Implement replacing a PDF page
2 parents ff2da3d + 141cf06 commit b422cc2

File tree

9 files changed

+252
-7
lines changed

9 files changed

+252
-7
lines changed

.dockerignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
**/.classpath
2+
**/.dockerignore
3+
**/.env
4+
**/.git
5+
**/.gitignore
6+
**/.project
7+
**/.settings
8+
**/.toolstarget
9+
**/.vs
10+
**/.vscode
11+
**/*.*proj.user
12+
**/*.dbmdl
13+
**/*.jfm
14+
**/azds.yaml
15+
**/bin
16+
**/charts
17+
**/docker-compose*
18+
**/Dockerfile*
19+
**/node_modules
20+
**/npm-debug.log
21+
**/obj
22+
**/secrets.dev.yaml
23+
**/values.dev.yaml
24+
LICENSE
25+
README.md
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
2+
3+
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
4+
WORKDIR /app
5+
EXPOSE 80
6+
EXPOSE 443
7+
8+
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
9+
WORKDIR /src
10+
COPY ["nuget.config", "."]
11+
COPY ["samples/WebApplicationFillForm/WebApplicationFillForm.csproj", "samples/WebApplicationFillForm/"]
12+
COPY ["src/Kevsoft.PDFtk/Kevsoft.PDFtk.csproj", "src/Kevsoft.PDFtk/"]
13+
RUN dotnet restore "samples/WebApplicationFillForm/WebApplicationFillForm.csproj"
14+
COPY . .
15+
WORKDIR "/src/samples/WebApplicationFillForm"
16+
RUN dotnet build "WebApplicationFillForm.csproj" -c Release -o /app/build
17+
18+
FROM build AS publish
19+
RUN dotnet publish "WebApplicationFillForm.csproj" -c Release -o /app/publish
20+
21+
FROM base AS final
22+
WORKDIR /app
23+
COPY --from=publish /app/publish .
24+
ENTRYPOINT ["dotnet", "WebApplicationFillForm.dll"]

samples/WebApplicationFillForm/Properties/launchSettings.json

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{
1+
{
22
"iisSettings": {
33
"windowsAuthentication": false,
44
"anonymousAuthentication": true,
@@ -17,12 +17,19 @@
1717
},
1818
"WebApplicationFillForm": {
1919
"commandName": "Project",
20-
"dotnetRunMessages": "true",
2120
"launchBrowser": true,
22-
"applicationUrl": "https://localhost:5001;http://localhost:5000",
2321
"environmentVariables": {
2422
"ASPNETCORE_ENVIRONMENT": "Development"
25-
}
23+
},
24+
"dotnetRunMessages": "true",
25+
"applicationUrl": "https://localhost:5001;http://localhost:5000"
26+
},
27+
"Docker": {
28+
"commandName": "Docker",
29+
"launchBrowser": true,
30+
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
31+
"publishAllPorts": true,
32+
"useSSL": true
2633
}
2734
}
28-
}
35+
}

samples/WebApplicationFillForm/WebApplicationFillForm.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
<PropertyGroup>
44
<TargetFramework>net5.0</TargetFramework>
5+
<UserSecretsId>58e1799c-a4c0-41aa-9399-ac70ecee6ba2</UserSecretsId>
6+
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
7+
<DockerfileContext>..\..</DockerfileContext>
58
</PropertyGroup>
69

710
<ItemGroup>
11+
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.13" />
812
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.2" />
913
</ItemGroup>
1014

src/Kevsoft.PDFtk/IPDFtk.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,5 +183,33 @@ Task<IPDFtkResult<byte[]>> FillFormAsync(string pdfFilePath,
183183
IReadOnlyDictionary<string, string> fieldData,
184184
bool flatten = true,
185185
bool dropXfa = false);
186+
187+
188+
/// <summary>
189+
/// Replaces a page in a PDF with another PDF
190+
/// </summary>
191+
/// <param name="fileBytes">A byte array of the PDF file input.</param>
192+
/// <param name="page">The page to replace</param>
193+
/// <param name="replacementFileBytes">A byte array of the PDF file to replace the page with.</param>
194+
/// <returns>A result with the PDF form filled as a byte array.</returns>
195+
Task<IPDFtkResult<byte[]>> ReplacePage(byte[] fileBytes, int page, byte[] replacementFileBytes);
196+
197+
/// <summary>
198+
/// Replaces a page in a PDF with another PDF
199+
/// </summary>
200+
/// <param name="pdfFile">A stream of the PDF file input.</param>
201+
/// <param name="page">The page to replace</param>
202+
/// <param name="replacementPdfFile">A stream of the PDF file to replace the page with.</param>
203+
/// <returns>A result with the PDF form filled as a byte array.</returns>
204+
Task<IPDFtkResult<byte[]>> ReplacePage(Stream pdfFile, int page, Stream replacementPdfFile);
205+
206+
/// <summary>
207+
/// Replaces a page in a PDF with another PDF
208+
/// </summary>
209+
/// <param name="pdfFilePath">A PDF file path input.</param>
210+
/// <param name="page">The page to replace</param>
211+
/// <param name="replacementFilePath">A PDF file path to replace the page with.</param>
212+
/// <returns>A result with the PDF form filled as a byte array.</returns>
213+
Task<IPDFtkResult<byte[]>> ReplacePage(string pdfFilePath, int page, string replacementFilePath);
186214
}
187215
}

src/Kevsoft.PDFtk/IPDFtkResult.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,7 @@ public interface IPDFtkResult<out TResult>
2626
/// Success flag of the execution of PDFtk.
2727
/// </summary>
2828
bool Success { get; }
29+
30+
internal ExecutionResult ExecutionResult { get; }
2931
}
3032
}

src/Kevsoft.PDFtk/PDFtk.cs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public PDFtk()
1919
: this(PDFtkOptions.Default())
2020
{
2121
}
22-
22+
2323
/// <inheritdoc cref="PDFtk()"/>
2424
/// <param name="options">The options to use.</param>
2525
public PDFtk(PDFtkOptions options)
@@ -350,5 +350,58 @@ private static async Task<IPDFtkResult<byte[]>> ResolveSingleFileExecutionResult
350350

351351
return new PDFtkResult<byte[]>(executeProcessResult, bytes);
352352
}
353+
354+
/// <inheritdoc/>
355+
public async Task<IPDFtkResult<byte[]>> ReplacePage(byte[] fileBytes, int page, byte[] replacementFileBytes)
356+
{
357+
using var inputFile = await TempPDFtkFile.FromAsync(fileBytes);
358+
using var stampFile = await TempPDFtkFile.FromAsync(replacementFileBytes);
359+
360+
return await ReplacePage(inputFile.TempFileName, page, stampFile.TempFileName);
361+
}
362+
363+
/// <inheritdoc/>
364+
public async Task<IPDFtkResult<byte[]>> ReplacePage(Stream pdfFile, int page, Stream replacementPdfFile)
365+
{
366+
using var inputFile = await TempPDFtkFile.FromAsync(pdfFile);
367+
using var stampFile = await TempPDFtkFile.FromAsync(replacementPdfFile);
368+
369+
return await ReplacePage(inputFile.TempFileName, page, stampFile.TempFileName);
370+
}
371+
372+
/// <inheritdoc/>
373+
public async Task<IPDFtkResult<byte[]>> ReplacePage(string pdfFilePath, int page, string replacementFilePath)
374+
{
375+
var numberOfPagesAsync = await GetNumberOfPagesAsync(pdfFilePath);
376+
if (!numberOfPagesAsync.Success)
377+
return new PDFtkResult<byte[]>(numberOfPagesAsync.ExecutionResult, Array.Empty<byte>());
378+
var totalPages = numberOfPagesAsync.Result;
379+
if (page <= 0 || page > totalPages)
380+
throw new ArgumentException($"Invalid page to replace, min page is 1 and maximum is {totalPages}");
381+
382+
using var outputFile = TempPDFtkFile.Create();
383+
384+
var bounds = (firstPage: page == 1, lastPage: page == totalPages) switch
385+
{
386+
(firstPage: true, lastPage: false) => new[] { "B", $"A{page + 1}-end" },
387+
(firstPage: false, lastPage: true) => new[] { $"A1-{page - 1}", "B" },
388+
_ => new[] { $"A1-{page - 1}", "B", $"A{page + 1}-end" },
389+
};
390+
391+
var args = new List<string>(8)
392+
{
393+
$"A={pdfFilePath}",
394+
$"B={replacementFilePath}",
395+
"cat"
396+
};
397+
args.AddRange(bounds);
398+
args.Add("output");
399+
args.Add(outputFile.TempFileName);
400+
var executeProcessResult = await _pdftkProcess.ExecuteAsync(
401+
args.ToArray()
402+
);
403+
404+
return await ResolveSingleFileExecutionResultAsync(executeProcessResult, outputFile);
405+
}
353406
}
354407
}

src/Kevsoft.PDFtk/PDFtkResult.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
internal sealed class PDFtkResult<TResult> : IPDFtkResult<TResult>
44
{
55
private readonly ExecutionResult _executionResult;
6-
6+
77
internal PDFtkResult(ExecutionResult executionResult, TResult result)
88
{
99
_executionResult = executionResult;
@@ -15,5 +15,7 @@ internal PDFtkResult(ExecutionResult executionResult, TResult result)
1515
public TResult Result { get; }
1616
public int ExitCode => _executionResult.ExitCode;
1717
public bool Success => ExitCode == 0;
18+
19+
ExecutionResult IPDFtkResult<TResult>.ExecutionResult => _executionResult;
1820
}
1921
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using FluentAssertions;
5+
using Xunit;
6+
7+
namespace Kevsoft.PDFtk.Tests
8+
{
9+
public class ReplacePageTests
10+
{
11+
private readonly PDFtk _pdFtk = new();
12+
13+
private const int FirstPage = 1;
14+
private const int LastPage = 10;
15+
private const int MiddlePage = 5;
16+
17+
[Theory]
18+
[InlineData(FirstPage)]
19+
[InlineData(LastPage)]
20+
[InlineData(MiddlePage)]
21+
public async Task ShouldReturnPdfWithReplacedPage_ForInputFilesAsBytes(int page)
22+
{
23+
var fileBytes = await File.ReadAllBytesAsync(TestFiles.TestFile1Path);
24+
var replacementPdfBytes = await File.ReadAllBytesAsync(TestFiles.TestFileWith2PagesPath);
25+
26+
var result = await _pdFtk.ReplacePage(fileBytes, page, replacementPdfBytes);
27+
28+
result.Success.Should().BeTrue();
29+
result.Result.Should().NotBeEmpty();
30+
31+
(await _pdFtk.GetNumberOfPagesAsync(result.Result)).Result.Should().Be(11);
32+
}
33+
34+
[Theory]
35+
[InlineData(FirstPage)]
36+
[InlineData(LastPage)]
37+
[InlineData(MiddlePage)]
38+
public async Task ShouldReturnPdfWithReplacedPage_ForInputFilesAsStreams(int page)
39+
{
40+
await using var inputFileStream = File.OpenRead(TestFiles.TestFile1Path);
41+
await using var stampFileStream = File.OpenRead(TestFiles.TestFileWith2PagesPath);
42+
43+
var result = await _pdFtk.ReplacePage(inputFileStream, page, stampFileStream);
44+
45+
result.Success.Should().BeTrue();
46+
result.Result.Should().NotBeEmpty();
47+
(await _pdFtk.GetNumberOfPagesAsync(result.Result)).Result.Should().Be(11);
48+
}
49+
50+
[Theory]
51+
[InlineData(FirstPage)]
52+
[InlineData(LastPage)]
53+
[InlineData(MiddlePage)]
54+
public async Task ShouldReturnPdfWithReplacedPage_ForInputFilesAsFilePaths(int page)
55+
{
56+
var result = await _pdFtk.ReplacePage(TestFiles.TestFile1Path, page, TestFiles.TestFileWith2PagesPath);
57+
58+
result.Success.Should().BeTrue();
59+
result.Result.Should().NotBeEmpty();
60+
(await _pdFtk.GetNumberOfPagesAsync(result.Result)).Result.Should().Be(11);
61+
}
62+
63+
[Fact]
64+
public async Task ShouldReturnPdfWithReplacedPage_ForInvalidPdfFile()
65+
{
66+
var fileBytes = Guid.NewGuid().ToByteArray();
67+
var replacementFileBytes = await File.ReadAllBytesAsync(TestFiles.StampFilePath);
68+
69+
var result = await _pdFtk.ReplacePage(fileBytes, 1, replacementFileBytes);
70+
71+
result.Success.Should().BeFalse();
72+
result.Result.Should().BeEmpty();
73+
}
74+
75+
[Fact]
76+
public async Task ShouldReturnPdfWithReplacedPage_ForReplacementPdfFile()
77+
{
78+
var fileBytes = await File.ReadAllBytesAsync(TestFiles.TestFile1Path);
79+
var replacementFileBytes = Guid.NewGuid().ToByteArray();
80+
81+
var result = await _pdFtk.ReplacePage(fileBytes, 1, replacementFileBytes);
82+
83+
result.Success.Should().BeFalse();
84+
result.Result.Should().BeEmpty();
85+
}
86+
87+
[Theory]
88+
[InlineData(0)]
89+
[InlineData(11)]
90+
public async Task ShouldThrowExceptionWhenPageIsOutOfBounds(int page)
91+
{
92+
var fileBytes = await File.ReadAllBytesAsync(TestFiles.TestFile1Path);
93+
var replacementPdfBytes = await File.ReadAllBytesAsync(TestFiles.TestFileWith2PagesPath);
94+
95+
Func<Task> act = (async () => await _pdFtk.ReplacePage(fileBytes, page, replacementPdfBytes));
96+
97+
await act.Should().ThrowAsync<ArgumentException>();
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)