From 8e817e490ce0294d0aba522f5820e25e1c08d2b4 Mon Sep 17 00:00:00 2001 From: msynk Date: Mon, 13 Jul 2026 13:16:04 +0330 Subject: [PATCH 01/14] fix slow build process of BlazorUI projects #12649 --- .../Bit.BlazorUI.Assets.csproj | 5 +- .../Bit.BlazorUI.Extras.csproj | 8 +- .../Bit.BlazorUI.Icons.csproj | 5 +- src/BlazorUI/Bit.BlazorUI.slnx | 1 + src/BlazorUI/Bit.BlazorUI/Bit.BlazorUI.csproj | 8 +- src/BlazorUI/Demo/.gitignore | 4 +- .../Bit.BlazorUI.Demo.Server.csproj | 37 ++++++- .../Components/App.razor | 37 +++++-- .../IServiceCollectionExtensions.cs | 9 +- .../Demo/Bit.BlazorUI.Demo.Server/Program.cs | 4 +- .../Services/ScssCompilerService.cs | 97 +++++++++++++++++++ .../Startup/Middlewares.cs | 4 + .../Bit.BlazorUI.Demo.Client.Core.csproj | 11 ++- .../Services/AppRenderMode.cs | 1 + ...Bit.BlazorUI.Demo.Client.Web.Shared.csproj | 39 ++++++++ .../Components/AppBswupProgressBar.razor | 0 .../IServiceCollectionExtensions.cs | 6 +- .../Services/WebDeviceCoordinator.cs | 2 +- .../Services/WebExceptionHandler.cs | 2 +- .../Bit.BlazorUI.Demo.Client.Web.csproj | 7 +- 20 files changed, 254 insertions(+), 33 deletions(-) create mode 100644 src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs create mode 100644 src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Bit.BlazorUI.Demo.Client.Web.Shared.csproj rename src/BlazorUI/Demo/Client/{Bit.BlazorUI.Demo.Client.Web => Bit.BlazorUI.Demo.Client.Web.Shared}/Components/AppBswupProgressBar.razor (100%) rename src/BlazorUI/Demo/Client/{Bit.BlazorUI.Demo.Client.Web => Bit.BlazorUI.Demo.Client.Web.Shared}/Extensions/IServiceCollectionExtensions.cs (74%) rename src/BlazorUI/Demo/Client/{Bit.BlazorUI.Demo.Client.Web => Bit.BlazorUI.Demo.Client.Web.Shared}/Services/WebDeviceCoordinator.cs (51%) rename src/BlazorUI/Demo/Client/{Bit.BlazorUI.Demo.Client.Web => Bit.BlazorUI.Demo.Client.Web.Shared}/Services/WebExceptionHandler.cs (84%) diff --git a/src/BlazorUI/Bit.BlazorUI.Assets/Bit.BlazorUI.Assets.csproj b/src/BlazorUI/Bit.BlazorUI.Assets/Bit.BlazorUI.Assets.csproj index e7bb3e709a..45f594bd74 100644 --- a/src/BlazorUI/Bit.BlazorUI.Assets/Bit.BlazorUI.Assets.csproj +++ b/src/BlazorUI/Bit.BlazorUI.Assets/Bit.BlazorUI.Assets.csproj @@ -31,7 +31,10 @@ - + + diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj b/src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj index 8b21c9d076..af1735c49a 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj @@ -29,7 +29,13 @@ - + + + diff --git a/src/BlazorUI/Bit.BlazorUI.Icons/Bit.BlazorUI.Icons.csproj b/src/BlazorUI/Bit.BlazorUI.Icons/Bit.BlazorUI.Icons.csproj index fc4510a6ef..b729c8763d 100644 --- a/src/BlazorUI/Bit.BlazorUI.Icons/Bit.BlazorUI.Icons.csproj +++ b/src/BlazorUI/Bit.BlazorUI.Icons/Bit.BlazorUI.Icons.csproj @@ -17,7 +17,10 @@ - + + diff --git a/src/BlazorUI/Bit.BlazorUI.slnx b/src/BlazorUI/Bit.BlazorUI.slnx index ece4313cf9..2e34666267 100644 --- a/src/BlazorUI/Bit.BlazorUI.slnx +++ b/src/BlazorUI/Bit.BlazorUI.slnx @@ -25,6 +25,7 @@ + diff --git a/src/BlazorUI/Bit.BlazorUI/Bit.BlazorUI.csproj b/src/BlazorUI/Bit.BlazorUI/Bit.BlazorUI.csproj index b3a909f760..f54446c912 100644 --- a/src/BlazorUI/Bit.BlazorUI/Bit.BlazorUI.csproj +++ b/src/BlazorUI/Bit.BlazorUI/Bit.BlazorUI.csproj @@ -30,7 +30,13 @@ - + + + diff --git a/src/BlazorUI/Demo/.gitignore b/src/BlazorUI/Demo/.gitignore index 13a3d97128..236420cb2a 100644 --- a/src/BlazorUI/Demo/.gitignore +++ b/src/BlazorUI/Demo/.gitignore @@ -231,4 +231,6 @@ custom.aprof /Client/Bit.BlazorUI.Demo.Client.Core/wwwroot/scripts/app*.js -/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.DemoDb.db* \ No newline at end of file +/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.DemoDb.db* +# Bit.BlazorES2019 scripts copied into the source wwwroot at build time (see Bit.BlazorUI.Demo.Server.csproj) +Bit.BlazorUI.Demo.Server/wwwroot/_framework/ diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj index df3c161495..21ded4e25c 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj @@ -2,11 +2,24 @@ net10.0 + fcf350f4-eef5-430b-a5cb-144713a68932 + + + true + false + $(DefineConstants);INCLUDE_WASM + + + true true true - fcf350f4-eef5-430b-a5cb-144713a68932 @@ -21,6 +34,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + @@ -45,8 +60,26 @@ - + + + + + + + + + + + <_BitBlazorES2019Scripts Include="$(NuGetPackageRoot)bit.blazores2019\10.5.0-pre-08\contentFiles\any\net10.0\wwwroot\_framework\*.js" /> + + + diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor index 82c796a507..7b4244d00b 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor @@ -1,9 +1,16 @@ @using Bit.BlazorUI.Demo.Client.Core.Shared -@using Bit.BlazorUI.Demo.Client.Web.Components +@using Bit.BlazorUI.Demo.Client.Web.Shared.Components @{ +#if INCLUDE_WASM + var includeWasm = true; +#else + var includeWasm = false; +#endif var noPrerender = HttpContext.Request.Query["no-prerender"].Count > 0; - var renderMode = noPrerender ? AppRenderMode.NoPrerenderBlazorWebAssembly : AppRenderMode.Current; + var renderMode = noPrerender + ? (includeWasm ? AppRenderMode.NoPrerenderBlazorWebAssembly : AppRenderMode.NoPrerenderBlazorServer) + : AppRenderMode.Current; } @@ -71,9 +78,13 @@ - - - + @if (includeWasm) + { + @* These files live in the Client.Web project's wwwroot, so they only exist when the WASM client is part of the build. *@ + + + } + @@ -94,10 +105,18 @@ @if (HttpContext.Request.IsLightHouseRequest() is false) { - - - - + @if (includeWasm) + { + + + + + } + else + { + @* No WASM client in this build (Debug renders in Blazor Server mode), so boot blazor directly without Bswup/service-worker. *@ + + } diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Extensions/IServiceCollectionExtensions.cs b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Extensions/IServiceCollectionExtensions.cs index de2431e089..79f037c397 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Extensions/IServiceCollectionExtensions.cs +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Extensions/IServiceCollectionExtensions.cs @@ -23,9 +23,12 @@ public static void AddBlazor(this IServiceCollection services, IConfiguration co }; }); - services.AddRazorComponents() - .AddInteractiveServerComponents() - .AddInteractiveWebAssemblyComponents(); + var razorComponentsBuilder = services.AddRazorComponents() + .AddInteractiveServerComponents(); + +#if INCLUDE_WASM + razorComponentsBuilder.AddInteractiveWebAssemblyComponents(); +#endif services.AddClientWebServices(); } diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Program.cs b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Program.cs index 6e567f2935..765ca78079 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Program.cs +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Program.cs @@ -21,4 +21,6 @@ Bit.BlazorUI.Demo.Server.Startup.Middlewares.Use(app, builder.Environment, builder.Configuration); -app.Run(); +await Task.WhenAll( + app.RunAsync(), + Bit.BlazorUI.Demo.Server.Services.ScssCompilerService.WatchScssFiles(app) /* Development-only, no-op otherwise */); diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs new file mode 100644 index 0000000000..caf1329381 --- /dev/null +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs @@ -0,0 +1,97 @@ +using System.Diagnostics; +using Meziantou.Framework.Win32; + +namespace Bit.BlazorUI.Demo.Server.Services; + +/// +/// Compiles SCSS files into CSS in real-time during development, no extensions required. +/// Combined with the Watch items of the client/library csproj files, this lets dotnet watch +/// hot-apply style changes to the browser without a rebuild. +/// (Mirrors the ScssCompilerService of the bit Boilerplate template.) +/// +public class ScssCompilerService +{ + internal static async Task WatchScssFiles(WebApplication app) + { + if (app.Environment.IsDevelopment() is false) + return; + + if (OperatingSystem.IsWindows() is false) + return; // The JobObject used below (to terminate sass alongside the server) is Windows-only. + + var clientCorePath = Path.GetFullPath(Path.Combine(app.Environment.ContentRootPath, "../Client/Bit.BlazorUI.Demo.Client.Core")); + + var logger = app.Services.GetRequiredService>(); + + var toolPath = Path.Combine(clientCorePath, "node_modules/.bin/sass.cmd"); + + if (File.Exists(toolPath) is false) + { + logger.LogWarning("{SassTool} not found. Run a build of Bit.BlazorUI.Demo.Client.Core once (npm install) to enable real-time scss compilation.", toolPath); + return; + } + + // The sass process operates from the Client.Core directory. Folder:Folder pairs compile the + // isolated .razor.scss files in place; file:file pairs compile the global style bundles. + // The BlazorUI library styles are included as well, so editing a component's scss + // (e.g. BitButton.scss) instantly refreshes _content/Bit.BlazorUI/styles/*.css. + var sassPathsToWatch = new List + { + "Pages:Pages", "Components:Components", "Shared:Shared", + "Styles/app.scss:wwwroot/styles/app.css", + "../../../Bit.BlazorUI/Styles/bit.blazorui.scss:../../../Bit.BlazorUI/wwwroot/styles/bit.blazorui.css", + "../../../Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent.scss:../../../Bit.BlazorUI/wwwroot/styles/bit.blazorui.fluent.css", + "../../../Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent-dark.scss:../../../Bit.BlazorUI/wwwroot/styles/bit.blazorui.fluent-dark.css", + "../../../Bit.BlazorUI/Styles/Fluent/bit.blazorui.fluent-light.scss:../../../Bit.BlazorUI/wwwroot/styles/bit.blazorui.fluent-light.css", + "../../../Bit.BlazorUI.Extras/Styles/bit.blazorui.extras.scss:../../../Bit.BlazorUI.Extras/wwwroot/styles/bit.blazorui.extras.css", + "../../../Bit.BlazorUI.Icons/Styles/bit.blazorui.icons.scss:../../../Bit.BlazorUI.Icons/wwwroot/styles/bit.blazorui.icons.css", + "../../../Bit.BlazorUI.Assets/Styles/bit.blazorui.assets.scss:../../../Bit.BlazorUI.Assets/wwwroot/styles/bit.blazorui.assets.css", + }; + + var command = $"{string.Join(" ", sassPathsToWatch)} --style compressed --silence-deprecation=import --update --watch --color"; + + // Create a job object to ensure the sass process terminates with the server process. + using var job = new JobObject(); + job.SetLimits(new JobObjectLimits + { + Flags = JobObjectLimitFlags.KillOnJobClose + }); + + using var watchScssFilesProcess = new Process + { + StartInfo = new ProcessStartInfo + { + WorkingDirectory = clientCorePath, + FileName = toolPath, + Arguments = command, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + + watchScssFilesProcess.OutputDataReceived += (_, e) => { if (string.IsNullOrEmpty(e.Data) is false) logger.LogInformation(e.Data); }; + watchScssFilesProcess.ErrorDataReceived += (_, e) => { if (string.IsNullOrEmpty(e.Data) is false) logger.LogError(e.Data); }; + + logger.LogInformation("Running {toolPath} for {ProjectDirectory}", toolPath, clientCorePath); + if (watchScssFilesProcess.Start() is false) + { + logger.LogError("Failed to start {toolPath} for {ProjectDirectory}", toolPath, clientCorePath); + return; + } + + try + { + job.AssignProcess(watchScssFilesProcess); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to assign process to job for {toolPath}", toolPath); + } + + watchScssFilesProcess.BeginOutputReadLine(); + watchScssFilesProcess.BeginErrorReadLine(); + + await app.WaitForShutdownAsync(); + } +} diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Startup/Middlewares.cs b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Startup/Middlewares.cs index 5b4631a3f2..a13a60fe72 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Startup/Middlewares.cs +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Startup/Middlewares.cs @@ -18,7 +18,9 @@ public static void Use(WebApplication app, IWebHostEnvironment env, IConfigurati if (env.IsDevelopment()) { +#if INCLUDE_WASM app.UseWebAssemblyDebugging(); +#endif } else { @@ -93,7 +95,9 @@ public static void Use(WebApplication app, IWebHostEnvironment env, IConfigurati // Handle the rest of requests with blazor app.MapRazorComponents() .AddInteractiveServerRenderMode() +#if INCLUDE_WASM .AddInteractiveWebAssemblyRenderMode() +#endif .AddAdditionalAssemblies(AssemblyLoadContext.Default.Assemblies.Where(asm => asm.GetName().Name?.Contains("Bit.BlazorUI.Demo") is true).Except([Assembly.GetExecutingAssembly()]).ToArray()); } diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj index 2f35d48ce4..ffc89ef75f 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj @@ -39,7 +39,14 @@ - + + + + @@ -89,7 +96,7 @@ - + diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AppRenderMode.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AppRenderMode.cs index 8f6ad26ba7..90d402b4f7 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AppRenderMode.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AppRenderMode.cs @@ -13,6 +13,7 @@ public static class AppRenderMode private static IComponentRenderMode BlazorWebAssembly => new InteractiveWebAssemblyRenderMode(PrerenderEnabled); private static IComponentRenderMode BlazorServer => new InteractiveServerRenderMode(PrerenderEnabled); public static IComponentRenderMode NoPrerenderBlazorWebAssembly => new InteractiveWebAssemblyRenderMode(prerender: false); + public static IComponentRenderMode NoPrerenderBlazorServer => new InteractiveServerRenderMode(prerender: false); public static IComponentRenderMode Current => BuildConfiguration.IsDebug() ? BlazorServer /*For better development experience*/ diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Bit.BlazorUI.Demo.Client.Web.Shared.csproj b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Bit.BlazorUI.Demo.Client.Web.Shared.csproj new file mode 100644 index 0000000000..6c3e038796 --- /dev/null +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Bit.BlazorUI.Demo.Client.Web.Shared.csproj @@ -0,0 +1,39 @@ + + + + + + net10.0 + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Components/AppBswupProgressBar.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Components/AppBswupProgressBar.razor similarity index 100% rename from src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Components/AppBswupProgressBar.razor rename to src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Components/AppBswupProgressBar.razor diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Extensions/IServiceCollectionExtensions.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Extensions/IServiceCollectionExtensions.cs similarity index 74% rename from src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Extensions/IServiceCollectionExtensions.cs rename to src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Extensions/IServiceCollectionExtensions.cs index fe358052fc..646f8f49db 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Extensions/IServiceCollectionExtensions.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Extensions/IServiceCollectionExtensions.cs @@ -1,12 +1,12 @@ -using Bit.BlazorUI.Demo.Client.Web.Services; +using Bit.BlazorUI.Demo.Client.Web.Shared.Services; namespace Microsoft.Extensions.DependencyInjection; -public static class IServiceCollectionExtensions +public static class IClientWebServiceCollectionExtensions { public static IServiceCollection AddClientWebServices(this IServiceCollection services) { - // Services being registered here can get injected in web (blazor web assembly & blazor server) + // Services being registered here can get injected in web (blazor web assembly & blazor server). services.AddTransient(); services.AddTransient(); diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Services/WebDeviceCoordinator.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Services/WebDeviceCoordinator.cs similarity index 51% rename from src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Services/WebDeviceCoordinator.cs rename to src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Services/WebDeviceCoordinator.cs index ca9dae81f4..3e4f027a1a 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Services/WebDeviceCoordinator.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Services/WebDeviceCoordinator.cs @@ -1,4 +1,4 @@ -namespace Bit.BlazorUI.Demo.Client.Web.Services; +namespace Bit.BlazorUI.Demo.Client.Web.Shared.Services; public class WebDeviceCoordinator : IBitDeviceCoordinator { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Services/WebExceptionHandler.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Services/WebExceptionHandler.cs similarity index 84% rename from src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Services/WebExceptionHandler.cs rename to src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Services/WebExceptionHandler.cs index 3d9becc16b..6ae6860d9e 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Services/WebExceptionHandler.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Services/WebExceptionHandler.cs @@ -1,4 +1,4 @@ -namespace Bit.BlazorUI.Demo.Client.Web.Services; +namespace Bit.BlazorUI.Demo.Client.Web.Shared.Services; public partial class WebExceptionHandler : ExceptionHandlerBase { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Bit.BlazorUI.Demo.Client.Web.csproj b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Bit.BlazorUI.Demo.Client.Web.csproj index cd5794d4a6..f6e7f18204 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Bit.BlazorUI.Demo.Client.Web.csproj +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Bit.BlazorUI.Demo.Client.Web.csproj @@ -23,12 +23,6 @@ - - - - - - all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -41,6 +35,7 @@ + From cc29215a01e631c8b0817cbba5a1848de8891130 Mon Sep 17 00:00:00 2001 From: msynk Date: Tue, 14 Jul 2026 11:30:45 +0330 Subject: [PATCH 02/14] simplify build process --- src/BlazorUI/Demo/.gitignore | 4 +- .../Bit.BlazorUI.Demo.Server.csproj | 19 ---- .../Components/App.razor | 20 +++-- .../Services/ScssCompilerService.cs | 86 +++++++++++-------- .../Services/AppRenderMode.cs | 6 +- 5 files changed, 70 insertions(+), 65 deletions(-) diff --git a/src/BlazorUI/Demo/.gitignore b/src/BlazorUI/Demo/.gitignore index 236420cb2a..13a3d97128 100644 --- a/src/BlazorUI/Demo/.gitignore +++ b/src/BlazorUI/Demo/.gitignore @@ -231,6 +231,4 @@ custom.aprof /Client/Bit.BlazorUI.Demo.Client.Core/wwwroot/scripts/app*.js -/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.DemoDb.db* -# Bit.BlazorES2019 scripts copied into the source wwwroot at build time (see Bit.BlazorUI.Demo.Server.csproj) -Bit.BlazorUI.Demo.Server/wwwroot/_framework/ +/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.DemoDb.db* \ No newline at end of file diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj index 21ded4e25c..4a37a8723e 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj @@ -34,8 +34,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - @@ -64,23 +62,6 @@ - - - - - - - <_BitBlazorES2019Scripts Include="$(NuGetPackageRoot)bit.blazores2019\10.5.0-pre-08\contentFiles\any\net10.0\wwwroot\_framework\*.js" /> - - - - - diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor index 7b4244d00b..003e3764a5 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor @@ -6,11 +6,20 @@ var includeWasm = true; #else var includeWasm = false; +#endif +#if DEBUG + // While debugging, boot from the framework-served blazor.web.js instead of the ES2019-transpiled + // bit.blazor.web.es2019.js variant, which is only needed to support legacy (non-ES2019+) browsers. + var blazorWebScript = "_framework/blazor.web.js"; +#else + var blazorWebScript = "_framework/bit.blazor.web.es2019.js"; #endif var noPrerender = HttpContext.Request.Query["no-prerender"].Count > 0; + // Without the WASM client in the build, WebAssembly render modes can't boot, so fall back to Blazor Server + // even where AppRenderMode.Current would pick WebAssembly (e.g. Release with IncludeWasm=false). var renderMode = noPrerender ? (includeWasm ? AppRenderMode.NoPrerenderBlazorWebAssembly : AppRenderMode.NoPrerenderBlazorServer) - : AppRenderMode.Current; + : (includeWasm ? AppRenderMode.Current : AppRenderMode.BlazorServer); } @@ -107,15 +116,16 @@ { @if (includeWasm) { - - + + } else { - @* No WASM client in this build (Debug renders in Blazor Server mode), so boot blazor directly without Bswup/service-worker. *@ - + @* No WASM client in this build (Debug renders in Blazor Server mode), so boot blazor directly without Bswup/service-worker. + Uses the framework-served blazor.web.js (always available) rather than the ES2019 variant, which only ships in the WASM build. *@ + } diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs index caf1329381..3abdc946cd 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using Meziantou.Framework.Win32; namespace Bit.BlazorUI.Demo.Server.Services; @@ -16,14 +15,12 @@ internal static async Task WatchScssFiles(WebApplication app) if (app.Environment.IsDevelopment() is false) return; - if (OperatingSystem.IsWindows() is false) - return; // The JobObject used below (to terminate sass alongside the server) is Windows-only. + var logger = app.Services.GetRequiredService>(); var clientCorePath = Path.GetFullPath(Path.Combine(app.Environment.ContentRootPath, "../Client/Bit.BlazorUI.Demo.Client.Core")); - var logger = app.Services.GetRequiredService>(); - - var toolPath = Path.Combine(clientCorePath, "node_modules/.bin/sass.cmd"); + // The npm-installed dart-sass shim: sass.cmd on Windows, the sass shell script elsewhere. + var toolPath = Path.Combine(clientCorePath, OperatingSystem.IsWindows() ? "node_modules/.bin/sass.cmd" : "node_modules/.bin/sass"); if (File.Exists(toolPath) is false) { @@ -50,48 +47,67 @@ internal static async Task WatchScssFiles(WebApplication app) var command = $"{string.Join(" ", sassPathsToWatch)} --style compressed --silence-deprecation=import --update --watch --color"; - // Create a job object to ensure the sass process terminates with the server process. - using var job = new JobObject(); - job.SetLimits(new JobObjectLimits - { - Flags = JobObjectLimitFlags.KillOnJobClose - }); - - using var watchScssFilesProcess = new Process + // Scss watching is best-effort development tooling: any failure below must not fault this task, + // which would otherwise surface as an unobserved error in Program.cs's Task.WhenAll at shutdown. + Process? watchScssFilesProcess = null; + try { - StartInfo = new ProcessStartInfo + watchScssFilesProcess = new Process + { + StartInfo = new ProcessStartInfo + { + WorkingDirectory = clientCorePath, + FileName = toolPath, + Arguments = command, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + + watchScssFilesProcess.OutputDataReceived += (_, e) => { if (string.IsNullOrEmpty(e.Data) is false) logger.LogInformation(e.Data); }; + watchScssFilesProcess.ErrorDataReceived += (_, e) => { if (string.IsNullOrEmpty(e.Data) is false) logger.LogError(e.Data); }; + + logger.LogInformation("Running {toolPath} for {ProjectDirectory}", toolPath, clientCorePath); + if (watchScssFilesProcess.Start() is false) { - WorkingDirectory = clientCorePath, - FileName = toolPath, - Arguments = command, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true + logger.LogError("Failed to start {toolPath} for {ProjectDirectory}", toolPath, clientCorePath); + return; } - }; - watchScssFilesProcess.OutputDataReceived += (_, e) => { if (string.IsNullOrEmpty(e.Data) is false) logger.LogInformation(e.Data); }; - watchScssFilesProcess.ErrorDataReceived += (_, e) => { if (string.IsNullOrEmpty(e.Data) is false) logger.LogError(e.Data); }; + watchScssFilesProcess.BeginOutputReadLine(); + watchScssFilesProcess.BeginErrorReadLine(); - logger.LogInformation("Running {toolPath} for {ProjectDirectory}", toolPath, clientCorePath); - if (watchScssFilesProcess.Start() is false) + // dart-sass --watch runs until killed, so tie its lifetime to the server: kill the whole + // process tree when the host starts shutting down (dotnet watch restart, Ctrl+C) so no + // orphaned sass watcher survives. The finally is a backstop for exceptions before shutdown. + app.Lifetime.ApplicationStopping.Register(() => KillSassProcess(watchScssFilesProcess, logger)); + + await app.WaitForShutdownAsync(); + } + catch (Exception ex) { - logger.LogError("Failed to start {toolPath} for {ProjectDirectory}", toolPath, clientCorePath); - return; + logger.LogError(ex, "Failed to watch scss files with {toolPath}", toolPath); } + finally + { + KillSassProcess(watchScssFilesProcess, logger); + watchScssFilesProcess?.Dispose(); + } + } + private static void KillSassProcess(Process? process, ILogger logger) + { try { - job.AssignProcess(watchScssFilesProcess); + if (process is not null && process.HasExited is false) + { + process.Kill(entireProcessTree: true); + } } catch (Exception ex) { - logger.LogError(ex, "Failed to assign process to job for {toolPath}", toolPath); + logger.LogWarning(ex, "Failed to terminate the scss watch process."); } - - watchScssFilesProcess.BeginOutputReadLine(); - watchScssFilesProcess.BeginErrorReadLine(); - - await app.WaitForShutdownAsync(); } } diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AppRenderMode.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AppRenderMode.cs index 90d402b4f7..ce7e5cc9e6 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AppRenderMode.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AppRenderMode.cs @@ -9,9 +9,9 @@ public static class AppRenderMode // .NET 10 resolves the earlier prerendering issues, so this is enabled to serve fully-formed HTML. public static readonly bool PrerenderEnabled = true; - private static IComponentRenderMode Auto => new InteractiveAutoRenderMode(PrerenderEnabled); - private static IComponentRenderMode BlazorWebAssembly => new InteractiveWebAssemblyRenderMode(PrerenderEnabled); - private static IComponentRenderMode BlazorServer => new InteractiveServerRenderMode(PrerenderEnabled); + public static IComponentRenderMode Auto => new InteractiveAutoRenderMode(PrerenderEnabled); + public static IComponentRenderMode BlazorWebAssembly => new InteractiveWebAssemblyRenderMode(PrerenderEnabled); + public static IComponentRenderMode BlazorServer => new InteractiveServerRenderMode(PrerenderEnabled); public static IComponentRenderMode NoPrerenderBlazorWebAssembly => new InteractiveWebAssemblyRenderMode(prerender: false); public static IComponentRenderMode NoPrerenderBlazorServer => new InteractiveServerRenderMode(prerender: false); From cf7f81a364d0414d4e86e22870cc168ce15d771e Mon Sep 17 00:00:00 2001 From: msynk Date: Tue, 14 Jul 2026 12:07:44 +0330 Subject: [PATCH 03/14] resolve review comments --- .../Components/AppBswupProgressBar.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Components/AppBswupProgressBar.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Components/AppBswupProgressBar.razor index 1b46bed028..ae9127dbae 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Components/AppBswupProgressBar.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Components/AppBswupProgressBar.razor @@ -63,8 +63,8 @@
- -
+ +
From 76604d48dbe2b7cb6089f02d39c50085c5aea65d Mon Sep 17 00:00:00 2001 From: msynk Date: Tue, 14 Jul 2026 14:09:28 +0330 Subject: [PATCH 04/14] fix local findings and formatting --- .../Components/PdfViewer/BitPdfRenderMode.cs | 2 +- .../PdfViewer/BitPdfViewer.razor.cs | 8 ++--- .../Components/PdfViewer/BitPdfViewer.scss | 2 +- .../Components/PdfViewer/BitPdfViewer.ts | 8 ++--- .../PdfViewer/Core/BitPdfDocument.cs | 2 +- .../Components/PdfViewer/Core/BitPdfString.cs | 2 +- .../PdfViewer/Core/BitPdfStructTree.cs | 2 +- .../BitPdfUnsupportedEncryptionException.cs | 2 +- .../Components/PdfViewer/Core/BitPdfXRef.cs | 2 +- .../Core/Content/BitPdfTextExtractor.cs | 2 +- .../Core/Filters/BitPdfJpegDecoder.cs | 4 +-- .../Core/Fonts/BitPdfCffFontParser.cs | 2 +- .../PdfViewer/Core/Fonts/BitPdfFont.cs | 4 +-- .../Core/Fonts/BitPdfSfntGlyphMapper.cs | 2 +- .../Core/Fonts/BitPdfTrueTypeSanitizer.cs | 4 +-- .../PdfViewer/Core/Fonts/BitPdfType1Font.cs | 2 +- .../Core/Render/BitPdfCanvasRenderResult.cs | 2 +- .../Core/Render/BitPdfHtmlRenderer.cs | 30 +++++++++---------- .../PdfViewer/Core/Render/BitPdfImage.cs | 4 +-- .../Bit.BlazorUI.Demo.Server.csproj | 4 +-- .../Properties/launchSettings.json | 14 ++------- .../Services/ScssCompilerService.cs | 5 ++-- .../Bit.BlazorUI.Demo.Client.Core.csproj | 2 +- 23 files changed, 52 insertions(+), 59 deletions(-) diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfRenderMode.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfRenderMode.cs index af4f34f6dd..0d1eb4aa50 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfRenderMode.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfRenderMode.cs @@ -15,7 +15,7 @@ public enum BitPdfRenderMode /// a display list produced by the C# engine (the pdf.js model). Far fewer DOM /// nodes; selection, search and links still work through the DOM text layer, /// and zoom changes re-rasterize the canvases so text stays crisp. Requires - /// JavaScript — a prerendered page shows a blank canvas until the client + /// JavaScript - a prerendered page shows a blank canvas until the client /// loads. /// Canvas, diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfViewer.razor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfViewer.razor.cs index c2e63d6180..4f3b0ff07b 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfViewer.razor.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfViewer.razor.cs @@ -90,7 +90,7 @@ public partial class BitPdfViewer : BitComponentBase /// /// How painted text is emitted. merges - /// same-line, same-style runs into one span per visual line — far fewer DOM + /// same-line, same-style runs into one span per visual line - far fewer DOM /// nodes on per-glyph PDFs, with small intra-line position drift (explicit /// kerning between runs is approximated). Rotated text always stays exact. /// Default is . @@ -370,7 +370,7 @@ public void EnsureThumbsRendered(int[] pageNumbers) if (changed) { // Evict around the range just requested (what is visible in the - // sidebar), not the current page — scrolling the sidebar leaves the + // sidebar), not the current page - scrolling the sidebar leaves the // current page put, so centering on it would blank the very // thumbnails the user just scrolled to. EvictDistantThumbs(lo, hi); @@ -784,7 +784,7 @@ private void EvictDistantPages() private MarkupString RenderThumbContent(int index) { // Canvas mode: page fragments are canvas placeholders whose pixels are - // painted by JS into the MAIN surface only — a reused fragment would show + // painted by JS into the MAIN surface only - a reused fragment would show // a blank thumbnail. Render sidebar thumbnails as self-contained HTML // (Compact text keeps the tiny fragments light). if (RenderMode == BitPdfRenderMode.Canvas) @@ -1002,7 +1002,7 @@ private async Task RunSearchAsync() // Search a per-page extracted-text index (built lazily) rather than the // rendered DOM, so we only render the pages that actually contain matches - // — a 500-page document with matches on 3 pages renders 3, not 500. + // - a 500-page document with matches on 3 pages renders 3, not 500. _pageText ??= new string?[_document.PageCount]; string needle = _searchQuery; bool rendered = false; diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfViewer.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfViewer.scss index 859046de9b..060016a5df 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfViewer.scss +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfViewer.scss @@ -343,7 +343,7 @@ pointer-events: auto; } -/* Selection spans (data-bit-pdv-sel) hold the real Unicode but paint nothing — keep +/* Selection spans (data-bit-pdv-sel) hold the real Unicode but paint nothing - keep them transparent even while selected so only the highlight box shows and the painted glyphs beneath stay visible through it. Without this the browser paints the selected substitute glyphs opaque, stacking a wrong-font copy over the diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfViewer.ts b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfViewer.ts index c040fc54d9..5f5c8fedff 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfViewer.ts +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/BitPdfViewer.ts @@ -23,7 +23,7 @@ namespace BitBlazorUI { } } - // Scrolls `target` into view by scrolling ONLY `container` — unlike + // Scrolls `target` into view by scrolling ONLY `container` - unlike // scrollIntoView, which also scrolls every scrollable ancestor (including // the hosting page, yanking the whole document around when the viewer is // embedded mid-page). @@ -393,7 +393,7 @@ namespace BitBlazorUI { clone.style.transformOrigin = "top left"; // A cloned loses its pixels: substitute a snapshot image so // canvas-mode pages print their painted content. Pages with a cached - // display list re-rasterize at print resolution — the screen-resolution + // display list re-rasterize at print resolution - the screen-resolution // bitmap is sized for on-screen zoom and prints blurry. const srcCanvases = el.querySelectorAll("canvas[data-bit-pdv-canvas]"); const dstCanvases = clone.querySelectorAll("canvas[data-bit-pdv-canvas]"); @@ -540,7 +540,7 @@ namespace BitBlazorUI { return; } - // @font-face faces load lazily — only when DOM text uses them — and canvas + // @font-face faces load lazily - only when DOM text uses them - and canvas // fillText never waits for (or reliably triggers) a load: it draws with the // fallback immediately. In canvas mode no DOM references the embedded // families, so without an explicit load the FIRST paint renders tofu until @@ -752,7 +752,7 @@ namespace BitBlazorUI { const ranges: Range[] = []; container.querySelectorAll("[data-page]").forEach((page) => { - // Search only the coalesced selection layer ([data-bit-pdv-sel]) — it + // Search only the coalesced selection layer ([data-bit-pdv-sel]) - it // holds the real Unicode in reading order. The painted layer beneath is // presentational (real glyphs or Private-Use codepoints) and would // otherwise double-count. diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfDocument.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfDocument.cs index c72ebce11c..9e5d9294fe 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfDocument.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfDocument.cs @@ -209,7 +209,7 @@ private void Traverse(BitPdfDict node, InheritedAttributes inherited, } // A node explicitly typed /Pages but with no usable /Kids is a damaged - // interior node — skip it rather than materializing a phantom page. + // interior node - skip it rather than materializing a phantom page. if (BitPdfPrimitives.IsName(typeObj, "Pages")) { _xref.Warnings.Add("Interior /Pages node has no valid /Kids; skipping."); diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfString.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfString.cs index 0a5e2194ef..c129eb596e 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfString.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfString.cs @@ -58,7 +58,7 @@ private static char[] BuildPdfDocEncoding() t[0x1C] = '˝'; t[0x1D] = '˛'; t[0x1E] = '˚'; t[0x1F] = '˜'; // 0x80–0x9F: punctuation, ligatures, accented capitals. t[0x80] = '•'; t[0x81] = '†'; t[0x82] = '‡'; t[0x83] = '…'; - t[0x84] = '—'; t[0x85] = '–'; t[0x86] = 'ƒ'; t[0x87] = '⁄'; + t[0x84] = '-'; t[0x85] = '–'; t[0x86] = 'ƒ'; t[0x87] = '⁄'; t[0x88] = '‹'; t[0x89] = '›'; t[0x8A] = '−'; t[0x8B] = '‰'; t[0x8C] = '„'; t[0x8D] = '“'; t[0x8E] = '”'; t[0x8F] = '‘'; t[0x90] = '’'; t[0x91] = '‚'; t[0x92] = '™'; t[0x93] = 'fi'; diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfStructTree.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfStructTree.cs index 372536492f..8ae7d973d1 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfStructTree.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfStructTree.cs @@ -42,7 +42,7 @@ private static List ReadKids(IBitPdfXRef xref, object? kids private static void AddNode(IBitPdfXRef xref, object? item, List result, HashSet visited, int depth) { - // Marked-content leaves are plain integers or MCR/OBJR dicts; skip them — + // Marked-content leaves are plain integers or MCR/OBJR dicts; skip them - // the structure tree API exposes the element hierarchy, not content refs. if (item is double) { diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfUnsupportedEncryptionException.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfUnsupportedEncryptionException.cs index 10e3eb6a5d..2cc5b259ac 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfUnsupportedEncryptionException.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfUnsupportedEncryptionException.cs @@ -2,7 +2,7 @@ namespace Bit.BlazorUI; /// /// Thrown when a document declares an /Encrypt dictionary that this -/// library cannot handle — an unsupported security handler or revision, or a +/// library cannot handle - an unsupported security handler or revision, or a /// cryptographic primitive that is unavailable on the current platform (for /// example MD5/AES in the browser WebAssembly sandbox). Distinct from /// so callers can surface a clear "this diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfXRef.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfXRef.cs index 05e14036fe..165350c509 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfXRef.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfXRef.cs @@ -103,7 +103,7 @@ public void Parse() } // If the classic/stream path did not yield a usable catalog, brute-force - // scan the file for objects and rebuild — matching pdf.js recovery. + // scan the file for objects and rebuild - matching pdf.js recovery. if (Trailer is null || Root is null) { RebuildByScanning(); diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Content/BitPdfTextExtractor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Content/BitPdfTextExtractor.cs index 12442dea5a..023b2af10e 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Content/BitPdfTextExtractor.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Content/BitPdfTextExtractor.cs @@ -8,7 +8,7 @@ namespace Bit.BlazorUI; /// /// Extracts the visible text of a page by replaying its content stream and /// decoding show-text operators through each selected font. Positioning is -/// approximated with simple space/newline heuristics — enough for search and +/// approximated with simple space/newline heuristics - enough for search and /// copy, not a layout-faithful reconstruction. /// public static class BitPdfTextExtractor diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Filters/BitPdfJpegDecoder.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Filters/BitPdfJpegDecoder.cs index 4be6f3a028..c8f3aa786d 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Filters/BitPdfJpegDecoder.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Filters/BitPdfJpegDecoder.cs @@ -99,7 +99,7 @@ private sealed class HuffTable switch (marker) { case 0xC0: // SOF0 baseline - case 0xC1: // SOF1 extended sequential (Huffman) — same layout + case 0xC1: // SOF1 extended sequential (Huffman) - same layout { precision = data[segStart]; frameHeight = (data[segStart + 1] << 8) | data[segStart + 2]; @@ -120,7 +120,7 @@ private sealed class HuffTable } break; } - case 0xC2: // SOF2 progressive — not supported here + case 0xC2: // SOF2 progressive - not supported here return null; case 0xC4: // DHT ReadHuffmanTables(data, segStart, segEnd, huffDc, huffAc); diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfCffFontParser.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfCffFontParser.cs index 14fd0223aa..f628d6ede8 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfCffFontParser.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfCffFontParser.cs @@ -25,7 +25,7 @@ internal sealed class BitPdfCffFontParser /// The raw CFF Encoding offset from the Top DICT (0/1 = predefined). public int EncodingOffset { get; private set; } - /// true for a CID-keyed CFF (ROS present) — unsupported here. + /// true for a CID-keyed CFF (ROS present) - unsupported here. public bool IsCid { get; private set; } public static BitPdfCffFontParser? Parse(byte[] cff) diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfFont.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfFont.cs index 8ad9c3a28f..a159371ec2 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfFont.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfFont.cs @@ -381,7 +381,7 @@ private static (byte[]?, string?) ExtractEmbedded(BitPdfDict? descriptor) : (data, "cff"); } // Type1 program (/FontFile): parse it and build an OpenType/CFF font the - // browser can load. On any failure fall through to a substitute font — + // browser can load. On any failure fall through to a substitute font - // a rejected @font-face simply falls back to the generic family. if (descriptor.Get("FontFile") is BitPdfStream t1Stream) { @@ -723,7 +723,7 @@ private double WidthFor(int code) int index = code - _firstChar; if (index >= 0 && index < _widths.Length) { - // An explicit width of 0 is valid (e.g. combining marks) — use it + // An explicit width of 0 is valid (e.g. combining marks) - use it // rather than falling through to a substitute metric. return _widths[index] * _glyphWidthScale; } diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfSfntGlyphMapper.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfSfntGlyphMapper.cs index 243cfa77a7..567e36bfff 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfSfntGlyphMapper.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfSfntGlyphMapper.cs @@ -32,7 +32,7 @@ internal static class BitPdfSfntGlyphMapper var names = ReadPost(font, dir); // glyph name → gid // Map each byte code to a unique Private-Use-Area codepoint (glyph - // painting is by exact glyph id — no shaping, no Unicode collisions). + // painting is by exact glyph id - no shaping, no Unicode collisions). var result = new Dictionary(); for (int code = 0; code < 256; code++) { diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfTrueTypeSanitizer.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfTrueTypeSanitizer.cs index f83b84d75d..b1f0711604 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfTrueTypeSanitizer.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfTrueTypeSanitizer.cs @@ -1,6 +1,6 @@ // A conservative TrueType/OpenType (sfnt) sanitizer. Embedded PDF subset fonts // frequently ship with an unsorted table directory, wrong table checksums, a -// wrong head.checkSumAdjustment, or unpadded tables — all of which strict font +// wrong head.checkSumAdjustment, or unpadded tables - all of which strict font // parsers (including the browser's OTS) reject, so the @font-face silently fails // to load. This rebuilds a structurally valid sfnt: recognized tables are kept // verbatim, the directory is re-sorted, tables are 4-byte aligned, and all @@ -21,7 +21,7 @@ internal static class BitPdfTrueTypeSanitizer /// /// As , but replaces the font's cmap with - /// when supplied — used to inject a clean + /// when supplied - used to inject a clean /// synthetic Unicode cmap for subset fonts whose own cmap OTS rejects. /// public static byte[]? Sanitize(byte[] input, byte[]? replacementCmap) diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfType1Font.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfType1Font.cs index 09a793078b..31363b1519 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfType1Font.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Fonts/BitPdfType1Font.cs @@ -413,7 +413,7 @@ private bool Escape(int op, InterpContext ctx) ? Pop(ctx.PostScriptStack) : 0); break; - case 6: // seac (accented char) — not composed; ignore base for now + case 6: // seac (accented char) - not composed; ignore base for now case 7: // sbw case 0: // dotsection case 1: // vstem3 diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfCanvasRenderResult.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfCanvasRenderResult.cs index 628b668d6f..e4d650f179 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfCanvasRenderResult.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfCanvasRenderResult.cs @@ -6,7 +6,7 @@ namespace Bit.BlazorUI; /// /// The page's DOM part: the positioned page div containing the /// <canvas> placeholder, the selectable text layer, and link/ -/// annotation overlays — everything except the painted content. +/// annotation overlays - everything except the painted content. /// /// /// The display list to replay onto the canvas (JSON array of drawing ops), diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs index 49618204fd..180ed0da26 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs @@ -53,7 +53,7 @@ public sealed class BitPdfHtmlRenderer // Coalesced selection/text layer (the pdf.js model): text is accumulated here // separately from the painted glyph spans and emitted once, on top, at page end - // — decoupled from paint order. Adjacent runs on a baseline merge into a single + // - decoupled from paint order. Adjacent runs on a baseline merge into a single // transparent, selectable span per visual line (with spaces inserted for gaps), // so double-click words, triple-click lines, click-drag and copy behave like // normal text instead of fragmenting per glyph. @@ -280,7 +280,7 @@ public string Render() // in, so dragging over text selects while empty areas stay clickable. // font-size:0 collapses the flow-level
separators (the spans set their // own size): the brs still put line breaks in copied text, but their empty - // line boxes — which stack at the container's top-left — become zero-sized, + // line boxes - which stack at the container's top-left - become zero-sized, // so a multi-line selection no longer paints stray highlight blocks along // the page's left edge. if (_selLayer.Length > 0) @@ -1347,7 +1347,7 @@ private static bool IsAllowedUri(string uri) int colon = uri.IndexOf(':'); if (colon <= 0) { - return false; // no scheme, or leading ':' — treat as unsafe + return false; // no scheme, or leading ':' - treat as unsafe } // A URI scheme is letters/digits/+/-/. and must precede any '/', '?' or '#'. for (int i = 0; i < colon; i++) @@ -1381,8 +1381,8 @@ private void SetFont(BitPdfOperation op) { return null; } - // Key the cache by the font's object identity — the indirect reference if - // present (value-equal), otherwise the dictionary instance — rather than + // Key the cache by the font's object identity - the indirect reference if + // present (value-equal), otherwise the dictionary instance - rather than // "depth:name". Different resource dictionaries can reuse a resource name // for different fonts, so a name-based key returned the wrong font. object? raw = fonts.GetRaw(name); @@ -1488,7 +1488,7 @@ private void ShowText(object? operand) private void ShowType3Text(List glyphs) { // Type3 glyphs paint graphics directly (below), reached via a whitelisted - // show-text operator — flush any pending coalesced line to keep paint order. + // show-text operator - flush any pending coalesced line to keep paint order. FlushPaintedLine(); BitPdfFont font = _state.Font!; @@ -1603,18 +1603,18 @@ private void EmitText(string renderText, string realText, double runAdvance) AccumulateSelectionText(realText, trm, left, top, fontHeight, targetWidth, linear); } - // Emit only the PAINTED layer. It is never selectable or searchable — all + // Emit only the PAINTED layer. It is never selectable or searchable - all // selection and find-in-page run against the coalesced layer above. if (invisible) { // Render modes 3/7 (e.g. an OCR text layer over a scanned image) paint // nothing, so there is no glyph layer and no embedded @font-face to - // inline — the coalesced selection span alone carries the text. + // inline - the coalesced selection span alone carries the text. return; } // Canvas mode: one fillText/strokeText op per run at its exact device - // matrix (no DOM cost, so no coalescing) — the selection layer above + // matrix (no DOM cost, so no coalescing) - the selection layer above // already carries the real Unicode. if (_ops is not null) { @@ -1623,13 +1623,13 @@ private void EmitText(string renderText, string realText, double runAdvance) } // Compact mode: coalesce painted runs into one span per visual line. - // Embedded fonts — including PUA glyph-mapped ones, which is how every - // embedded font in this engine paints — coalesce too: the browser lays the + // Embedded fonts - including PUA glyph-mapped ones, which is how every + // embedded font in this engine paints - coalesce too: the browser lays the // merged run out with the font's own advance widths, so only explicit TJ // kerning between runs is approximated while data-w pins the line's total // advance. (A gap-bridging space missing from a subset font falls through // to the generic fallback in the font stack.) Only rotated/mirrored text - // is excluded — its geometry can't be reduced to a horizontal line. + // is excluded - its geometry can't be reduced to a horizontal line. if (TextCoalescing == BitPdfTextCoalescing.Compact && a > 1e-3 && d > 1e-3 && Math.Abs(b) < 1e-3 && Math.Abs(c) < 1e-3) @@ -1650,8 +1650,8 @@ private void EmitText(string renderText, string realText, double runAdvance) /// /// Emits one canvas text op. The matrix's linear part maps the run's local em - /// space (x right, y down, baseline at the origin) to device space — identical - /// to the HTML span's transform — and (e, f) is the baseline origin, matching + /// space (x right, y down, baseline at the origin) to device space - identical + /// to the HTML span's transform - and (e, f) is the baseline origin, matching /// canvas's alphabetic textBaseline. The interpreter measures the drawn text /// and applies scaleX width-correction to , the /// run's PDF-computed advance (the data-w mechanism, done inline). @@ -1944,7 +1944,7 @@ private void AppendFontStyle(StringBuilder sb, BitPdfFont font) /// /// The CSS font-family list for , registering the /// embedded face's @font-face rule on first use. Canvas text uses the - /// same registered faces — document.fonts serves them to + /// same registered faces - document.fonts serves them to /// ctx.fillText once loaded. /// private string FontFamilyList(BitPdfFont font) diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfImage.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfImage.cs index 9e02c1803e..aceadb79ed 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfImage.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfImage.cs @@ -48,7 +48,7 @@ internal static class BitPdfImage { // Browsers decode plain RGB/YCbCr JPEG correctly, so pass those through. // But CMYK JPEGs render wrong in browsers, and /SMask, /Mask and a - // non-default /Decode all need the pixels in hand — decode in C# then. + // non-default /Decode all need the pixels in hand - decode in C# then. BitPdfColorSpace jcs = BitPdfColorSpace.Create(dict.Get("ColorSpace", "CS"), xref, resources); bool needsCSharp = jcs.Components == 4 || dict.Get("SMask") is BitPdfStream @@ -235,7 +235,7 @@ private static void ApplySoftMask(BitPdfDict dict, IBitPdfXRef xref, BitPdfDict? // The SMask's own bytes need the same image decoding as a base image. We // don't yet decode DCT/JPX masks, so applying the raw compressed bytes as - // alpha would paint noise — leave the image fully opaque instead. + // alpha would paint noise - leave the image fully opaque instead. var maskFilters = GetFilterNames(smask.Dict); if (maskFilters.Contains("DCTDecode") || maskFilters.Contains("DCT") || maskFilters.Contains("JPXDecode") || maskFilters.Contains("JBIG2Decode")) diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj index 4a37a8723e..4d68fe267c 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj @@ -7,8 +7,8 @@ + builds, or on demand via `dotnet build/run -p:IncludeWasm=true` (required to run/debug the + Blazor WebAssembly client or the ?no-prerender WASM mode in Debug). --> true false $(DefineConstants);INCLUDE_WASM diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Properties/launchSettings.json b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Properties/launchSettings.json index a92c102458..6de12a49d5 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Properties/launchSettings.json +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Properties/launchSettings.json @@ -1,6 +1,9 @@ { "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { + // Debug renders in Blazor Server mode (the WASM client is excluded by default - see IncludeWasm in the + // csproj). To run/debug the Blazor WebAssembly client, build with the WASM client included, e.g.: + // dotnet run -p:IncludeWasm=true "Bit.BlazorUI.Demo.Server": { "commandName": "Project", "dotnetRunMessages": true, @@ -10,17 +13,6 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } - }, - // This configuration allows debugging the Blazor Web Assembly - "Bit.BlazorUI.Demo.Server-BlazorWebAssembly": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "http://localhost:5000;https://localhost:5001", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } } } } diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs index 3abdc946cd..59acb8b9b3 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs @@ -8,14 +8,15 @@ namespace Bit.BlazorUI.Demo.Server.Services; /// hot-apply style changes to the browser without a rebuild. /// (Mirrors the ScssCompilerService of the bit Boilerplate template.) /// -public class ScssCompilerService +public static class ScssCompilerService { internal static async Task WatchScssFiles(WebApplication app) { if (app.Environment.IsDevelopment() is false) return; - var logger = app.Services.GetRequiredService>(); + // A static type can't be an ILogger category, so create the logger by name via the factory. + var logger = app.Services.GetRequiredService().CreateLogger(nameof(ScssCompilerService)); var clientCorePath = Path.GetFullPath(Path.Combine(app.Environment.ContentRootPath, "../Client/Bit.BlazorUI.Demo.Client.Core")); diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj index ffc89ef75f..565e4c29f9 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj @@ -41,7 +41,7 @@ From 28d9ca91c23e79d5658dfe873763ebc17f96310d Mon Sep 17 00:00:00 2001 From: msynk Date: Tue, 14 Jul 2026 16:15:23 +0330 Subject: [PATCH 05/14] resolve review comments II --- .../Components/PdfViewer/Core/BitPdfString.cs | 2 +- .../Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs | 2 +- ...ionExtensions.cs => ClientWebServiceCollectionExtensions.cs} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Extensions/{IServiceCollectionExtensions.cs => ClientWebServiceCollectionExtensions.cs} (90%) diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfString.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfString.cs index c129eb596e..0a5e2194ef 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfString.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/BitPdfString.cs @@ -58,7 +58,7 @@ private static char[] BuildPdfDocEncoding() t[0x1C] = '˝'; t[0x1D] = '˛'; t[0x1E] = '˚'; t[0x1F] = '˜'; // 0x80–0x9F: punctuation, ligatures, accented capitals. t[0x80] = '•'; t[0x81] = '†'; t[0x82] = '‡'; t[0x83] = '…'; - t[0x84] = '-'; t[0x85] = '–'; t[0x86] = 'ƒ'; t[0x87] = '⁄'; + t[0x84] = '—'; t[0x85] = '–'; t[0x86] = 'ƒ'; t[0x87] = '⁄'; t[0x88] = '‹'; t[0x89] = '›'; t[0x8A] = '−'; t[0x8B] = '‰'; t[0x8C] = '„'; t[0x8D] = '“'; t[0x8E] = '”'; t[0x8F] = '‘'; t[0x90] = '’'; t[0x91] = '‚'; t[0x92] = '™'; t[0x93] = 'fi'; diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs index 59acb8b9b3..51d881bcb6 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs @@ -46,7 +46,7 @@ internal static async Task WatchScssFiles(WebApplication app) "../../../Bit.BlazorUI.Assets/Styles/bit.blazorui.assets.scss:../../../Bit.BlazorUI.Assets/wwwroot/styles/bit.blazorui.assets.css", }; - var command = $"{string.Join(" ", sassPathsToWatch)} --style compressed --silence-deprecation=import --update --watch --color"; + var command = $"{string.Join(" ", sassPathsToWatch)} --load-path=. --style compressed --silence-deprecation=import --update --watch --color"; // Scss watching is best-effort development tooling: any failure below must not fault this task, // which would otherwise surface as an unobserved error in Program.cs's Task.WhenAll at shutdown. diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Extensions/IServiceCollectionExtensions.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Extensions/ClientWebServiceCollectionExtensions.cs similarity index 90% rename from src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Extensions/IServiceCollectionExtensions.cs rename to src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Extensions/ClientWebServiceCollectionExtensions.cs index 646f8f49db..7244737f1d 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Extensions/IServiceCollectionExtensions.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web.Shared/Extensions/ClientWebServiceCollectionExtensions.cs @@ -2,7 +2,7 @@ namespace Microsoft.Extensions.DependencyInjection; -public static class IClientWebServiceCollectionExtensions +public static class ClientWebServiceCollectionExtensions { public static IServiceCollection AddClientWebServices(this IServiceCollection services) { From 958fd5ee629170ee34204f52616491360dac627c Mon Sep 17 00:00:00 2001 From: msynk Date: Tue, 14 Jul 2026 16:34:32 +0330 Subject: [PATCH 06/14] resolve review comments III --- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Program.cs | 6 +++++- .../Properties/launchSettings.json | 3 --- src/BlazorUI/Demo/README.md | 10 +++++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Program.cs b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Program.cs index 765ca78079..a9e82f87ee 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Program.cs +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Program.cs @@ -21,6 +21,10 @@ Bit.BlazorUI.Demo.Server.Startup.Middlewares.Use(app, builder.Environment, builder.Configuration); +// Start the host first so startup failures propagate immediately, then await the +// SCSS watcher alongside shutdown (the watcher ties its own lifetime to app shutdown). +await app.StartAsync(); + await Task.WhenAll( - app.RunAsync(), + app.WaitForShutdownAsync(), Bit.BlazorUI.Demo.Server.Services.ScssCompilerService.WatchScssFiles(app) /* Development-only, no-op otherwise */); diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Properties/launchSettings.json b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Properties/launchSettings.json index 6de12a49d5..6bc92281cc 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Properties/launchSettings.json +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Properties/launchSettings.json @@ -1,9 +1,6 @@ { "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { - // Debug renders in Blazor Server mode (the WASM client is excluded by default - see IncludeWasm in the - // csproj). To run/debug the Blazor WebAssembly client, build with the WASM client included, e.g.: - // dotnet run -p:IncludeWasm=true "Bit.BlazorUI.Demo.Server": { "commandName": "Project", "dotnetRunMessages": true, diff --git a/src/BlazorUI/Demo/README.md b/src/BlazorUI/Demo/README.md index c711f35408..3dcad92b45 100644 --- a/src/BlazorUI/Demo/README.md +++ b/src/BlazorUI/Demo/README.md @@ -1,3 +1,11 @@ # Bit.BlazorUI.Demo -https://blazorui.bitplatform.dev/ \ No newline at end of file +https://blazorui.bitplatform.dev/ + +## Debugging Blazor Server vs. WebAssembly + +Debug builds render in Blazor Server mode; the WASM client is excluded by default +(see `IncludeWasm` in the csproj). To run/debug the Blazor WebAssembly client, +build with the WASM client included, e.g.: + + dotnet run -p:IncludeWasm=true \ No newline at end of file From 398260cc088fb8aecfea77b17e9c08f39205c404 Mon Sep 17 00:00:00 2001 From: msynk Date: Sat, 18 Jul 2026 15:57:19 +0330 Subject: [PATCH 07/14] resolve review comments IV --- .../Bit.BlazorUI.Assets/Bit.BlazorUI.Assets.csproj | 4 ---- .../Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj | 10 +++++----- .../Bit.BlazorUI.Icons/Bit.BlazorUI.Icons.csproj | 7 ------- src/BlazorUI/Bit.BlazorUI/Bit.BlazorUI.csproj | 10 +++++----- .../Bit.BlazorUI.Demo.Client.Core.csproj | 10 +++++----- 5 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/BlazorUI/Bit.BlazorUI.Assets/Bit.BlazorUI.Assets.csproj b/src/BlazorUI/Bit.BlazorUI.Assets/Bit.BlazorUI.Assets.csproj index 45f594bd74..24c07171ed 100644 --- a/src/BlazorUI/Bit.BlazorUI.Assets/Bit.BlazorUI.Assets.csproj +++ b/src/BlazorUI/Bit.BlazorUI.Assets/Bit.BlazorUI.Assets.csproj @@ -31,10 +31,6 @@ - - diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj b/src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj index af1735c49a..30b863a642 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj @@ -29,12 +29,12 @@ - - + hot-apply them when the demo server's ScssCompilerService regenerates them. Scss sources are + intentionally not watched here: the in-app compiler recompiles them without a rebuild. Paths + are rooted with $(MSBuildProjectDirectory) because dotnet watch resolves relative Watch items + of referenced projects against the startup project's directory. --> diff --git a/src/BlazorUI/Bit.BlazorUI.Icons/Bit.BlazorUI.Icons.csproj b/src/BlazorUI/Bit.BlazorUI.Icons/Bit.BlazorUI.Icons.csproj index b729c8763d..c1c43bb2bf 100644 --- a/src/BlazorUI/Bit.BlazorUI.Icons/Bit.BlazorUI.Icons.csproj +++ b/src/BlazorUI/Bit.BlazorUI.Icons/Bit.BlazorUI.Icons.csproj @@ -16,13 +16,6 @@ - - - - - diff --git a/src/BlazorUI/Bit.BlazorUI/Bit.BlazorUI.csproj b/src/BlazorUI/Bit.BlazorUI/Bit.BlazorUI.csproj index f54446c912..67cb7ee6d4 100644 --- a/src/BlazorUI/Bit.BlazorUI/Bit.BlazorUI.csproj +++ b/src/BlazorUI/Bit.BlazorUI/Bit.BlazorUI.csproj @@ -30,12 +30,12 @@ - - + hot-apply them when the demo server's ScssCompilerService regenerates them. Scss sources are + intentionally not watched here: the in-app compiler recompiles them without a rebuild. Paths + are rooted with $(MSBuildProjectDirectory) because dotnet watch resolves relative Watch items + of referenced projects against the startup project's directory. --> diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj index 565e4c29f9..e584926490 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj @@ -39,12 +39,12 @@ - - + server project). Scss sources are intentionally not watched here: the in-app compiler recompiles + them without a rebuild. Paths are rooted with $(MSBuildProjectDirectory) because dotnet watch + resolves relative Watch items of referenced projects against the startup project's directory. --> From 393a8039d6ba1290f1248e5a27ce49d794253ebe Mon Sep 17 00:00:00 2001 From: msynk Date: Sat, 18 Jul 2026 18:36:01 +0330 Subject: [PATCH 08/14] fix scss hot-reload issues --- .../Services/ScssCompilerService.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs index 51d881bcb6..397b9b0f9d 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs @@ -46,7 +46,29 @@ internal static async Task WatchScssFiles(WebApplication app) "../../../Bit.BlazorUI.Assets/Styles/bit.blazorui.assets.scss:../../../Bit.BlazorUI.Assets/wwwroot/styles/bit.blazorui.assets.css", }; - var command = $"{string.Join(" ", sassPathsToWatch)} --load-path=. --style compressed --silence-deprecation=import --update --watch --color"; + // dart-sass --watch only watches files that live under a watched root: the directory of each + // compiled entry point plus every --load-path directory. The library bundle entry points sit in + // each project's Styles/ folder, but their component partials are imported from a sibling + // Components/ tree (e.g. bit.blazorui.scss -> ... -> Styles/components.scss -> ../Components/Buttons/ + // ActionButton/BitActionButton.scss). Those partials are outside every watched root, so editing one + // would NOT retrigger a compile and the css would go stale on hot reload. Adding each Components/ + // folder as a load-path brings it under a watched root. Do not remove these: without them, editing a + // component's scss (BitButton.scss, etc.) silently stops hot-reloading its styles. + var loadPaths = new List + { + "--load-path=.", + "--load-path=../../../Bit.BlazorUI/Components", + "--load-path=../../../Bit.BlazorUI.Extras/Components", + }; + + // --no-source-map is required for style hot reload to work. dart-sass emits a *.css.map next to + // every *.css; dotnet watch watches those too and pushes each change to the browser. The injected + // aspnetcore-browser-refresh.js only does a live, in-place stylesheet swap for paths ending in + // ".css" - for any other static file (including ".css.map") it falls back to a full location.reload(). + // With source maps on, every scss edit therefore fires a burst of full-page reloads that race with + // (and clobber) the clean css swaps, so the page never reliably shows the change. Dropping the maps + // leaves only ".css" updates, which hot-swap cleanly without reloading the page. + var command = $"{string.Join(" ", sassPathsToWatch)} {string.Join(" ", loadPaths)} --style compressed --no-source-map --silence-deprecation=import --update --watch --color"; // Scss watching is best-effort development tooling: any failure below must not fault this task, // which would otherwise surface as an unobserved error in Program.cs's Task.WhenAll at shutdown. From 9085ddc88145f580081de76a666a34dc84099cb7 Mon Sep 17 00:00:00 2001 From: msynk Date: Sun, 19 Jul 2026 09:11:37 +0330 Subject: [PATCH 09/14] resolve review comments V --- src/BlazorUI/Demo/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BlazorUI/Demo/README.md b/src/BlazorUI/Demo/README.md index 3dcad92b45..c5293fbf2c 100644 --- a/src/BlazorUI/Demo/README.md +++ b/src/BlazorUI/Demo/README.md @@ -8,4 +8,4 @@ Debug builds render in Blazor Server mode; the WASM client is excluded by defaul (see `IncludeWasm` in the csproj). To run/debug the Blazor WebAssembly client, build with the WASM client included, e.g.: - dotnet run -p:IncludeWasm=true \ No newline at end of file + dotnet run --project Bit.BlazorUI.Demo.Server -p:IncludeWasm=true \ No newline at end of file From 7a37df29501e7246c2f3fd3f4fbb8b801f7598d8 Mon Sep 17 00:00:00 2001 From: msynk Date: Sun, 19 Jul 2026 11:21:45 +0330 Subject: [PATCH 10/14] resolve review comments VI --- .../Services/ScssCompilerService.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs index 397b9b0f9d..69677a243c 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs @@ -106,7 +106,17 @@ internal static async Task WatchScssFiles(WebApplication app) // orphaned sass watcher survives. The finally is a backstop for exceptions before shutdown. app.Lifetime.ApplicationStopping.Register(() => KillSassProcess(watchScssFilesProcess, logger)); - await app.WaitForShutdownAsync(); + // Park until the host begins shutting down. Waiting on ApplicationStopping (instead of + // app.WaitForShutdownAsync, which itself calls host.StopAsync) keeps Program.cs's + // WaitForShutdownAsync as the single code path that drives host shutdown. + try + { + await Task.Delay(Timeout.Infinite, app.Lifetime.ApplicationStopping); + } + catch (OperationCanceledException) + { + // The normal shutdown signal, not a failure. + } } catch (Exception ex) { From 63d4f8fdde828c1ace6dfc22bef3cb07a96afecb Mon Sep 17 00:00:00 2001 From: msynk Date: Sun, 19 Jul 2026 12:08:18 +0330 Subject: [PATCH 11/14] resolve review comments VII --- .../Services/ScssCompilerService.cs | 6 ++++-- .../Services/AppRenderMode.cs | 10 +++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs index 69677a243c..c7a55c5dbf 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs @@ -88,8 +88,10 @@ internal static async Task WatchScssFiles(WebApplication app) } }; - watchScssFilesProcess.OutputDataReceived += (_, e) => { if (string.IsNullOrEmpty(e.Data) is false) logger.LogInformation(e.Data); }; - watchScssFilesProcess.ErrorDataReceived += (_, e) => { if (string.IsNullOrEmpty(e.Data) is false) logger.LogError(e.Data); }; + // Pass the sass output as a logging argument, not as the message template: scss snippets in + // sass's error output contain braces that ILogger would parse as format placeholders. + watchScssFilesProcess.OutputDataReceived += (_, e) => { if (string.IsNullOrEmpty(e.Data) is false) logger.LogInformation("{SassOutput}", e.Data); }; + watchScssFilesProcess.ErrorDataReceived += (_, e) => { if (string.IsNullOrEmpty(e.Data) is false) logger.LogError("{SassError}", e.Data); }; logger.LogInformation("Running {toolPath} for {ProjectDirectory}", toolPath, clientCorePath); if (watchScssFilesProcess.Start() is false) diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AppRenderMode.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AppRenderMode.cs index ce7e5cc9e6..b85305a211 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AppRenderMode.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AppRenderMode.cs @@ -9,11 +9,11 @@ public static class AppRenderMode // .NET 10 resolves the earlier prerendering issues, so this is enabled to serve fully-formed HTML. public static readonly bool PrerenderEnabled = true; - public static IComponentRenderMode Auto => new InteractiveAutoRenderMode(PrerenderEnabled); - public static IComponentRenderMode BlazorWebAssembly => new InteractiveWebAssemblyRenderMode(PrerenderEnabled); - public static IComponentRenderMode BlazorServer => new InteractiveServerRenderMode(PrerenderEnabled); - public static IComponentRenderMode NoPrerenderBlazorWebAssembly => new InteractiveWebAssemblyRenderMode(prerender: false); - public static IComponentRenderMode NoPrerenderBlazorServer => new InteractiveServerRenderMode(prerender: false); + public static IComponentRenderMode Auto { get; } = new InteractiveAutoRenderMode(PrerenderEnabled); + public static IComponentRenderMode BlazorWebAssembly { get; } = new InteractiveWebAssemblyRenderMode(PrerenderEnabled); + public static IComponentRenderMode BlazorServer { get; } = new InteractiveServerRenderMode(PrerenderEnabled); + public static IComponentRenderMode NoPrerenderBlazorWebAssembly { get; } = new InteractiveWebAssemblyRenderMode(prerender: false); + public static IComponentRenderMode NoPrerenderBlazorServer { get; } = new InteractiveServerRenderMode(prerender: false); public static IComponentRenderMode Current => BuildConfiguration.IsDebug() ? BlazorServer /*For better development experience*/ From 516aa99e28b89c224eeb4e6e7665a5a4596670a2 Mon Sep 17 00:00:00 2001 From: msynk Date: Sun, 19 Jul 2026 13:11:26 +0330 Subject: [PATCH 12/14] resolve review comments VIII --- .../Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs index 0353e18b05..a821ae8e63 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs @@ -65,8 +65,9 @@ public sealed class BitPdfHtmlRenderer /// /// How painted text runs are emitted. - /// merges same-line, same-style substitute-font runs into one span per visual - /// line (embedded-font runs always stay per-run exact). Default is + /// merges same-line, same-style runs - including embedded-font (PUA + /// glyph-mapped) ones - into one span per visual line; only rotated or + /// mirrored text stays per-run exact. Default is /// . /// public BitPdfTextCoalescing TextCoalescing { get; set; } = BitPdfTextCoalescing.Exact; From c98c4a2a47128a243e92645a2d07c447d697d244 Mon Sep 17 00:00:00 2001 From: msynk Date: Sun, 19 Jul 2026 13:21:31 +0330 Subject: [PATCH 13/14] resolve review comments IX --- .../Services/ScssCompilerService.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs index c7a55c5dbf..e06de5db37 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Services/ScssCompilerService.cs @@ -103,13 +103,11 @@ internal static async Task WatchScssFiles(WebApplication app) watchScssFilesProcess.BeginOutputReadLine(); watchScssFilesProcess.BeginErrorReadLine(); - // dart-sass --watch runs until killed, so tie its lifetime to the server: kill the whole - // process tree when the host starts shutting down (dotnet watch restart, Ctrl+C) so no - // orphaned sass watcher survives. The finally is a backstop for exceptions before shutdown. - app.Lifetime.ApplicationStopping.Register(() => KillSassProcess(watchScssFilesProcess, logger)); - - // Park until the host begins shutting down. Waiting on ApplicationStopping (instead of - // app.WaitForShutdownAsync, which itself calls host.StopAsync) keeps Program.cs's + // dart-sass --watch runs until killed, so tie its lifetime to the server: park until the + // host begins shutting down (dotnet watch restart, Ctrl+C), then the finally kills the whole + // process tree so no orphaned sass watcher survives - Program.cs awaits this task, so the + // cleanup always completes before the process exits. Waiting on ApplicationStopping (instead + // of app.WaitForShutdownAsync, which itself calls host.StopAsync) keeps Program.cs's // WaitForShutdownAsync as the single code path that drives host shutdown. try { @@ -140,6 +138,10 @@ private static void KillSassProcess(Process? process, ILogger logger) process.Kill(entireProcessTree: true); } } + catch (InvalidOperationException) + { + // The process never started or is already disposed - nothing to terminate. + } catch (Exception ex) { logger.LogWarning(ex, "Failed to terminate the scss watch process."); From 3852ec771c0828bf65e8eab8c7bc5bbcbf537e7d Mon Sep 17 00:00:00 2001 From: msynk Date: Sun, 19 Jul 2026 13:34:40 +0330 Subject: [PATCH 14/14] resolve review comments X --- .../PdfViewer/Core/Render/BitPdfHtmlRenderer.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs index a821ae8e63..6719904d30 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/PdfViewer/Core/Render/BitPdfHtmlRenderer.cs @@ -66,8 +66,8 @@ public sealed class BitPdfHtmlRenderer /// /// How painted text runs are emitted. /// merges same-line, same-style runs - including embedded-font (PUA - /// glyph-mapped) ones - into one span per visual line; only rotated or - /// mirrored text stays per-run exact. Default is + /// glyph-mapped) ones - into one span per visual line; only non-upright + /// (rotated, mirrored, or skewed) text stays per-run exact. Default is /// . /// public BitPdfTextCoalescing TextCoalescing { get; set; } = BitPdfTextCoalescing.Exact; @@ -1618,8 +1618,9 @@ private void EmitText(string renderText, string realText, double runAdvance) // merged run out with the font's own advance widths, so only explicit TJ // kerning between runs is approximated while data-w pins the line's total // advance. (A gap-bridging space missing from a subset font falls through - // to the generic fallback in the font stack.) Only rotated/mirrored text - // is excluded - its geometry can't be reduced to a horizontal line. + // to the generic fallback in the font stack.) Only non-upright text + // (rotated/mirrored/skewed) is excluded - its geometry can't be reduced + // to a horizontal line. if (TextCoalescing == BitPdfTextCoalescing.Compact && a > 1e-3 && d > 1e-3 && Math.Abs(b) < 1e-3 && Math.Abs(c) < 1e-3)