window.onload = function () {
// DsPdfViewer.LicenseKey = "***key***";
let viewer, React;
// Configure viewer options
const viewerOptions = {
workerSrc: "/document-solutions/javascript-pdf-viewer/demos/product-bundles/build/dspdfviewer.worker.js",
supportApi: getSupportApiSettings(),
userData: {
sampleName: "SaveChangesSample",
docName: `${Date.now()}.pdf`
}
};
// Initialize the PDF viewer instance
viewer = new DsPdfViewer("#viewer", viewerOptions);
// Add the annotation editor panel
viewer.addAnnotationEditorPanel();
// Retrieve the React library used by the viewer
React = viewer.getType("React");
// Create a custom 'Save Changes' button
viewer.toolbar.addItem({
key: "custom-save",
icon: {
type: "svg",
content: React.createElement(
"svg",
{ xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "#ff0000" },
React.createElement("path", {
d: "M20.913 9.058c0.057-0.26 0.087-0.531 0.087-0.808 0-2.071-1.679-3.75-3.75-3.75-0.333 0-0.657 0.044-0.964 0.125-0.581-1.813-2.28-3.125-4.286-3.125-2.047 0-3.775 1.367-4.32 3.238-0.533-0.155-1.097-0.238-1.68-0.238-3.314 0-6 2.686-6 6s2.686 6 6 6h3v4.5h6v-4.5h5.25c2.071 0 3.75-1.679 3.75-3.75 0-1.845-1.333-3.379-3.087-3.692zM13.5 15v4.5h-3v-4.5h-3.75l5.25-5.25 5.25 5.25h-3.75z"
})
)
},
title: "Save Document",
enabled: false,
action: function () {
// Save changes to the server
viewer.saveChanges().then(function (success) {
if (success) {
const documentUrl = `${window.top.SUPPORTAPI_URL}/GetPdfFromCloud?docName=${viewerOptions.userData.docName}&clientId=${viewer.supportApi.clientId}`;
viewer.open(documentUrl).then(function () {
alert("The document has been saved in the cloud. It will be available for 10 minutes.");
});
}
});
},
onUpdate: function () {
return {
enabled: viewer.hasDocument,
title: "Save Changes"
};
}
});
// Add default 'Download' and custom 'Save' buttons to the viewer toolbar
viewer.toolbarLayout.viewer.default.unshift("download", "custom-save");
viewer.toolbarLayout.viewer.mobile.unshift("download", "custom-save");
// Configure the annotation editor toolbar layout
const annotationEditorToolbarItems = [
"custom-save", "download", "edit-select", "edit-sign-tool", "$split", "edit-text", "edit-free-text", "edit-ink",
"edit-square", "edit-circle", "edit-line", "edit-polyline", "edit-polygon", "edit-stamp", "edit-link", "$split",
"edit-redact", "edit-redact-apply", "edit-erase", "$split", "new-document", "$split", "new-page", "delete-page"
];
viewer.toolbarLayout.annotationEditor = {
default: annotationEditorToolbarItems,
mobile: annotationEditorToolbarItems,
fullscreen: annotationEditorToolbarItems
};
// Open the initial document
viewer.open("/document-solutions/javascript-pdf-viewer/demos/product-bundles/assets/pdf/viewer-save-changes.pdf");
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Custom save button</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./src/styles.css">
<script src="/document-solutions/javascript-pdf-viewer/demos/product-bundles/build/dspdfviewer.js"></script>
<script src="/document-solutions/javascript-pdf-viewer/demos/product-bundles/build/wasmSupportApi.js"></script>
<script src="/document-solutions/javascript-pdf-viewer/demos/resource/js/init.js"></script>
<script src="./src/app.js"></script>
</head>
<body>
<div id="viewer"></div>
</body>
</html>
#viewer { height: 100%; }
// This sample demonstrates how to handle PDF document modifications in a web API.
// It includes loading a document, applying final changes, flattening annotations,
// and saving the updated file back to storage.
// The example uses disk-based storage but can be adapted for cloud services.
using System;
using System.IO;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using GrapeCity.Documents.Pdf;
using GrapeCity.Documents.Pdf.Annotations;
using GrapeCity.Documents.Pdf.ViewerSupportApi.Controllers;
using GrapeCity.Documents.Pdf.ViewerSupportApi.Models;
using Newtonsoft.Json.Linq;
namespace GcPdfViewerSupportApiDemo.Controllers
{
[Route("api/pdf-viewer")]
[ApiController]
public class SampleSupportController : GcPdfViewerController
{
static SampleSupportController()
{
Settings.DocumentModified += OnDocumentModified;
}
/// <summary>
/// Handles document modification event.
/// </summary>
private static void OnDocumentModified(object sender, DocumentModifiedEventArgs e)
{
var document = e.Document;
ApplyFinalChanges(document);
FlattenAnnotations(document);
var userData = e.DocumentLoader.Info.documentOptions.userData as JObject;
var docName = userData?["docName"]?.ToString() ?? "default.pdf";
SaveDocumentToCloud(e.DocumentLoader.ClientId, document, docName);
}
#region ** ViewerSaveChanges Sample
/// <summary>
/// Retrieves a PDF document from cloud storage.
/// </summary>
[HttpGet("GetPdfFromCloud")]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> GetPdfFromCloud(string docName, string clientId)
{
var fileBytes = await GetDocumentFromCloud(docName, clientId);
if (fileBytes == null)
return NotFound($"Document '{docName}' not found.");
return File(fileBytes, "application/pdf", docName);
}
/// <summary>
/// Saves the modified PDF document to cloud storage.
/// </summary>
private static void SaveDocumentToCloud(string clientId, GcPdfDocument document, string docName)
{
SaveToDisk(document, docName);
}
/// <summary>
/// Downloads a PDF document from cloud storage.
/// </summary>
public static async Task<byte[]> GetDocumentFromCloud(string docName, string clientId)
{
return LoadFromDisk(docName);
}
#endregion
#region ** PDF Processing Helpers
/// <summary>
/// Applies final modifications before saving the document.
/// </summary>
private static void ApplyFinalChanges(GcPdfDocument document)
{
document.Metadata.ModifyDate = DateTime.UtcNow;
}
/// <summary>
/// Flattens annotations, converting them into static PDF content.
/// </summary>
private static void FlattenAnnotations(GcPdfDocument document)
{
var annotations = document.Pages.SelectMany(page => page.Annotations).ToList();
ConvertAnnotationsToContent(annotations);
}
/// <summary>
/// Converts annotations into page content and removes them.
/// </summary>
private static void ConvertAnnotationsToContent(List<AnnotationBase> annotations)
{
foreach (var group in annotations.GroupBy(a => a.Page))
{
var page = group.Key;
if (page == null) continue;
var graphics = page.Graphics;
var size = page.GetRenderSize(graphics.Resolution, graphics.Resolution);
var destinationRectangle = new RectangleF(0, 0, size.Width, size.Height);
page.DrawAnnotations(graphics, destinationRectangle, group.ToList());
foreach (var annotation in group)
{
page.Annotations.Remove(annotation);
}
}
}
#endregion
#region ** Disk Storage Example
/// <summary>
/// Loads a PDF document from disk storage.
/// </summary>
/// <param name="docName">The name of the document.</param>
/// <returns>The file bytes if found; otherwise, throws an exception.</returns>
/// <exception cref="FileNotFoundException">Thrown when the file is not found.</exception>
private static byte[] LoadFromDisk(string docName)
{
string path = Path.Combine("Documents", docName);
if (!System.IO.File.Exists(path))
throw new FileNotFoundException($"File '{docName}' not found in storage.", path);
return System.IO.File.ReadAllBytes(path);
}
/// <summary>
/// Saves a PDF document to disk storage.
/// </summary>
private static void SaveToDisk(GcPdfDocument document, string docName)
{
string directory = "Documents";
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
string path = Path.Combine(directory, docName);
document.Save(path);
}
#endregion
}
}