PDF Text Markup Annotations

This demo illustrates how you can find text and add markup to it with the help of the PDF Document API.

Options
Text to Find
Markup Type
@model AspNetCoreDemos.OfficeFileAPI.PdfPreviewModel

<img id="previewImage" src="@Url.Action(Model.PreviewDocumentAction, Model.ControllerName)" class="preview-image-bordered @(Model.IsViewWithoutSidePanel ? "preview-image-without-side-panel" : "preview-image-with-side-panel")" />

<script type="text/javascript">
    PdfPreview = {
        basePath: '@Url.Action(Model.PreviewDocumentAction, Model.ControllerName)',
        Update: function (param) {
            var iframeElement = document.getElementById("previewImage");
            if (!iframeElement)
                return;
            var additionalParams = "&" + new Date().valueOf();
            if (param)
                additionalParams = param;
            iframeElement.src = this.basePath + "?" + additionalParams;
        }
    };
</script>
@model AspNetCoreDemos.OfficeFileAPI.PdfTextMarkupAnnotationsModel
@using DevExtreme.AspNet.Mvc

@{ Html.BeginForm("PdfTextMarkupAnnotationsUploadFiles", "ContentManipulation", FormMethod.Post); }
@Html.HiddenFor(model => model.DocumentUrl)

<script type="text/javascript">
    function FindAndMarkup() {
        $.ajax({
            type: "POST",
            url: '@Url.Action("PdfFindAndMarkup", "ContentManipulation")',
            data: {
                text: $("#Find").dxTextBox('instance').option('value'),
                markupType: $("#MarkupType").dxSelectBox('instance').option('value')
            },
            success: function () { PdfPreview.Update(); }
        });
    }

    function fileUploader_valueChanged(e) {
        var files = e.value;
        if (files.length > 0) {
            $("#selected-files .selected-item").remove();

            $.each(files, function (i, file) {
                var $selectedItem = $("<div />").addClass("selected-item");
                $selectedItem.append(file.name);
                $selectedItem.appendTo($("#selected-files"));
            });
            $("#selected-files").show();

            $('#DocumentUrl').val(files[0].name);
        }
        else
            $("#selected-files").hide();
    }

    function UpdatePreview() {
        var params = "DocumentUrl=" + $('#DocumentUrl').val();
        PdfPreview.Update(params);
    }
</script>

<div class="demo-view-container">
    <div style="margin-left: -8px">
        @(Html.DevExtreme().FileUploader()
                           .ID("file-uploader")
                           .Name("myFile")
                           .Multiple(false)
                           .ShowFileList(false)
                           .Accept(".pdf")
                           .AllowedFileExtensions(new List<string> { ".pdf" })
                           .MaxFileSize(10485760)
                           .LabelText("Maximum file size 10Mb.")
                           .UploadMode(FileUploadMode.Instantly)
                           .UploadUrl(Url.Action("DocumentUpload", "ContentManipulation"))
                           .OnValueChanged("fileUploader_valueChanged")
                           .OnUploaded("UpdatePreview")
        )
    </div>
    <div class="demo-preview-border">
        @(Html.DevExtreme().ScrollView()
                           .ID("scrollview")
                           .ScrollByContent(true)
                           .ScrollByThumb(true)
                           .ShowScrollbar(ShowScrollbarMode.OnHover)
                           .Direction(ScrollDirection.Both)
                           .Height("580px")
                           .Content(@<text>
                                <div id="scrollview-content">
                                    @await Html.PartialAsync("PdfFileView", Model.PreviewModel)
                                </div>
                           </text>)
        )
    </div>
</div>

<div class="options">
    <div class="caption">Options</div>
    <div class="option">
        <div class="label">Text to Find</div>
        @(Html.DevExtreme().TextBox().ID("Find"))
    </div>
    <div class="option">
        <div class="label">Markup Type</div>
        @(Html.DevExtreme().SelectBox()
                           .ID("MarkupType")
                           .DataSource(Html.GetEnumSelectList<AspNetCoreDemos.OfficeFileAPI.MarkupType>()
                           .Select(i => new { Value = int.Parse(i.Value), Text = i.Text }))
                           .ValueExpr("Value")
                           .DisplayExpr("Text")
                           .Value(0)
        )
    </div>
    <div class="option-buttons">
        @(Html.DevExtreme().Button()
                           .Text("Find and Markup")
                           .Type(ButtonType.Default)
                           .StylingMode(ButtonStylingMode.Contained)
                           .OnClick("FindAndMarkup")
        )
        @(Html.DevExtreme().Button()
                           .Text("Download")
                           .Type(ButtonType.Default)
                           .StylingMode(ButtonStylingMode.Contained)
                           .UseSubmitBehavior(true)
                           .ElementAttr("style", "float: right")
        )
    </div>
</div>

@{ Html.EndForm(); }
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;

namespace AspNetCoreDemos.OfficeFileAPI {
    public partial class ContentManipulationController : DocumentProcessingController {
        public ContentManipulationController(ILogger<ContentManipulationController> logger, IWebHostEnvironment hostingEnvironment)
            : base(logger, hostingEnvironment) {
        }
    }
}
using System.IO;
using Microsoft.AspNetCore.Mvc;
using DevExpress.Pdf;

