/// <summary> /// Initialize with the page that we should track. /// </summary> /// <param name="pageInfo">A stream of cache tags and the PDF pages associated with it.</param> /// <remarks>We are really only interested in the first PdfPage we get - and we will re-subscribe, but not hold onto it.</remarks> public PDFPageViewModel(IObservable <Tuple <string, IObservable <PdfPage> > > pageInfo, IBlobCache cache = null) { _cache = cache ?? Blobs.LocalStorage; // Render an image when: // - We have at least one render request // - ImageStream is subscribed to // - the file or page updates somehow (new pageInfo iteration). // // Always pull from the cache first, but if that doesn't work, then render and place // in the cache. // Get the size of the thing when it is requested. This sequence must be finished before // any size calculations can be done! var imageSize = from pgInfo in pageInfo from sz in _cache.GetOrFetchPageSize(pgInfo.Item1, () => pgInfo.Item2.Take(1).Select(pdf => pdf.Size.ToIWalkerSize())) select new { PGInfo = pgInfo, Size = sz }; var publishedSize = imageSize .Do(info => _pageSize = info.Size) .Select(info => info.PGInfo) .Publish().RefCount(); _pageSizeLoaded = publishedSize.AsUnit(); // The render request must be "good" - that is well formed. Otherwise // we will just ignore it. This is because sometimes when things are being "sized" the // render request is malformed. RenderImage = ReactiveCommand.Create(); var renderRequest = RenderImage .Cast <Tuple <RenderingDimension, double, double> >() .Where(info => (info.Item1 == RenderingDimension.Horizontal && info.Item2 > 0) || (info.Item1 == RenderingDimension.Vertical && info.Item3 > 0) || (info.Item1 == RenderingDimension.MustFit && info.Item2 > 0 && info.Item3 > 0) ); // Generate an image when we have a render request and a everything else is setup. ImageStream = from requestInfo in Observable.CombineLatest(publishedSize, renderRequest, (pSize, rr) => new { pgInfo = pSize, RenderRequest = rr }) let imageDimensions = CalcRenderingSize(requestInfo.RenderRequest.Item1, requestInfo.RenderRequest.Item2, requestInfo.RenderRequest.Item3) from imageData in _cache.GetOrFetchPageImageData(requestInfo.pgInfo.Item1, imageDimensions.Item1, imageDimensions.Item2, () => requestInfo.pgInfo.Item2.SelectMany(pdfPg => { var ms = new MemoryStream(); var ra = WindowsRuntimeStreamExtensions.AsRandomAccessStream(ms); var opt = new PdfPageRenderOptions() { DestinationWidth = (uint)imageDimensions.Item1, DestinationHeight = (uint)imageDimensions.Item2 }; return(Observable.FromAsync(() => pdfPg.RenderToStreamAsync(ra).AsTask()) .Select(_ => ms.ToArray())); })) select new MemoryStream(imageData); }
/// <summary> /// Get everything setup to show the PDF document /// </summary> /// <param name="docSequence"></param> /// <param name="initialPage">The page that should be shown when we start up. Zero indexed</param> /// <param name="screen">The screen that hosts everything (routing!)</param> public FullTalkAsStripViewModel(IScreen screen, PDFFile file) { Debug.Assert(file != null); Debug.Assert(screen != null); HostScreen = screen; // We basically re-set each time a new file comes down from the top. Pages = new ReactiveList <PDFPageViewModel>(); var pageSizeChanged = file.WhenAny(x => x.NumberOfPages, x => x.Value) .DistinctUntilChanged() .ObserveOn(RxApp.MainThreadScheduler); var b = from newPageLength in pageSizeChanged let np = Pages.Count from allpages in CreateNPages(newPageLength - np, np, file) select new { numPages = newPageLength, freshPages = allpages }; b .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(info => SetNPages(info.numPages, info.freshPages)); // Page navigation. Make sure things are clean and we don't over-burden the UI before // we pass the info back to the UI! _moveToPage = new ReplaySubject <int>(1); _loaded = new ReplaySubject <Unit>(1); MoveToPage = _moveToPage .CombineLatest(_loaded, (p, _) => p) .Select(scrubPageIndex) .DistinctUntilChanged(); PageForward = ReactiveCommand.Create(); PageForward .Cast <int>() .Select(pn => pn + 1) .Subscribe(_moveToPage); PageBack = ReactiveCommand.Create(); PageBack .Cast <int>() .Select(pn => pn - 1) .Subscribe(_moveToPage); PageMove = ReactiveCommand.Create(); PageMove .Cast <int>() .Subscribe(_moveToPage); }