Compare commits

...

19 Commits

Author SHA1 Message Date
a367c12551 chore(Web): bump to 3.5 2025-10-28 16:57:20 +01:00
35a328f8dc feat(pdfburner): adjust background rendering in BurnElementAnnotsToPDF
- Added scaling factors (1.95 * 0.93, 2.52 * 0.67) to the background generation step
- Included TODO note to calculate length dynamically based on largest Y value
- Improved visual accuracy of PDF background rendering for element annotations
2025-10-28 16:56:00 +01:00
d259a15b4b refactor: add DigitalData.Core.Abstractions.Interfaces import and make Background method width/height configurable 2025-10-28 16:43:59 +01:00
23e0e5ddbe fix(pdfburner): correct annotation X-coordinate handling in BurnElementAnnotsToPDF
Adjusted logic in `BurnElementAnnotsToPDF` to properly use annotation X position
(`annot.X / inchFactor`) instead of offset-based calculation with `frameX` and
`frameXShift`. This ensures more accurate placement of form fields and images
when burning annotations into PDFs.
2025-10-28 16:40:06 +01:00
0bb85c28c1 fix: adjust annotation position calculation in PDFBurner
- Updated BurnElementAnnotsToPDF method to correctly align image annotations relative to the frame position.
- Introduced frame-based coordinate shifting (frameXShift and frameYShift) to handle signature positioning accurately.
- Replaced variable `x` with `frameX` for better clarity and consistency.
2025-10-28 16:27:55 +01:00
a11d9a0e0e refactor(PDFBurner): adjust annotation positioning and unit conversions in PDFBurner
- Renamed variable `magin` → `margin` for clarity
- Revised coordinate calculations for annotation positioning (`x`, `y`)
- Adjusted width and height units to use inches instead of fixed inchFactor multipliers
- Updated y-offset logic for form fields (`yOffsetsOfFF`) to improve layout alignment
- Simplified image annotation scaling (removed division by 100)
- Improved code readability and comment consistency
2025-10-28 15:27:52 +01:00
b9bb058137 fix(AnnotationCreateDto): rename WIDTH and HEIGHT properties to Width and Height in AnnotationCreateDto 2025-10-28 14:42:14 +01:00
0818d7d9eb feat(dto): add position and size properties to AnnotationCreateDto 2025-10-28 14:14:09 +01:00
9d200800c5 feat: add position and size properties to ElementAnnotation entity
- Added `X`, `Y`, `Width`, and `Height` properties with `float` database mapping.
- Retains existing fields and database annotations.
- Prepares the entity for spatial layout handling in documents.
2025-10-28 14:10:32 +01:00
6feb601670 fix(annotations): include bounding box coordinates in mapped signature and frame data
- Added extraction of `x`, `y`, `width`, and `height` from annotations and frames in `mapSignature`.
- Ensures positional data is correctly preserved when mapping signatures and frames from PSPDFKit annotations.
- No functional changes to annotation creation, deletion, or validation.
2025-10-28 13:58:03 +01:00
39c12ada45 refactor(annotations): include bounding box info in mapped signature fields
- Added `x`, `y`, `width`, and `height` properties to form field mapping in `mapSignature`.
- Preserves original form field values while enriching them with annotation coordinates.
- No functional changes to annotation creation, deletion, or validation.
2025-10-28 13:49:26 +01:00
985ad4dc29 fix(pdf-burner): correct image annotation type and scaling
- Fixed conditional check in BurnElementAnnotsToPDF to correctly handle AnnotationType.Image.
- Adjusted AddImageAnnotation method to scale width and height by dividing by 100.
- No functional changes to other PDF burning or annotation logic.
2025-10-28 13:00:43 +01:00
038ac2aed0 fix(annotations): correct signature frame mapping by filtering by pageIndex
- Updated `mapSignature` to filter signature annotations by `pageIndex` when finding nearest elements.
- Ensures correct association of frames and signature data for each page.
- Prevents errors caused by cross-page annotation references.
2025-10-28 12:59:49 +01:00
5e74de0ce7 refactor(pdf-burner): adjust annotation positioning logic in BurnElementAnnotsToPDF
- Introduced configurable inch factor, margins, width, and height for annotations
- Added dynamic Y-offsets for form fields based on annotation names
- Simplified AddFormFieldValue logic for direct coordinates
- Removed hardcoded offsets previously embedded in the method
2025-10-28 11:48:19 +01:00
0ce7ae9494 fix(pdfburner): correct page indexing for annotation rendering
- Removed unnecessary +1 offset in Manager.SelectPage calls for image and ink annotations
- Ensured correct page index is used when adding annotations and form field values
- Improved annotation rendering accuracy when burning to PDF
2025-10-28 10:48:15 +01:00
7041a4694a fix: ensure annotations are properly burned after background addition in PDFBurner 2025-10-28 10:43:19 +01:00
75e47d10e3 feat(pdfburner): add ink annotation support and refactor annotation handling
- Added new AddInkAnnotation(page As Integer, value As String) method to handle ink annotations from element data.
- Introduced new Ink model class for deserializing ink annotation data (lines and strokeColor).
- Updated BurnElementAnnotsToPDF to support Ink annotation type.
- Refactored annotation type handling for better extensibility.
- Improved annotation burning logic for mixed annotation types (form fields, images, inks).
2025-10-28 10:06:38 +01:00
7f9125b3aa refactor: add AddImageAnnotation-method 2025-10-28 09:43:43 +01:00
fee256a51a add y offset to AddFormFieldValue metot 2025-10-28 09:25:48 +01:00
8 changed files with 178 additions and 30 deletions

