This commit is contained in:
DJGosnell 2023-11-17 15:08:14 -05:00
Родитель 7ddf6b1519
Коммит f4904f48d1
15 изменённых файлов: 477 добавлений и 84 удалений

Просмотреть файл

@ -12,7 +12,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="2.*" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.6" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -12,8 +12,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
</ItemGroup>

Просмотреть файл

@ -0,0 +1,36 @@
using System.IO;
using System.Threading.Tasks;
using NUnit.Framework;
namespace DtronixPdf.Tests
{
public class PdfAnnotationTests
{
[Test]
public void LoadsDocument()
{
using var document = PdfDocument.Load("TestPdf.pdf", null);
Assert.AreEqual(1, document.Pages);
}
[Test]
public void LoadsMemoryDocument()
{
using var stream = File.OpenRead("TestPdf.pdf");
using var document = PdfDocument.Load(stream, null);
Assert.AreEqual(1, document.Pages);
}
[Test]
public void SavesDocument()
{
using var document = PdfDocument.Load("TestPdf.pdf", null);
using var sw = new MemoryStream();
document.Save(sw);
Assert.Greater(sw.Length, 10000);
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
namespace DtronixPdf.Annotations;
/// <summary>
/// Specifies the appearance mode for an annotation in a PDF document.
/// </summary>
public enum AnnotationAppearanceMode
{
/// <summary>
/// The annotation appears in its normal state.
/// </summary>
Normal = 0,
/// <summary>
/// The annotation appears in a rollover state.
/// </summary>
Rollover = 1,
/// <summary>
/// The annotation appears in a pressed down state.
/// </summary>
Down = 2,
/// <summary>
/// Represents the count of appearance modes.
/// </summary>
Count = 3
}

Просмотреть файл

@ -0,0 +1,63 @@
using System;
namespace DtronixPdf.Annotations;
/// <summary>
/// Defines the flags for PDF annotations.
/// </summary>
/// <remarks>
/// The flags are used to control the visibility and behavior of annotations.
/// </remarks>
[Flags]
public enum AnnotationFlag
{
/// <summary>
/// Represents the default state.
/// </summary>
None = 0,
/// <summary>
/// Represents a state where the object is invisible.
/// </summary>
Invisible = 1 << 0,
/// <summary>
/// Represents a state where the object is hidden.
/// </summary>
Hidden = 1 << 1,
/// <summary>
/// Represents a state where the object is printable.
/// </summary>
Print = 1 << 2,
/// <summary>
/// Represents a state where the object cannot be zoomed.
/// </summary>
NoZoom = 1 << 3,
/// <summary>
/// Represents a state where the object cannot be rotated.
/// </summary>
NoRotate = 1 << 4,
/// <summary>
/// Represents a state where the object cannot be viewed.
/// </summary>
NoView = 1 << 5,
/// <summary>
/// Represents a state where the object is read-only.
/// </summary>
Readonly = 1 << 6,
/// <summary>
/// Represents a state where the object is locked.
/// </summary>
Locked = 1 << 7,
/// <summary>
/// Represents a state where the object's viewability can be toggled.
/// </summary>
ToggleNoView = 1 << 8
}

Просмотреть файл

@ -0,0 +1,157 @@
namespace DtronixPdf.Annotations;
/// <summary>
/// Represents the types of annotations that can be used in a PDF document.
/// </summary>
/// <remarks>
/// Each member of this enumeration corresponds to a specific type of annotation, such as text, link, freetext, etc.
/// </remarks>
public enum AnnotationType
{
/// <summary>
/// Represents an unknown annotation type.
/// </summary>
Unknown = 0,
/// <summary>
/// Represents a text annotation.
/// </summary>
Text = 1,
/// <summary>
/// Represents a link annotation.
/// </summary>
Link = 2,
/// <summary>
/// Represents a free text annotation.
/// </summary>
FreeText = 3,
/// <summary>
/// Represents a line annotation.
/// </summary>
Line = 4,
/// <summary>
/// Represents a square annotation.
/// </summary>
Square = 5,
/// <summary>
/// Represents a circle annotation.
/// </summary>
Circle = 6,
/// <summary>
/// Represents a polygon annotation.
/// </summary>
Polygon = 7,
/// <summary>
/// Represents a polyline annotation.
/// </summary>
Polyline = 8,
/// <summary>
/// Represents a highlight annotation.
/// </summary>
Highlight = 9,
/// <summary>
/// Represents an underline annotation.
/// </summary>
Underline = 10,
/// <summary>
/// Represents a squiggly underline annotation.
/// </summary>
Squiggly = 11,
/// <summary>
/// Represents a strikeout annotation.
/// </summary>
Strikeout = 12,
/// <summary>
/// Represents a stamp annotation.
/// </summary>
Stamp = 13,
/// <summary>
/// Represents a caret annotation.
/// </summary>
Caret = 14,
/// <summary>
/// Represents an ink annotation.
/// </summary>
Ink = 15,
/// <summary>
/// Represents a popup annotation.
/// </summary>
Popup = 16,
/// <summary>
/// Represents a file attachment annotation.
/// </summary>
FileAttachment = 17,
/// <summary>
/// Represents a sound annotation.
/// </summary>
Sound = 18,
/// <summary>
/// Represents a movie annotation.
/// </summary>
Movie = 19,
/// <summary>
/// Represents a widget annotation.
/// </summary>
Widget = 20,
/// <summary>
/// Represents a screen annotation.
/// </summary>
Screen = 21,
/// <summary>
/// Represents a printer mark annotation.
/// </summary>
PrinterMark = 22,
/// <summary>
/// Represents a trapnet annotation.
/// </summary>
TrapNet = 23,
/// <summary>
/// Represents a watermark annotation.
/// </summary>
Watermark = 24,
/// <summary>
/// Represents a 3D annotation.
/// </summary>
ThreeD = 25,
/// <summary>
/// Represents a rich media annotation.
/// </summary>
RichMedia = 26,
/// <summary>
/// Represents an XFA widget annotation.
/// </summary>
XfaWidget = 27,
/// <summary>
/// Represents a redact annotation.
/// </summary>
Redact = 28,
}

Просмотреть файл

@ -0,0 +1,49 @@
using System;
using System.Reflection.Metadata;
using System.Threading;
using DtronixCommon;
using PDFiumCore;
namespace DtronixPdf.Annotations;
public abstract class PdfAnnotationBase : IDisposable
{
protected readonly PdfPage Page;
protected readonly AnnotationType Type;
protected readonly FpdfAnnotationT Annotation;
private bool _isDisposed = false;
protected PdfAnnotationBase(PdfPage page, AnnotationType type, FpdfAnnotationT annotation)
{
Page = page;
Type = type;
Annotation = annotation;
}
/*
public static PdfAnnotation Create(PdfPage page, AnnotationType type)
{
var annotation = PdfActionSync.Default.SyncExec(() => fpdf_annot.FPDFPageCreateAnnot(page.PageInstance, (int)type));
if (annotation == null)
throw new Exception("Unable to create annotation.");
return new PdfAnnotation(page, type, annotation);
}*/
public virtual void Dispose()
{
if (_isDisposed)
return;
_isDisposed = true;
OnDispose();
PdfActionSync.Default.SyncExec(() => fpdf_annot.FPDFPageCloseAnnot(Annotation));
}
protected abstract void OnDispose();
}

Просмотреть файл

@ -0,0 +1,33 @@
using System;
using System.Reflection.Metadata;
using System.Threading;
using DtronixCommon;
using PDFiumCore;
namespace DtronixPdf.Annotations;
public sealed class PdfAnnotationLine : PdfAnnotationBase
{
private PdfAnnotationLine(PdfPage page, FpdfAnnotationT annotation)
:base(page, AnnotationType.Line, annotation)
{
var obj = fpdf_annot.FPDFAnnotGetObject(annotation, 0);
}
public static PdfAnnotationLine Create(PdfPage page)
{
var annotation = PdfActionSync.Default.SyncExec(() =>
fpdf_annot.FPDFPageCreateAnnot(page.PageInstance, (int)AnnotationType.Line));
if (annotation == null)
throw new Exception("Unable to create annotation.");
return new PdfAnnotationLine(page, annotation);
}
protected override void OnDispose()
{
}
}

Просмотреть файл

@ -8,7 +8,7 @@
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
@ -16,7 +16,7 @@
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<PackageReference Include="DtronixCommon" Version="0.7.0" />
<PackageReference Include="PDFiumCore" Version="119.0.6043" />
<PackageReference Include="DtronixCommon" Version="0.8.0" />
<PackageReference Include="PDFiumCore" Version="121.0.6124" />
</ItemGroup>
</Project>

Просмотреть файл

@ -13,8 +13,6 @@ namespace DtronixPdf
private static PdfiumManager _managerDefaultInstance;
public static PdfiumManager Default => _managerDefaultInstance ??= new PdfiumManager();
private readonly PdfActionSynchronizer _synchronizer;
private readonly List<PdfDocument> LoadedDocuments = new ();
private static readonly ConcurrentBag<PdfiumManager> LoadedManagers = new ();
@ -22,8 +20,6 @@ namespace DtronixPdf
private PdfiumManager()
{
LoadedManagers.Add(this);
_synchronizer = new PdfActionSynchronizer();
}
/// <summary>
@ -37,7 +33,7 @@ namespace DtronixPdf
IsInitialized = true;
// Initialize the library.
Default._synchronizer.SyncExec(fpdfview.FPDF_InitLibrary);
PdfActionSync.Default.SyncExec(fpdfview.FPDF_InitLibrary);
}
public static void Unload()
@ -53,7 +49,7 @@ namespace DtronixPdf
IsInitialized = false;
Default._synchronizer.SyncExec(fpdfview.FPDF_DestroyLibrary);
PdfActionSync.Default.SyncExec(fpdfview.FPDF_DestroyLibrary);
}
internal void AddDocument(PdfDocument document)

Просмотреть файл

@ -0,0 +1,87 @@
using System;
using System.Threading;
namespace DtronixPdf;
/// <summary>
/// The 'PdfActionSynchronizer' class is responsible for synchronizing the execution of actions and functions in a thread-safe manner.
/// It uses a semaphore to ensure that only one thread can execute the action or function at a time.
/// This class is used throughout the DtronixPdf library to ensure safe and synchronized access to shared resources.
/// </summary>
public class PdfActionSync
{
public static PdfActionSync Default { get; } = new PdfActionSync();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public void SyncExec(Action action)
{
try
{
_semaphore.Wait();
action();
}
catch
{
throw;
}
finally
{
_semaphore.Release();
}
}
public T SyncExec<T>(Func<T> function)
{
try
{
_semaphore.Wait();
var result = function();
return result;
}
catch
{
throw;
}
finally
{
_semaphore.Release();
}
}
public TRet SyncExec<TArg1, TRet>(Func<TArg1, TRet> function, TArg1 arg1)
{
try
{
_semaphore.Wait();
var result = function(arg1);
return result;
}
catch
{
throw;
}
finally
{
_semaphore.Release();
}
}
public TRet SyncExec<TArg1, TArg2, TRet>(Func<TArg1, TArg2, TRet> function, TArg1 arg1, TArg2 arg2)
{
try
{
_semaphore.Wait();
var result = function(arg1, arg2);
return result;
}
catch
{
throw;
}
finally
{
_semaphore.Release();
}
}
}

Просмотреть файл

@ -1,41 +0,0 @@
using System;
using System.Threading;
namespace DtronixPdf
{
public class PdfActionSynchronizer
{
public readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
public void SyncExec(Action action)
{
try
{
Semaphore.Wait();
action();
Semaphore.Release();
}
catch
{
Semaphore.Release();
throw;
}
}
public T SyncExec<T>(Func<T> function)
{
try
{
Semaphore.Wait();
var result = function();
Semaphore.Release();
return result;
}
catch
{
Semaphore.Release();
throw;
}
}
}
}

Просмотреть файл

@ -8,8 +8,6 @@ namespace DtronixPdf
{
private readonly FpdfBitmapT _pdfBitmap;
private readonly PdfActionSynchronizer _synchronizer;
public float Scale { get; }
public BoundaryF Viewport { get; }
@ -33,7 +31,6 @@ namespace DtronixPdf
/// <param name="viewport"></param>
internal PdfBitmap(
FpdfBitmapT pdfBitmap,
PdfActionSynchronizer synchronizer,
float scale,
BoundaryF viewport)
{
@ -42,7 +39,6 @@ namespace DtronixPdf
Width = (int)viewport.Width;
Height = (int)viewport.Height;
Pointer = fpdfview.FPDFBitmapGetBuffer(_pdfBitmap);
_synchronizer = synchronizer;
Scale = scale;
Viewport = viewport;
}
@ -54,7 +50,7 @@ namespace DtronixPdf
IsDisposed = true;
_synchronizer.SyncExec(() => fpdfview.FPDFBitmapDestroy(_pdfBitmap));
PdfActionSync.Default.SyncExec(() => fpdfview.FPDFBitmapDestroy(_pdfBitmap));
}
}
}

