Add a custom "Save" button

The red "cloud" button calls saveChanges() and reloads the modified PDF into the viewer. On the server, the custom OnDocumentModified() handler (see SampleSupportController.cs) applies the changes and flattens annotations before returning the updated document. Use the "Download original PDF" button to save the PDF to your disk. You can add your own server-side processing in the same way.

The demo is being dynamically compiled to support real-time code editing... For quicker access to features, switch to the "JavaScript" tab for a smoother experience! :)
app.js
index.html
styles.css
SampleSupportController.cs
loading...
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"); };
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 } }