View File

@@ -24,6 +24,26 @@ public record AnnotationCreateDto
///
/// </summary>
public string Type { get; init; } = null!;
/// <summary>
///
/// </summary>
public double? X { get; init; }
/// <summary>
///
/// </summary>
public double? Y { get; init; }
/// <summary>
///
/// </summary>
public double? Width { get; init; }
/// <summary>
///
/// </summary>
public double? Height { get; init; }
}
/// <summary>

View File

@@ -6,7 +6,6 @@ Imports DigitalData.Core.Abstraction.Application.Repository
Imports DigitalData.Core.Abstractions
Imports DigitalData.Modules.Base
Imports DigitalData.Modules.Logging
Imports DocumentFormat.OpenXml.Drawing.Diagrams
Imports EnvelopeGenerator.CommonServices.Jobs.FinalizeDocument.FinalizeDocumentExceptions
Imports EnvelopeGenerator.Domain.Entities
Imports EnvelopeGenerator.PdfEditor
@@ -51,21 +50,72 @@ Namespace Jobs.FinalizeDocument
Public Function BurnElementAnnotsToPDF(pSourceBuffer As Byte(), elements As List(Of Signature)) As Byte()
' Add background
Using doc As Pdf(Of MemoryStream, MemoryStream) = Pdf.FromMemory(pSourceBuffer)
Return doc.Background(elements).ExportStream().ToArray()
End Using
'TODO: take the length from the largest y
pSourceBuffer = doc.Background(elements, 1.9500000000000002 * 0.93, 2.52 * 0.67).ExportStream().ToArray()
For Each element In elements
Dim x = ToInches(element.Left)
Dim y = ToInches(element.Top)
For Each annot In element.Annotations
If annot.Type = AnnotationType.FormField Then
AddFormFieldValue(x, y, 100, 180, element.Page, annot.Value)
Dim oResult As GdPictureStatus
Using oSourceStream As New MemoryStream(pSourceBuffer)
' Open PDF
oResult = Manager.InitFromStream(oSourceStream)
If oResult <> GdPictureStatus.OK Then
Throw New BurnAnnotationException($"Could not open document for burning: [{oResult}]")
End If
Next
Next
' Imported from background (add to configuration)
Dim margin As Double = 0.2
Dim inchFactor As Double = 72
' Y offset of form fields
Dim keys = {"position", "city", "date"} ' add to configuration
Dim unitYOffsets = 0.2
Dim yOffsetsOfFF = keys.
Select(Function(k, i) New With {Key .Key = k, Key .Value = unitYOffsets * i + 1}).
ToDictionary(Function(x) x.Key, Function(x) x.Value)
'Add annotations
For Each element In elements
Dim frameX = (element.Left - 0.7 - margin)
Dim frame = element.Annotations.FirstOrDefault(Function(a) a.Name = "frame")
Dim frameY = element.Top - 0.5 - margin
Dim frameYShift = frame.Y - frameY * inchFactor
Dim frameXShift = frame.X - frameX * inchFactor
For Each annot In element.Annotations
Dim yOffsetofFF As Double = If(yOffsetsOfFF.TryGetValue(annot.Name, yOffsetofFF), yOffsetofFF, 0)
Dim y = frameY + yOffsetofFF
If annot.Type = AnnotationType.FormField Then
AddFormFieldValue(annot.X / inchFactor, y, annot.Width / inchFactor, annot.Height / inchFactor, element.Page, annot.Value)
ElseIf annot.Type = AnnotationType.Image Then
AddImageAnnotation(
annot.X / inchFactor,
If(annot.Name = "signature", (annot.Y - frameYShift) / inchFactor, y),
annot.Width / inchFactor,
annot.Height / inchFactor,
element.Page,
annot.Value
)
ElseIf annot.Type = AnnotationType.Ink Then
AddInkAnnotation(element.Page, annot.Value)
End If
Next
Next
'Save PDF
Using oNewStream As New MemoryStream()
oResult = Manager.SaveDocumentToPDF(oNewStream)
If oResult <> GdPictureStatus.OK Then
Throw New BurnAnnotationException($"Could not save document to stream: [{oResult}]")
End If
Manager.Close()
Return oNewStream.ToArray()
End Using
End Using
End Using
End Function
Public Function BurnInstantJSONAnnotsToPDF(pSourceBuffer As Byte(), pInstantJSONList As List(Of String)) As Byte()
@@ -135,6 +185,11 @@ Namespace Jobs.FinalizeDocument
End Sub
Private Sub AddImageAnnotation(x As Double, y As Double, width As Double, height As Double, page As Integer, base64 As String)
Manager.SelectPage(page)
Manager.AddEmbeddedImageAnnotFromBase64(base64, x, y, width, height)
End Sub
Private Sub AddImageAnnotation(pAnnotation As Annotation, pAttachments As Dictionary(Of String, Attachment))
Dim oAttachment = pAttachments.Where(Function(a) a.Key = pAnnotation.imageAttachmentId).
SingleOrDefault()
@@ -151,6 +206,23 @@ Namespace Jobs.FinalizeDocument
Manager.AddEmbeddedImageAnnotFromBase64(oAttachment.Value.binary, oX, oY, oWidth, oHeight)
End Sub
Private Sub AddInkAnnotation(page As Integer, value As String)
Dim ink = JsonConvert.DeserializeObject(Of Ink)(value)
Dim oSegments = ink.lines.points
Dim oColor = ColorTranslator.FromHtml(ink.strokeColor)
Manager.SelectPage(page)
For Each oSegment As List(Of List(Of Single)) In oSegments
Dim oPoints = oSegment.
Select(AddressOf ToPointF).
ToArray()
Manager.AddFreeHandAnnot(oColor, oPoints)
Next
End Sub
Private Sub AddInkAnnotation(pAnnotation As Annotation)
Dim oSegments = pAnnotation.lines.points
Dim oColor = ColorTranslator.FromHtml(pAnnotation.strokeColor)
@@ -166,7 +238,8 @@ Namespace Jobs.FinalizeDocument
End Sub
Private Sub AddFormFieldValue(x As Double, y As Double, width As Double, height As Double, page As Integer, value As String)
Manager.SelectPage(page + 1)
Manager.SelectPage(page)
' Add the text annotation
Dim ant = Manager.AddTextAnnot(x, y, width, height, value)
@@ -324,6 +397,12 @@ Namespace Jobs.FinalizeDocument
Public Property strokeColor As String
End Class
Friend Class Ink
Public Property lines As Lines
Public Property strokeColor As String
End Class
Public Class EGName
Public Shared ReadOnly NoName As String = Guid.NewGuid().ToString()
@@ -354,4 +433,4 @@ Namespace Jobs.FinalizeDocument
End Class
#End Region
End Class
End Namespace
End Namespace

