3

Hy, I'm working in a Xamarin PCL project with the platforms Android and UWP. As a feature the user should be able to open an pdf file.

For this I'm using Mozilla pdf.js. I have followed this link to get it done on Android and UWP. https://developer.xamarin.com/recipes/cross-platform/xamarin-forms/controls/display-pdf/ Only for UWP I can't get it to function.

Here is my custom renderer for UWP

[assembly: ExportRenderer(typeof(PdfView), 
typeof(PDF.UWP.Renderers.PdfViewRenderer))]
namespace PDF.UWP.Renderers
{
/// <summary>
/// The asset folder of the UWP app must contain the pdfjs folder and files.
/// </summary>
public class PdfViewRenderer: WebViewRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
    {
        base.OnElementChanged(e);
        if (e.NewElement != null)
        {
            PdfView pdfView = Element as PdfView;
            string sFile = string.Format("ms-appx-web://{0}", WebUtility.UrlEncode(pdfView.Uri));
            Control.Source = new Uri(string.Format("ms-appx-web:///Assets/pdfjs/web/viewer.html?file={0}", sFile));
        }
    }
}
}

Here is my PdfView class.

public class PdfView: WebView
{
    public static readonly BindableProperty DocumentInfoProperty =
        BindableProperty.Create(propertyName: nameof(TheDocumentInfo), returnType: typeof(DocumentInfo),
            declaringType: typeof(PdfView), defaultValue: default(DocumentInfo));

    public DocumentInfo TheDocumentInfo
    {
        get { return (DocumentInfo)GetValue(DocumentInfoProperty); }
        set { SetValue(DocumentInfoProperty, value); }
    }

    public string Uri { get { return TheDocumentInfo.LocalUrl; } }
    public string FileName { get { return TheDocumentInfo.FileName; } }
}

The file location on uwp = "ms-appx-web://C%3A%5CUsers%5CUser%5CAppData%5CLocal%5CPackages%5CPDFTEST.UWP_v4j5n0js0cwst%5CLocalState%5CPDFTest.pdf"

And this is correct.

But the error is:

Message: Unexpected server response (0) while retrieving PDF "ms-appx-web://C:/Users/User/AppData/Local/Packages/PDFTest.UWP_v4j5n0js0cwst/LocalState/PDFTest.pdf/".

UPDATE

I have recreated the renderer to:

protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
    {
        base.OnElementChanged(e);
        if (e.NewElement != null)
        {
            // TODO: testen
            PdfView pdfView = Element as PdfView;
            string sFile = string.Format("ms-appx-web://{0}/{1}", pdfView.Uri.Replace(pdfView.FileName, ""), WebUtility.UrlEncode(pdfView.FileName));
            Control.Source = new Uri(string.Format("ms-appx-web:///Assets/pdfjs/web/viewer.html?file={0}", sFile));
        }
    }

I don't know if this is the solution to my problem but this error is gone. The error now is:

PDF.js v1.1.366 (build: 9e9df56)

Message: stream must have data

UPDATE (I don't know if u should add this in this question or if I should made another one).

My error is still

stream must have data

I know now why. Because I'm developing a UWP application and I want to access a file outside my instal folder. This location is

C:\Users\User\AppData\Local\Packages\PDFTest.UWP_v4j5n0js0cwst\LocalState\

Apperently I can't access files outside my instalation folder. This includes coping the file to my install folder and read it there.

UPDATE I have 2 versions of pdf.js in my project. (1.1.366 and 1.9.426) This is my code now

    protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
    {
        base.OnElementChanged(e);
        if (e.NewElement != null)
        {
            PdfView pdfView = Element as PdfView;
            var uriString = "ms-appdata:///local/" + WebUtility.UrlEncode(pdfView.FileName);
            Control.Source = new Uri(string.Format("ms-appx-web:///Assets/pdfjs/web/viewer.html?file={0}", uriString));
        }
    }

When I try to open the file with Launcher.LaunchFileAsync and use the uriString it opens my browser and shows me the file.

In my application I get the following error's

(v1.1.366) Stream must have data.

(v1.9.426) Unexpected server response (0) while retrieving PDF "ms-appdata:///local/PDFTest.pdf".

I know that the pdf uri is correct and accessible but it still doesn't work. (for v1.9.426 I have added in viewer.js

var HOSTED_VIEWER_ORIGINS = ['null', 'http://mozilla.github.io', 'https://mozilla.github.io', 'ms-appdata://', 'ms-appx-web://PDFTest.uwp'];)

link to the testproject

Ties Theunissen
  • 136
  • 2
  • 16

1 Answers1

1

I know now why. Because I'm developing a UWP application and I want to access a file outside my instal folder. This location is

You have use ms-appx-web: uri scheme as your pdf file access path. Actually, your pdf path is C:\Users\User\AppData\Local\Packages\PDFTest.UWP_v4j5n0js0cwst\LocalState\ that stored in the app's local folder. So the file will not be accessed.

I have also tested with "ms-appdata:///local/" uri scheme to access the pdf file base on your project. Unfortunately, It can't be recognised by viewer.js.

And then, I tried to convert pdf file into Base64String then opening it by calling the openPdfAsBase64 JS function in the viewer.js.

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);  
}

Usage

protected  override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
    base.OnElementChanged(e);
    if (e.NewElement != null)
    {   
        Control.Source = new Uri("ms-appx-web:///Assets/pdfjs3/web/viewer.html");
        Control.LoadCompleted += Control_LoadCompleted;
    }
}

private async void Control_LoadCompleted(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
    CustomWebView pdfView = Element as CustomWebView;
    if (string.IsNullOrEmpty(pdfView?.Filename)) return;

    var ret = await OpenAndConvert(pdfView?.Filename);       

    var obj = await Control.InvokeScriptAsync("openPdfAsBase64", new[] { ret });
}

You could use this viewer.js that was added openPdfAsBase64 method.

Got hints following: How to embed the PDFjs into your C# Project.

Nico Zhu
  • 32,367
  • 2
  • 15
  • 36
  • Still getting the error "stream must have data", I think your anser is what I need but the only thing what is wrong (I guess) is that my uri is wrong. What could a good uri be in this case? The uri in this case is: "Uri = "C:\\Users\\User\\AppData\\Local\\Packages\\PDFTest.UWP_v4j5n0js0cwst\\LocalState\\PDFTest.pdf" but could this be a good uri? – Ties Theunissen Dec 11 '17 at 08:39
  • 1
    Please try to use `pdfView.Uri = "PDFTest.pdf"`. – Nico Zhu Dec 11 '17 at 08:41
  • Could you share a simple sample ? – Nico Zhu Dec 11 '17 at 10:15
  • I'm creating one now – Ties Theunissen Dec 11 '17 at 10:17
  • a test project is included – Ties Theunissen Dec 11 '17 at 11:10
  • Yes, I just finished implementing your answer. When I copied the viewer.js into the original it didn't work so I copied the intire pdfjs into the project and it works perfectly, The only problem is dat it is very slow with the rendering of the pdf. And the error pops up "worker is destroyed" so I'm going after that now. Anyway de pdf shows now so many Thanks! – Ties Theunissen Dec 12 '17 at 09:02
  • Yes, It does not have a good support on `UWP`, I think you could refer [this](https://www.syncfusion.com/products/xamarin/pdfviewer) to realize your pdf reader. – CoCaIceDew Dec 12 '17 at 09:17
  • But for syncfusion you have to pay :) – Ties Theunissen Dec 12 '17 at 09:28
  • I get this error : Message = "Exception from HRESULT: 0x80020101" And can't finding out why, some one has experienced this? – hermance Dec 19 '18 at 13:59