Skip to content

Embedding PDF.JS in CEFSharp

aimcosoftware edited this page May 25, 2023 · 19 revisions

Using PDF.JS as default PDF viewer

PDF.JS is Mozilla's open source PDF renderer, available here. The same one that is used in Firefox.

There are several advantages to using PDF.JS

  • Has a complete API for manipulating the viewer and PDF, accessible from javascript.
  • All loaded from local HTML, CSS and JS files, so can be easily modified.
  • Page navigation from the index is added to browser history
  • Restores last scroll position when revisiting the document

To use it in CEFSharp:

  • unzip pdf.js in a convenient folder for your project
  • Add a sub folder to download and read PDFs
  • Register the folder in a custom scheme handler
  • Add javascript to post message when viewer loaded
  • disable the default PDF extension in CEFSharp

Basic code flow

  • Disable default handling so PDFs trigger a download
  • Use download and request handlers to trigger PDF handling
  • Download PDF to sub folder of the custom scheme
  • After isComplete, load PDF.JS viewer from custom scheme

Reloading and history

The PDF.JS viewer is loaded with a query string of the local scheme url and PDF url e.g pdfjs://viewer/web/viewer.html?file=pdfjs://viewer/cache/pdffile.pdf&url=https://original.com/pdffile.pdf.

The query parameter file is used by the viewer, url is used to provide the PDF url.

This means that on reload and history navigation, the PDF will be loaded from the custom scheme, as expected.

If you want to reload from the server, you would need to implement your own Reload(IgnoreCache) method.

Download PDF.JS and copy it to a convenient folder

Example folder structure with cache folder added for PDF downloads:

build         pdf.js code
cache         add folder for PDFs
web           pdf.js viewer 

Create custom scheme handler for root folder

Friend PDFEnabled As Boolean
Friend PDFCachePath As String

Friend Sub RegisterPDFJS(ByRef Settings As CefSettings, RootPath As String)

	'Disable default handling - PDFs will be downloaded
	Settings.CefCommandLineArgs.Add("disable-pdf-extension")

	Dim Scheme As New CefCustomScheme
	Scheme.SchemeName = "pdfjs"
	Scheme.DomainName = "viewer"
	Scheme.SchemeHandlerFactory = New SchemeHandler.FolderSchemeHandlerFactory(RootPath)
	Settings.RegisterScheme(Scheme)
	PDFCachePath = $"{RootPath}\cache\"
	PDFEnabled = True
End Sub

Add download and request handlers

'Set download and request handlers
Browser.DownloadHandler = Me
Browser.RequestHandler = Me

' Locals to control PDF download and viewing
Private PDFCacheUrl As String = ""
Private PDFSavePath As String = ""

Private Sub OnBeforeDownload(chromiumWebBrowser As IWebBrowser, browser As IBrowser, downloadItem As DownloadItem, callback As IBeforeDownloadCallback) Implements IDownloadHandler.OnBeforeDownload
	If PDFIsDownload(downloadItem) Then
		'Probably want to use an MD5 file name here
		PDFSavePath = $"{PDFCachePath}{downloadItem.SuggestedFileName}"
		PDFCacheUrl = $"pdfjs://viewer/cache/{downloadItem.SuggestedFileName}"
		callback.Continue(PDFSavePath, False)
	End If
End Sub

Private Sub OnDownloadUpdated(chromiumWebBrowser As IWebBrowser, browser As IBrowser, downloadItem As DownloadItem, callback As IDownloadItemCallback) Implements IDownloadHandler.OnDownloadUpdated
	If PDFIsDownload(downloadItem) Then
		If downloadItem.IsComplete Then
			chromiumWebBrowser.LoadUrl($"pdfjs://viewer/web/viewer.html?file={PDFCacheUrl}&url={downloadItem.Url}")
		End If
	End If
End Sub

' Is the download a PDF?
Private Function PDFIsDownload(Item As DownloadItem) As Boolean
	Return PDFEnabled And Item.MimeType = "application/pdf" And Not Item.Url.StartsWith("blob")
End Function

Browser address

You can provide the original PDF url, derived from the query of the custom scheme url. e.g.

Readonly Property Address() As String
	Get
		Return If(Browser.Address.StartsWith("pdfjs"), Browser.Address.split("=").Last, Browser.Address)
	End Get
End Property

Other things you may want to implement

  • Handle IsCancelled and IsValid properties in DownloadItem
  • Keep reference to callback handler to cancel download externally
  • Implement stop and reload with IgnoreCache commands