1+ using Microsoft . AspNetCore . Cors ;
2+ using Microsoft . AspNetCore . Hosting ;
3+ using Microsoft . AspNetCore . Mvc ;
4+ using Syncfusion . Drawing ;
5+ using Syncfusion . Pdf ;
6+ using Syncfusion . Pdf . Graphics ;
7+ using Syncfusion . Pdf . Parsing ;
8+ using Syncfusion . Pdf . Security ;
9+ using System ;
10+ using System . Collections . Generic ;
11+ using System . Globalization ;
12+ using System . IO ;
13+ using System . Text . Json ;
14+
15+
16+ namespace DigitalSignServer . Controllers
17+ {
18+ [ ApiController ]
19+ [ Route ( "api/[controller]" ) ]
20+ public class PdfViewerController : ControllerBase
21+ {
22+ private readonly IWebHostEnvironment _env ;
23+ private IConfiguration _configuration ;
24+
25+ public PdfViewerController ( IWebHostEnvironment env , IConfiguration configuration )
26+ {
27+ _env = env ;
28+ _configuration = configuration ;
29+ path = _configuration [ "DOCUMENT_PATH" ] ;
30+ path = string . IsNullOrEmpty ( path ) ? Path . Combine ( env . ContentRootPath , "Data" ) : Path . Combine ( env . ContentRootPath , path ) ;
31+ }
32+ string path ;
33+
34+ private class Bounds
35+ {
36+ public int X { get ; set ; }
37+ public int Y { get ; set ; }
38+ public int Height { get ; set ; }
39+ public int Width { get ; set ; }
40+ }
41+
42+ // POST: /api/PdfViewer/AddVisibleSignature
43+ [ HttpPost ]
44+ [ EnableCors ( "AllowAllOrigins" ) ]
45+ [ Route ( "AddVisibleSignature" ) ]
46+ public IActionResult AddVisibleSignature ( [ FromBody ] Dictionary < string , object > jsonObject )
47+ {
48+ if ( jsonObject != null && jsonObject . ContainsKey ( "pdfdata" ) )
49+ {
50+ string pdfdata = jsonObject [ "pdfdata" ] ? . ToString ( ) ?? string . Empty ;
51+ string [ ] split = pdfdata . Split ( new string [ ] { "data:application/pdf;base64," } , StringSplitOptions . None ) ;
52+ if ( split . Length > 1 )
53+ {
54+ string pdfdataString = split [ 1 ] ;
55+ if ( ! string . IsNullOrEmpty ( pdfdataString ) )
56+ {
57+ // Load the PDF
58+ byte [ ] documentBytes = Convert . FromBase64String ( pdfdataString ) ;
59+ using PdfLoadedDocument loadedDocument = new PdfLoadedDocument ( documentBytes ) ;
60+
61+ // Existing field (first field) if user chose an existing field
62+ PdfLoadedSignatureField formField = null ;
63+ if ( loadedDocument . Form ? . Fields ? . Count > 0 )
64+ {
65+ formField = loadedDocument . Form . Fields [ 0 ] as PdfLoadedSignatureField ;
66+ }
67+
68+ // First page
69+ PdfPageBase loadedPage = loadedDocument . Pages [ 0 ] ;
70+
71+ // Certificate (update file name/password as needed)
72+ PdfCertificate pdfCertificate = new PdfCertificate ( GetDocumentPath ( "localhost.pfx" ) , "Syncfusion@123" ) ;
73+
74+ // Optional signature image
75+ PdfImage image = null ;
76+ if ( jsonObject . ContainsKey ( "imagedata" ) )
77+ {
78+ string imageData = jsonObject [ "imagedata" ] ? . ToString ( ) ?? string . Empty ;
79+ string [ ] imgSplit = imageData . Split ( new string [ ] {
80+ "data:image/png;base64," , "data:image/jpeg;base64," , "data:image/jpg;base64,"
81+ } , StringSplitOptions . None ) ;
82+
83+ if ( imgSplit . Length > 1 && ! string . IsNullOrEmpty ( imgSplit [ 1 ] ) )
84+ {
85+ byte [ ] imageBytes = Convert . FromBase64String ( imgSplit [ 1 ] ) ;
86+ MemoryStream imageStream = new MemoryStream ( imageBytes ) ;
87+ image = new PdfBitmap ( imageStream ) ;
88+ }
89+ }
90+
91+ // Signature text
92+ PdfStandardFont font = new PdfStandardFont ( PdfFontFamily . Helvetica , 8 ) ;
93+ PdfSignature signature ;
94+ string descriptionText = "" ;
95+ string signerName = "" ;
96+ string reason = "" ;
97+ string locationInfo = "" ;
98+ DateTime signingDate = DateTime . Now ;
99+
100+ if ( jsonObject . ContainsKey ( "signerName" ) )
101+ {
102+ signerName = jsonObject [ "signerName" ] ? . ToString ( ) ?? "" ;
103+ if ( ! string . IsNullOrEmpty ( signerName ) )
104+ descriptionText += "Digitally signed by " + signerName + "\n " ;
105+ }
106+ if ( jsonObject . ContainsKey ( "reason" ) )
107+ {
108+ reason = jsonObject [ "reason" ] ? . ToString ( ) ?? "" ;
109+ if ( ! string . IsNullOrEmpty ( reason ) )
110+ descriptionText += "Reason: " + reason + "\n " ;
111+ }
112+ if ( jsonObject . ContainsKey ( "location" ) )
113+ {
114+ locationInfo = jsonObject [ "location" ] ? . ToString ( ) ?? "" ;
115+ if ( ! string . IsNullOrEmpty ( locationInfo ) )
116+ descriptionText += "Location: " + locationInfo + "\n " ;
117+ }
118+ if ( jsonObject . ContainsKey ( "date" ) )
119+ {
120+ string dateStr = jsonObject [ "date" ] ? . ToString ( ) ?? "" ;
121+ if ( ! string . IsNullOrWhiteSpace ( dateStr ) )
122+ {
123+ descriptionText += "Date: " + dateStr ;
124+
125+ var formats = new [ ] { "MM-dd-yyyy" , "MM-dd-yy" } ; // support both if needed
126+ if ( ! DateTime . TryParseExact ( dateStr , formats , CultureInfo . InvariantCulture ,
127+ DateTimeStyles . None , out var givenDate ) )
128+ {
129+ return BadRequest ( $ "Invalid date format: { dateStr } . Expected MM-dd-yyyy.") ;
130+ }
131+
132+ signingDate = new DateTime (
133+ givenDate . Year , givenDate . Month , givenDate . Day ,
134+ signingDate . Hour , signingDate . Minute , signingDate . Second ) ;
135+ }
136+ }
137+
138+ string displayMode = jsonObject . ContainsKey ( "displayMode" )
139+ ? jsonObject [ "displayMode" ] ? . ToString ( ) ?? ""
140+ : "" ;
141+
142+ bool isSignatureField = jsonObject . ContainsKey ( "isSignatureField" )
143+ && bool . TryParse ( jsonObject [ "isSignatureField" ] ? . ToString ( ) , out bool v ) && v ;
144+
145+ if ( isSignatureField )
146+ {
147+ // Sign existing field
148+ loadedDocument . FlattenAnnotations ( ) ;
149+ signature = new PdfSignature ( loadedDocument , loadedPage , pdfCertificate , "Signature" , formField , signingDate ) ;
150+
151+ if ( ! string . Equals ( displayMode , "SIGNER DETAILS ONLY" , StringComparison . OrdinalIgnoreCase ) && image != null && formField != null )
152+ {
153+ float boundsWidth = formField . Bounds . Width * 0.57f ;
154+ if ( string . Equals ( displayMode , "IMAGE ONLY" , StringComparison . OrdinalIgnoreCase ) || descriptionText . Length == 0 )
155+ boundsWidth = formField . Bounds . Width ;
156+
157+ float boundsHeight = formField . Bounds . Height ;
158+ Bounds imgBounds = GetVisibleSignImageBounds ( boundsHeight , boundsWidth , image . Height , image . Width ) ;
159+
160+ signature . Appearance . Normal . Graphics . DrawImage (
161+ image ,
162+ imgBounds . X , imgBounds . Y ,
163+ imgBounds . Width , imgBounds . Height
164+ ) ;
165+ }
166+ }
167+ else
168+ {
169+ // New signature with given bounds
170+ signature = new PdfSignature ( loadedDocument , loadedPage , pdfCertificate , "Signature" , signingDate ) ;
171+
172+ Bounds signatureBounds = JsonSerializer . Deserialize < Bounds > (
173+ jsonObject [ "signatureBounds" ] ? . ToString ( ) ?? "{}" ,
174+ new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
175+ ) ;
176+
177+ signature . Bounds = new Rectangle ( signatureBounds . X , signatureBounds . Y , signatureBounds . Width , signatureBounds . Height ) ;
178+
179+ if ( ! string . Equals ( displayMode , "SIGNER DETAILS ONLY" , StringComparison . OrdinalIgnoreCase ) && image != null )
180+ {
181+ float boundsWidth = signatureBounds . Width * 0.57f ;
182+ if ( string . Equals ( displayMode , "IMAGE ONLY" , StringComparison . OrdinalIgnoreCase ) || descriptionText . Length == 0 )
183+ boundsWidth = signatureBounds . Width ;
184+
185+ float boundsHeight = signatureBounds . Height ;
186+ Bounds imgBounds = GetVisibleSignImageBounds ( boundsHeight , boundsWidth , image . Height , image . Width ) ;
187+
188+ signature . Appearance . Normal . Graphics . DrawImage (
189+ image ,
190+ imgBounds . X , imgBounds . Y ,
191+ imgBounds . Width , imgBounds . Height
192+ ) ;
193+ }
194+ }
195+
196+ // Draw signer details text (if requested)
197+ if ( ! string . Equals ( displayMode , "IMAGE ONLY" , StringComparison . OrdinalIgnoreCase ) && descriptionText . Length > 0 )
198+ {
199+ PdfStringFormat format = new PdfStringFormat
200+ {
201+ Alignment = PdfTextAlignment . Left ,
202+ LineAlignment = PdfVerticalAlignment . Middle
203+ } ;
204+
205+ if ( string . Equals ( displayMode , "WITH SIGNER DETAILS" , StringComparison . OrdinalIgnoreCase ) )
206+ {
207+ signature . Appearance . Normal . Graphics . DrawString (
208+ descriptionText ,
209+ font ,
210+ PdfBrushes . Black ,
211+ new RectangleF ( signature . Bounds . Width * 0.6f , 0 , signature . Bounds . Width * 0.4f , signature . Bounds . Height ) ,
212+ format
213+ ) ;
214+ }
215+ else
216+ {
217+ signature . Appearance . Normal . Graphics . DrawString (
218+ descriptionText ,
219+ font ,
220+ PdfBrushes . Black ,
221+ new RectangleF ( 0 , 0 , signature . Bounds . Width , signature . Bounds . Height ) ,
222+ format
223+ ) ;
224+ }
225+ }
226+
227+ // Signature settings
228+ if ( jsonObject . ContainsKey ( "signatureType" ) || jsonObject . ContainsKey ( "digestAlgorithm" ) )
229+ {
230+ PdfSignatureSettings settings = signature . Settings ;
231+
232+ if ( jsonObject . ContainsKey ( "signatureType" ) )
233+ {
234+ string sigType = jsonObject [ "signatureType" ] ? . ToString ( ) ?? "" ;
235+ if ( string . Equals ( sigType , "CADES" , StringComparison . OrdinalIgnoreCase ) )
236+ settings . CryptographicStandard = CryptographicStandard . CADES ;
237+ else if ( string . Equals ( sigType , "CMS" , StringComparison . OrdinalIgnoreCase ) )
238+ settings . CryptographicStandard = CryptographicStandard . CMS ;
239+ }
240+
241+ if ( jsonObject . ContainsKey ( "digestAlgorithm" ) )
242+ {
243+ string algo = jsonObject [ "digestAlgorithm" ] ? . ToString ( ) ?? "" ;
244+ settings . DigestAlgorithm = ( DigestAlgorithm ) Enum . Parse ( typeof ( DigestAlgorithm ) , algo , ignoreCase : true ) ;
245+ }
246+ }
247+
248+ signature . Certificated = true ;
249+
250+ // Save
251+ using MemoryStream str = new MemoryStream ( ) ;
252+ loadedDocument . Save ( str ) ;
253+ loadedDocument . Close ( true ) ;
254+
255+ byte [ ] docBytes = str . ToArray ( ) ;
256+ string docBase64 = "data:application/pdf;base64," + Convert . ToBase64String ( docBytes ) ;
257+
258+ return Content ( docBase64 ) ;
259+ }
260+ }
261+ }
262+
263+ return Content ( "data:application/pdf;base64," ) ;
264+ }
265+
266+ private Bounds GetVisibleSignImageBounds ( float boundsHeight , float boundsWidth , float imageHeight , float imageWidth )
267+ {
268+ // calculate aspect ratios
269+ float imageAspect = imageWidth / imageHeight ;
270+ float boundsAspect = boundsWidth / boundsHeight ;
271+ float drawWidth , drawHeight , offsetX , offsetY ;
272+ if ( imageAspect > boundsAspect )
273+ {
274+ // Image is wider relative to bounds
275+ drawWidth = boundsWidth ;
276+ drawHeight = boundsWidth / imageAspect ;
277+ offsetX = 0 ;
278+ offsetY = ( boundsHeight - drawHeight ) / 2 ;
279+ }
280+ else
281+ {
282+ // Image is taller relative to bounds
283+ drawHeight = boundsHeight ;
284+ drawWidth = boundsHeight * imageAspect ;
285+ offsetX = ( boundsWidth - drawWidth ) / 2 ;
286+ offsetY = 0 ;
287+ }
288+ return new Bounds ( ) { X = ( int ) offsetX , Y = ( int ) offsetY , Width = ( int ) drawWidth , Height = ( int ) drawHeight } ;
289+ }
290+
291+
292+ private string GetDocumentPath ( string document )
293+ {
294+ if ( ! System . IO . File . Exists ( document ) )
295+ {
296+ string documentPath = Path . Combine ( path , document ) ;
297+ if ( System . IO . File . Exists ( documentPath ) )
298+ return documentPath ;
299+ else
300+ return string . Empty ;
301+ }
302+ else
303+ return document ;
304+ }
305+ }
306+ }
0 commit comments