Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd577b9609 | |||
| 100afbdc32 |
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ using QuestPDF.ReportSample.Layouts;
|
||||
|
||||
//ImagePlaceholder.Solid = true;
|
||||
|
||||
// var model = DataSource.GetReport();
|
||||
// var report = new StandardReport(model);
|
||||
// report.ShowInPreviewer();
|
||||
//
|
||||
// return;
|
||||
var model = DataSource.GetReport();
|
||||
var report = new StandardReport(model);
|
||||
report.ShowInPreviewer();
|
||||
|
||||
return;
|
||||
|
||||
Document
|
||||
.Create(container =>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Rendering.SceneGraph;
|
||||
using Avalonia.Skia;
|
||||
using DynamicData;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer;
|
||||
@@ -10,9 +11,10 @@ class InteractiveCanvas : ICustomDrawOperation
|
||||
{
|
||||
public Rect Bounds { get; set; }
|
||||
public ICollection<PreviewPage> Pages { get; set; }
|
||||
public InspectionElement? InspectionElement { get; set; }
|
||||
|
||||
private float Width => (float)Bounds.Width;
|
||||
private float Height => (float)Bounds.Height;
|
||||
private float ViewportWidth => (float)Bounds.Width;
|
||||
private float ViewportHeight => (float)Bounds.Height;
|
||||
|
||||
public float Scale { get; private set; } = 1;
|
||||
public float TranslateX { get; set; }
|
||||
@@ -28,7 +30,7 @@ class InteractiveCanvas : ICustomDrawOperation
|
||||
public float TotalHeight => TotalPagesHeight + SafeZone * 2 / Scale;
|
||||
public float MaxWidth => Pages.Any() ? Pages.Max(x => x.Width) : 0;
|
||||
|
||||
public float MaxTranslateY => TotalHeight - Height / Scale;
|
||||
public float MaxTranslateY => TotalHeight - ViewportHeight / Scale;
|
||||
|
||||
public float ScrollPercentY
|
||||
{
|
||||
@@ -46,7 +48,7 @@ class InteractiveCanvas : ICustomDrawOperation
|
||||
{
|
||||
get
|
||||
{
|
||||
var viewPortSize = Height / Scale / TotalHeight;
|
||||
var viewPortSize = ViewportHeight / Scale / TotalHeight;
|
||||
return Math.Clamp(viewPortSize, 0, 1);
|
||||
}
|
||||
}
|
||||
@@ -61,19 +63,19 @@ class InteractiveCanvas : ICustomDrawOperation
|
||||
|
||||
private void LimitTranslate()
|
||||
{
|
||||
if (TotalPagesHeight > Height / Scale)
|
||||
if (TotalPagesHeight > ViewportHeight / Scale)
|
||||
{
|
||||
TranslateY = Math.Min(TranslateY, MaxTranslateY);
|
||||
TranslateY = Math.Max(TranslateY, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
TranslateY = (TotalPagesHeight - Height / Scale) / 2;
|
||||
TranslateY = (TotalPagesHeight - ViewportHeight / Scale) / 2;
|
||||
}
|
||||
|
||||
if (Width / Scale < MaxWidth)
|
||||
if (ViewportWidth / Scale < MaxWidth)
|
||||
{
|
||||
var maxTranslateX = (Width / 2 - SafeZone) / Scale - MaxWidth / 2;
|
||||
var maxTranslateX = (ViewportWidth / 2 - SafeZone) / Scale - MaxWidth / 2;
|
||||
|
||||
TranslateX = Math.Min(TranslateX, -maxTranslateX);
|
||||
TranslateX = Math.Max(TranslateX, maxTranslateX);
|
||||
@@ -104,6 +106,72 @@ class InteractiveCanvas : ICustomDrawOperation
|
||||
|
||||
LimitTranslate();
|
||||
}
|
||||
|
||||
public int ActivePage { get; set; } = 1;
|
||||
|
||||
public IEnumerable<(int pageNumber, float beginY, float endY)> GetPagePosition()
|
||||
{
|
||||
var pageNumber = 1;
|
||||
var currentPagePosition = SafeZone / Scale;
|
||||
|
||||
foreach (var page in Pages)
|
||||
{
|
||||
yield return (pageNumber, currentPagePosition, currentPagePosition + page.Height);
|
||||
currentPagePosition += page.Height + PageSpacing;
|
||||
pageNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetActivePage(float x, float y)
|
||||
{
|
||||
y /= Scale;
|
||||
y += TranslateY;
|
||||
|
||||
ActivePage = GetPagePosition().FirstOrDefault(p => p.beginY <= y && y <= p.endY).pageNumber;
|
||||
}
|
||||
|
||||
public void ScrollToInspectionElement(InspectionElement element)
|
||||
{
|
||||
var location = element.Location.MinBy(x => x.PageNumber);
|
||||
var pagePosition = GetPagePosition().ElementAt(location.PageNumber - 1);
|
||||
var page = Pages.ElementAt(location.PageNumber - 1);
|
||||
|
||||
var widthScale = ViewportWidth / location.Width;
|
||||
var heightScale = ViewportHeight / location.Height;
|
||||
var targetScale = Math.Min(widthScale, heightScale);
|
||||
targetScale *= 0.7f; // slightly zoom out to show entire element with padding
|
||||
|
||||
Scale = targetScale;
|
||||
|
||||
TranslateY = pagePosition.beginY + location.Top + location.Height / 2 - ViewportHeight / Scale / 2;
|
||||
TranslateX = page.Width / 2 - location.Left - location.Width / 2;
|
||||
}
|
||||
|
||||
public (int pageNumber, float x, float y)? FindClickedPointOnThePage(float x, float y)
|
||||
{
|
||||
x -= ViewportWidth / 2;
|
||||
x /= Scale;
|
||||
x += TranslateX;
|
||||
|
||||
y /= Scale;
|
||||
y += TranslateY;
|
||||
|
||||
var location = GetPagePosition().FirstOrDefault(p => p.beginY <= y && y <= p.endY);
|
||||
|
||||
if (location == default)
|
||||
return null;
|
||||
|
||||
var page = Pages.ElementAt(location.pageNumber - 1);
|
||||
|
||||
x += page.Width / 2;
|
||||
|
||||
if (x < 0 || page.Width < x)
|
||||
return null;
|
||||
|
||||
y -= location.beginY;
|
||||
|
||||
return (location.pageNumber, x, y);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -124,20 +192,32 @@ class InteractiveCanvas : ICustomDrawOperation
|
||||
|
||||
var originalMatrix = canvas.TotalMatrix;
|
||||
|
||||
canvas.Translate(Width / 2, 0);
|
||||
|
||||
canvas.Translate(ViewportWidth / 2, 0);
|
||||
canvas.Scale(Scale);
|
||||
canvas.Translate(TranslateX, -TranslateY + SafeZone / Scale);
|
||||
canvas.Translate(TranslateX, -TranslateY);
|
||||
|
||||
var topMatrix = canvas.TotalMatrix;;
|
||||
|
||||
var positions = GetPagePosition().ToList();
|
||||
|
||||
foreach (var page in Pages)
|
||||
foreach (var pageIndex in Enumerable.Range(0, Pages.Count))
|
||||
{
|
||||
canvas.Translate(-page.Width / 2f, 0);
|
||||
canvas.SetMatrix(topMatrix);
|
||||
|
||||
var page = Pages.ElementAt(pageIndex);
|
||||
var position = positions.ElementAt(pageIndex);
|
||||
|
||||
canvas.Translate(-page.Width / 2f, position.beginY);
|
||||
DrawBlankPage(canvas, page.Width, page.Height);
|
||||
canvas.DrawPicture(page.Picture);
|
||||
canvas.Translate(page.Width / 2f, page.Height + PageSpacing);
|
||||
DrawInspectionElement(canvas, pageIndex + 1);
|
||||
}
|
||||
|
||||
canvas.SetMatrix(topMatrix);
|
||||
DrawActivePage(canvas);
|
||||
|
||||
canvas.SetMatrix(originalMatrix);
|
||||
|
||||
DrawInnerGradient(canvas);
|
||||
}
|
||||
|
||||
@@ -173,7 +253,7 @@ class InteractiveCanvas : ICustomDrawOperation
|
||||
#region inner viewport gradient
|
||||
|
||||
private const int InnerGradientSize = (int)SafeZone;
|
||||
private static readonly SKColor InnerGradientColor = SKColor.Parse("#666");
|
||||
private static readonly SKColor InnerGradientColor = SKColor.Parse("#555");
|
||||
|
||||
private void DrawInnerGradient(SKCanvas canvas)
|
||||
{
|
||||
@@ -195,7 +275,69 @@ class InteractiveCanvas : ICustomDrawOperation
|
||||
SKShaderTileMode.Clamp)
|
||||
};
|
||||
|
||||
canvas.DrawRect(0, 0, Width, InnerGradientSize, fogPaint);
|
||||
canvas.DrawRect(0, 0, ViewportWidth, InnerGradientSize, fogPaint);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interactivity
|
||||
|
||||
private void DrawActivePage(SKCanvas canvas)
|
||||
{
|
||||
if (ActivePage == default)
|
||||
return;
|
||||
|
||||
var page = Pages.ElementAt(ActivePage - 1);
|
||||
var pagePosition = GetPagePosition().ElementAt(ActivePage - 1);
|
||||
|
||||
var thickness = 6f / Scale;
|
||||
|
||||
using var strokePaint = new SKPaint
|
||||
{
|
||||
StrokeWidth = thickness,
|
||||
IsStroke = true,
|
||||
Color = SKColor.Parse("#000")
|
||||
};
|
||||
|
||||
canvas.DrawRect(- page.Width / 2 -thickness / 2, pagePosition.beginY -thickness / 2, page.Width + thickness, page.Height + thickness, strokePaint);
|
||||
}
|
||||
|
||||
private void DrawInspectionElement(SKCanvas canvas, int pageNumber)
|
||||
{
|
||||
if (InspectionElement == null || InspectionElement.Location == null)
|
||||
return;
|
||||
|
||||
var location = InspectionElement.Location.FirstOrDefault(x => x.PageNumber == pageNumber);
|
||||
|
||||
if (location == null)
|
||||
return;
|
||||
|
||||
var size = 6 / Scale;
|
||||
size = Math.Min(size, 3);
|
||||
|
||||
using var strokePaint1 = new SKPaint
|
||||
{
|
||||
StrokeWidth = size,
|
||||
IsStroke = true,
|
||||
Color = SKColor.Parse("#42A5F5")
|
||||
};
|
||||
|
||||
using var strokePaint2 = new SKPaint
|
||||
{
|
||||
StrokeWidth = size,
|
||||
IsStroke = true,
|
||||
PathEffect = SKPathEffect.CreateDash(new[] { size * 3, size * 3 }, 0),
|
||||
Color = SKColor.Parse("#1E88E5")
|
||||
};
|
||||
|
||||
using var backgroundPaint = new SKPaint
|
||||
{
|
||||
Color = SKColor.Parse("#442196F3"),
|
||||
};
|
||||
|
||||
canvas.DrawRect(location.Left, location.Top, location.Width, location.Height, backgroundPaint);
|
||||
canvas.DrawRect(location.Left + size / 2, location.Top + size / 2, location.Width - size, location.Height - size, strokePaint1);
|
||||
canvas.DrawRect(location.Left + size / 2, location.Top + size / 2, location.Width - size, location.Height - size, strokePaint2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:previewer="clr-namespace:QuestPDF.Previewer"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="QuestPDF.Previewer.MyHierarchy">
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
<RowDefinition Height="Auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Panel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Name="Highlight" Background="transparent" DoubleTapped="InputElement_OnDoubleTapped" PointerEnter="Highlight_OnPointerEnter" PointerLeave="Indentation_OnPointerLeave" PointerPressed="Clicked"></Panel>
|
||||
<Panel Grid.Row="0" Grid.Column="0" Name="Indentation"></Panel>
|
||||
|
||||
<Panel Grid.Row="0" Grid.Column="1" Name="Panel" Width="16" Height="16" Margin="0,0,8,0" Background="transparent" PointerPressed="Toggle" PointerEnter="Highlight_OnPointerEnter" PointerLeave="Indentation_OnPointerLeave">
|
||||
<Viewbox Width="24" Height="24">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="#AFFF" Name="Icon" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Panel>
|
||||
|
||||
<Panel Grid.Row="0" Grid.Column="2">
|
||||
<TextBlock Name="ElementName" FontSize="12" Foreground="#AFFF" Margin="0,6" IsHitTestVisible="False"></TextBlock>
|
||||
</Panel>
|
||||
|
||||
<ItemsRepeater Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Name="Repeater" HorizontalCacheLength="1000000" VerticalCacheLength="100000">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate DataType="previewer:InspectionElement">
|
||||
<previewer:MyHierarchy Hierarchy="{Binding}" OnSelected="MyHierarchy_OnOnSelected"></previewer:MyHierarchy>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Windows.Input;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media;
|
||||
using Color = System.Drawing.Color;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
public partial class MyHierarchy : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<InspectionElement> HierarchyProperty =
|
||||
AvaloniaProperty.Register<MyHierarchy, InspectionElement>(nameof(Hierarchy));
|
||||
|
||||
public InspectionElement Hierarchy
|
||||
{
|
||||
get => GetValue(HierarchyProperty);
|
||||
set => SetValue(HierarchyProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<InspectionElement?> SelectionProperty =
|
||||
AvaloniaProperty.Register<MyHierarchy, InspectionElement?>(nameof(Selection));
|
||||
|
||||
public InspectionElement? Selection
|
||||
{
|
||||
get => GetValue(SelectionProperty);
|
||||
set => SetValue(SelectionProperty, value);
|
||||
}
|
||||
|
||||
public event Action<InspectionElement>? OnSelected;
|
||||
|
||||
public bool Extended { get; set; }
|
||||
|
||||
public MyHierarchy()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
HierarchyProperty.Changed.Subscribe(x =>
|
||||
{
|
||||
Configure();
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
Configure();
|
||||
}
|
||||
|
||||
private void Configure()
|
||||
{
|
||||
var panel = this.FindControl<Panel>("Panel");
|
||||
panel.IsVisible = Hierarchy?.Children?.Any() ?? false;
|
||||
|
||||
this.FindControl<Panel>("Indentation").Width = (Hierarchy?.Level ?? 0) * 24;
|
||||
|
||||
UpdateIcon();
|
||||
|
||||
this.FindControl<TextBlock>("ElementName").Text = Hierarchy?.Text;
|
||||
this.FindControl<TextBlock>("ElementName").Foreground = new SolidColorBrush(Avalonia.Media.Color.Parse(Hierarchy?.FontColor ?? "#FFF"));
|
||||
}
|
||||
|
||||
private const string ExtendedIcon = "M7,15L12,10L17,15H7Z";
|
||||
private const string CollapsedIcon = "M7,10L12,15L17,10H7Z";
|
||||
|
||||
private void Toggle(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
Extended = !Extended;
|
||||
|
||||
UpdateIcon();
|
||||
this.FindControl<ItemsRepeater>("Repeater").Items = Extended ? Hierarchy?.Children : Array.Empty<InspectionElement>();
|
||||
}
|
||||
|
||||
private void UpdateIcon()
|
||||
{
|
||||
var iconControl = this.FindControl<Avalonia.Controls.Shapes.Path>("Icon");
|
||||
|
||||
iconControl.Data = new PathGeometry
|
||||
{
|
||||
Figures = PathFigures.Parse(Extended ? ExtendedIcon : CollapsedIcon)
|
||||
};
|
||||
}
|
||||
|
||||
private void InputElement_OnDoubleTapped(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Toggle(null, null);
|
||||
}
|
||||
|
||||
private void Highlight_OnPointerEnter(object? sender, PointerEventArgs e)
|
||||
{
|
||||
Cursor = Cursor.Parse("hand");
|
||||
this.FindControl<Panel>("Highlight").Background = new SolidColorBrush(Avalonia.Media.Color.Parse("#1FFF"));
|
||||
}
|
||||
|
||||
private void Indentation_OnPointerLeave(object? sender, PointerEventArgs e)
|
||||
{
|
||||
Cursor = Cursor.Default;
|
||||
this.FindControl<Panel>("Highlight").Background = new SolidColorBrush(Avalonia.Media.Color.Parse("#0000"));
|
||||
}
|
||||
|
||||
private void Clicked(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
OnSelected?.Invoke(Hierarchy);
|
||||
}
|
||||
|
||||
private void MyHierarchy_OnOnSelected(InspectionElement selected)
|
||||
{
|
||||
OnSelected?.Invoke(selected);
|
||||
Selection = selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,22 @@ namespace QuestPDF.Previewer
|
||||
set => SetValue(ScrollViewportSizeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<InspectionElement> HierarchyProperty = AvaloniaProperty.Register<PreviewerControl, InspectionElement>(nameof(Hierarchy));
|
||||
|
||||
public InspectionElement Hierarchy
|
||||
{
|
||||
get => GetValue(HierarchyProperty);
|
||||
set => SetValue(HierarchyProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<InspectionElement> CurrentSelectionProperty = AvaloniaProperty.Register<PreviewerControl, InspectionElement>(nameof(CurrentSelection));
|
||||
|
||||
public InspectionElement CurrentSelection
|
||||
{
|
||||
get => GetValue(CurrentSelectionProperty);
|
||||
set => SetValue(CurrentSelectionProperty, value);
|
||||
}
|
||||
|
||||
public PreviewerControl()
|
||||
{
|
||||
PagesProperty.Changed.Subscribe(x =>
|
||||
@@ -49,7 +65,72 @@ namespace QuestPDF.Previewer
|
||||
InvalidateVisual();
|
||||
});
|
||||
|
||||
CurrentSelectionProperty.Changed.Subscribe(x =>
|
||||
{
|
||||
InteractiveCanvas.InspectionElement = CurrentSelection;
|
||||
//InteractiveCanvas.ScrollToInspectionElement(CurrentSelection);
|
||||
InvalidateVisual();
|
||||
});
|
||||
|
||||
ClipToBounds = true;
|
||||
|
||||
PointerPressed += (sender, args) =>
|
||||
{
|
||||
var position = args.GetPosition(this);
|
||||
InteractiveCanvas.SetActivePage((float)position.X, (float)position.Y);
|
||||
|
||||
var clickedPosition = InteractiveCanvas.FindClickedPointOnThePage((float)position.X, (float)position.Y);
|
||||
|
||||
if (clickedPosition != null)
|
||||
FindHighlightedElement(clickedPosition.Value.pageNumber, clickedPosition.Value.x, clickedPosition.Value.y);
|
||||
|
||||
InvalidateVisual();
|
||||
};
|
||||
}
|
||||
|
||||
void FindHighlightedElement(int pageNumber, float x, float y)
|
||||
{
|
||||
var possible = FlattenHierarchy(Hierarchy, 0)
|
||||
.Select(x =>
|
||||
{
|
||||
var location = x.element.Location.First(y => y.PageNumber == pageNumber);
|
||||
|
||||
return new
|
||||
{
|
||||
Element = x.element,
|
||||
Level = x.level,
|
||||
Size = location.Width * location.Height
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var minSize = possible.Min(x => x.Size);
|
||||
|
||||
CurrentSelection = possible
|
||||
.Where(x => Math.Abs(x.Size - minSize) < 1)
|
||||
.OrderByDescending(x => x.Level)
|
||||
.First()
|
||||
.Element;
|
||||
|
||||
IEnumerable<(InspectionElement element, int level)> FlattenHierarchy(InspectionElement element, int level)
|
||||
{
|
||||
var location = element.Location.FirstOrDefault(x => x.PageNumber == pageNumber);
|
||||
|
||||
if (location == null)
|
||||
yield break;
|
||||
|
||||
if (x < location.Left || location.Left + location.Width < x)
|
||||
yield break;
|
||||
|
||||
if (y < location.Top || location.Top + location.Height < y)
|
||||
yield break;
|
||||
|
||||
yield return (element, level);
|
||||
|
||||
foreach (var childIndex in Enumerable.Range(0, element.Children.Count))
|
||||
foreach (var nestedChild in FlattenHierarchy(element.Children[childIndex], level + childIndex + 1))
|
||||
yield return nestedChild;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
||||
|
||||
@@ -3,95 +3,153 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:previewer="clr-namespace:QuestPDF.Previewer"
|
||||
xmlns:visualBasic="clr-namespace:Microsoft.VisualBasic;assembly=Microsoft.VisualBasic.Core"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="QuestPDF.Previewer.PreviewerWindow"
|
||||
x:DataType="previewer:PreviewerWindowViewModel"
|
||||
x:CompileBindings="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ExtendClientAreaToDecorationsHint="true"
|
||||
ExtendClientAreaTitleBarHeightHint="-1"
|
||||
Background="#666"
|
||||
ExtendClientAreaTitleBarHeightHint="56"
|
||||
Background="#555"
|
||||
Icon="/Resources/Logo.png"
|
||||
UseLayoutRounding="True"
|
||||
Title="QuestPDF Document Preview">
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="Button.actions">
|
||||
<Style Selector=".MainMenu TextBlock">
|
||||
<Setter Property="Foreground" Value="#AFFF" />
|
||||
<Setter Property="Margin" Value="0,0,16,0" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
</Style>
|
||||
|
||||
<Style Selector=".MainMenu TextBlock:pointerover">
|
||||
<Setter Property="Foreground" Value="#FFF" />
|
||||
<Setter Property="TextDecorations" Value="Underline" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.actions">
|
||||
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
<Setter Property="Padding" Value="10"/>
|
||||
<Setter Property="CornerRadius" Value="100"/>
|
||||
<Setter Property="Background" Value="#888"/>
|
||||
<Setter Property="Padding" Value="12" />
|
||||
<Setter Property="Margin" Value="-1" />
|
||||
<Setter Property="Background" Value="transparent"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.actions Path">
|
||||
<Setter Property="Fill" Value="#8FFF"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.active Path">
|
||||
<Setter Property="Fill" Value="#FFFF"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pointerover Path">
|
||||
<Setter Property="Fill" Value="#FFFF"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="#999"/>
|
||||
<Setter Property="Background" Value="#333"/>
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
</Window.Styles>
|
||||
|
||||
<Panel>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32" />
|
||||
<RowDefinition Height="56" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="300" MaxWidth="500" Width="300" />
|
||||
<ColumnDefinition Width="4" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
TextAlignment="Center" Text="QuestPDF Previewer" FontSize="14" Foreground="#DFFF" FontWeight="Regular" IsHitTestVisible="False" />
|
||||
|
||||
<previewer:PreviewerControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
|
||||
<Panel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" Background="#333">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="/Resources/Logo.png" Width="32" Height="32" Margin="12,12,20,12"></Image>
|
||||
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,0,4"
|
||||
TextAlignment="Center" Text="QuestPDF Previewer" FontSize="16" Foreground="#FFF" FontWeight="SemiBold" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Classes="MainMenu">
|
||||
<TextBlock Text="Getting Started" />
|
||||
<TextBlock Text="Documentation" />
|
||||
<TextBlock Text="GitHub" />
|
||||
<TextBlock Text="Sponsor project" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="0" Background="#444">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="32" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Panel Grid.Row="0">
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Margin="16,8"
|
||||
TextAlignment="Center" Text="Document hierarchy" Foreground="#DFFF" FontWeight="SemiBold" IsHitTestVisible="False" />
|
||||
</Panel>
|
||||
|
||||
<Panel Grid.Row="1">
|
||||
<ScrollViewer>
|
||||
<previewer:MyHierarchy Hierarchy="{Binding Items}" Margin="-8,0,0,0" Selection="{Binding SelectedItem, Mode=TwoWay}"></previewer:MyHierarchy>
|
||||
</ScrollViewer>
|
||||
</Panel>
|
||||
|
||||
<Panel Grid.Row="3">
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Margin="16,8"
|
||||
TextAlignment="Center" Text="Selected element properties" Foreground="#DFFF" FontWeight="SemiBold" IsHitTestVisible="False" />
|
||||
</Panel>
|
||||
|
||||
<Panel Grid.Row="4">
|
||||
<ItemsRepeater Items="{Binding SelectedItem.Metadata}" Margin="16, 2">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate DataType="previewer:Metadata">
|
||||
<Grid Margin="0, 4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100" />
|
||||
<ColumnDefinition Width="16" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0" Text="{Binding Label}" Foreground="#AFFF" FontSize="12" />
|
||||
<TextBlock Grid.Column="2" Text="{Binding Value}" Foreground="#AFFF" FontSize="12" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</Panel>
|
||||
</Grid>
|
||||
|
||||
<GridSplitter Grid.Column="1" Grid.Row="1" Background="#444" ResizeDirection="Columns" />
|
||||
|
||||
<previewer:PreviewerControl Grid.Row="1" Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CurrentScroll="{Binding CurrentScroll, Mode=TwoWay}"
|
||||
ScrollViewportSize="{Binding ScrollViewportSize, Mode=OneWayToSource}"
|
||||
CurrentSelection="{Binding SelectedItem, Mode=TwoWay}"
|
||||
Hierarchy="{Binding Items}"
|
||||
Pages="{Binding Pages, Mode=OneWay}" />
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Vertical" VerticalAlignment="Bottom" Spacing="16" Margin="32">
|
||||
<Button Classes="actions"
|
||||
Command="{Binding ShowPdfCommand, Mode=OneTime}"
|
||||
IsVisible="{Binding !!Pages.Count}"
|
||||
ToolTip.Tip="Generates PDF file and shows it in the default browser. Useful for testing compatibility and interactive links.">
|
||||
<Viewbox Width="24" Height="24">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="White" Data="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H13C12.59,21.75 12.2,21.44 11.86,21.1C11.53,20.77 11.25,20.4 11,20H6V4H13V9H18V10.18C18.71,10.34 19.39,10.61 20,11V8L14,2M20.31,18.9C21.64,16.79 21,14 18.91,12.68C16.8,11.35 14,12 12.69,14.08C11.35,16.19 12,18.97 14.09,20.3C15.55,21.23 17.41,21.23 18.88,20.32L22,23.39L23.39,22L20.31,18.9M16.5,19A2.5,2.5 0 0,1 14,16.5A2.5,2.5 0 0,1 16.5,14A2.5,2.5 0 0,1 19,16.5A2.5,2.5 0 0,1 16.5,19Z" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
|
||||
<Button Classes="actions"
|
||||
Command="{Binding ShowDocumentationCommand, Mode=OneTime}"
|
||||
ToolTip.Tip="Opens official QuestPDF documentation">
|
||||
<Viewbox Width="24" Height="24">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="White" Data="M19 1L14 6V17L19 12.5V1M21 5V18.5C19.9 18.15 18.7 18 17.5 18C15.8 18 13.35 18.65 12 19.5V6C10.55 4.9 8.45 4.5 6.5 4.5C4.55 4.5 2.45 4.9 1 6V20.65C1 20.9 1.25 21.15 1.5 21.15C1.6 21.15 1.65 21.1 1.75 21.1C3.1 20.45 5.05 20 6.5 20C8.45 20 10.55 20.4 12 21.5C13.35 20.65 15.8 20 17.5 20C19.15 20 20.85 20.3 22.25 21.05C22.35 21.1 22.4 21.1 22.5 21.1C22.75 21.1 23 20.85 23 20.6V6C22.4 5.55 21.75 5.25 21 5M10 18.41C8.75 18.09 7.5 18 6.5 18C5.44 18 4.18 18.19 3 18.5V7.13C3.91 6.73 5.14 6.5 6.5 6.5C7.86 6.5 9.09 6.73 10 7.13V18.41Z" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
|
||||
<Button Classes="actions"
|
||||
Command="{Binding SponsorProjectCommand, Mode=OneTime}"
|
||||
ToolTip.Tip="Do you like QuestPDF? Please consider sponsoring the project. It really helps!">
|
||||
<Viewbox Width="24" Height="24">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="White" Data="M12,21.1L10.5,22.4C3.9,16.5 0.5,13.4 0.5,9.6C0.5,8.4 0.9,7.3 1.5,6.4C1.5,6.6 1.5,6.8 1.5,7C1.5,11.7 5.4,15.2 12,21.1M13.6,17C18.3,12.7 21.5,9.9 21.6,7C21.6,5 20.1,3.5 18.1,3.5C16.5,3.5 15,4.5 14.5,5.9H12.6C12,4.5 10.5,3.5 9,3.5C7,3.5 5.5,5 5.5,7C5.5,9.9 8.6,12.7 13.4,17L13.5,17.1M18,1.5C21.1,1.5 23.5,3.9 23.5,7C23.5,10.7 20.1,13.8 13.5,19.8C6.9,13.9 3.5,10.8 3.5,7C3.5,3.9 5.9,1.5 9,1.5C10.7,1.5 12.4,2.3 13.5,3.6C14.6,2.3 16.3,1.5 18,1.5Z" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<ScrollBar Grid.Row="1" Grid.Column="1"
|
||||
Orientation="Vertical"
|
||||
AllowAutoHide="False"
|
||||
Minimum="0" Maximum="1"
|
||||
IsVisible="{Binding VerticalScrollbarVisible, Mode=OneWay}"
|
||||
Value="{Binding CurrentScroll, Mode=TwoWay}"
|
||||
ViewportSize="{Binding ScrollViewportSize, Mode=OneWay}"></ScrollBar>
|
||||
<ScrollBar Grid.Row="1" Grid.Column="3"
|
||||
Orientation="Vertical"
|
||||
AllowAutoHide="False"
|
||||
Minimum="0" Maximum="1"
|
||||
IsVisible="{Binding VerticalScrollbarVisible, Mode=OneWay}"
|
||||
Value="{Binding CurrentScroll, Mode=TwoWay}"
|
||||
ViewportSize="{Binding ScrollViewportSize, Mode=OneWay}"></ScrollBar>
|
||||
</Grid>
|
||||
</Panel>
|
||||
</Window>
|
||||
@@ -1,4 +1,5 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using Avalonia;
|
||||
using ReactiveUI;
|
||||
using Unit = System.Reactive.Unit;
|
||||
using Avalonia.Threading;
|
||||
@@ -51,6 +53,8 @@ namespace QuestPDF.Previewer
|
||||
ShowPdfCommand = ReactiveCommand.Create(ShowPdf);
|
||||
ShowDocumentationCommand = ReactiveCommand.Create(() => OpenLink("https://www.questpdf.com/documentation/api-reference.html"));
|
||||
SponsorProjectCommand = ReactiveCommand.Create(() => OpenLink("https://github.com/sponsors/QuestPDF"));
|
||||
|
||||
LoadItems();
|
||||
}
|
||||
|
||||
private void ShowPdf()
|
||||
@@ -85,5 +89,89 @@ namespace QuestPDF.Previewer
|
||||
foreach (var page in oldPages)
|
||||
page.Picture.Dispose();
|
||||
}
|
||||
|
||||
private InspectionElement _selectedItem;
|
||||
public InspectionElement SelectedItem
|
||||
{
|
||||
get => _selectedItem;
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedItem, value);
|
||||
}
|
||||
|
||||
private InspectionElement items;
|
||||
public InspectionElement Items
|
||||
{
|
||||
get => items;
|
||||
set => this.RaiseAndSetIfChanged(ref items, value);
|
||||
}
|
||||
|
||||
public void LoadItems()
|
||||
{
|
||||
var text = File.ReadAllText("hierarchy.json");
|
||||
Items = JsonSerializer.Deserialize<InspectionElement>(text);
|
||||
UpdateLevel(Items);
|
||||
}
|
||||
|
||||
private void UpdateLevel(InspectionElement root)
|
||||
{
|
||||
var currentLevel = 1;
|
||||
Traverse(root);
|
||||
|
||||
void Traverse(InspectionElement element)
|
||||
{
|
||||
element.Level = currentLevel;
|
||||
|
||||
currentLevel++;
|
||||
element.Children.ForEach(Traverse);
|
||||
currentLevel--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class InspectionElementLocation
|
||||
{
|
||||
public int PageNumber { get; set; }
|
||||
public float Top { get; set; }
|
||||
public float Left { get; set; }
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
}
|
||||
|
||||
public class Metadata
|
||||
{
|
||||
public string Label { get; set; }
|
||||
public string Value { get; set; }
|
||||
|
||||
public Metadata(string label, string value)
|
||||
{
|
||||
Label = label;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public class InspectionElement
|
||||
{
|
||||
public string Element { get; set; }
|
||||
public List<InspectionElementLocation> Location { get; set; }
|
||||
public Dictionary<string, string> Properties { get; set; }
|
||||
public List<InspectionElement> Children { get; set; }
|
||||
public int Level { get; set; }
|
||||
|
||||
public string FontColor => Element == "DebugPointer" ? "#FFF" : "#AFFF";
|
||||
public string Text => Element == "DebugPointer" ? Properties.First(x => x.Key == "Target").Value : Element;
|
||||
public bool Expanded { get; set; }
|
||||
|
||||
public IList<Metadata> Metadata => ListMetadata().ToList();
|
||||
|
||||
public IEnumerable<Metadata> ListMetadata()
|
||||
{
|
||||
yield return new Metadata("Element name", Element);
|
||||
yield return new Metadata("Position left", Location[0].Left.ToString("N2"));
|
||||
yield return new Metadata("Position top", Location[0].Top.ToString("N2"));
|
||||
yield return new Metadata("Width", Location[0].Width.ToString("N2"));
|
||||
yield return new Metadata("Height", Location[0].Height.ToString("N2"));
|
||||
|
||||
foreach (var property in Properties)
|
||||
yield return new Metadata(property.Key, property.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
<Visible>false</Visible>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<None Update="hierarchy.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -16,6 +16,7 @@ namespace QuestPDF.Drawing
|
||||
var container = new Container();
|
||||
|
||||
container
|
||||
.DebugPointer("Document")
|
||||
.Column(column =>
|
||||
{
|
||||
Pages
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Drawing.Proxy;
|
||||
using QuestPDF.Elements;
|
||||
@@ -10,6 +12,8 @@ using QuestPDF.Elements.Text.Items;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
@@ -75,9 +79,218 @@ namespace QuestPDF.Drawing
|
||||
ApplyCaching(content);
|
||||
|
||||
RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
|
||||
|
||||
if (metadata.ApplyInspection)
|
||||
ApplyInspection(content);
|
||||
|
||||
RenderPass(pageContext, canvas, content, metadata, debuggingState);
|
||||
|
||||
if (metadata.ApplyInspection)
|
||||
{
|
||||
var x = TraverseStatisticsToJson(content);
|
||||
var y = JsonSerializer.Serialize(x);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string TraverseStructure(Element container)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var nestingLevel = 0;
|
||||
|
||||
Traverse(container);
|
||||
return builder.ToString();
|
||||
|
||||
void Traverse(Element item)
|
||||
{
|
||||
var indent = new string(' ', nestingLevel * 4);
|
||||
var title = item.GetType().Name;
|
||||
builder.AppendLine(indent + title);
|
||||
|
||||
nestingLevel++;
|
||||
|
||||
foreach (var child in item.GetChildren())
|
||||
Traverse(child);
|
||||
|
||||
nestingLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
internal static string TraverseStatistics(Element container, int pageNumber)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var nestingLevel = 0;
|
||||
|
||||
Traverse(container);
|
||||
return builder.ToString();
|
||||
|
||||
void Traverse(Element item)
|
||||
{
|
||||
if (item is DebuggingProxy or CacheProxy or Container)
|
||||
{
|
||||
Traverse(item.GetChildren().First());
|
||||
return;
|
||||
}
|
||||
|
||||
var inspectionItem = item as InspectionProxy;
|
||||
|
||||
if (inspectionItem == null)
|
||||
{
|
||||
var children = item.GetChildren().ToList();
|
||||
|
||||
if (children.Count > 1)
|
||||
nestingLevel++;
|
||||
|
||||
children.ForEach(Traverse);
|
||||
|
||||
if (children.Count > 1)
|
||||
nestingLevel--;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!inspectionItem.Statistics.ContainsKey(pageNumber))
|
||||
return;
|
||||
|
||||
var statistics = inspectionItem.Statistics[pageNumber];
|
||||
|
||||
var indent = new string(' ', nestingLevel * 4);
|
||||
var title = statistics.Element.GetType().Name;
|
||||
|
||||
builder.AppendLine(indent + title);
|
||||
builder.AppendLine(indent + new string('-', title.Length));
|
||||
|
||||
builder.AppendLine(indent + "Size: " + statistics.Size);
|
||||
builder.AppendLine(indent + "Position: " + statistics.Position);
|
||||
|
||||
foreach (var configuration in DebuggingState.GetElementConfiguration(statistics.Element))
|
||||
builder.AppendLine(indent + configuration);
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
Traverse(inspectionItem.Child);
|
||||
}
|
||||
}
|
||||
|
||||
internal static InspectionElement TraverseStatisticsToJson(Element container)
|
||||
{
|
||||
return Traverse(container);
|
||||
|
||||
InspectionElement? Traverse(Element item)
|
||||
{
|
||||
InspectionElement? result = null;
|
||||
Element currentItem = item;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (currentItem is InspectionProxy proxy)
|
||||
{
|
||||
if (proxy.Child.GetType() == typeof(Container))
|
||||
{
|
||||
currentItem = proxy.Child;
|
||||
continue;
|
||||
}
|
||||
|
||||
var statistics = GetInspectionElement(proxy);
|
||||
|
||||
if (statistics == null)
|
||||
return null;
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
result = statistics;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Children.Add(statistics);
|
||||
}
|
||||
|
||||
currentItem = proxy.Child;
|
||||
}
|
||||
else
|
||||
{
|
||||
var children = currentItem.GetChildren().ToList();
|
||||
|
||||
if (children.Count == 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else if (children.Count == 1)
|
||||
{
|
||||
currentItem = children.First();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
children
|
||||
.Select(Traverse)
|
||||
.Where(x => x != null)
|
||||
.ToList()
|
||||
.ForEach(result.Children.Add);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static InspectionElement? GetInspectionElement(InspectionProxy inspectionProxy)
|
||||
{
|
||||
var locations = inspectionProxy
|
||||
.Statistics
|
||||
.Keys
|
||||
.Select(x =>
|
||||
{
|
||||
var statistics = inspectionProxy.Statistics[x];
|
||||
|
||||
return new InspectionElementLocation
|
||||
{
|
||||
PageNumber = x,
|
||||
Top = statistics.Position.Y,
|
||||
Left = statistics.Position.X,
|
||||
Width = statistics.Size.Width,
|
||||
Height = statistics.Size.Height,
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return new InspectionElement
|
||||
{
|
||||
Element = inspectionProxy.Child.GetType().Name,
|
||||
Location = locations,
|
||||
Properties = GetElementConfiguration(inspectionProxy.Child),
|
||||
Children = new List<InspectionElement>()
|
||||
};
|
||||
}
|
||||
|
||||
static Dictionary<string, string> GetElementConfiguration(IElement element)
|
||||
{
|
||||
return element
|
||||
.GetType()
|
||||
.GetProperties()
|
||||
.Select(x => new
|
||||
{
|
||||
Property = x.Name.PrettifyName(),
|
||||
Value = x.GetValue(element)
|
||||
})
|
||||
.Where(x => !(x.Value is IElement))
|
||||
.Where(x => x.Value is string || !(x.Value is IEnumerable))
|
||||
.Where(x => !(x.Value is TextStyle))
|
||||
.ToDictionary(x => x.Property, x => FormatValue(x.Value));
|
||||
|
||||
string FormatValue(object value)
|
||||
{
|
||||
const int maxLength = 100;
|
||||
|
||||
var text = value?.ToString() ?? "-";
|
||||
|
||||
if (text.Length < maxLength)
|
||||
return text;
|
||||
|
||||
return text.AsSpan(0, maxLength).ToString() + "...";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
@@ -157,11 +370,19 @@ namespace QuestPDF.Drawing
|
||||
|
||||
content.VisitChildren(x =>
|
||||
{
|
||||
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
|
||||
x.CreateProxy(y => y is ElementProxy ? y : new DebuggingProxy(debuggingState, y));
|
||||
});
|
||||
|
||||
return debuggingState;
|
||||
}
|
||||
|
||||
private static void ApplyInspection(Container content)
|
||||
{
|
||||
content.VisitChildren(x =>
|
||||
{
|
||||
x.CreateProxy(y => y is ElementProxy ? y : new InspectionProxy(y));
|
||||
});
|
||||
}
|
||||
|
||||
internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace QuestPDF.Drawing
|
||||
|
||||
public bool ApplyCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached;
|
||||
public bool ApplyDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
||||
public bool ApplyInspection { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
||||
|
||||
public static DocumentMetadata Default => new DocumentMetadata();
|
||||
}
|
||||
|
||||
@@ -94,36 +94,36 @@ namespace QuestPDF.Drawing.Proxy
|
||||
item.Stack.ToList().ForEach(Traverse);
|
||||
nestingLevel--;
|
||||
}
|
||||
|
||||
static IEnumerable<string> GetElementConfiguration(IElement element)
|
||||
{
|
||||
if (element is DebugPointer)
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
internal static IEnumerable<string> GetElementConfiguration(IElement element)
|
||||
{
|
||||
if (element is DebugPointer)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
return element
|
||||
.GetType()
|
||||
.GetProperties()
|
||||
.Select(x => new
|
||||
{
|
||||
Property = x.Name.PrettifyName(),
|
||||
Value = x.GetValue(element)
|
||||
})
|
||||
.Where(x => !(x.Value is IElement))
|
||||
.Where(x => x.Value is string || !(x.Value is IEnumerable))
|
||||
.Where(x => !(x.Value is TextStyle))
|
||||
.Select(x => $"{x.Property}: {FormatValue(x.Value)}");
|
||||
|
||||
string FormatValue(object value)
|
||||
return element
|
||||
.GetType()
|
||||
.GetProperties()
|
||||
.Select(x => new
|
||||
{
|
||||
const int maxLength = 100;
|
||||
Property = x.Name.PrettifyName(),
|
||||
Value = x.GetValue(element)
|
||||
})
|
||||
.Where(x => !(x.Value is IElement))
|
||||
.Where(x => x.Value is string || !(x.Value is IEnumerable))
|
||||
.Where(x => !(x.Value is TextStyle))
|
||||
.Select(x => $"{x.Property}: {FormatValue(x.Value)}");
|
||||
|
||||
string FormatValue(object value)
|
||||
{
|
||||
const int maxLength = 100;
|
||||
|
||||
var text = value?.ToString() ?? "-";
|
||||
var text = value?.ToString() ?? "-";
|
||||
|
||||
if (text.Length < maxLength)
|
||||
return text;
|
||||
if (text.Length < maxLength)
|
||||
return text;
|
||||
|
||||
return text.AsSpan(0, maxLength).ToString() + "...";
|
||||
}
|
||||
return text.AsSpan(0, maxLength).ToString() + "...";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing.Proxy
|
||||
{
|
||||
internal class InspectionProxy : ElementProxy
|
||||
{
|
||||
public Dictionary<int, InspectionStateItem> Statistics { get; set; } = new();
|
||||
|
||||
public InspectionProxy(Element child)
|
||||
{
|
||||
Child = child;
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (Canvas is SkiaCanvasBase canvas)
|
||||
{
|
||||
var matrix = canvas.Canvas.TotalMatrix;
|
||||
|
||||
var inspectionItem = new InspectionStateItem
|
||||
{
|
||||
Element = Child,
|
||||
Position = new Position(matrix.TransX, matrix.TransY),
|
||||
Size = availableSpace
|
||||
};
|
||||
|
||||
Statistics[PageContext.CurrentPage] = inspectionItem;
|
||||
}
|
||||
|
||||
base.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace QuestPDF.Drawing.Proxy
|
||||
{
|
||||
public class InspectionState
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing.Proxy
|
||||
{
|
||||
internal class InspectionStateItem
|
||||
{
|
||||
public Element Element { get; internal set; }
|
||||
public Size Size { get; internal set; }
|
||||
public Position Position { get; internal set; }
|
||||
}
|
||||
|
||||
internal class InspectionElementLocation
|
||||
{
|
||||
public int PageNumber { get; internal set; }
|
||||
public float Top { get; set; }
|
||||
public float Left { get; set; }
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
}
|
||||
|
||||
internal class InspectionElement
|
||||
{
|
||||
public string Element { get; internal set; }
|
||||
public ICollection<InspectionElementLocation> Location { get; internal set; }
|
||||
public Dictionary<string, string> Properties { get; internal set; }
|
||||
public ICollection<InspectionElement> Children { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
return Children;
|
||||
}
|
||||
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return Children
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace QuestPDF.Elements
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container
|
||||
.Container()
|
||||
.Background(BackgroundColor)
|
||||
.Layers(layers =>
|
||||
{
|
||||
@@ -40,6 +41,7 @@ namespace QuestPDF.Elements
|
||||
|
||||
layers
|
||||
.PrimaryLayer()
|
||||
.DebugPointer("Page content")
|
||||
.MinWidth(MinSize.Width)
|
||||
.MinHeight(MinSize.Height)
|
||||
|
||||
|
||||
@@ -46,11 +46,6 @@ namespace QuestPDF.Elements
|
||||
return Items;
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||
{
|
||||
Items.ForEach(x => x.Child = create(x.Child));
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (!Items.Any())
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace QuestPDF.Fluent
|
||||
Child = container
|
||||
});
|
||||
|
||||
return container;
|
||||
return container.DebugPointer("Column Item");;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
var container = new Container();
|
||||
Decoration.Before = container;
|
||||
return container;
|
||||
return container.DebugPointer("Decoration Before");
|
||||
}
|
||||
|
||||
public void Before(Action<IContainer> handler)
|
||||
@@ -24,7 +24,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
var container = new Container();
|
||||
Decoration.Content = container;
|
||||
return container;
|
||||
return container.DebugPointer("Decoration Content");
|
||||
}
|
||||
|
||||
public void Content(Action<IContainer> handler)
|
||||
@@ -36,7 +36,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
var container = new Container();
|
||||
Decoration.After = container;
|
||||
return container;
|
||||
return container.DebugPointer("Decoration After");
|
||||
}
|
||||
|
||||
public void After(Action<IContainer> handler)
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace QuestPDF.Fluent
|
||||
};
|
||||
|
||||
Row.Items.Add(element);
|
||||
return element;
|
||||
return element.DebugPointer("Row Item");
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the RelativeItem method.")]
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<PackageTags>pdf report file export generate generation tool create creation render portable document format quest html library converter open source free standard core</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0;net6.0</TargetFrameworks>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
Reference in New Issue
Block a user