Embedding PDF.JS in CEFSharp - cefsharp/CefSharp GitHub Wiki
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