0

I have a Xamarin.Forms application supporting only UWP. I need to be able to load pdf files from the web and display the content in my application. I cannot find a solution that would work with UWP and handle pdf files that are not part of a project.

halfer
  • 19,824
  • 17
  • 99
  • 186
David Shochet
  • 5,035
  • 11
  • 57
  • 105
  • 1
    You can check SyncFusion https://help.syncfusion.com/xamarin/sfpdfviewer/getting-started – Bruno Caceiro Jan 17 '19 at 16:42
  • @Bruno Caceiro Thank you for your suggestion. I could not find how much it costs. I cannot pay a significant amount, and my company does not qualify for the free license... – David Shochet Jan 17 '19 at 18:20

2 Answers2

2

For your requirement, you could refer this code sample that custom a WebView and load pdf file with pdf.js host web application.

[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))]
namespace DisplayPDF.WinPhone81
{
    public class CustomWebViewRenderer : WebViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement != null)
            {
                var customWebView = Element as CustomWebView;
                Control.Source = new Uri(string.Format("ms-appx-web:///Assets/pdfjs/web/viewer.html?file={0}", string.Format ("ms-appx-web:///Assets/Content/{0}", WebUtility.UrlEncode(customWebView.Uri))));
            }
        }
    }
}

Update

This way it works. But the pdf should be a part of the project. Does it mean that there is no way to display random files, e.g. downloaded from the web?

I recollect the case that I answered in the last few months. For displaying random files, you could convert pdf file into Base64String then opening it by calling the openPdfAsBase64 JS function in the viewer.js. For detail please refer this case reply.

For better understand, I have create a code sample that you could refer. before using it you need place hello.pdf file in the LocalState folder. Because, the default file load path is LocalState folder, but it is empty at fist start.

PDFViewRenderer.cs

public class PDFViewRenderer :WebViewRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
    {
        base.OnElementChanged(e);
        if (e.NewElement != null)
        {
            Control.Source = new Uri("ms-appx-web:///Assets/pdfjs/web/viewer.html");
            Control.LoadCompleted += Control_LoadCompleted;
        }
    }
    private async void Control_LoadCompleted(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
    {
        PDFView pdfView = Element as PDFView;
        if (string.IsNullOrEmpty(pdfView?.FileName)) return;
        try
        {
            var Base64Data = await OpenAndConvert(pdfView?.FileName);
            var obj = await Control.InvokeScriptAsync("openPdfAsBase64", new[] { Base64Data });
        }
        catch (Exception ex)
        {
            throw ex;
        }

    }
    private async Task<string> OpenAndConvert(string FileName)
    {
        var folder = ApplicationData.Current.LocalFolder;
        var file = await folder.GetFileAsync(FileName);
        var filebuffer = await file.OpenAsync(FileAccessMode.Read);
        var reader = new DataReader(filebuffer.GetInputStreamAt(0));
        var bytes = new byte[filebuffer.Size];
        await reader.LoadAsync((uint)filebuffer.Size);
        reader.ReadBytes(bytes);
        return Convert.ToBase64String(bytes);
    }
}

PDFView.cs

public class PDFView : WebView
{
    public PDFView()
    {

    }
    public static readonly BindableProperty FileNameProperty = BindableProperty.Create(
    propertyName: "FileName",
    returnType: typeof(string),
    declaringType: typeof(PDFView),
    defaultValue: default(string));

    public string FileName
    {
        get { return (string)GetValue(FileNameProperty); }
        set { SetValue(FileNameProperty, value); }
    }
}

Usage

<Grid>
    <local:PDFView FileName="hello.pdf"/>
</Grid>

Please note you need use this viewer.js that was added openPdfAsBase64 method.

