Skip to content

Commit 7f79456

Browse files
Merge pull request #1 from TheEightBot/feature/xaml-enhancements
2 parents 26ee7e1 + b644478 commit 7f79456

6 files changed

Lines changed: 275 additions & 2 deletions

File tree

README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,21 +91,49 @@ Install-Package MauiNativePdfView
9191

9292
### 1. Add Namespace
9393

94+
**Option A: Custom Schema (Recommended)**
95+
```xml
96+
xmlns:pdf="http://eightbot.com/maui/pdfview"
97+
```
98+
99+
**Option B: CLR Namespace**
94100
```xml
95101
xmlns:pdf="clr-namespace:MauiNativePdfView;assembly=MauiNativePdfView"
96102
```
97103

98104
### 2. Basic XAML
99105

100106
```xml
107+
<!-- Simple string binding - auto-converts to PdfSource! -->
108+
<pdf:PdfView Source="https://example.com/document.pdf" />
109+
110+
<!-- Or with full control -->
101111
<pdf:PdfView x:Name="pdfViewer"
102112
EnableZoom="True"
103113
EnableSwipe="True"
104114
DocumentLoaded="OnDocumentLoaded"
105115
PageChanged="OnPageChanged" />
106116
```
107117

108-
### 3. Load a PDF
118+
### String to PdfSource Conversion
119+
120+
The library supports automatic string conversion in XAML:
121+
122+
```xml
123+
<!-- URL - automatically becomes UriPdfSource -->
124+
<pdf:PdfView Source="https://example.com/document.pdf" />
125+
126+
<!-- Asset - simple filename becomes AssetPdfSource -->
127+
<pdf:PdfView Source="sample.pdf" />
128+
129+
<!-- Asset with explicit prefix -->
130+
<pdf:PdfView Source="asset://documents/guide.pdf" />
131+
132+
<!-- File URI -->
133+
<pdf:PdfView Source="file:///path/to/document.pdf" />
134+
```
135+
136+
### 3. Load a PDF (Code-Behind)
109137

110138
```csharp
111139
// From file
@@ -171,6 +199,9 @@ private void OnPageChanged(object sender, PageChangedEventArgs e)
171199

172200
### PdfSource Types
173201

202+
The `PdfSource` class supports automatic string conversion via implicit operators and TypeConverter, making it easy to use in both XAML and code.
203+
204+
**Factory Methods (Code-Behind):**
174205
```csharp
175206
// File path
176207
var source = PdfSource.FromFile(string filePath, string? password = null);
@@ -188,6 +219,26 @@ var source = PdfSource.FromBytes(byte[] data, string? password = null);
188219
var source = PdfSource.FromAsset(string assetName, string? password = null);
189220
```
190221

222+
**Implicit Conversion (Convenient):**
223+
```csharp
224+
// String to PdfSource - auto-detects type
225+
PdfSource source = "https://example.com/doc.pdf"; // → UriPdfSource
226+
PdfSource source = "sample.pdf"; // → AssetPdfSource
227+
PdfSource source = "/path/to/file.pdf"; // → FilePdfSource
228+
229+
// Uri to PdfSource
230+
PdfSource source = new Uri("https://example.com/doc.pdf");
231+
```
232+
233+
**String Conversion Rules:**
234+
| Pattern | Result |
235+
|---------|--------|
236+
| `http://...` or `https://...` | `UriPdfSource` |
237+
| `asset://filename.pdf` | `AssetPdfSource` |
238+
| `file:///path/to/file.pdf` | `FilePdfSource` |
239+
| `filename.pdf` (no path separators) | `AssetPdfSource` |
240+
| `/path/to/file.pdf` (rooted path) | `FilePdfSource` |
241+
191242
### Enums
192243