View File

@@ -36,6 +36,34 @@ public class ElementAnnotation
[Column("TYPE", TypeName = "nvarchar(50)")]
public string Type { get; set; }
[Column("POSITION_X", TypeName = "float")]
public double
#if NET
?
#endif
X { get; set; }
[Column("POSITION_Y", TypeName = "float")]
public double
#if NET
?
#endif
Y { get; set; }
[Column("WIDTH", TypeName = "float")]
public double
#if NET
?
#endif
Width { get; set; }
[Column("HEIGHT", TypeName = "float")]
public double
#if NET
?
#endif
Height { get; set; }
[Required]
[Column("ADDED_WHEN", TypeName = "datetime")]
public DateTime AddedWhen { get; set; }
@@ -52,10 +80,10 @@ public class ElementAnnotation
ChangedWho { get; set; }
[ForeignKey("ElementId")]
public virtual Signature
public virtual Signature
#if NET
?
#endif
#endif
Element { get; set; }
}

View File

@@ -2,6 +2,8 @@
using iText.Kernel.Pdf.Canvas;
using EnvelopeGenerator.Domain.Interfaces;
using iText.Kernel.Colors;
using DigitalData.Core.Abstractions.Interfaces;
#if NETFRAMEWORK
using System;
using System.IO;
@@ -100,7 +102,7 @@ namespace EnvelopeGenerator.PdfEditor
});
#endregion
public Pdf<TInputStream, TOutputStream> Background<TSignature>(IEnumerable<TSignature> signatures)
public Pdf<TInputStream, TOutputStream> Background<TSignature>(IEnumerable<TSignature> signatures, double widthPx = 1.9500000000000002, double heightPx = 2.52)
where TSignature : ISignature
{
// once per page
@@ -118,8 +120,8 @@ namespace EnvelopeGenerator.PdfEditor
double magin = .2;
double x = (signature.X - .7 - magin) * inchFactor;
double y = (signature.Y - .5 - magin) * inchFactor;
double width = 1.9500000000000002 * inchFactor;
double height = 2.52 * inchFactor;
double width = widthPx * inchFactor;
double height = heightPx * inchFactor;
double bottomLineLength = 2.5;

View File

@@ -12,9 +12,9 @@
<PackageTags>digital data envelope generator web</PackageTags>
<Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description>
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
<Version>3.4.1</Version>
<AssemblyVersion>3.4.1</AssemblyVersion>
<FileVersion>3.4.1</FileVersion>
<Version>3.5.0</Version>
<AssemblyVersion>3.5.0</AssemblyVersion>
<FileVersion>3.5.0</FileVersion>
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
</PropertyGroup>

View File

@@ -321,29 +321,43 @@ function mapSignature(iJSON) {
// formFields
...iJSON.formFieldValues.filter(field => !field.name.includes("label")).map((field) => {
const nameParts = field.name.split('#');
const [x, y, width, height] = iJSON.annotations.find(iAnnot => iAnnot.id === field.name).bbox;
return {
elementId: Number(nameParts[2]),
name: nameParts[3],
value: field.value,
type: field.type
type: field.type,
x: x,
y: y,
width: width,
height: height
};
}),
// frames
...iJSON.annotations.filter(annot => annot.description === 'FRAME').map((annot) => {
const preElement = findNearest(annot, e => e.bbox[0], e => e.bbox[1], ...iJSON.annotations.filter(field => field.id.includes("signature")));
const preElement = findNearest(annot, e => e.bbox[0], e => e.bbox[1], iJSON.annotations.filter(
field => field.id.includes("signature") && field.pageIndex === annot.pageIndex
));
const idPartsOfPre = preElement.id.split('#');
const [x, y, width, height] = annot.bbox;
return {
elementId: Number(idPartsOfPre[2]),
name: 'frame',
value: fixBase64(iJSON.attachments[annot.imageAttachmentId]?.binary),
type: annot.type
type: annot.type,
x: x,
y: y,
width: width,
height: height
};
}),
// signatures
...iJSON.annotations.filter(annot => annot.isSignature).map(annot => {
const preElement = findNearest(annot, e => e.bbox[0], e => e.bbox[1], ...iJSON.annotations.filter(field => field.id.includes("signature")));
const preElement = findNearest(annot, e => e.bbox[0], e => e.bbox[1], iJSON.annotations.filter(
field => field.id.includes("signature") && field.pageIndex === annot.pageIndex
));
const idPartsOfPre = preElement.id.split('#');
let value;
@@ -357,11 +371,16 @@ function mapSignature(iJSON) {
else
throw new Error("Signature mapping failed: The data structure from the third-party library is incompatible or missing required fields.");
const [x, y, width, height] = annot.bbox;
return {
elementId: Number(idPartsOfPre[2]),
name: 'signature',
value,
type: annot.type
type: annot.type,
x: x,
y: y,
width: width,
height: height
};
})
];

View File

@@ -14,7 +14,7 @@ function detailedCurrentDate() {
}).format();
}
function findNearest(origin, getX, getY, ...dests) {
function findNearest(origin, getX, getY, dests) {
const distanceToOrigin = (point) => Math.sqrt((getX(origin) - getX(point))**2 + (getY(origin) - getY(point))**2);
return dests.reduce(
(nearest, dest) => {

View File

@@ -1 +1 @@
function detailedCurrentDate(){return new Intl.DateTimeFormat("de-DE",{day:"2-digit",month:"2-digit",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:"shortOffset"}).format()}function findNearest(n,t,i,...r){const u=r=>Math.sqrt((t(n)-t(r))**2+(i(n)-i(r))**2);return r.reduce((n,t)=>{const i=u(t);return i<n.dist?{dist:i,dest:t}:n},{dist:Infinity,dest:null}).dest}const B64ToBuff=n=>new Uint8Array(Array.from(atob(n),n=>n.charCodeAt(0))).buffer,getLocaleDateString=()=>(new Date).toLocaleDateString("de-DE");
function detailedCurrentDate(){return new Intl.DateTimeFormat("de-DE",{day:"2-digit",month:"2-digit",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:"shortOffset"}).format()}function findNearest(n,t,i,r){const u=r=>Math.sqrt((t(n)-t(r))**2+(i(n)-i(r))**2);return r.reduce((n,t)=>{const i=u(t);return i<n.dist?{dist:i,dest:t}:n},{dist:Infinity,dest:null}).dest}const B64ToBuff=n=>new Uint8Array(Array.from(atob(n),n=>n.charCodeAt(0))).buffer,getLocaleDateString=()=>(new Date).toLocaleDateString("de-DE");