Compare commits

...

14 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
f79422e795 Fix Mermaid diagram rendering by updating markdown-it highlight function
Co-authored-by: emako <24737061+emako@users.noreply.github.com>
2025-08-01 18:05:19 +00:00
ema
e5fc61c795 Replace the mermaid.min.js
https://cdn.jsdelivr.net/npm/mermaid@11.4.1/dist/mermaid.min.js
2025-08-02 01:56:08 +08:00
copilot-swe-agent[bot]
5bb15aee41 Implement Mermaid diagram support for MarkdownViewer
Co-authored-by: emako <24737061+emako@users.noreply.github.com>
2025-08-01 17:01:46 +00:00
copilot-swe-agent[bot]
079ecd5464 Initial plan 2025-08-01 16:50:42 +00:00
Piteriuz
efe47ff43b Update Polish translation file (#1727)
- Add missing translations for MW_OpenWithMenu and InfoPanel_CantPreventClosing
- Remove duplicate entry for MW_BrowseFolder
2025-08-01 22:21:44 +08:00
ema
417876edd2 Prepare support for .pdn
Some checks failed
MSBuild / build (push) Has been cancelled
MSBuild / publish (push) Has been cancelled
2025-07-31 01:27:43 +08:00
ema
03b1ca557f Fix image .jxr error reading from UseColorProfile 2025-07-31 01:22:13 +08:00
ema
f39b53a5c6 Support Point Cloud Data (.pcd) for 3D spatial
Some checks failed
MSBuild / build (push) Has been cancelled
MSBuild / publish (push) Has been cancelled
1. Only support PCD files with PointXYZ format.
2. Not supported for Color or Intensity formats.
2025-07-28 16:48:13 +08:00
ema
68cb555bad Unsupported .pmx incorrectly handled in advance 2025-07-28 16:19:30 +08:00
dependabot[bot]
d88c8ab8de Bump Magick.NET-Q8-AnyCPU from 14.6.0 to 14.7.0
Some checks failed
MSBuild / build (push) Has been cancelled
MSBuild / publish (push) Has been cancelled
---
updated-dependencies:
- dependency-name: Magick.NET-Q8-AnyCPU
  dependency-version: 14.7.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-24 16:35:58 +08:00
copilot-swe-agent[bot]
a38b7a450a Fix code alignment by adding space to align .Replace method calls
Some checks failed
MSBuild / build (push) Has been cancelled
MSBuild / publish (push) Has been cancelled
Co-authored-by: emako <24737061+emako@users.noreply.github.com>
2025-07-23 23:57:13 +08:00
copilot-swe-agent[bot]
8ad5f39eab Add missing using System.Linq to fix compile error
Co-authored-by: emako <24737061+emako@users.noreply.github.com>
2025-07-23 23:57:13 +08:00
copilot-swe-agent[bot]
83cfd2a3d8 Remove unnecessary Translations.config and simplify RTL detection code
Co-authored-by: emako <24737061+emako@users.noreply.github.com>
2025-07-23 23:57:13 +08:00
copilot-swe-agent[bot]
4840a87858 Add RTL support for markdown files
Co-authored-by: emako <24737061+emako@users.noreply.github.com>
2025-07-23 23:57:13 +08:00
10 changed files with 2681 additions and 10 deletions

View File

@@ -17,6 +17,8 @@
using Assimp;
using HelixToolkit.Wpf;
using PcdSharp.IO;
using PcdSharp.Struct;
using System;
using System.IO;
using System.Linq;
@@ -54,6 +56,86 @@ public partial class HelixPanel
modelVisual.Content = model;
}
}
if (importerType == ImporterType.Extended_PCD)
{
// Only support PCD files with PointXYZ format
// Not supported for Color or Intensity formats
var xyzCloud = PCDReader.Read<PointXYZ>(_path);
// Create a single geometry for all points to improve performance
var pointCloudGeometry = new MeshGeometry3D();
var positions = new Point3DCollection();
var triangleIndices = new Int32Collection();
// Create material for points
var pointMaterial = new DiffuseMaterial(new SolidColorBrush(Color.FromRgb(0x60, 0x80, 0xFF)));
// Adaptive point size based on total number of points
var totalPoints = xyzCloud.Points.Count;
var pointSize = totalPoints > 10000 ? 0.05d : totalPoints > 1000 ? 0.1d : 0.2d;
// Limit points for performance (show every Nth point if too many)
var step = Math.Max(1, totalPoints / 50000); // Limit to ~50k points max
var vertexIndex = 0;
for (int i = 0; i < xyzCloud.Points.Count; i += step)
{
var point = xyzCloud.Points[i];
// Create a small cube for each point
var halfSize = pointSize / 2;
// Add 8 vertices for the cube
var baseIndex = vertexIndex;
// Front face vertices
positions.Add(new Point3D(point.X - halfSize, point.Y - halfSize, point.Z + halfSize));
positions.Add(new Point3D(point.X + halfSize, point.Y - halfSize, point.Z + halfSize));
positions.Add(new Point3D(point.X + halfSize, point.Y + halfSize, point.Z + halfSize));
positions.Add(new Point3D(point.X - halfSize, point.Y + halfSize, point.Z + halfSize));
// Back face vertices
positions.Add(new Point3D(point.X - halfSize, point.Y - halfSize, point.Z - halfSize));
positions.Add(new Point3D(point.X + halfSize, point.Y - halfSize, point.Z - halfSize));
positions.Add(new Point3D(point.X + halfSize, point.Y + halfSize, point.Z - halfSize));
positions.Add(new Point3D(point.X - halfSize, point.Y + halfSize, point.Z - halfSize));
// Add triangle indices for the cube (12 triangles, 36 indices)
// Front face
triangleIndices.Add(baseIndex + 0); triangleIndices.Add(baseIndex + 1); triangleIndices.Add(baseIndex + 2);
triangleIndices.Add(baseIndex + 0); triangleIndices.Add(baseIndex + 2); triangleIndices.Add(baseIndex + 3);
// Back face
triangleIndices.Add(baseIndex + 4); triangleIndices.Add(baseIndex + 6); triangleIndices.Add(baseIndex + 5);
triangleIndices.Add(baseIndex + 4); triangleIndices.Add(baseIndex + 7); triangleIndices.Add(baseIndex + 6);
// Left face
triangleIndices.Add(baseIndex + 4); triangleIndices.Add(baseIndex + 0); triangleIndices.Add(baseIndex + 3);
triangleIndices.Add(baseIndex + 4); triangleIndices.Add(baseIndex + 3); triangleIndices.Add(baseIndex + 7);
// Right face
triangleIndices.Add(baseIndex + 1); triangleIndices.Add(baseIndex + 5); triangleIndices.Add(baseIndex + 6);
triangleIndices.Add(baseIndex + 1); triangleIndices.Add(baseIndex + 6); triangleIndices.Add(baseIndex + 2);
// Top face
triangleIndices.Add(baseIndex + 3); triangleIndices.Add(baseIndex + 2); triangleIndices.Add(baseIndex + 6);
triangleIndices.Add(baseIndex + 3); triangleIndices.Add(baseIndex + 6); triangleIndices.Add(baseIndex + 7);
// Bottom face
triangleIndices.Add(baseIndex + 4); triangleIndices.Add(baseIndex + 5); triangleIndices.Add(baseIndex + 1);
triangleIndices.Add(baseIndex + 4); triangleIndices.Add(baseIndex + 1); triangleIndices.Add(baseIndex + 0);
vertexIndex += 8;
}
pointCloudGeometry.Positions = positions;
pointCloudGeometry.TriangleIndices = triangleIndices;
// Create the model
var pointCloudModel = new GeometryModel3D
{
Geometry = pointCloudGeometry,
Material = pointMaterial,
BackMaterial = pointMaterial
};
modelVisual.Content = pointCloudModel;
}
else
{
var modelImporter = new ModelImporter();
@@ -90,6 +172,7 @@ file static class Importer
".stl" or ".obj" or ".3ds" or ".lwo" or ".ply" => ImporterType.Default,
".fbx" or ".3mf" or ".glb" or ".gltf" or ".dae" or ".dxf" => ImporterType.Extended,
".pmx" => ImporterType.Extended_MMD,
".pcd" => ImporterType.Extended_PCD,
_ => ImporterType.Unknown,
};
}
@@ -97,8 +180,28 @@ file static class Importer
file enum ImporterType
{
/// <summary>
/// Reserved or unspecified import type
/// </summary>
Unknown,
/// <summary>
/// Default importer supported by HelixToolkit
/// </summary>
Default,
/// <summary>
/// Extended importer supported by Assimp
/// </summary>
Extended,
/// <summary>
/// Extended MMD (MikuMikuDance) importer
/// </summary>
Extended_MMD,
/// <summary>
/// Extended PCD (Point Cloud Data) importer for 3D spatial data
/// </summary>
Extended_PCD,
}

