Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Caly.Core/App.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<converters:ZeroPageIndexConverter x:Key="zeroPageIndexConverter"/>
<converters:BoolToBrushConverter x:Key="BoolToBrushConverter"/>
<x:Double x:Key="ControlContentThemeFontSize">12</x:Double>
</ResourceDictionary>

Expand Down
10 changes: 9 additions & 1 deletion Caly.Core/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -132,6 +134,12 @@ public override void OnFrameworkInitializationCompleted()
// We need to make sure IPdfDocumentsService singleton is initiated in UI thread
_pdfDocumentsService = Services.GetRequiredService<IPdfDocumentsService>();

// Dark mode synchronoization
if (desktop?.MainWindow?.DataContext is Caly.Core.ViewModels.MainViewModel vm)
{
vm.SyncSettings();
}

base.OnFrameworkInitializationCompleted();
}

Expand Down
2 changes: 2 additions & 0 deletions Caly.Core/Assets/Icons.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
<StreamGeometry x:Key="arrow_rotate_clockwise_regular">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</StreamGeometry>
<StreamGeometry x:Key="arrow_rotate_counterclockwise_regular">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</StreamGeometry>

<StreamGeometry x:Key="moon_icon">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</StreamGeometry>

<!-- Not in use, for later
<StreamGeometry x:Key="line_horizontal_3_regular">M2 4.5C2 4.22386 2.22386 4 2.5 4H17.5C17.7761 4 18 4.22386 18 4.5C18 4.77614 17.7761 5 17.5 5H2.5C2.22386 5 2 4.77614 2 4.5Z M2 9.5C2 9.22386 2.22386 9 2.5 9H17.5C17.7761 9 18 9.22386 18 9.5C18 9.77614 17.7761 10 17.5 10H2.5C2.22386 10 2 9.77614 2 9.5Z M2.5 14C2.22386 14 2 14.2239 2 14.5C2 14.7761 2.22386 15 2.5 15H17.5C17.7761 15 18 14.7761 18 14.5C18 14.2239 17.7761 14 17.5 14H2.5Z</StreamGeometry>
<StreamGeometry x:Key="error_circle_regular">M12,2 C17.523,2 22,6.478 22,12 C22,17.522 17.523,22 12,22 C6.477,22 2,17.522 2,12 C2,6.478 6.477,2 12,2 Z M12,3.667 C7.405,3.667 3.667,7.405 3.667,12 C3.667,16.595 7.405,20.333 12,20.333 C16.595,20.333 20.333,16.595 20.333,12 C20.333,7.405 16.595,3.667 12,3.667 Z M11.9986626,14.5022358 C12.5502088,14.5022358 12.9973253,14.9493523 12.9973253,15.5008984 C12.9973253,16.0524446 12.5502088,16.4995611 11.9986626,16.4995611 C11.4471165,16.4995611 11,16.0524446 11,15.5008984 C11,14.9493523 11.4471165,14.5022358 11.9986626,14.5022358 Z M11.9944624,7 C12.3741581,6.99969679 12.6881788,7.28159963 12.7381342,7.64763535 L12.745062,7.7494004 L12.7486629,12.2509944 C12.7489937,12.6652079 12.4134759,13.0012627 11.9992625,13.0015945 C11.6195668,13.0018977 11.3055461,12.7199949 11.2555909,12.3539592 L11.2486629,12.2521941 L11.245062,7.7506001 C11.2447312,7.33638667 11.580249,7.00033178 11.9944624,7 Z</StreamGeometry>
Expand Down
4 changes: 4 additions & 0 deletions Caly.Core/Caly.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,8 @@
<SubType>Designer</SubType>
</AvaloniaResource>
</ItemGroup>

<ItemGroup>
<Folder Include="Messages\" />
</ItemGroup>
</Project>
15 changes: 15 additions & 0 deletions Caly.Core/Controls/PdfDocumentsTabsControl.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,21 @@
VerticalAlignment="Center"
HorizontalAlignment="Center"/>

<controls:VerticalSeparator/>

<!-- Toggle dark mode -->
<Button DockPanel.Dock="Left"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Background="Transparent"
Command="{Binding Parent.ToggleDarkModeForAllCommand}">
<PathIcon Width="17"
Height="17"
Data="{StaticResource moon_icon}">
<ToolTip.Tip>Toggle Dark Mode</ToolTip.Tip>
</PathIcon>
</Button>

<Grid/>