193244
```csharp

samples/MauiPdfViewerSample/PdfTestPage.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
33
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4-
xmlns:pdf="clr-namespace:MauiNativePdfView;assembly=MauiNativePdfView"
4+
xmlns:pdf="http://eightbot.com/maui/pdfview"
55
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
66
x:Class="MauiPdfViewerSample.PdfTestPage"
77
Title="PDF Experience">
@@ -156,6 +156,7 @@
156156
<RoundRectangle CornerRadius="20" />
157157
</Border.StrokeShape>
158158
<pdf:PdfView x:Name="PdfViewer"
159+
Source="sample.pdf"
159160
HeightRequest="460"
160161
HorizontalOptions="FillAndExpand"
161162
VerticalOptions="FillAndExpand"

src/MauiNativePdfView/Abstractions/PdfSource.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1+
using System.ComponentModel;
2+
13
namespace MauiNativePdfView.Abstractions;
24

35
/// <summary>
46
/// Base class for PDF source types.
57
/// </summary>
8+
/// <remarks>
9+
/// Supports implicit conversion from strings and URIs for convenient XAML usage.
10+
/// String conversion follows these rules:
11+
/// <list type="bullet">
12+
/// <item><description>URLs (http:// or https://) → UriPdfSource</description></item>
13+
/// <item><description>Asset paths (asset://) → AssetPdfSource</description></item>
14+
/// <item><description>File URIs (file://) → FilePdfSource</description></item>
15+
/// <item><description>Simple filenames → AssetPdfSource</description></item>
16+
/// <item><description>Full paths → FilePdfSource</description></item>
17+
/// </list>
18+
/// </remarks>
19+
[TypeConverter(typeof(PdfSourceTypeConverter))]
620
public abstract class PdfSource
721
{
822
/// <summary>
@@ -11,6 +25,60 @@ public abstract class PdfSource
1125
/// </summary>
1226
public string? Password { get; set; }
1327

28+
/// <summary>
29+
/// Implicitly converts a string to a PdfSource.
30+
/// </summary>
31+
/// <param name="source">The source string (URL, asset path, or file path).</param>
32+
public static implicit operator PdfSource?(string? source)
33+
{
34+
if (string.IsNullOrWhiteSpace(source))
35+
return null;
36+
37+
var trimmedValue = source.Trim();
38+
39+
// HTTP/HTTPS URLs
40+
if (trimmedValue.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
41+
trimmedValue.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
42+
{
43+
return new UriPdfSource(new Uri(trimmedValue));
44+
}
45+
46+
// Asset prefix
47+
if (trimmedValue.StartsWith("asset://", StringComparison.OrdinalIgnoreCase))
48+
{
49+
return new AssetPdfSource(trimmedValue["asset://".Length..]);
50+
}
51+
52+
// File URI
53+
if (trimmedValue.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
54+
{
55+
return new FilePdfSource(new Uri(trimmedValue).LocalPath);
56+
}
57+
58+
// Simple filename (no path separators) → treat as asset
59+
if (!Path.IsPathRooted(trimmedValue) &&
60+
!trimmedValue.Contains(Path.DirectorySeparatorChar) &&
61+
!trimmedValue.Contains(Path.AltDirectorySeparatorChar))
62+
{
63+
return new AssetPdfSource(trimmedValue);
64+
}
65+
66+
// Default to file path
67+
return new FilePdfSource(trimmedValue);
68+
}
69+
70+
/// <summary>
71+
/// Implicitly converts a Uri to a PdfSource.
72+
/// </summary>
73+
/// <param name="uri">The URI to convert.</param>
74+
public static implicit operator PdfSource?(Uri? uri)
75+
{
76+
if (uri == null)
77+
return null;
78+
79+
return new UriPdfSource(uri);
80+
}
81+
1482
/// <summary>
1583
/// Creates a PDF source from a file path.
1684
/// </summary>
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using System.ComponentModel;
2+
using System.Globalization;
3+
4+
namespace MauiNativePdfView.Abstractions;
5+
6+
/// <summary>
7+
/// TypeConverter for converting strings to PdfSource types in XAML.
8+
/// </summary>
9+
/// <remarks>
10+
/// Supports the following string formats:
11+
/// <list type="bullet">
12+
/// <item><description>URLs (http:// or https://) → UriPdfSource</description></item>
13+
/// <item><description>Asset paths (asset://) → AssetPdfSource</description></item>
14+
/// <item><description>File URIs (file://) → FilePdfSource</description></item>
15+
/// <item><description>Simple filenames (no path separators) → AssetPdfSource</description></item>
16+
/// <item><description>Other strings → FilePdfSource</description></item>
17+
/// </list>
18+
/// </remarks>
19+
public class PdfSourceTypeConverter : TypeConverter
20+
{
21+
/// <summary>
22+
/// Returns whether this converter can convert from the specified type.
23+
/// </summary>
24+
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
25+
=> sourceType == typeof(string) || sourceType == typeof(Uri) || base.CanConvertFrom(context, sourceType);
26+
27+
/// <summary>
28+
/// Returns whether this converter can convert to the specified type.
29+
/// </summary>
30+
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
31+
=> destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
32+
33+
/// <summary>
34+
/// Converts from a string or Uri to a PdfSource.
35+
/// </summary>
36+
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
37+
{
38+
if (value is Uri uri)
39+
{
40+
return new UriPdfSource(uri);
41+
}
42+
43+
if (value is string stringValue)
44+
{
45+
return ParseString(stringValue);
46+
}
47+
48+
return base.ConvertFrom(context, culture, value);
49+
}
50+
51+
/// <summary>
52+
/// Converts a PdfSource to a string representation.
53+
/// </summary>
54+
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
55+
{
56+
if (destinationType == typeof(string))
57+
{
58+
return value switch
59+
{
60+
UriPdfSource uriSource => uriSource.Uri.ToString(),
61+
FilePdfSource fileSource => fileSource.FilePath,
62+
AssetPdfSource assetSource => $"asset://{assetSource.AssetName}",
63+
_ => null
64+
};
65+
}
66+
67+
return base.ConvertTo(context, culture, value, destinationType);
68+
}
69+
70+
/// <summary>
71+
/// Converts a string to the appropriate PdfSource type based on the string format.
72+
/// </summary>
73+
private static PdfSource ParseString(string value)
74+
{
75+
if (string.IsNullOrWhiteSpace(value))
76+
{
77+
throw new InvalidOperationException($"Cannot convert empty or null string to {nameof(PdfSource)}");
78+
}
79+
80+
var trimmedValue = value.Trim();
81+
82+
// Check for HTTP/HTTPS URLs
83+
if (trimmedValue.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
84+
trimmedValue.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
85+
{
86+
return new UriPdfSource(new Uri(trimmedValue));
87+
}
88+
89+
// Check for asset:// prefix (custom scheme for embedded assets)
90+
if (trimmedValue.StartsWith("asset://", StringComparison.OrdinalIgnoreCase))
91+
{
92+
var assetName = trimmedValue["asset://".Length..];
93+
return new AssetPdfSource(assetName);
94+
}
95+
96+
// Check for file:// URI scheme
97+
if (trimmedValue.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
98+
{
99+
var uri = new Uri(trimmedValue);
100+
return new FilePdfSource(uri.LocalPath);
101+
}
102+
103+
// Default: treat as a file path or asset name
104+
// If it looks like a relative path without extension or with common asset extensions,
105+
// and doesn't contain path separators at the start, treat as asset
106+
if (!trimmedValue.StartsWith("/") &&
107+
!trimmedValue.StartsWith("\\") &&
108+
!trimmedValue.Contains(":/") &&
109+
!trimmedValue.Contains(":\\") &&
110+
(trimmedValue.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase) ||
111+
!trimmedValue.Contains(Path.DirectorySeparatorChar) &&
112+
!trimmedValue.Contains(Path.AltDirectorySeparatorChar)))
113+
{
114+
// Could be an asset name like "sample.pdf" or a simple filename
115+
// We'll treat simple names as assets, full paths as files
116+
if (!Path.IsPathRooted(trimmedValue) &&
117+
!trimmedValue.Contains(Path.DirectorySeparatorChar) &&
118+
!trimmedValue.Contains(Path.AltDirectorySeparatorChar))
119+
{
120+
return new AssetPdfSource(trimmedValue);
121+
}
122+
}
123+
124+
// Treat as file path
125+
return new FilePdfSource(trimmedValue);
126+
}
127+
}

src/MauiNativePdfView/MauiAppBuilderExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ namespace MauiNativePdfView;
77
/// </summary>
88
public static class MauiAppBuilderExtensions
99
{
10+
/// <summary>
11+
/// Ensures the MauiNativePdfView assembly is linked and available for XAML resolution.
12+
/// This method is called automatically by <see cref="UseMauiNativePdfView"/> but can be
13+
/// called explicitly if needed for custom namespace schema resolution.
14+
/// </summary>
15+
/// <remarks>
16+
/// This method ensures the linker preserves the assembly containing the custom XAML
17+
/// namespace types, preventing them from being trimmed during compilation.
18+
/// </remarks>
19+
public static void Init()
20+
{
21+
// This method ensures the assembly is referenced and not trimmed by the linker.
22+
// The actual initialization is handled by UseMauiNativePdfView.
23+
}
24+
1025
/// <summary>
1126
/// Configures MauiNativePdfView with the application.
1227
/// </summary>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Microsoft.Maui.Controls;
2+
3+
// Define custom XAML namespace schema for MauiNativePdfView
4+
// This allows users to use: xmlns:pdf="http://eightbot.com/maui/pdfview"
5+
// instead of: xmlns:pdf="clr-namespace:MauiNativePdfView;assembly=MauiNativePdfView"
6+
7+
[assembly: XmlnsDefinition("http://eightbot.com/maui/pdfview", "MauiNativePdfView")]
8+
[assembly: XmlnsDefinition("http://eightbot.com/maui/pdfview", "MauiNativePdfView.Abstractions")]
9+
10+
// Suggest "pdf" as the default prefix when users add this namespace
11+
[assembly: Microsoft.Maui.Controls.XmlnsPrefix("http://eightbot.com/maui/pdfview", "pdf")]

0 commit comments

Comments
 (0)