PDF Reading has often involved third party libraries or incomplete components or implementing the entire step ground up. Microsoft never had a PDF SDK solution earlier. But with release of Windows 8.1, WinRT has got itself a spanking new PDF SDK. This opens up possibilities for reusing a lot of rich content in your Windows 8 Store Apps. Specifically rendering Electronic Magazines that are already released as PDFs. Today we will see how we can convert each page of a magazine to a PNG and then render it on screen. As a sample, we will be taking our own DNC .NET Magazine authored by DotNetCurry authors
The DotNetCurry Magazine (DNC Mag) Pdf Reader
We start off with the Split View project template that gives us a Home Page full of tiles and a Details page that has a List and Details section.
The data comes from a SampleData.json file in the DataModel folder.
The Overall App Implementation
We want the first tile on the Home Page to show an “Open File” tile, clicking on which we should bring up a File Picker to select PDF files. Once the PDF is opened, we convert each page into an image and save them in the temporary cache of our application. We set the path of the image in the data source and bind it to the ListView for it to render the Images.
Updating the Sample Data Source
The Sample Data Source has a big set of data. We don’t actually need the sample and we should be creating our own data source. But for this demo, we’ll simply change the properties to more appropriate names and run with it.
We trim the SampleData.json to one group and a bunch of Items. The SampleGroup object property names are modified to be FileName, FilePath, ImagePath, LasAccessed (reserved for future use in case of a real Most Recently Used (MRU) list). The SampleGroupItem object is trimmed down to just UniqueId, PageNumber and ImagePath. The JSON data is as follows
"Groups":[
{
"UniqueId": "Group-1",
"FileName": "Open File",
"FilePath": "Open an existing PDF file",
"ImagePath": "Assets/DarkGray.png",
"Items":
[
{
"UniqueId": "Group-1-Item-1",
"PageNumber": "Item FileName: 1",
"ImagePath": "Assets/LightGray.png"
},
{
"UniqueId": "Group-1-Item-2",
"PageNumber": "Item FileName: 1",
"ImagePath": "Assets/DarkGray.png"
},
{
"UniqueId": "Group-1-Item-3",
"PageNumber": "Item FileName: 1",
"ImagePath": "Assets/MediumGray.png"
},
{
"UniqueId": "Group-1-Item-4",
"PageNumber": "Item FileName: 1",
"ImagePath": "Assets/DarkGray.png"
},
{
"UniqueId": "Group-1-Item-5",
"PageNumber": "Item FileName: 1",
"ImagePath": "Assets/MediumGray.png"
}
]
}]
The corresponding source objects are modified as follows:
public class SampleDataItem
{
public SampleDataItem(String uniqueId, String pageNumber, String imagePath)
{
this.UniqueId = uniqueId;
this.PageNumber = pageNumber;
this.ImagePath = imagePath;
}
public string UniqueId { get; private set; }
public string PageNumber { get; private set; }
public string ImagePath { get; private set; }
public override string ToString()
{
return this.PageNumber;
}
}
The Container Group used in the HomePage is as follows:
public class SampleDataGroup
{
public SampleDataGroup(String uniqueId, String title, String subtitle, String imagePath, DateTime lastAccessed
{
this.UniqueId = uniqueId;
this.FileName = title;
this.FilePath = subtitle;
this.LastAccessed = lastAccessed;
this.ImagePath = imagePath;
this.Items = new ObservableCollection<SampleDataItem>();
}
public string UniqueId { get; private set; }
public string FileName { get; private set; }
public string FilePath { get; private set; }
public DateTime LastAccessed { get; private set; }
public string ImagePath { get; private set; }
public ObservableCollection<SampleDataItem> Items { get; private set; }
public override string ToString()
{
return this.FileName;
}
}
We have to update the GetSampleDataAsync method that deserializes the JSON data also. We update the appropriate keys and constructors. The final code is as follows:
private async Task GetSampleDataAsync()
{
if (this._groups.Count != 0)
return;
Uri dataUri = new Uri("ms-appx:///DataModel/SampleData.json");
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(dataUri);
string jsonText = await FileIO.ReadTextAsync(file);
JsonObject jsonObject = JsonObject.Parse(jsonText);
JsonArray jsonArray = jsonObject["Groups"].GetArray();
foreach (JsonValue groupValue in jsonArray)
{
JsonObject groupObject = groupValue.GetObject();
SampleDataGroup group = new SampleDataGroup(groupObject["UniqueId"].GetString(),
groupObject["FileName"].GetString(),
groupObject["FilePath"].GetString(),
groupObject["ImagePath"].GetString(),
DateTime.Now);
foreach (JsonValue itemValue in groupObject["Items"].GetArray())
{
JsonObject itemObject = itemValue.GetObject();
group.Items.Add(new SampleDataItem(itemObject["UniqueId"].GetString(),
itemObject["PageNumber"].GetString(),
itemObject["ImagePath"].GetString()));
}
this.Groups.Add(group);
}
}
Updating the XAML Markup
We update the Bindings in the ItemsPage.xaml to use FileName and FilePath instead of Title and Description.
The ItemsPage.xaml looks as follows at Design Time
The changes to SplitPage.xaml are more extensive. We simply keep a 120px left padding for the back button and allocate the rest of the page to the ListView. From the ListView’s data template, we remove all other controls and keep the Image only. The final view at Design Time is as follows:
Opening and Reading PDF
After setting our application up, time to implement the File Open and read logic.
Updating the ItemView_ItemClick event
In the ItemsPage.xaml, when user clicks on any of the Tiles, the event is raised. We update the code to verify if the group.FileName property is set to “Open File”. If it is, we create a FileOpenPicker instance and set the Filter to PDFs.
async void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
SampleDataGroup group = (SampleDataGroup)e.ClickedItem;
StorageFile file = null;
if (group.FileName == "Open File")
{
FileOpenPicker filePicker = new FileOpenPicker();
filePicker.FileTypeFilter.Add(".pdf");
filePicker.ViewMode = PickerViewMode.Thumbnail;
filePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
filePicker.SettingsIdentifier = "picker1";
filePicker.CommitButtonText = "Open Pdf File";
file = await filePicker.PickSingleFileAsync();
}
else
{
//var groupId = ((SampleDataGroup)e.ClickedItem).UniqueId;
// TODO: Implement MRU functionality
}
if (file != null)
{
this.Frame.Navigate(typeof(SplitPage), file);
}
}
If the user selected a File, we navigate to the SplitPage and pass the StorageFile handle to it.
In Future, when we have real Most Recently Used lists (MRUs), we’ll have an image of the first page and we can open the file directly. But the first item will always remain as the “Open File Tile”.
Loading the File
After navigating to the SplitPage, we restore the StorageFile handle and call the LoadPdfFileAsync that does the load and conversion to PNG images.
private async void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
StorageFile selectedFile = e.NavigationParameter as StorageFile;
if (selectedFile != null)
{
await LoadPdfFileAsync(selectedFile);
}
}
The PdfDocument object
PdfDocument is the class that encapsulates PDF manipulation for us. We create an instance of it by Load the file using a static helper method:
PdfDocument pdfDocument = await PdfDocument.LoadFromFileAsync(pdfFile);
Next we initialize an observable collection of SampleDataItem objects and set it to the DefaultViewModel
ObservableCollection<SampleDataItem> items = new ObservableCollection<SampleDataItem>();
this.DefaultViewModel["Items"] = items;
Next we check if the PDF has atleast one page and we start looping through the pages. We get to a page using the GetPage call on the PdfDocument object
var pdfPage = pdfDocument.GetPage((uint)pageIndex);
To convert to PNGs, we get the TempFolder for the app and create a new File handle. Next we open a random access stream to that File.
StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder;
StorageFile pngFile = await tempFolder.CreateFileAsync(Guid.NewGuid().ToString() + ".png", CreationCollisionOption.ReplaceExisting);
IRandomAccessStream randomStream = await pngFile.OpenAsync(FileAccessMode.ReadWrite);
Before writing to the PNG stream, we can setup PDF rendering options. I have done a crude calculation of the Width of the Image control in the List and I pass that as the destination width.
PdfPageRenderOptions pdfPageRenderOptions = new PdfPageRenderOptions();
pdfPageRenderOptions.DestinationWidth = (uint)(this.ActualWidth - 130);
Finally we use the pdfPage’s RenderToStreamAsync API to write the PNG file out. Once the file is flushed, we add a new item to the data source. Since this is an observable collection, we’ll start seeing ‘pages’ as they come in.
await pdfPage.RenderToStreamAsync(randomStream, pdfPageRenderOptions);
await randomStream.FlushAsync();
randomStream.Dispose();
pdfPage.Dispose();
items.Add(new SampleDataItem(
pageIndex.ToString(),
pageIndex.ToString(),
pngFile.Path));
And that’s about it! Let’s run the app and see how it works.
PDF Reader Demo Time
The Launch page is rather boring at the moment as it looks like this:
When we click on the “Open File” tile, the File Picker starts off at the suggested Documents folder and shows the PDF files available.
We have here the Anniversary issue of DNC Magazine so when we select and click on Open Pdf File, we get the following:
If you hover mouse over the List or tap it, you will see that the scroll bar is rather large implying not all pages are loaded, but it progressively gets smaller as more pages load. As seen below, we have a screen shot of another page from the Magazine.
Pretty darn amazing!
Conclusion
That was a quick introduction to the new PdfDocument API that’s a part of the upcoming Windows 8.1 SDK for Store Apps ‘fondly’ known as WinRT. This demo got my imagination running wild with things we can do with PDFs now, hope the article was able to inspire you as well!
Download the entire source code of this article (Github)
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Sumit is a .NET consultant and has been working on Microsoft Technologies since his college days. He edits, he codes and he manages content when at work. C# is his first love, but he is often seen flirting with Java and Objective C. You can follow him on twitter at @
sumitkm or email him at sumitkm [at] gmail