View File

@@ -31,17 +31,20 @@ public class Plugin : IViewer
/// </summary>
private static readonly HashSet<string> WellKnownExtensions = new(
[
// Default
// Default supported by HelixToolkit
".stl", ".obj", ".3ds", ".lwo", ".ply",
// Extended
// Extended supported by Assimp
".fbx", ".3mf", ".blend", ".glb", ".gltf", ".dae",
#if S_DXF
".dxf",
#endif
// Extended_MMD
".pmx",
// TBD: MMD (MikuMikuDance)
//".pmx",
// PCD (Point Cloud Data)
".pcd",
]);
private HelixPanel _hp;

View File

@@ -55,6 +55,7 @@
<PackageReference Include="HelixToolkit" Version="2.27.0" />
<PackageReference Include="HelixToolkit.Wpf" Version="2.27.0" />
<PackageReference Include="AssimpNet" Version="5.0.0-beta1" />
<PackageReference Include="PcdSharp" Version="1.0.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,104 @@
// Copyright © 2017-2025 QL-Win Contributors
//
// This file is part of QuickLook program.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using QuickLook.Common.Helpers;
using QuickLook.Common.Plugin;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Xml.Linq;
namespace QuickLook.Plugin.ImageViewer.AnimatedImage.Providers;
internal class PdnProvider : AnimationProvider
{
public PdnProvider(Uri path, MetaProvider meta, ContextObject contextObject) : base(path, meta, contextObject)
{
Animator = new Int32AnimationUsingKeyFrames();
Animator.KeyFrames.Add(new DiscreteInt32KeyFrame(0,
KeyTime.FromTimeSpan(TimeSpan.Zero)));
}
public override Task<BitmapSource> GetThumbnail(Size renderSize)
{
// Skip thumbnail
return new Task<BitmapSource>(() => null);
}
public override Task<BitmapSource> GetRenderedFrame(int index)
{
return new Task<BitmapSource>(() =>
{
try
{
using TextReader reader = new StreamReader(Path.LocalPath, Encoding.UTF8);
string line = reader.ReadLine();
if (!line.StartsWith("PDN"))
return null;
int indexOfStart = line.IndexOf("<");
int indexOfEnd = line.LastIndexOf(">");
if (indexOfStart < 0 || indexOfEnd < 0)
return null;
string xml = line.Substring(indexOfStart, indexOfEnd - indexOfStart + 1);
// <pdnImage>
// <custom>
// <thumb png="..." />
// </custom>
// </pdnImage>
XDocument doc = XDocument.Parse(xml);
var pngBase64 = doc.Root
?.Element("custom")
?.Element("thumb")
?.Attribute("png")
?.Value;
if (pngBase64 != null)
{
byte[] imageBytes = Convert.FromBase64String(pngBase64);
MemoryStream ms = new(imageBytes);
BitmapImage bitmap = new();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = ms;
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
return null;
}
catch (Exception e)
{
ProcessHelper.WriteLog(e.ToString());
return null;
}
});
}
public override void Dispose()
{
}
}

View File

@@ -79,8 +79,7 @@ public class Plugin : IViewer
useColorProfile ? [] : [".bmp", ".jpg", ".jpeg", ".jfif", ".tif", ".tiff"],
typeof(NativeProvider)));
AnimatedImage.AnimatedImage.Providers.Add(
new KeyValuePair<string[], Type>(
useColorProfile ? [] : [".jxr"],
new KeyValuePair<string[], Type>([".jxr"],
typeof(WmpProvider)));
AnimatedImage.AnimatedImage.Providers.Add(
new KeyValuePair<string[], Type>([".icns"],

View File

@@ -59,7 +59,7 @@
<PackageReference Include="QuickLook.ImageGlass.WebP" Version="1.4.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.6.0">
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.7.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3351.48">

View File

@@ -16,10 +16,12 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using Microsoft.Web.WebView2.Core;
using QuickLook.Common.Helpers;
using QuickLook.Plugin.HtmlViewer;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -78,7 +80,21 @@ public class MarkdownPanel : WebpagePanel
var content = encoding.GetString(bytes);
var template = ReadString("/md2html.html");
var html = template.Replace("{{content}}", content);
// Support automatic RTL for markdown files
bool isRtl = false;
if (CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft)
{
string isSupportRTL = TranslationHelper.Get("IsSupportRTL",
failsafe: bool.TrueString,
domain: Assembly.GetExecutingAssembly().GetName().Name);
if (bool.TrueString.Equals(isSupportRTL, StringComparison.OrdinalIgnoreCase))
isRtl = true;
}
var html = template.Replace("{{content}}", content)
.Replace("{{rtl}}", isRtl ? "rtl" : "ltr");
return html;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html dir="{{rtl}}">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
@@ -19,6 +19,7 @@
<script src="highlight.js/highlight.min.js"></script>
<script src="js/markdownItAnchor.umd.js"></script>
<script src="js/mermaid.min.js"></script>
</head>
<body class="markdown-body">
<link rel="stylesheet" href="css/github-markdown.css" />
@@ -144,6 +145,61 @@
cursor: ew-resize;
user-select: none;
}
/* RTL support */
html[dir="rtl"] .container {
flex-direction: row-reverse;
}
html[dir="rtl"] #toc {
right: 0;
left: auto;
}
html[dir="rtl"] .table-of-contents a {
border-right: 2px solid transparent;
border-left: none;
margin-right: -1em;
margin-left: 0;
padding-right: calc(1em - 2px);
padding-left: 0;
}
html[dir="rtl"] .table-of-contents .active {
border-right: 2px solid var(--fgColor-accent);
border-left: none;
margin-right: -1em;
margin-left: 0;
padding-right: calc(1em - 2px);
padding-left: 0;
}
/* Mermaid diagram styles */
.mermaid {
display: flex;
justify-content: center;
margin: 1em 0;
}
.mermaid svg {
max-width: 100%;
height: auto;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.mermaid-placeholder {
border: 2px dashed var(--borderColor-default);
background: var(--bgColor-muted);
color: var(--fgColor-muted);
border-radius: 6px;
}
.mermaid-placeholder pre {
background: var(--bgColor-default);
border: 1px solid var(--borderColor-default);
color: var(--fgColor-default);
}
</style>
<textarea id="text-input" style="display: none">{{content}}</textarea>
@@ -160,6 +216,9 @@
typographer: false,
quotes: "“”‘’",
highlight: function (str, lang) {
if (lang === 'mermaid') {
return '<div class="mermaid">' + str + '</div>';
}
if (lang && hljs.getLanguage(lang)) {
try {
return (
@@ -190,6 +249,62 @@
document.getElementById("text-input").value
);
// Initialize Mermaid after markdown rendering
if (window.mermaid) {
// Determine theme based on system preference
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
mermaid.initialize({
startOnLoad: false,
theme: isDarkMode ? 'dark' : 'default',
securityLevel: 'loose', // Allow HTML in diagrams for enhanced features
fontFamily: 'Segoe UI, Helvetica, Arial, sans-serif',
themeVariables: {
primaryColor: isDarkMode ? '#58a6ff' : '#0969da',
primaryTextColor: isDarkMode ? '#f0f6fc' : '#24292f',
primaryBorderColor: isDarkMode ? '#30363d' : '#d0d7de',
lineColor: isDarkMode ? '#484f58' : '#656d76',
sectionBkgColor: isDarkMode ? '#21262d' : '#f6f8fa',
altSectionBkgColor: isDarkMode ? '#161b22' : '#ffffff',
gridColor: isDarkMode ? '#21262d' : '#f6f8fa',
cScale0: isDarkMode ? '#58a6ff' : '#0969da',
cScale1: isDarkMode ? '#a5f3fc' : '#54aeff',
cScale2: isDarkMode ? '#ff7b72' : '#d1242f'
}
});
// Render Mermaid diagrams with error handling
setTimeout(() => {
try {
mermaid.init(undefined, document.querySelectorAll('.mermaid'));
} catch (error) {
console.warn('Mermaid rendering error:', error);
}
}, 100);
// Listen for theme changes and re-render diagrams
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (window.mermaid) {
const newTheme = e.matches ? 'dark' : 'default';
mermaid.initialize({
startOnLoad: false,
theme: newTheme,
securityLevel: 'loose',
fontFamily: 'Segoe UI, Helvetica, Arial, sans-serif'
});
// Re-render all Mermaid diagrams
setTimeout(() => {
try {
mermaid.init(undefined, document.querySelectorAll('.mermaid'));
} catch (error) {
console.warn('Mermaid re-rendering error:', error);
}
}, 100);
}
});
}
/* codes below are adopted from https://codepen.io/jtojnar/full/Juiop */
var ToC = `<nav role='navigation' class='table-of-contents'>
<h2>Contents</h2>
@@ -337,6 +452,21 @@
if (e.code === "Space") {
e.preventDefault();
}
// Support keyboard shortcuts for RTL and LTR text direction
// RTL: Ctrl + RShift
// LTR: Ctrl + LShift
if ((e.ctrlKey || e.metaKey)) {
if (e.shiftKey && e.location === KeyboardEvent.DOM_KEY_LOCATION_RIGHT) {
// Right Shift + Ctrl: RTL
document.documentElement.setAttribute('dir', 'rtl');
e.preventDefault();
} else if (e.shiftKey && e.location === KeyboardEvent.DOM_KEY_LOCATION_LEFT) {
// Left Shift + Ctrl: LTR
document.documentElement.setAttribute('dir', 'ltr');
e.preventDefault();
}
}
});
});
</script>

View File

@@ -418,9 +418,9 @@
<MW_BrowseFolder>Przeglądaj {0}</MW_BrowseFolder>
<MW_StayTop>Przypnij do ekranu</MW_StayTop>
<MW_PreventClosing>Zablokuj zamykanie</MW_PreventClosing>
<MW_BrowseFolder>Przeglądaj {0}</MW_BrowseFolder>
<MW_Open>Otwórz {0}</MW_Open>
<MW_OpenWith>Otwórz w {0}</MW_OpenWith>
<MW_OpenWithMenu>Otwórz za pomocą...</MW_OpenWithMenu>
<MW_Run>Uruchom {0}</MW_Run>
<MW_Share>Udostępnij</MW_Share>
<MW_Reload>Przeładuj</MW_Reload>
@@ -441,6 +441,7 @@
<InfoPanel_File>{0} plik</InfoPanel_File>
<InfoPanel_Files>{0} plików</InfoPanel_Files>
<InfoPanel_FolderAndFile>({0} i {1})</InfoPanel_FolderAndFile>
<InfoPanel_CantPreventClosing>Anulowanie dla funkcji blokady zamykania okna nie jest wspierane</InfoPanel_CantPreventClosing>
</pl>
<pt-BR>
<UI_FontFamily>Segoe UI</UI_FontFamily>