chore: import upstream snapshot with attribution
Spell checking / Check Spelling (push) Has been cancelled
Spell checking / Update PR (push) Has been cancelled
Publish Dev Docs Website / build (push) Failing after 1s
Publish Dev Docs Website / deploy (push) Has been skipped
Spell checking / Report (Push) (push) Has been cancelled
Spell checking / Report (PR) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:16:02 +08:00
commit 79031da543
8235 changed files with 1365916 additions and 0 deletions
@@ -0,0 +1,16 @@
// 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.
namespace Microsoft.PowerToys.FilePreviewCommon
{
public enum BgcodeBlockType
{
FileMetadataBlock = 0,
GCodeBlock = 1,
SlicerMetadataBlock = 2,
PrinterMetadataBlock = 3,
PrintMetadataBlock = 4,
ThumbnailBlock = 5,
}
}
@@ -0,0 +1,12 @@
// 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.
namespace Microsoft.PowerToys.FilePreviewCommon
{
public enum BgcodeChecksumType
{
None = 0,
CRC32 = 1,
}
}
@@ -0,0 +1,14 @@
// 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.
namespace Microsoft.PowerToys.FilePreviewCommon
{
public enum BgcodeCompressionType
{
NoCompression = 0,
DeflateAlgorithm = 1,
HeatshrinkAlgorithm11 = 2,
HeatshrinkAlgorithm12 = 3,
}
}
@@ -0,0 +1,129 @@
// 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.
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
namespace Microsoft.PowerToys.FilePreviewCommon
{
/// <summary>
/// Bgcode file helper class.
/// </summary>
public static class BgcodeHelper
{
private const uint MagicNumber = 'G' | 'C' << 8 | 'D' << 16 | 'E' << 24;
/// <summary>
/// Gets any thumbnails found in a bgcode file.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader"/> instance to the bgcode file.</param>
/// <returns>The thumbnails found in a bgcode file.</returns>
public static IEnumerable<BgcodeThumbnail> GetThumbnails(BinaryReader reader)
{
var magicNumber = reader.ReadUInt32();
if (magicNumber != MagicNumber)
{
throw new InvalidDataException("Invalid magic number.");
}
var version = reader.ReadUInt32();
if (version != 1)
{
// Version 1 is the only one that exists
throw new InvalidDataException("Unsupported version.");
}
var checksum = (BgcodeChecksumType)reader.ReadUInt16();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
var blockType = (BgcodeBlockType)reader.ReadUInt16();
var compression = (BgcodeCompressionType)reader.ReadUInt16();
var uncompressedSize = reader.ReadUInt32();
var size = compression == BgcodeCompressionType.NoCompression ? uncompressedSize : reader.ReadUInt32();
switch (blockType)
{
case BgcodeBlockType.FileMetadataBlock:
case BgcodeBlockType.PrinterMetadataBlock:
case BgcodeBlockType.PrintMetadataBlock:
case BgcodeBlockType.SlicerMetadataBlock:
case BgcodeBlockType.GCodeBlock:
reader.BaseStream.Seek(2 + size, SeekOrigin.Current); // Skip
break;
case BgcodeBlockType.ThumbnailBlock:
var format = (BgcodeThumbnailFormat)reader.ReadUInt16();
reader.BaseStream.Seek(4, SeekOrigin.Current); // Skip width and height
var data = ReadAndDecompressData(reader, compression, (int)size);
if (data != null)
{
yield return new BgcodeThumbnail(format, data);
}
break;
}
if (checksum == BgcodeChecksumType.CRC32)
{
reader.BaseStream.Seek(4, SeekOrigin.Current); // Skip checksum
}
}
}
/// <summary>
/// Gets the best thumbnail available in a bgcode file.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader"/> instance to the gcode file.</param>
/// <returns>The best thumbnail available in the gcode file.</returns>
public static BgcodeThumbnail? GetBestThumbnail(BinaryReader reader)
{
return GetThumbnails(reader)
.OrderByDescending(x => x.Format switch
{
BgcodeThumbnailFormat.PNG => 2,
BgcodeThumbnailFormat.QOI => 1,
BgcodeThumbnailFormat.JPG => 0,
_ => 0,
})
.ThenByDescending(x => x.Data.Length)
.FirstOrDefault();
}
private static byte[]? ReadAndDecompressData(BinaryReader reader, BgcodeCompressionType compression, int size)
{
// Though the spec doesn't actually mention it, the reference encoder code never applies compression to thumbnails data
// which makes complete sense as this data is PNG, JPEG or QOI encoded so already compressed as much as possible!
switch (compression)
{
case BgcodeCompressionType.NoCompression:
return reader.ReadBytes(size);
case BgcodeCompressionType.DeflateAlgorithm:
var buffer = new byte[size];
using (var deflateStream = new DeflateStream(reader.BaseStream, CompressionMode.Decompress, true))
{
deflateStream.ReadExactly(buffer, 0, size);
}
return buffer;
default:
reader.BaseStream.Seek(size, SeekOrigin.Current); // Skip unknown or unsupported compression types
return null;
}
}
}
}
@@ -0,0 +1,66 @@
// 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.
using System.Drawing;
using System.IO;
namespace Microsoft.PowerToys.FilePreviewCommon
{
/// <summary>
/// Represents a bgcode thumbnail.
/// </summary>
public class BgcodeThumbnail
{
/// <summary>
/// Gets the bgcode thumbnail image format.
/// </summary>
public BgcodeThumbnailFormat Format { get; }
/// <summary>
/// Gets the bgcode thumbnail image data.
/// </summary>
public byte[] Data { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BgcodeThumbnail"/> class.
/// </summary>
/// <param name="format">The bgcode thumbnail image format.</param>
/// <param name="data">The bgcode thumbnail image data.</param>
public BgcodeThumbnail(BgcodeThumbnailFormat format, byte[] data)
{
Format = format;
Data = data;
}
/// <summary>
/// Gets a <see cref="Bitmap"/> representing this thumbnail.
/// </summary>
/// <returns>A <see cref="Bitmap"/> representing this thumbnail.</returns>
public Bitmap? GetBitmap()
{
switch (Format)
{
case BgcodeThumbnailFormat.JPG:
case BgcodeThumbnailFormat.PNG:
return BitmapFromByteArray();
case BgcodeThumbnailFormat.QOI:
return BitmapFromQoiByteArray();
default:
return null;
}
}
private Bitmap BitmapFromByteArray()
{
return new Bitmap(new MemoryStream(Data));
}
private Bitmap BitmapFromQoiByteArray()
{
return QoiImage.FromStream(new MemoryStream(Data));
}
}
}
@@ -0,0 +1,24 @@
// 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.
namespace Microsoft.PowerToys.FilePreviewCommon
{
public enum BgcodeThumbnailFormat
{
/// <summary>
/// PNG image format.
/// </summary>
PNG = 0,
/// <summary>
/// JPG image format.
/// </summary>
JPG = 1,
/// <summary>
/// QOI image format.
/// </summary>
QOI = 2,
}
}
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<Import Project="$(RepoRoot)src\Monaco.props" />
<Import Project="$(RepoRoot)src\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>
<Description>PowerToys FilePreviewCommon</Description>
<AssemblyName>PowerToys.FilePreviewCommon</AssemblyName>
<UseWindowsForms>true</UseWindowsForms>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Markdig.Signed" />
<PackageReference Include="UTF.Unknown" />
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
// 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.
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.FilePreviewCommon.Monaco.Formatters;
[JsonSerializable(typeof(JsonDocument))]
internal sealed partial class FilePreviewJsonSerializerContext : JsonSerializerContext
{
}
@@ -0,0 +1,21 @@
// 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.
namespace Microsoft.PowerToys.FilePreviewCommon.Monaco.Formatters
{
public interface IFormatter
{
/// <summary>
/// Gets the language to which the formatter is applied
/// </summary>
string LangSet { get; }
/// <summary>
/// Format the value
/// </summary>
/// <param name="value">The value to format</param>
/// <returns>The value formatted</returns>
string Format(string value);
}
}
@@ -0,0 +1,37 @@
// 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.
using System.Text.Encodings.Web;
using System.Text.Json;
namespace Microsoft.PowerToys.FilePreviewCommon.Monaco.Formatters
{
public class JsonFormatter : IFormatter
{
/// <inheritdoc/>
public string LangSet => "json";
private static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
private static readonly FilePreviewJsonSerializerContext _filePreviewJsonSerializerContext = new(_serializerOptions);
/// <inheritdoc/>
public string Format(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
using (var jDocument = JsonDocument.Parse(value, new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip }))
{
return JsonSerializer.Serialize(jDocument, _filePreviewJsonSerializerContext.JsonDocument);
}
}
}
}
@@ -0,0 +1,41 @@
// 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.
using System.Text;
using System.Xml;
namespace Microsoft.PowerToys.FilePreviewCommon.Monaco.Formatters
{
public class XmlFormatter : IFormatter
{
/// <inheritdoc/>
public string LangSet => "xml";
/// <inheritdoc/>
public string Format(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(value);
var stringBuilder = new StringBuilder();
var xmlWriterSettings = new XmlWriterSettings()
{
OmitXmlDeclaration = xmlDocument.FirstChild?.NodeType != XmlNodeType.XmlDeclaration,
Indent = true,
};
using (var xmlWriter = XmlWriter.Create(stringBuilder, xmlWriterSettings))
{
xmlDocument.Save(xmlWriter);
}
return stringBuilder.ToString();
}
}
}
@@ -0,0 +1,81 @@
// 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.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Microsoft.PowerToys.FilePreviewCommon
{
/// <summary>
/// Gcode file helper class.
/// </summary>
public static class GcodeHelper
{
/// <summary>
/// Gets any thumbnails found in a gcode file.
/// </summary>
/// <param name="reader">The <see cref="TextReader"/> instance to the gcode file.</param>
/// <returns>The thumbnails found in a gcode file.</returns>
public static IEnumerable<GcodeThumbnail> GetThumbnails(TextReader reader)
{
string? line;
var format = GcodeThumbnailFormat.Unknown;
StringBuilder? capturedText = null;
while ((line = reader.ReadLine()) != null)
{
if (line.StartsWith("; thumbnail", StringComparison.InvariantCulture))
{
var parts = line[11..].Split(" ");
switch (parts[1])
{
case "begin":
format = parts[0].ToUpperInvariant() switch
{
"" => GcodeThumbnailFormat.PNG,
"_JPG" => GcodeThumbnailFormat.JPG,
"_QOI" => GcodeThumbnailFormat.QOI,
_ => GcodeThumbnailFormat.Unknown,
};
capturedText = new StringBuilder();
break;
case "end":
if (capturedText != null)
{
yield return new GcodeThumbnail(format, capturedText.ToString());
capturedText = null;
}
break;
}
}
else
{
capturedText?.Append(line[2..]);
}
}
}
/// <summary>
/// Gets the best thumbnail available in a gcode file.
/// </summary>
/// <param name="reader">The <see cref="TextReader"/> instance to the gcode file.</param>
/// <returns>The best thumbnail available in the gcode file.</returns>
public static GcodeThumbnail? GetBestThumbnail(TextReader reader)
{
return GetThumbnails(reader)
.Where(x => x.Format != GcodeThumbnailFormat.Unknown)
.OrderByDescending(x => (int)x.Format)
.ThenByDescending(x => x.Data.Length)
.FirstOrDefault();
}
}
}
@@ -0,0 +1,71 @@
// 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.
using System;
using System.Drawing;
using System.IO;
namespace Microsoft.PowerToys.FilePreviewCommon
{
/// <summary>
/// Represents a gcode thumbnail.
/// </summary>
public class GcodeThumbnail
{
/// <summary>
/// Gets the gcode thumbnail image format.
/// </summary>
public GcodeThumbnailFormat Format { get; }
/// <summary>
/// Gets the gcode thumbnail image data in base64.
/// </summary>
public string Data { get; }
/// <summary>
/// Initializes a new instance of the <see cref="GcodeThumbnail"/> class.
/// </summary>
/// <param name="format">The gcode thumbnail image format.</param>
/// <param name="data">The gcode thumbnail image data in base64.</param>
public GcodeThumbnail(GcodeThumbnailFormat format, string data)
{
Format = format;
Data = data;
}
/// <summary>
/// Gets a <see cref="Bitmap"/> representing this thumbnail.
/// </summary>
/// <returns>A <see cref="Bitmap"/> representing this thumbnail.</returns>
public Bitmap? GetBitmap()
{
switch (Format)
{
case GcodeThumbnailFormat.JPG:
case GcodeThumbnailFormat.PNG:
return BitmapFromBase64String();
case GcodeThumbnailFormat.QOI:
return BitmapFromQoiBase64String();
default:
return null;
}
}
private Bitmap BitmapFromBase64String()
{
var bitmapBytes = Convert.FromBase64String(Data);
return new Bitmap(new MemoryStream(bitmapBytes));
}
private Bitmap BitmapFromQoiBase64String()
{
var bitmapBytes = Convert.FromBase64String(Data);
return QoiImage.FromStream(new MemoryStream(bitmapBytes));
}
}
}
@@ -0,0 +1,32 @@
// 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.
namespace Microsoft.PowerToys.FilePreviewCommon
{
/// <summary>
/// The gcode thumbnail image format.
/// </summary>
public enum GcodeThumbnailFormat
{
/// <summary>
/// Unknown image format.
/// </summary>
Unknown,
/// <summary>
/// JPG image format.
/// </summary>
JPG,
/// <summary>
/// QOI image format.
/// </summary>
QOI,
/// <summary>
/// PNG image format.
/// </summary>
PNG,
}
}
@@ -0,0 +1,104 @@
// 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.
using Markdig;
using Markdig.Extensions.Figures;
using Markdig.Extensions.Tables;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Microsoft.PowerToys.FilePreviewCommon
{
/// <summary>
/// Callback if extension blocks external images.
/// </summary>
public delegate void ImagesBlockedCallBack();
/// <summary>
/// Markdig Extension to process html nodes in markdown AST.
/// </summary>
public class HTMLParsingExtension : IMarkdownExtension
{
/// <summary>
/// Callback if extension blocks external images.
/// </summary>
private readonly ImagesBlockedCallBack imagesBlockedCallBack;
/// <summary>
/// Initializes a new instance of the <see cref="HTMLParsingExtension"/> class.
/// </summary>
/// <param name="imagesBlockedCallBack">Callback function if image is blocked by extension.</param>
/// <param name="filePath">Absolute path of markdown file.</param>
public HTMLParsingExtension(ImagesBlockedCallBack imagesBlockedCallBack, string filePath = "")
{
this.imagesBlockedCallBack = imagesBlockedCallBack;
FilePath = filePath;
}
/// <summary>
/// Gets or sets path to directory containing markdown file.
/// </summary>
public string FilePath { get; set; }
/// <inheritdoc/>
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (pipeline != null)
{
// Make sure we don't have a delegate twice
pipeline.DocumentProcessed -= PipelineOnDocumentProcessed;
pipeline.DocumentProcessed += PipelineOnDocumentProcessed;
}
}
/// <inheritdoc/>
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
/// <summary>
/// Process nodes in markdown AST.
/// </summary>
/// <param name="document">Markdown Document.</param>
public void PipelineOnDocumentProcessed(MarkdownDocument document)
{
foreach (var node in document.Descendants())
{
if (node is Block)
{
if (node is Table)
{
node.GetAttributes().AddClass("table table-striped table-bordered");
}
else if (node is QuoteBlock)
{
node.GetAttributes().AddClass("blockquote");
}
else if (node is Figure)
{
node.GetAttributes().AddClass("figure");
}
else if (node is FigureCaption)
{
node.GetAttributes().AddClass("figure-caption");
}
}
else if (node is Inline)
{
if (node is LinkInline link)
{
if (link.IsImage)
{
link.Url = "#";
link.GetAttributes().AddClass("img-fluid");
imagesBlockedCallBack();
}
}
}
}
}
}
}
+40
View File
@@ -0,0 +1,40 @@
// 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.
using System;
using System.IO;
using System.Threading.Tasks;
namespace Microsoft.PowerToys.FilePreviewCommon
{
public static class Helper
{
public static Task<bool> CleanupTempDirAsync(string folder)
{
return Task.Run(() =>
{
return CleanupTempDir(folder);
});
}
public static bool CleanupTempDir(string folder)
{
try
{
var dir = new DirectoryInfo(folder);
foreach (var file in dir.EnumerateFiles("*.html"))
{
file.Delete();
}
return true;
}
catch (Exception)
{
}
return false;
}
}
}
@@ -0,0 +1,53 @@
// 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.
using System.IO;
using Markdig;
namespace Microsoft.PowerToys.FilePreviewCommon
{
public static class MarkdownHelper
{
/// <summary>
/// Markdown HTML header for light theme.
/// </summary>
private static readonly string HtmlLightHeader = "<!doctype html><style>body{width:100%;margin:0;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}.container{padding:5%}body img{max-width:100%;height:auto}body h1,body h2,body h3,body h4,body h5,body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}body h1,body h2{padding-bottom:.3em;border-bottom:1px solid #eaecef}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji}body h3{font-size:1.25em}body h4{font-size:1em}body h5{font-size:.875em}body h6{font-size:.85em;color:#6a737d}pre{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;background-color:#f6f8fa;border-radius:3px;padding:16px;font-size:85%}a{color:#0366d6}strong{font-weight:600}em{font-style:italic}code{padding:.2em .4em;margin:0;font-size:85%;background-color:#f6f8fa;border-radius:3px}hr{border-color:#EEE -moz-use-text-color #FFF;border-style:solid none;border-width:.5px 0;margin:18px 0}table{display:block;width:100%;overflow:auto;border-spacing:0;border-collapse:collapse}tbody{display:table-row-group;vertical-align:middle;border-color:inherit;vertical-align:inherit;border-color:inherit}table tr{background-color:#fff;border-top:1px solid #c6cbd1}tr{display:table-row;vertical-align:inherit;border-color:inherit}table td,table th{padding:6px 13px;border:1px solid #dfe2e5}th{font-weight:600;display:table-cell;vertical-align:inherit;font-weight:bold;text-align:-internal-center}thead{display:table-header-group;vertical-align:middle;border-color:inherit}td{display:table-cell;vertical-align:inherit}code,pre,tt{font-family:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;color:#24292e;overflow-x:auto}pre code{display:block;font-size:inherit;color:inherit;word-break:normal}blockquote{background-color:#fff;border-radius:3px;padding:15px;font-size:14px;display:block;margin-block-start:1em;margin-block-end:1em;margin-inline-start:40px;margin-inline-end:40px;padding:0 1em;color:#6a737d;border-left:.25em solid #dfe2e5}</style><body><div class=\"container\">";
/// <summary>
/// Markdown HTML header for dark theme.
/// </summary>
private static readonly string HtmlDarkHeader = "<!doctype html><style>body{width:100%;margin:0;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\";font-size:1rem;font-weight:400;line-height:1.5;color:#d4d4d4;text-align:left;background-color:#1e1e1e}.container{padding:5%}body img{max-width:100%;height:auto}body h1,body h2,body h3,body h4,body h5,body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}body h1,body h2{padding-bottom:.3em;border-bottom:1px solid #474747}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji}body h3{font-size:1.25em}body h4{font-size:1em}body h5{font-size:.875em}body h6{font-size:.85em;color:#d4d4d4}pre{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;background-color:#161616;border-radius:3px;padding:16px;font-size:85%}a{color:#0366d6}strong{font-weight:600}em{font-style:italic}code{padding:.2em .4em;margin:0;font-size:85%;background-color:#161616;border-radius:3px}hr{border-color:#EEE -moz-use-text-color #FFF;border-style:solid none;border-width:.5px 0;margin:18px 0}table{display:block;width:100%;overflow:auto;border-spacing:0;border-collapse:collapse}tbody{display:table-row-group;vertical-align:middle;border-color:inherit;vertical-align:inherit;border-color:inherit}table tr{background-color:#1e1e1e;border-top:1px solid #c6cbd1}tr{display:table-row;vertical-align:inherit;border-color:inherit}table td,table th{padding:6px 13px;border:1px solid #474747}th{font-weight:600;display:table-cell;vertical-align:inherit;font-weight:bold;text-align:-internal-center}thead{display:table-header-group;vertical-align:middle;border-color:inherit}td{display:table-cell;vertical-align:inherit}code,pre,tt{font-family:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;color:#d4d4d4;overflow-x:auto}pre code{display:block;font-size:inherit;color:inherit;word-break:normal}blockquote{background-color:#282828;border-radius:3px;padding:15px;font-size:14px;display:block;margin-block-start:1em;margin-block-end:1em;margin-inline-start:40px;margin-inline-end:40px;padding:0 1em;color:#d4d4d4;border-left:.25em solid #d4d4d4}</style><body><div class=\"container\">";
/// <summary>
/// Markdown HTML footer.
/// </summary>
private static readonly string HtmlFooter = "</div></body></html>";
public static string MarkdownHtml(string fileContent, string theme, string filePath, ImagesBlockedCallBack imagesBlockedCallBack)
{
var htmlHeader = theme == "dark" ? HtmlDarkHeader : HtmlLightHeader;
// Extension to modify markdown AST.
HTMLParsingExtension extension = new HTMLParsingExtension(imagesBlockedCallBack);
extension.FilePath = Path.GetDirectoryName(filePath) ?? string.Empty;
// if you have a string with double space, some people view it as a new line.
// while this is against spec, even GH supports this. Technically looks like GH just trims whitespace
// https://github.com/microsoft/PowerToys/issues/10354
var softlineBreak = new Markdig.Extensions.Hardlines.SoftlineBreakAsHardlineExtension();
MarkdownPipelineBuilder pipelineBuilder;
pipelineBuilder = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseEmojiAndSmiley().UseYamlFrontMatter().UseMathematics();
pipelineBuilder.Extensions.Add(extension);
pipelineBuilder.Extensions.Add(softlineBreak);
MarkdownPipeline pipeline = pipelineBuilder.Build();
string parsedMarkdown = Markdown.ToHtml(fileContent, pipeline);
string markdownHTML = $"{htmlHeader}{parsedMarkdown}{HtmlFooter}";
return markdownHTML;
}
}
}
@@ -0,0 +1,117 @@
// 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.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.Json;
using Microsoft.PowerToys.FilePreviewCommon.Monaco.Formatters;
namespace Microsoft.PowerToys.FilePreviewCommon
{
public static class MonacoHelper
{
/// <summary>
/// Name of the virtual host
/// </summary>
public const string VirtualHostName = "PowerToysLocalMonaco";
/// <summary>
/// Formatters applied before rendering the preview
/// </summary>
public static readonly IReadOnlyCollection<IFormatter> Formatters = new List<IFormatter>
{
new JsonFormatter(),
new XmlFormatter(),
}.AsReadOnly();
private static readonly Lazy<string> _monacoDirectory = new(GetRuntimeMonacoDirectory);
/// <summary>
/// Gets the path of the Monaco assets folder.
/// </summary>
public static string MonacoDirectory => _monacoDirectory.Value;
private static string GetRuntimeMonacoDirectory()
{
string baseDirectory = AppContext.BaseDirectory ?? string.Empty;
// AppContext.BaseDirectory returns a stray \\ so we want to remove that.
baseDirectory = Path.TrimEndingDirectorySeparator(baseDirectory);
// If the executable is within "WinUI3Apps", correct the path first.
// The idea of GetFileName here is getting the last directory in the path.
if (Path.GetFileName(baseDirectory) == "WinUI3Apps")
{
baseDirectory = Path.Combine(baseDirectory, "..");
}
string monacoPath = Path.Combine(baseDirectory, "Assets", "Monaco");
return Directory.Exists(monacoPath) ?
monacoPath :
throw new DirectoryNotFoundException($"Monaco assets directory not found at {monacoPath}");
}
public static JsonDocument GetLanguages()
{
JsonDocument languageListDocument;
using (StreamReader jsonFileReader = new StreamReader(new FileStream(MonacoDirectory + "\\monaco_languages.json", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
languageListDocument = JsonDocument.Parse(jsonFileReader.ReadToEnd());
jsonFileReader.Close();
}
return languageListDocument;
}
/// <summary>
/// Converts a file extension to a language monaco id.
/// </summary>
/// <param name="fileExtension">The extension of the file (without the dot).</param>
/// <returns>The monaco language id</returns>
public static string GetLanguage(string fileExtension)
{
fileExtension = fileExtension.ToLower(CultureInfo.CurrentCulture);
try
{
JsonDocument languageListDocument = GetLanguages();
JsonElement languageList = languageListDocument.RootElement.GetProperty("list");
foreach (JsonElement e in languageList.EnumerateArray())
{
if (e.TryGetProperty("extensions", out var extensions))
{
for (int j = 0; j < extensions.GetArrayLength(); j++)
{
if (extensions[j].ToString() == fileExtension)
{
return e.GetProperty("id").ToString();
}
}
}
}
return "plaintext";
}
catch (Exception)
{
return "plaintext";
}
}
public static string ReadIndexHtml()
{
string html;
using (StreamReader htmlFileReader = new StreamReader(new FileStream(MonacoDirectory + "\\index.html", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
html = htmlFileReader.ReadToEnd();
htmlFileReader.Close();
}
return html;
}
}
}
+188
View File
@@ -0,0 +1,188 @@
// 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.
using System;
using System.Buffers.Binary;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
//// Based on https://github.com/phoboslab/qoi/blob/master/qoi.h
namespace Microsoft.PowerToys.FilePreviewCommon
{
/// <summary>
/// QOI Image helper.
/// </summary>
public static class QoiImage
{
#pragma warning disable SA1310 // Field names should not contain underscore
private const byte QOI_OP_INDEX = 0x00; // 00xxxxxx
private const byte QOI_OP_DIFF = 0x40; // 01xxxxxx
private const byte QOI_OP_LUMA = 0x80; // 10xxxxxx
private const byte QOI_OP_RUN = 0xc0; // 11xxxxxx
private const byte QOI_OP_RGB = 0xfe; // 11111110
private const byte QOI_OP_RGBA = 0xff; // 11111111
private const byte QOI_MASK_2 = 0xc0; // 11000000
private const int QOI_MAGIC = 'q' << 24 | 'o' << 16 | 'i' << 8 | 'f';
private const int QOI_HEADER_SIZE = 14;
private const uint QOI_PIXELS_MAX = 400000000;
private const byte QOI_PADDING_LENGTH = 8;
#pragma warning restore SA1310 // Field names should not contain underscore
private record struct QoiPixel(byte R, byte G, byte B, byte A)
{
public readonly int GetColorHash() => (R * 3) + (G * 5) + (B * 7) + (A * 11);
}
/// <summary>
/// Creates a <see cref="Bitmap"/> from the specified QOI data stream.
/// </summary>
/// <param name="stream">A <see cref="Stream"/> that contains the QOI data.</param>
/// <returns>The <see cref="Bitmap"/> this method creates.</returns>
/// <exception cref="ArgumentException">The stream does not have a valid QOI image format.</exception>
public static Bitmap FromStream(Stream stream)
{
var fileSize = stream.Length;
if (fileSize < QOI_HEADER_SIZE + QOI_PADDING_LENGTH)
{
throw new ArgumentException("Not enough data for a QOI file");
}
Bitmap? bitmap = null;
try
{
using var reader = new BinaryReader(stream, Encoding.UTF8, true);
var headerMagic = ReadUInt32BigEndian(reader);
if (headerMagic != QOI_MAGIC)
{
throw new ArgumentException("Invalid QOI file header");
}
var width = ReadUInt32BigEndian(reader);
var height = ReadUInt32BigEndian(reader);
var channels = reader.ReadByte();
var colorSpace = reader.ReadByte();
if (width == 0 || height == 0 || channels < 3 || channels > 4 || colorSpace > 1 || height >= QOI_PIXELS_MAX / width)
{
throw new ArgumentException("Invalid QOI file data");
}
var pixelFormat = channels == 4 ? PixelFormat.Format32bppArgb : PixelFormat.Format24bppRgb;
bitmap = new Bitmap((int)width, (int)height, pixelFormat);
var bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, pixelFormat);
var dataLength = bitmapData.Height * bitmapData.Stride;
var index = new QoiPixel[64];
var pixel = new QoiPixel(0, 0, 0, 255);
var run = 0;
var chunksLen = fileSize - QOI_PADDING_LENGTH;
var x = 0;
var rowAdd = bitmapData.Stride - (channels * bitmapData.Width);
for (var dataIndex = 0; dataIndex < dataLength; dataIndex += channels)
{
if (run > 0)
{
run--;
}
else if (stream.Position < chunksLen)
{
var b1 = reader.ReadByte();
if (b1 == QOI_OP_RGB)
{
pixel.R = reader.ReadByte();
pixel.G = reader.ReadByte();
pixel.B = reader.ReadByte();
}
else if (b1 == QOI_OP_RGBA)
{
pixel.R = reader.ReadByte();
pixel.G = reader.ReadByte();
pixel.B = reader.ReadByte();
pixel.A = reader.ReadByte();
}
else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX)
{
pixel = index[b1];
}
else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF)
{
pixel.R += (byte)(((b1 >> 4) & 0x03) - 2);
pixel.G += (byte)(((b1 >> 2) & 0x03) - 2);
pixel.B += (byte)((b1 & 0x03) - 2);
}
else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA)
{
var b2 = reader.ReadByte();
var vg = (b1 & 0x3f) - 32;
pixel.R += (byte)(vg - 8 + ((b2 >> 4) & 0x0f));
pixel.G += (byte)vg;
pixel.B += (byte)(vg - 8 + (b2 & 0x0f));
}
else if ((b1 & QOI_MASK_2) == QOI_OP_RUN)
{
run = b1 & 0x3f;
}
index[pixel.GetColorHash() % 64] = pixel;
}
unsafe
{
var bitmapPixel = (byte*)bitmapData.Scan0 + dataIndex;
bitmapPixel[0] = pixel.B;
bitmapPixel[1] = pixel.G;
bitmapPixel[2] = pixel.R;
if (channels == 4)
{
bitmapPixel[3] = pixel.A;
}
}
x++;
if (x == bitmapData.Width)
{
// We align dataIndex with the stride
dataIndex += rowAdd;
x = 0;
}
}
bitmap.UnlockBits(bitmapData);
return bitmap;
}
catch
{
bitmap?.Dispose();
throw;
}
}
private static uint ReadUInt32BigEndian(BinaryReader reader)
{
var buffer = reader.ReadBytes(4);
return BinaryPrimitives.ReadUInt32BigEndian(buffer);
}
}
}