mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
Add a new image provider for `MarkdownTextBlock` that allows loading
images from additional sources:
- **file scheme**
- Enables loading images using the `file:` scheme.
- Intentionally restricts file URIs to absolute paths to ensure correct
resolution when passed through the CmdPal extension/host boundary. (In
most cases, 3rd-party extensions will provide the paths, but the CmdPal
host performs the actual loading and would otherwise resolve paths
relative to itself.)
- **data scheme**
- Enables loading images from URIs with the `data:` scheme (both Base64
and URL-encoded forms).
- Note: the Markdown control itself cannot handle large input and may
hang before the code introduced in this PR is invoked.
- **ms-appx scheme**
- This scheme is now supported for loading images.
- However, since the Command Palette host performs the loading,
`ms-appx:` resolution applies to the host and not the extensions, which
limits its usefulness.
- **ms-appdata scheme**
- This scheme is now supported for loading images.
- Similar to `ms-appx:`, resolution applies to the host, not the
extensions, limiting its usefulness.
---
Additionally, this PR introduces the concept of **_image source
hints_**, implemented as query string parameters piggy-backed on the
original URI.
These hints allow users to influence the behavior of images within
Markdown content.
- `--x-cmdpal-fit`
- `none`: no automatic scaling, provides image as is (default)
- `fit`: scale to fit the available space
- `--x-cmdpal-upscale`
- `true`: allow upscaling
- `false`: downscale only (default)
- `--x-cmdpal-width`: desired width in pixels
- `--x-cmdpal-height`: desired height in pixels
- `--x-cmdpal-maxwidth`: max width in pixels
- `--x-cmdpal-maxheight`: max height in pixels
---
Since `MarkdownTextBlock` requires conforming to the `IImageProvider`
interface—which accepts only a raw URI and must return an `Image`
control—this PR also introduces a new class `RtbInlineImageFactory`.
The factory hooks into the root text block upon loading and listens for
events related to **layout** and **DPI changes**, ensuring that images
adapt correctly to the control’s environment.
```csharp
public interface IImageProvider
{
Task<Image> GetImage(string url);
bool ShouldUseThisProvider(string url);
}
```
---
Pictures? Videos!
Loading images from new schemes:
https://github.com/user-attachments/assets/e0f4308d-30b2-4c81-86db-353048c708c1
New image source scaling options:
https://github.com/user-attachments/assets/ec5b007d-3140-4f0a-b163-7b278233ad40
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #41752
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
---------
Co-authored-by: Michael Jolley <mike@baldbeardedbuilder.com>
220 lines
12 KiB
C#
220 lines
12 KiB
C#
// Copyright (c) Microsoft Corporation
|
||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||
// See the LICENSE file in the project root for more information.
|
||
|
||
#nullable enable
|
||
|
||
using System;
|
||
using System.Threading.Tasks;
|
||
using Microsoft.CommandPalette.Extensions;
|
||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||
using Windows.Storage;
|
||
|
||
namespace SamplePagesExtension.Pages;
|
||
|
||
internal sealed partial class SampleMarkdownImagesPage : ContentPage
|
||
{
|
||
private static readonly Task InitializationTask;
|
||
|
||
private static string? _sampleMarkdownText;
|
||
|
||
static SampleMarkdownImagesPage()
|
||
{
|
||
InitializationTask = Task.Run(static async () =>
|
||
{
|
||
try
|
||
{
|
||
// prepare data files
|
||
// 1) prepare something in our AppData Temp Folder
|
||
var spaceFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Images/Space.png"));
|
||
var tempFile = await spaceFile!.CopyAsync(ApplicationData.Current!.TemporaryFolder!, "Space.png", NameCollisionOption.ReplaceExisting);
|
||
|
||
// 2) and also get an SVG directly from the package
|
||
var svgChipmunkFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Images/FluentEmojiChipmunk.svg"));
|
||
|
||
_sampleMarkdownText = GetContentMarkup(
|
||
new Uri(tempFile.Path!, UriKind.Absolute),
|
||
new Uri(svgChipmunkFile.Path!, UriKind.Absolute));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ExtensionHost.LogMessage(ex.ToString());
|
||
}
|
||
});
|
||
return;
|
||
|
||
static string GetContentMarkup(Uri path1, Uri path2)
|
||
{
|
||
return
|
||
$$"""
|
||
# Images in Markdown Content
|
||
|
||
## Available sources:
|
||
|
||
- ``
|
||
|
||
- ``
|
||
- ℹ️ Only absolute paths are supported.
|
||
|
||
- ``
|
||
- ⚠️ Only for small amount of data. Parsing large data blocks the UI.
|
||
|
||
- ``
|
||
- ⚠️ This loads from CmdPal's AppData folder, not Extension's, so it's not useful for extensions.
|
||
|
||
- ``
|
||
- ⚠️ This loads from CmdPal's AppData folder, not Extension's, so it's not useful for extensions.
|
||
|
||
## Examples:
|
||
|
||
### Web URL
|
||
```xml
|
||

|
||
```
|
||

|
||
|
||
```xml
|
||

|
||
```
|
||

|
||
|
||
### File URL (PNG):
|
||
```xml
|
||

|
||
```
|
||
|
||

|
||
|
||
### File URL (SVG):
|
||
```xml
|
||

|
||
```
|
||
|
||

|
||
|
||
```xml
|
||

|
||
```
|
||
|
||

|
||
|
||
```xml
|
||

|
||
```
|
||

|
||
|
||
## Data URL (PNG):
|
||
⚠️ Passing large data into Markdown Content is unadvisable, parsing large data URLs can be slow and cause hangs.
|
||
```xml
|
||

|
||
```
|
||
|
||

|
||
|
||
### Data URL (SVG):
|
||
⚠️ Passing large data into Markdown Content is unadvisable, parsing large data URLs can be slow and cause hangs.
|
||
```xml
|
||

|
||
```
|
||
|
||

|
||
|
||
### Data URL (SVG 2):
|
||
⚠️ Passing large data into Markdown Content is unadvisable, parsing large data URLs can be slow and cause hangs.
|
||
```xml
|
||
<img alt="emoji 2"
|
||
width="48"
|
||
height="48"
|
||
src="data:image/svg+xml;base64,PHN2ZyB....iIvPjwvZz48L3N2Zz4=" />
|
||
```
|
||
|
||
<img alt="emoji 2"
|
||
width="48"
|
||
height="48"
|
||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiB2aWV3Qm94PSIwIDAgMzIgMzIiPjxnIGZpbGw9Im5vbmUiPjxwYXRoIGZpbGw9IiNGRkIwMkUiIGQ9Ik0xNS45OTkgMjkuOTk4YzkuMzM0IDAgMTMuOTk5LTYuMjY4IDEzLjk5OS0xNGMwLTcuNzMtNC42NjUtMTMuOTk4LTE0LTEzLjk5OEM2LjY2NSAyIDIgOC4yNjggMiAxNS45OTlzNC42NjQgMTMuOTk5IDEzLjk5OSAxMy45OTkiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTAuNSAxOWE0LjUgNC41IDAgMSAwIDAtOWE0LjUgNC41IDAgMCAwIDAgOW0xMSAwYTQuNSA0LjUgMCAxIDAgMC05YTQuNSA0LjUgMCAwIDAgMCA5Ii8+PHBhdGggZmlsbD0iIzQwMkEzMiIgZD0iTTguMDcgNy45ODhjLS41OTQuNTYyLS45NTIgMS4yNC0xLjA5NiAxLjY3YS41LjUgMCAxIDEtLjk0OC0uMzE2Yy4xOS0uNTcuNjMtMS4zOTIgMS4zNTUtMi4wOEM4LjExMyA2LjU2NyA5LjE0OCA2IDEwLjUgNmEuNS41IDAgMCAxIDAgMWMtMS4wNDggMC0xLjg0Ni40MzMtMi40My45ODhNMTIgMTdhMiAyIDAgMSAwIDAtNGEyIDIgMCAwIDAgMCA0bTggMGEyIDIgMCAxIDAgMC00YTIgMiAwIDAgMCAwIDRtNS4wMjYtNy4zNDJjLS4xNDQtLjQzLS41MDMtMS4xMDgtMS4wOTUtMS42N0MyMy4zNDYgNy40MzMgMjIuNTQ4IDcgMjEuNSA3YS41LjUgMCAxIDEgMC0xYzEuMzUyIDAgMi4zODcuNTY3IDMuMTIgMS4yNjJjLjcyMy42ODggMS4xNjQgMS41MSAxLjM1NCAyLjA4YS41LjUgMCAxIDEtLjk0OC4zMTYiLz48cGF0aCBmaWxsPSIjQkIxRDgwIiBkPSJNMTMuMTcgMjJjLS4xMS4zMTMtLjE3LjY1LS4xNyAxdjJhMyAzIDAgMSAwIDYgMHYtMmMwLS4zNS0uMDYtLjY4Ny0uMTctMUwxNiAyMXoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTMuMTcgMjJhMy4wMDEgMy4wMDEgMCAwIDEgNS42NiAweiIvPjwvZz48L3N2Zz4=" />
|
||
|
||
### MS-APPX URL:
|
||
⚠️ This loads from CmdPal's AppData folder, not Extension's, so it's not useful for extensions.
|
||
```xml
|
||

|
||
```
|
||
|
||

|
||
|
||
### MS-APPDATA URL:
|
||
⚠️ This loads from CmdPal's AppData folder, not Extension's, so it's not useful for extensions.
|
||
```xml
|
||

|
||
```
|
||
|
||
---
|
||
|
||
# Scaling
|
||
|
||
For URIs that support query parameters (file, http, ms-appx, ms-appdata), you can provide hints to control scaling
|
||
|
||
- `--x-cmdpal-fit`
|
||
- `none`: no automatic scaling, provides image as is (default)
|
||
- `fit`: scale to fit the available space
|
||
- `--x-cmdpal-upscale`
|
||
- `true`: allow upscaling
|
||
- `false`: downscale only (default)
|
||
- `--x-cmdpal-width`: desired width in pixels
|
||
- `--x-cmdpal-height`: desired height in pixels
|
||
- `--x-cmdpal-maxwidth`: max width in pixels
|
||
- `--x-cmdpal-maxheight`: max height in pixels
|
||
|
||
Currently no support for data: scheme as it doesn't support query parameters at all.
|
||
|
||
## Examples
|
||
|
||
### No scaling
|
||
```xml
|
||

|
||
```
|
||
|
||

|
||
|
||
### Scale to fit (scaling down only by default)
|
||
```xml
|
||

|
||
```
|
||
|
||

|
||
|
||
### Scale to fit (in both direction)
|
||
```xml
|
||

|
||
```
|
||
|
||

|
||
|
||
### Scale to exact width
|
||
```xml
|
||

|
||
```
|
||
|
||

|
||
""";
|
||
}
|
||
}
|
||
|
||
private string _currentContent;
|
||
|
||
public SampleMarkdownImagesPage()
|
||
{
|
||
Name = "Sample Markdown with Images Page";
|
||
_currentContent = "Initializing...";
|
||
IsLoading = true;
|
||
|
||
_ = InitializationTask!.ContinueWith(_ =>
|
||
{
|
||
_currentContent = _sampleMarkdownText!;
|
||
RaiseItemsChanged();
|
||
IsLoading = false;
|
||
});
|
||
}
|
||
|
||
public override IContent[] GetContent() => [new MarkdownContent(_currentContent ?? string.Empty)];
|
||
}
|