namespace AspNetCoreDemos.OfficeFileAPI {
    public partial class ContentManipulationController {
        const string textMarkupDefaultFile = "/Documents/Pdf/TextMarkup.pdf";
        protected override string SessionKey => "TextMarkupAnnotationsFileKey";

        public IActionResult PdfTextMarkupAnnotations() {
            return GetDemoView<PdfTextMarkupAnnotationsModel>("PdfTextMarkupAnnotations", SessionKey, HostingEnvironment.ContentRootPath + textMarkupDefaultFile);
        }

        public IActionResult DocumentViewPartial(PdfTextMarkupAnnotationsModel textMarkupModel) {
            return DocumentViewPartialAnotherDocument(textMarkupModel);
        }

        public IActionResult PdfTextMarkupAnnotationsUploadFiles() {
            using(PdfDocumentProcessor processor = new PdfDocumentProcessor()) {
                byte[] data;
                if(!HttpContext.Session.TryGetValue(SessionKey, out data))
                    return new EmptyResult();
                using(MemoryStream inputStream = new MemoryStream(data)) {
                    processor.LoadDocument(inputStream);
                    MemoryStream resultStream = new MemoryStream();
                    processor.SaveDocument(resultStream);
                    return CreateFileStreamResult(resultStream, "application/pdf", "pdf", "Result");
                }
            }
        }

        public IActionResult PdfFindAndMarkup(string text, int markupType) {
            using(PdfTextMarkupAnnotationsModel model = new PdfTextMarkupAnnotationsModel()) {
                byte[] data;
                if(HttpContext.Session.TryGetValue(SessionKey, out data)) {
                    model.LoadDocument(data);
                    HttpContext.Session.Set(SessionKey, model.FindAndMarkup(text, (PdfTextMarkupAnnotationType)markupType));
                }
                return new EmptyResult();
            }
        }

        public IActionResult DownloadResult() {
            byte[] data;
            if(HttpContext.Session.TryGetValue(SessionKey, out data))
                return CreateFileStreamResult(new MemoryStream(data), "application/pdf", "pdf", "Result");
            return new BadRequestResult();
        }
    }
}
using System;
using System.Collections.Generic;
using System.IO;
using DevExpress.Pdf;

namespace AspNetCoreDemos.OfficeFileAPI {
    public class PdfModelBase : IDisposable {
        readonly PdfDocumentProcessor processor;
        MemoryStream stream;
        List<PdfPageModel> items = new List<PdfPageModel>();

        public MemoryStream Stream { get { return stream; } }
        protected PdfDocumentProcessor Processor { get { return processor; } }
        protected PdfDocument Document { get { return processor.Document; } }
        public List<PdfPageModel> Items { get { return items; } }

        public string DocumentUrl { get; set; }
        public int PageIndex { get; set; }
        internal virtual string SessionKey { get; }
        public PdfPreviewModel PreviewModel { get; internal set; }

        public PdfModelBase() {
            processor = new PdfDocumentProcessor();
            PreviewModel = new PdfPreviewModel();
        }

        protected void CreateEmptyDocument() {
            stream = new MemoryStream();
            Processor.CreateEmptyDocument(stream);
            Document.Creator = "PDF Document Processor Demo";
            Document.Producer = "Developer Express Inc., " + AssemblyInfo.Version;
            Document.Author = "DevExpress Inc.";
        }

        public virtual void LoadDocument(byte[] data) {
            using(MemoryStream stream = new MemoryStream(data))
                LoadDocument(stream);
        }

        protected void LoadDocument(Stream stream) {
            processor.LoadDocument(stream, true);
            for(int pageNumber = 1; pageNumber <= processor.Document.Pages.Count; pageNumber++)
                Items.Add(new PdfPageModel(processor, pageNumber));
        }

        public void Dispose() {
            if(processor != null)
                processor.Dispose();
            GC.SuppressFinalize(this);
        }
    }
}
using System.ComponentModel.DataAnnotations;
using System.IO;
using DevExpress.Pdf;

namespace AspNetCoreDemos.OfficeFileAPI {
    public enum MarkupType {
        Highlight = 0,
        Underline = 1,
        [Display(Name = "Squiggly underline")]
        Squiggly = 2,
        Strikeout = 3
    }

    public class PdfTextMarkupAnnotationsModel : PdfModelBase {

        public PdfTextMarkupAnnotationsModel() {
            PreviewModel.PreviewDocumentAction = "DocumentViewPartial";
            PreviewModel.ControllerName = "ContentManipulation";
        }

        internal override string SessionKey { get { return "TextMarkupAnnotationsFileKey"; } }

        public byte[] FindAndMarkup(string textToFind, PdfTextMarkupAnnotationType markupType) {
            PdfDocumentProcessor processor = Processor;
            PdfTextSearchResults searchResults = processor.FindText(textToFind);
            if(searchResults.Status == PdfTextSearchStatus.Found) {
                while(searchResults.Status == PdfTextSearchStatus.Found) {
                    processor.AddTextMarkupAnnotation(searchResults.PageNumber, searchResults.Rectangles, markupType);
                    searchResults = processor.FindText(textToFind);
                }
            }
            using (MemoryStream stream = new MemoryStream()) {
                processor.SaveDocument(stream);
                return stream.ToArray();
            }
        }
    }
}