</DockPanel>
Expand Down
4 changes: 3 additions & 1 deletion Caly.Core/Controls/PdfPageItem.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
<RotateTransform Angle="{Binding Rotation}" />
</LayoutTransformControl.LayoutTransform>

<Grid Background="White"
<Grid Background="{Binding $parent[controls:PdfPageItemsControl].((vm:PdfDocumentViewModel)DataContext).IsDarkMode, Converter={StaticResource BoolToBrushConverter}}"
Width="{Binding Width}"
Height="{Binding Height}">

Expand All @@ -84,6 +84,8 @@
Foreground="{DynamicResource SystemAccentColor}"/>

<controls:SkiaPdfPageControl Picture="{Binding PdfPicture}"
IsDarkMode="{Binding $parent[controls:PdfPageItemsControl].((vm:PdfDocumentViewModel)DataContext).IsDarkMode}"
ImageMask="{Binding ImageMask}"
VisibleArea="{Binding VisibleArea}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
Expand Down
71 changes: 56 additions & 15 deletions Caly.Core/Controls/SkiaPdfPageControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,18 @@ private sealed class SkiaDrawOperation : ICustomDrawOperation
private readonly IRef<SKPicture>? _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<SKPicture>? picture, SKFilterQuality filterQuality)
public SkiaDrawOperation(Rect bounds, SKRect visibleArea, IRef<SKPicture>? picture, SKFilterQuality filterQuality, bool isDarkMode, SKPath imageMask)
{
_picture = picture;
_visibleArea = visibleArea;
_filterQuality = filterQuality;
_isDarkMode = isDarkMode;
_imageMask = imageMask;
Bounds = bounds;
}

Expand Down Expand Up @@ -86,6 +90,7 @@ public void Render(ImmediateDrawingContext context)

using (ISkiaSharpApiLease lease = leaseFeature.Lease())
{

var canvas = lease?.SkCanvas;
if (canvas is null)
{
Expand All @@ -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();
}
Expand Down Expand Up @@ -148,14 +165,38 @@ public Rect? VisibleArea
set => SetValue(VisibleAreaProperty, value);
}


public static readonly StyledProperty<bool> IsDarkModeProperty =
AvaloniaProperty.Register<SkiaPdfPageControl, bool>(nameof(IsDarkMode));

public bool IsDarkMode
{
get => GetValue(IsDarkModeProperty);
set => SetValue(IsDarkModeProperty, value);
}

public static readonly StyledProperty<SKPath> ImageMaskProperty =
AvaloniaProperty.Register<SkiaPdfPageControl, SKPath>(nameof(ImageMask));


public SKPath ImageMask
{
get => GetValue(ImageMaskProperty);
set => SetValue(ImageMaskProperty, value);
}
static SkiaPdfPageControl()
{
ClipToBoundsProperty.OverrideDefaultValue<SkiaPdfPageControl>(true);

AffectsRender<SkiaPdfPageControl>(PictureProperty, VisibleAreaProperty);
AffectsMeasure<SkiaPdfPageControl>(PictureProperty, VisibleAreaProperty);
IsDarkModeProperty.Changed.AddClassHandler<SkiaPdfPageControl>((x, e) => x.OnDarkModeChanged(e));
}

private void OnDarkModeChanged(AvaloniaPropertyChangedEventArgs e)
{
InvalidateVisual();
}
/// <summary>
/// This operation is executed on UI thread.
/// </summary>
Expand Down Expand Up @@ -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));
}
}
}
23 changes: 23 additions & 0 deletions Caly.Core/Converters/BoolToBrushConverter.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
8 changes: 6 additions & 2 deletions Caly.Core/Models/CalySettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public sealed class CalySettings
{
Width = 1000,
Height = 500,
PaneSize = 350
PaneSize = 350,
IsDarkModeEnabled = false
};

// TODO - Add version for compatibility checks
Expand All @@ -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
}
}
}
4 changes: 4 additions & 0 deletions Caly.Core/Services/JsonSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion Caly.Core/Services/PdfDocumentsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,9 @@ private async Task OpenLoadDocumentInternal(IStorageFile? storageFile, string? p
Task<int> 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);

Expand Down
45 changes: 45 additions & 0 deletions Caly.Core/Services/PdfPigPdfService.Pictures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -115,5 +122,43 @@ internal sealed partial class PdfPigPdfService
return recorder.EndRecording();
}
}


public async Task<SKPath?> 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);
}


}
}
Loading