Просмотреть файл

@ -10,16 +10,13 @@ namespace DtronixPdf
{
internal readonly FpdfDocumentT Instance;
internal readonly PdfActionSynchronizer Synchronizer;
private bool _isDisposed = false;
private IntPtr? _documentPointer;
public int Pages { get; private set; }
private PdfDocument(PdfActionSynchronizer synchronizer, FpdfDocumentT instance)
private PdfDocument(FpdfDocumentT instance)
{
Synchronizer = synchronizer;
Instance = instance;
PdfiumManager.Default.AddDocument(this);
}
@ -28,15 +25,17 @@ namespace DtronixPdf
{
PdfiumManager.Initialize();
var synchronizer = new PdfActionSynchronizer();
var document = synchronizer.SyncExec(() => fpdfview.FPDF_LoadDocument(path, password));
var pages = synchronizer.SyncExec(() => fpdfview.FPDF_GetPageCount(document));
var document = PdfActionSync.Default.SyncExec(
static (path, password) => fpdfview.FPDF_LoadDocument(path, password),
path, password);
var pages = PdfActionSync.Default.SyncExec(
static document => fpdfview.FPDF_GetPageCount(document),
document);
if (document == null)
return null;
var pdfDocument = new PdfDocument(synchronizer, document) { Pages = pages, };
var pdfDocument = new PdfDocument(document) { Pages = pages, };
return pdfDocument;
@ -44,8 +43,6 @@ namespace DtronixPdf
public static unsafe PdfDocument Load(Stream stream, string password)
{
var synchronizer = new PdfActionSynchronizer();
var length = (int)stream.Length;
var ptr = NativeMemory.Alloc((nuint)length);
@ -61,7 +58,7 @@ namespace DtronixPdf
PdfiumManager.Initialize();
int pages = -1;
var result = synchronizer.SyncExec(() =>
var result = PdfActionSync.Default.SyncExec(() =>
{
var document = fpdfview.FPDF_LoadMemDocument(pointer, length, password);
pages = fpdfview.FPDF_GetPageCount(document);
@ -71,7 +68,7 @@ namespace DtronixPdf
if (result == null)
return null;
var pdfDocument = new PdfDocument(synchronizer, result)
var pdfDocument = new PdfDocument(result)
{
Pages = pages,
_documentPointer = pointer
@ -82,14 +79,12 @@ namespace DtronixPdf
public static PdfDocument Create()
{
var synchronizer = new PdfActionSynchronizer();
var result = synchronizer.SyncExec(fpdf_edit.FPDF_CreateNewDocument);
var result = PdfActionSync.Default.SyncExec(fpdf_edit.FPDF_CreateNewDocument);
if (result == null)
return null;
return new PdfDocument(synchronizer, result);
return new PdfDocument(result);
}
public PdfPage GetPage(int pageIndex)
@ -108,7 +103,7 @@ namespace DtronixPdf
/// <returns>True on success, false on failure.</returns>
public bool ImportPages(PdfDocument document, string pageRange, int insertIndex)
{
return Synchronizer.SyncExec(() =>
return PdfActionSync.Default.SyncExec(() =>
fpdf_ppo.FPDF_ImportPages(Instance, document.Instance, pageRange, insertIndex) == 1);
}
@ -132,7 +127,7 @@ namespace DtronixPdf
/// <returns>True on success, false on failure.</returns>
public void DeletePage(int pageIndex)
{
Synchronizer.SyncExec(() => fpdf_edit.FPDFPageDelete(Instance, pageIndex));
PdfActionSync.Default.SyncExec(() => fpdf_edit.FPDFPageDelete(Instance, pageIndex));
}
@ -162,7 +157,7 @@ namespace DtronixPdf
#define FPDF_REMOVE_SECURITY 3
*/
var result = Synchronizer.SyncExec(() => fpdf_save.FPDF_SaveAsCopy(Instance, writer, 1));
var result = PdfActionSync.Default.SyncExec(() => fpdf_save.FPDF_SaveAsCopy(Instance, writer, 1));
return result == 1;
}
@ -174,7 +169,7 @@ namespace DtronixPdf
_isDisposed = true;
Synchronizer.SyncExec(() => fpdfview.FPDF_CloseDocument(Instance));
PdfActionSync.Default.SyncExec(() => fpdfview.FPDF_CloseDocument(Instance));
PdfiumManager.Default.RemoveDocument(this);

Просмотреть файл

@ -9,7 +9,7 @@ namespace DtronixPdf
public partial class PdfPage : IDisposable
{
internal readonly PdfDocument Document;
private readonly FpdfPageT _pageInstance;
internal readonly FpdfPageT PageInstance;
private bool _isDisposed = false;
public float Width { get; private set; }
@ -17,12 +17,10 @@ namespace DtronixPdf
public int InitialIndex { get; private set; }
internal FpdfPageT PageInstance => _pageInstance;
private PdfPage(PdfDocument document, FpdfPageT pageInstance)
{
Document = document;
_pageInstance = pageInstance;
PageInstance = pageInstance;
}
internal static PdfPage Create(
@ -128,7 +126,7 @@ namespace DtronixPdf
};
Document.Synchronizer.SyncExec(() =>
fpdfview.FPDF_RenderPageBitmapWithMatrix(bitmap, _pageInstance, matrix, clipping,
fpdfview.FPDF_RenderPageBitmapWithMatrix(bitmap, PageInstance, matrix, clipping,
(int)config.Flags));
config.CancellationToken.ThrowIfCancellationRequested();