diff --git a/Caly.Core/App.axaml b/Caly.Core/App.axaml index a8149587..6f8a3472 100644 --- a/Caly.Core/App.axaml +++ b/Caly.Core/App.axaml @@ -11,6 +11,7 @@ + 12 diff --git a/Caly.Core/App.axaml.cs b/Caly.Core/App.axaml.cs index ea799527..4944935b 100644 --- a/Caly.Core/App.axaml.cs +++ b/Caly.Core/App.axaml.cs @@ -74,8 +74,10 @@ public override void OnFrameworkInitializationCompleted() // Initialise dependencies var services = new ServiceCollection(); - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + IClassicDesktopStyleApplicationLifetime? desktop = null; + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime d) { + desktop = d; desktop.ShutdownMode = ShutdownMode.OnMainWindowClose; desktop.MainWindow = new MainWindow { @@ -132,6 +134,12 @@ public override void OnFrameworkInitializationCompleted() // We need to make sure IPdfDocumentsService singleton is initiated in UI thread _pdfDocumentsService = Services.GetRequiredService(); + // Dark mode synchronoization + if (desktop?.MainWindow?.DataContext is Caly.Core.ViewModels.MainViewModel vm) + { + vm.SyncSettings(); + } + base.OnFrameworkInitializationCompleted(); } diff --git a/Caly.Core/Assets/Icons.axaml b/Caly.Core/Assets/Icons.axaml index 0c93d782..0bee49e3 100644 --- a/Caly.Core/Assets/Icons.axaml +++ b/Caly.Core/Assets/Icons.axaml @@ -31,6 +31,8 @@ M12 3C16.9706 3 21 7.02944 21 12C21 15.0777 19.4407 17.865 16.9769 19.5009L18.75 19.5C19.1642 19.5 19.5 19.8358 19.5 20.25C19.5 20.6297 19.2178 20.9435 18.8518 20.9932L18.75 21H14.75C14.3703 21 14.0565 20.7178 14.0068 20.3518L14 20.25V16.25C14 15.8358 14.3358 15.5 14.75 15.5C15.1297 15.5 15.4435 15.7822 15.4932 16.1482L15.5 16.25L15.501 18.635C17.9241 17.3557 19.5 14.8247 19.5 12C19.5 7.85786 16.1421 4.5 12 4.5C7.85786 4.5 4.5 7.85786 4.5 12C4.5 12.4142 4.16421 12.75 3.75 12.75C3.33579 12.75 3 12.4142 3 12C3 7.02944 7.02944 3 12 3ZM12 9.25C13.5188 9.25 14.75 10.4812 14.75 12C14.75 13.5188 13.5188 14.75 12 14.75C10.4812 14.75 9.25 13.5188 9.25 12C9.25 10.4812 10.4812 9.25 12 9.25ZM12 10.75C11.3096 10.75 10.75 11.3096 10.75 12C10.75 12.6904 11.3096 13.25 12 13.25C12.6904 13.25 13.25 12.6904 13.25 12C13.25 11.3096 12.6904 10.75 12 10.75Z M12 3C7.02944 3 3 7.02944 3 12C3 15.0777 4.55928 17.865 7.0231 19.5009L5.25 19.5C4.83579 19.5 4.5 19.8358 4.5 20.25C4.5 20.6297 4.78215 20.9435 5.14823 20.9932L5.25 21H9.25C9.6297 21 9.94349 20.7178 9.99315 20.3518L10 20.25V16.25C10 15.8358 9.66421 15.5 9.25 15.5C8.8703 15.5 8.55651 15.7822 8.50685 16.1482L8.5 16.25L8.49903 18.635C6.07593 17.3557 4.5 14.8247 4.5 12C4.5 7.85786 7.85786 4.5 12 4.5C16.1421 4.5 19.5 7.85786 19.5 12C19.5 12.4142 19.8358 12.75 20.25 12.75C20.6642 12.75 21 12.4142 21 12C21 7.02944 16.9706 3 12 3ZM12 9.25C10.4812 9.25 9.25 10.4812 9.25 12C9.25 13.5188 10.4812 14.75 12 14.75C13.5188 14.75 14.75 13.5188 14.75 12C14.75 10.4812 13.5188 9.25 12 9.25ZM12 10.75C12.6904 10.75 13.25 11.3096 13.25 12C13.25 12.6904 12.6904 13.25 12 13.25C11.3096 13.25 10.75 12.6904 10.75 12C10.75 11.3096 11.3096 10.75 12 10.75Z + M9.66862399,33.0089622 C14.6391867,41.6182294 25.647814,44.5679822 34.2570813,39.5974194 C36.6016136,38.243803 38.5753268,36.4126078 40.0785961,34.229664 C40.5811964,33.4998226 40.256086,32.4918794 39.421758,32.193262 C32.6414364,29.766492 29.0099482,26.9542522 26.9026684,22.9317305 C24.6842213,18.6970048 24.110919,14.0582926 25.662851,7.69987534 C25.8774494,6.82064469 25.1829812,5.98348115 24.27924,6.03196802 C21.4771404,6.18230425 18.739608,6.98721743 16.2570813,8.42050489 C7.64781404,13.3910676 4.69806124,24.3996949 9.66862399,33.0089622 Z M24.6881449,24.0918536 C26.9913881,28.4884439 30.80035,31.5226926 37.1145781,33.998575 C35.9639388,35.3646621 34.5800621,36.524195 33.0070813,37.4323559 C25.5935456,41.7125627 16.1138943,39.1724978 11.8336875,31.7589622 C7.55348069,24.3454265 10.0935456,14.8657752 17.5070813,10.5855684 C19.0445047,9.69793654 20.6992772,9.08707059 22.4136896,8.76727896 L22.882692,8.68729053 C21.6894389,14.6550319 22.2911719,19.5163454 24.6881449,24.0918536 Z + + + diff --git a/Caly.Core/Controls/PdfPageItem.axaml b/Caly.Core/Controls/PdfPageItem.axaml index a123e0c1..e02934d4 100644 --- a/Caly.Core/Controls/PdfPageItem.axaml +++ b/Caly.Core/Controls/PdfPageItem.axaml @@ -70,7 +70,7 @@ - @@ -84,6 +84,8 @@ Foreground="{DynamicResource SystemAccentColor}"/> diff --git a/Caly.Core/Controls/SkiaPdfPageControl.cs b/Caly.Core/Controls/SkiaPdfPageControl.cs index 31a03d12..8b4fdd33 100644 --- a/Caly.Core/Controls/SkiaPdfPageControl.cs +++ b/Caly.Core/Controls/SkiaPdfPageControl.cs @@ -42,14 +42,18 @@ private sealed class SkiaDrawOperation : ICustomDrawOperation private readonly IRef? _picture; private readonly SKFilterQuality _filterQuality; private readonly SKRect _visibleArea; + private readonly bool _isDarkMode; + private readonly SKPath _imageMask; private readonly Lock _lock = new Lock(); - public SkiaDrawOperation(Rect bounds, SKRect visibleArea, IRef? picture, SKFilterQuality filterQuality) + public SkiaDrawOperation(Rect bounds, SKRect visibleArea, IRef? picture, SKFilterQuality filterQuality, bool isDarkMode, SKPath imageMask) { _picture = picture; _visibleArea = visibleArea; _filterQuality = filterQuality; + _isDarkMode = isDarkMode; + _imageMask = imageMask; Bounds = bounds; } @@ -86,6 +90,7 @@ public void Render(ImmediateDrawingContext context) using (ISkiaSharpApiLease lease = leaseFeature.Lease()) { + var canvas = lease?.SkCanvas; if (canvas is null) { @@ -95,24 +100,36 @@ public void Render(ImmediateDrawingContext context) canvas.Save(); canvas.ClipRect(_visibleArea); - using (var p = new SKPaint()) + + if (_isDarkMode) + { + canvas = DarkModeRender.GenerateDarkModePage(canvas, _picture.Item, _imageMask, _filterQuality); + } + // Original rendering (no dark mode) + else { - p.FilterQuality = _filterQuality; - p.IsDither = false; - p.FakeBoldText = false; - p.IsAntialias = false; + + using (var p = new SKPaint()) + { + p.FilterQuality = _filterQuality; + p.IsDither = false; + p.FakeBoldText = false; + p.IsAntialias = false; + + canvas.DrawPicture(_picture.Item, p); + - canvas.DrawPicture(_picture.Item, p); #if DEBUG - using (var skFont = SKTypeface.Default.ToFont(_picture.Item.CullRect.Height / 4f, 1f)) - using (var fontPaint = new SKPaint(skFont)) - { - fontPaint.Style = SKPaintStyle.Fill; - fontPaint.Color = SKColors.Blue.WithAlpha(100); - canvas.DrawText(_picture.Item.UniqueId.ToString(), _picture.Item.CullRect.Width / 4f, _picture.Item.CullRect.Height / 2f, fontPaint); - } + using (var skFont = SKTypeface.Default.ToFont(_picture.Item.CullRect.Height / 4f, 1f)) + using (var fontPaint = new SKPaint(skFont)) + { + fontPaint.Style = SKPaintStyle.Fill; + fontPaint.Color = SKColors.Blue.WithAlpha(100); + canvas.DrawText(_picture.Item.UniqueId.ToString(), _picture.Item.CullRect.Width / 4f, _picture.Item.CullRect.Height / 2f, fontPaint); + } #endif + } } canvas.Restore(); } @@ -148,14 +165,38 @@ public Rect? VisibleArea set => SetValue(VisibleAreaProperty, value); } + + public static readonly StyledProperty IsDarkModeProperty = + AvaloniaProperty.Register(nameof(IsDarkMode)); + + public bool IsDarkMode + { + get => GetValue(IsDarkModeProperty); + set => SetValue(IsDarkModeProperty, value); + } + + public static readonly StyledProperty ImageMaskProperty = + AvaloniaProperty.Register(nameof(ImageMask)); + + + public SKPath ImageMask + { + get => GetValue(ImageMaskProperty); + set => SetValue(ImageMaskProperty, value); + } static SkiaPdfPageControl() { ClipToBoundsProperty.OverrideDefaultValue(true); AffectsRender(PictureProperty, VisibleAreaProperty); AffectsMeasure(PictureProperty, VisibleAreaProperty); + IsDarkModeProperty.Changed.AddClassHandler((x, e) => x.OnDarkModeChanged(e)); } + private void OnDarkModeChanged(AvaloniaPropertyChangedEventArgs e) + { + InvalidateVisual(); + } /// /// This operation is executed on UI thread. /// @@ -188,7 +229,7 @@ public override void Render(DrawingContext context) var filter = RenderOptions.GetBitmapInterpolationMode(this); - context.Custom(new SkiaDrawOperation(viewPort, tile, picture, filter.ToSKFilterQuality())); + context.Custom(new SkiaDrawOperation(viewPort, tile, picture, filter.ToSKFilterQuality(), IsDarkMode, ImageMask)); } } } diff --git a/Caly.Core/Converters/BoolToBrushConverter.cs b/Caly.Core/Converters/BoolToBrushConverter.cs new file mode 100644 index 00000000..ff72eaf9 --- /dev/null +++ b/Caly.Core/Converters/BoolToBrushConverter.cs @@ -0,0 +1,23 @@ +using Avalonia.Data.Converters; +using Avalonia.Media; +using System; +using System.Globalization; + +namespace Caly.Core.Converters +{ + // Its used for dark mode (true = black, false = white) + public class BoolToBrushConverter : IValueConverter + { + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value is bool isDark && isDark + ? new SolidColorBrush(Colors.Black) + : new SolidColorBrush(Colors.White); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Caly.Core/Models/CalySettings.cs b/Caly.Core/Models/CalySettings.cs index 034598ea..b8adb0d1 100644 --- a/Caly.Core/Models/CalySettings.cs +++ b/Caly.Core/Models/CalySettings.cs @@ -26,7 +26,8 @@ public sealed class CalySettings { Width = 1000, Height = 500, - PaneSize = 350 + PaneSize = 350, + IsDarkModeEnabled = false }; // TODO - Add version for compatibility checks @@ -37,9 +38,12 @@ public sealed class CalySettings public int PaneSize { get; set; } + public bool IsDarkModeEnabled { get; set; } + public enum CalySettingsProperty { - PaneSize = 0 + PaneSize = 0, + IsDarkModeEnabled = 1 } } } diff --git a/Caly.Core/Services/JsonSettingsService.cs b/Caly.Core/Services/JsonSettingsService.cs index 08837406..23e9337e 100644 --- a/Caly.Core/Services/JsonSettingsService.cs +++ b/Caly.Core/Services/JsonSettingsService.cs @@ -141,6 +141,10 @@ public void SetProperty(CalySettingsProperty property, object value) case CalySettingsProperty.PaneSize: _current.PaneSize = (int)(double)value; break; + + case CalySettingsProperty.IsDarkModeEnabled: + _current.IsDarkModeEnabled = (bool)value; + break; } } catch (Exception ex) diff --git a/Caly.Core/Services/PdfDocumentsService.cs b/Caly.Core/Services/PdfDocumentsService.cs index 6fd6428e..009e9f77 100644 --- a/Caly.Core/Services/PdfDocumentsService.cs +++ b/Caly.Core/Services/PdfDocumentsService.cs @@ -248,7 +248,9 @@ private async Task OpenLoadDocumentInternal(IStorageFile? storageFile, string? p Task openDocTask = documentViewModel.OpenDocument(storageFile, password, cancellationToken); // We need a lock to avoid issues with tabs when opening documents in parallel - _mainViewModel.PdfDocuments.AddSafely(documentViewModel); + // _mainViewModel.PdfDocuments.AddSafely(documentViewModel); + // AddPdfDocument sets parent of doc and then calls AddSafely + _mainViewModel.AddPdfDocument(documentViewModel); _mainViewModel.SelectedDocumentIndex = Math.Max(0, _mainViewModel.PdfDocuments.Count - 1); diff --git a/Caly.Core/Services/PdfPigPdfService.Pictures.cs b/Caly.Core/Services/PdfPigPdfService.Pictures.cs index c09a3ccf..48a548cd 100644 --- a/Caly.Core/Services/PdfPigPdfService.Pictures.cs +++ b/Caly.Core/Services/PdfPigPdfService.Pictures.cs @@ -19,12 +19,19 @@ // SOFTWARE. using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Caly.Core.Utilities; using Caly.Pdf.Models; +using Caly.Pdf.PageFactories; using SkiaSharp; using SkiaSharp.HarfBuzz; +using UglyToad.PdfPig; +using UglyToad.PdfPig.Content; +using UglyToad.PdfPig.Core; +using UglyToad.PdfPig.Graphics.Operations; namespace Caly.Core.Services { @@ -115,5 +122,43 @@ internal sealed partial class PdfPigPdfService return recorder.EndRecording(); } } + + + public async Task GenerateImageMaskAsync(int pageNumber, float scale, CancellationToken token) + { + if (_document == null || pageNumber < 1 || pageNumber > _document.NumberOfPages) + { + return null; + } + + return await ExecuteWithLockAsync(() => + { + if (IsDisposed()) + { + return null; + } + + var page = _document.GetPage(pageNumber); + if (page == null) + { + return null; + } + + var mask = new SKPath(); + foreach (var image in page.GetImages()) + { + var rect = new SKRect( + (float)image.Bounds.Left * scale, + (float)(page.Height - image.Bounds.Top) * scale, + (float)image.Bounds.Right * scale, + (float)(page.Height - image.Bounds.Bottom) * scale + ); + mask.AddRect(rect); + } + return mask; + }, token).ConfigureAwait(false); + } + + } } diff --git a/Caly.Core/Services/PdfPigPdfService.Thumbnail.cs b/Caly.Core/Services/PdfPigPdfService.Thumbnail.cs index 2134d220..f29d5a0c 100644 --- a/Caly.Core/Services/PdfPigPdfService.Thumbnail.cs +++ b/Caly.Core/Services/PdfPigPdfService.Thumbnail.cs @@ -18,15 +18,17 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using Avalonia.Media.Imaging; +using Avalonia.Skia; +using Avalonia.Threading; +using Caly.Core.Utilities; +using Caly.Core.ViewModels; +using SkiaSharp; using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Avalonia.Media.Imaging; -using Avalonia.Threading; -using Caly.Core.ViewModels; -using SkiaSharp; namespace Caly.Core.Services { @@ -55,14 +57,23 @@ private async Task SetThumbnail(PdfPageViewModel vm, SKPicture picture, Cancella } SKMatrix scale = SKMatrix.CreateScale(tWidth / (float)vm.Width, tHeight / (float)vm.Height); - + using (var surface = SKSurface.Create(new SKImageInfo(tWidth, tHeight))) - using (var canvas = surface.Canvas) { + var canvas = surface.Canvas; token.ThrowIfCancellationRequested(); - canvas.Clear(SKColors.White); - canvas.DrawPicture(picture, ref scale); + // Dark mode rendering + if (_settingsService.GetSettings().IsDarkModeEnabled) + { + canvas = DarkModeRender.GenerateDarkModePage(canvas, picture, vm.ImageMask.Clone(), scale: scale); + } + // Original rendering (no dark mode) + else + { + canvas.Clear(SKColors.White); + canvas.DrawPicture(picture, ref scale); + } #if DEBUG using (var skFont = SKTypeface.Default.ToFont(tHeight / 5f, 1f)) diff --git a/Caly.Core/Services/PdfPigPdfService.cs b/Caly.Core/Services/PdfPigPdfService.cs index afd3e386..00919306 100644 --- a/Caly.Core/Services/PdfPigPdfService.cs +++ b/Caly.Core/Services/PdfPigPdfService.cs @@ -58,7 +58,8 @@ internal sealed partial class PdfPigPdfService : IPdfService private const string DateTimeFormat = "yyyy-MM-dd HH:mm:ss zzz"; private readonly ITextSearchService _textSearchService; - + private readonly ISettingsService _settingsService; + private Stream? _fileStream; private PdfDocument? _document; private Uri? _filePath; @@ -93,9 +94,10 @@ public PdfPigPdfService() } #endif - public PdfPigPdfService(ITextSearchService textSearchService) + public PdfPigPdfService(ITextSearchService textSearchService, ISettingsService settingsService) { _textSearchService = textSearchService; + _settingsService = settingsService; var channel = Channel.CreateUnboundedPrioritized(new UnboundedPrioritizedChannelOptions() { diff --git a/Caly.Core/Utilities/DarkModeRender.cs b/Caly.Core/Utilities/DarkModeRender.cs new file mode 100644 index 00000000..01f6855a --- /dev/null +++ b/Caly.Core/Utilities/DarkModeRender.cs @@ -0,0 +1,73 @@ +using Avalonia.Controls; +using SkiaSharp; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Caly.Core.Utilities +{ + internal class DarkModeRender + { + + public static SKCanvas GenerateDarkModePage(SKCanvas canvas, SKPicture picture, SKPath imageMask, SKFilterQuality filterQuality = SKFilterQuality.None, SKMatrix scale = default) + { + canvas.Clear(SKColors.Black); + using (var invertPaint = new SKPaint()) + { + invertPaint.FilterQuality = filterQuality; + + // Invert lightness across whole page + SKHighContrastConfig config = new() + { + Grayscale = false, + InvertStyle = SKHighContrastConfigInvertStyle.InvertLightness, + Contrast = 0.0f + }; + + invertPaint.ColorFilter = SKColorFilter.CreateHighContrast(config); + + if(scale == default) + { + canvas.DrawPicture(picture, invertPaint); + } + else + { + canvas.DrawPicture(picture, ref scale, invertPaint); + } + + } + + // Image mask is used for drawing unprocessed images - pictures in the PDF that should not be inverted + if (imageMask != null) + { + if (scale != default) + { + imageMask.Transform(scale); + } + + using (var imagePaint = new SKPaint()) + { + imagePaint.FilterQuality = filterQuality; + + canvas.Save(); + + canvas.ClipPath(imageMask); + + if (scale == default) + { + canvas.DrawPicture(picture, imagePaint); + } + else + { + canvas.DrawPicture(picture, ref scale, imagePaint); + } + + canvas.Restore(); + } + } + return canvas; + } + } +} diff --git a/Caly.Core/ViewModels/MainViewModel.cs b/Caly.Core/ViewModels/MainViewModel.cs index 32d89dbb..1864430c 100644 --- a/Caly.Core/ViewModels/MainViewModel.cs +++ b/Caly.Core/ViewModels/MainViewModel.cs @@ -49,6 +49,8 @@ public sealed partial class MainViewModel : ViewModelBase [ObservableProperty] private bool _isSettingsPaneOpen; + [ObservableProperty] private bool _isDarkMode; + public string Version => CalyExtensions.CalyVersion; partial void OnSelectedDocumentIndexChanged(int oldValue, int newValue) @@ -110,6 +112,7 @@ public MainViewModel() Dispatcher.UIThread.Post(() => Exception = new ExceptionViewModel(ex)); } }); + SyncSettings(); } private PdfDocumentViewModel? GetCurrentPdfDocument() @@ -228,5 +231,36 @@ private void ActivatePreviousDocument() } SelectedDocumentIndex = newIndex; } + + [RelayCommand] + public void ToggleDarkModeForAll() + { + IsDarkMode = !IsDarkMode; + var settingsService = App.Current?.Services?.GetService(); + settingsService?.SetProperty(Models.CalySettings.CalySettingsProperty.IsDarkModeEnabled, IsDarkMode); + var doc = GetCurrentPdfDocument(); + if (doc != null) + { + doc.ClearAllThumbnails(); + doc.RefreshAllThumbnails(); + } + + foreach (var d in PdfDocuments) + { + d.IsDarkMode = IsDarkMode; + } + } + + public void AddPdfDocument(PdfDocumentViewModel doc) + { + doc.Parent = this; + PdfDocuments.AddSafely(doc); + } + + public void SyncSettings() + { + var settingsService = App.Current?.Services?.GetService(); + IsDarkMode = settingsService?.GetSettings().IsDarkModeEnabled ?? false; + } } } \ No newline at end of file diff --git a/Caly.Core/ViewModels/PdfDocumentViewModel.cs b/Caly.Core/ViewModels/PdfDocumentViewModel.cs index 7156cd43..b0787d6a 100644 --- a/Caly.Core/ViewModels/PdfDocumentViewModel.cs +++ b/Caly.Core/ViewModels/PdfDocumentViewModel.cs @@ -55,6 +55,7 @@ public override string ToString() private readonly ISettingsService _settingsService; private readonly CancellationTokenSource _cts = new(); + internal string? LocalPath { get; private set; } [ObservableProperty] private ObservableCollection _pages = []; @@ -93,7 +94,6 @@ public int? SelectedPageIndex SelectedPageIndexString = value.HasValue ? value.Value.ToString("0") : string.Empty; } } - [ObservableProperty] private int _pageCount; [ObservableProperty] private string? _fileName; @@ -105,6 +105,8 @@ public int? SelectedPageIndex private readonly Lazy _loadPagesTask; public Task LoadPagesTask => _loadPagesTask.Value; + [ObservableProperty] private bool _isDarkMode; + /// /// The task that opens the document. Can be awaited to make sure the document is done opening. /// @@ -125,7 +127,7 @@ public PdfDocumentViewModel() throw new InvalidOperationException("Should only be called in Design mode."); } - _pdfService = new PdfPigPdfService(new LiftiTextSearchService()); + _pdfService = new PdfPigPdfService(new LiftiTextSearchService(), null); _settingsService = new JsonSettingsService(null); _paneSize = 50; @@ -147,11 +149,12 @@ public PdfDocumentViewModel(IPdfService pdfService, ISettingsService settingsSer _settingsService = settingsService; _paneSize = _settingsService.GetSettings().PaneSize; - + _loadPagesTask = new Lazy(LoadPages); _loadBookmarksTask = new Lazy(LoadBookmarks); _loadPropertiesTask = new Lazy(LoadProperties); _buildSearchIndex = new Lazy(BuildSearchIndex); + _isDarkMode = _settingsService.GetSettings().IsDarkModeEnabled; _searchResultsDisposable = SearchResults .GetWeakCollectionChangedObservable() @@ -164,7 +167,7 @@ public PdfDocumentViewModel(IPdfService pdfService, ISettingsService settingsSer { throw new NullReferenceException("The TextSelectionHandler is null, cannot process search results."); } - + switch (e.Action) { case NotifyCollectionChangedAction.Reset: @@ -317,6 +320,8 @@ await Task.Run(async () => Width = defaultWidth }; + await newPage.LoadImageMaskAsync(_cts.Token); + App.Messenger.Send(new LoadPageSizeMessage(newPage)); Pages.Add(newPage); } @@ -363,5 +368,15 @@ private bool CanGoToNextPage() return SelectedPageIndex.Value < PageCount; } + + public void RefreshAllThumbnails() + { + foreach (var page in Pages) + { + App.Messenger.Send(new LoadThumbnailMessage(page)); + } + } + + public MainViewModel? Parent { get; set; } } } diff --git a/Caly.Core/ViewModels/PdfPageViewModel.cs b/Caly.Core/ViewModels/PdfPageViewModel.cs index 0bc844c9..fd5d5d1b 100644 --- a/Caly.Core/ViewModels/PdfPageViewModel.cs +++ b/Caly.Core/ViewModels/PdfPageViewModel.cs @@ -45,6 +45,7 @@ public override string ToString() internal readonly IPdfService PdfService; + [ObservableProperty] private PdfTextLayer? _pdfTextLayer; @@ -105,6 +106,8 @@ public override string ToString() public bool IsPortrait => Rotation == 0 || Rotation == 180; + [ObservableProperty] private SKPath _imageMask; + private long _isSizeSet; public bool IsSizeSet() { @@ -143,6 +146,7 @@ public PdfPageViewModel(int pageNumber, IPdfService pdfService) public async Task LoadPageSizeImmediate(CancellationToken cancellationToken) { await PdfService.SetPageSizeAsync(this, cancellationToken); + await LoadImageMaskAsync(cancellationToken); } public async Task SetPageTextLayerImmediate(CancellationToken token) @@ -171,5 +175,20 @@ public ValueTask DisposeAsync() App.Messenger.Send(new UnloadThumbnailMessage(this)); return ValueTask.CompletedTask; } + + public async Task LoadImageMaskAsync(CancellationToken token) + { + if (PdfService is PdfPigPdfService pdfPigService) + { + try + { + ImageMask = await pdfPigService.GenerateImageMaskAsync(PageNumber, 1.0f, token); + } + catch (Exception ex) + { + Debug.WriteExceptionToFile(ex); + } + } + } } } diff --git a/Caly.iOS/Caly.iOS.csproj b/Caly.iOS/Caly.iOS.csproj index 5767b72d..05caa4fb 100644 --- a/Caly.iOS/Caly.iOS.csproj +++ b/Caly.iOS/Caly.iOS.csproj @@ -1,7 +1,7 @@  Exe - net8.0-ios + net9.0-ios 10.0 manual enable