Nico Zhu
  • 32,367
  • 2
  • 15
  • 36
  • I am getting an error: Message: file origin does not match viewer's. I tried another file with the same result. Could you please tell what is wrong? – David Shochet Jan 18 '19 at 13:48
  • If I comment out "origin !== viewerOrigin" condition in 2 places, I get another error: Unexpected server response (500) while retrieving PDF "ms-appx-web:///Assets/Content/myFile.pdf". I made isSameOrigin() return true in two places, and it did not help... – David Shochet Jan 18 '19 at 16:17
  • Please share a mini sample and I will check. – Nico Zhu Jan 21 '19 at 02:07
  • 1
    Please note you need make `myFile.pdf` Build Action as `Content`. – Nico Zhu Jan 21 '19 at 08:19
  • This way it works. But the pdf should be a part of the project. Does it mean that there is no way to display random files, e.g. downloaded from the web? – David Shochet Jan 22 '19 at 13:12
  • I used PCLStorage to download the pdf in code behind. It puts the pdf in e.g. C:\Users\userme\AppData\Local\Packages\818F3EF5.MyApp_gkbk8c6n1cpxt\LocalState\documents. Is it the right way to go? If so, can I just append it to ms-appdata:///local/ or something? Thanks. – David Shochet Jan 22 '19 at 20:58
  • It seems like something is wrong with the viewer script you modified, as I am getting an Exception from HRESULT: 0x80020101 on: var obj = await Control.InvokeScriptAsync("openPdfAsBase64", new[] { ret }); – David Shochet Jan 23 '19 at 15:37
  • Also, when I set PdfViewer.Uri = fileName; in code behind, nothing happens. Shouldn't it call OnElementChanged? – David Shochet Jan 23 '19 at 15:54
  • I replaced build and web folders with the ones I found here: https://github.com/heejune/WinForm-PDFjs Now there is no exception, and the pdf viewer is on the screen, but it is empty. And, again, setting Uri in code behind doesn't do anything. – David Shochet Jan 23 '19 at 21:39
  • `fileName` is a dependency of CustomWebView. – Nico Zhu Jan 24 '19 at 02:04
  • No, in this case it is just a variable where I stored the name of the pdf file. I just wanted to show you that I assign the pdf name to the Uri property in code behind, and the execution does not seem to come to the custom renderer. But this is only one issue. Even if I set the Uri (file name) hard-coded in xaml, the pdf viewer is empty. – David Shochet Jan 24 '19 at 12:59
  • In the update part, the pdf file stored in the `LocalState` but not `\LocalState\Content`, I think you need modify the file path. – Nico Zhu Jan 25 '19 at 02:27
  • Sorry, may I ask what update part we are talking about? Where should I change the path? And should I create the Content folder programmatically, as there is no such a folder in LocalState? Thank you. – David Shochet Jan 25 '19 at 18:47
  • Please check `OpenAndConvert` method, the path of pdf file is `\LocalState\test.pdf`, but your file stored in the `\LocalState\Content\test.pdf`. So you could copy the file to `LocalState` before invoke `OpenAndConvert`. – Nico Zhu Jan 28 '19 at 02:25
  • I did copy the file there, and yet the pdf viewer is empty. I don't get any error messages, just nothing is displayed. The pdf viewer itself is there though, with its toolbar. – David Shochet Jan 28 '19 at 13:26
  • 1
    I have created a code sample please check the above case reply. – Nico Zhu Jan 29 '19 at 08:01
  • Thanks, now I succeeded in opening a pdf with FileName set in xaml. I have two issues though. 1) Even though the pdf is open, an error is shown: "PDF.js v1.4.20 (build: b15f335) Message: Worker was destroyed". 2) When I assign a file name from code behind (PdfViewer.FileName = fileName;), nothing happens. Thank you again. – David Shochet Jan 29 '19 at 13:51
  • Never mind the 2). Just please help me with the "Worker was destroyed" error, if you can. Thanks. – David Shochet Jan 29 '19 at 18:38
  • 1
    Sorry guy, I found that, but it is viewer.js internal error. I also have no idea. – Nico Zhu Jan 30 '19 at 03:25
0

It's been a long time but maybe it will help someone. I was able to fix the error "Worker was destroyed" by simply adding an empty string when calling viewer.html.

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
             base.OnElementChanged(e);
             if (e.NewElement != null)
             {
                string empty = string.Empty;

                 Control.Source = new Uri($"ms-appx-web:///Assets/pdfjs/web/viewer.html?file={empty}");
                 Control.LoadCompleted += Control_LoadCompleted;
        }
  
Jeper92
  • 21
  • 2