Skip to content

Commit 38daecc

Browse files
committed
fix: address all code review issues from PR #8
- Remove reflection (Assembly.GetExecutingAssembly) for AOT safety - Gate diagnostics behind LayoutDiagnostics record + EnableDiagnostics flag - Remove InternalsVisibleTo for Playground, make ResourceLoaders public - Use ConcurrentDictionary in MemoryResourceLoader for thread safety - Add 10MB resource size limit in MemoryResourceLoader - Fix silent catch in FontManager.PreloadFontFromResourcesAsync - Fix race condition with atomic AddOrUpdate in FontManager - Add min 1px bitmap guard to all Render overloads - Escape innerHTML in context menu for XSS safety - Remove leftover debug variable in LayoutEngine - Add playground link to README, wiki Home and Getting Started
1 parent 77d630f commit 38daecc

15 files changed

Lines changed: 95 additions & 74 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
A .NET library for rendering images from YAML templates with a full CSS flexbox layout engine. Perfect for generating receipts, labels, tickets, and structured documents.
99

10+
**[Try it in the browser](https://robonet.github.io/FlexRender/)** -- WASM playground with live preview, no installation required.
11+
1012
## Features
1113

1214
- **YAML Templates** -- define complex image layouts in readable YAML format

docs/wiki/Getting-Started.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
This guide walks you through installing FlexRender, creating your first template, and rendering it using code, dependency injection, or the CLI.
44

5+
> **Want to try without installing?** Use the [browser playground](https://robonet.github.io/FlexRender/) -- edit YAML templates and see results instantly.
6+
57
## Installation
68

79
### All-in-one (recommended)

docs/wiki/Home.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
A modular .NET library for rendering images from YAML templates with a full CSS flexbox layout engine. Render-backend agnostic with SkiaSharp as the default backend. Fully AOT-compatible with zero reflection.
99

10+
**[Try it in the browser](https://robonet.github.io/FlexRender/)** -- WASM playground with live preview, no installation required.
11+
1012
## Why FlexRender?
1113

1214
- **YAML-first** -- define complex image layouts in readable YAML, no design tools needed

src/FlexRender.Core/Configuration/FlexRenderBuilder.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,14 @@ public sealed class FlexRenderBuilder
7070

7171
/// <summary>
7272
/// Gets the list of configured resource loaders.
73+
/// Custom loaders can be inserted before <see cref="Build"/> is called to control
74+
/// resolution priority (lower index = higher priority).
7375
/// </summary>
7476
/// <remarks>
75-
/// Loaders are added lazily when <see cref="Build"/> is called to ensure
76-
/// they receive the fully configured <see cref="Options"/> instance.
77+
/// Built-in loaders (file, base64, embedded) are added lazily when <see cref="Build"/>
78+
/// is called to ensure they receive the fully configured <see cref="Options"/> instance.
7779
/// </remarks>
78-
internal List<IResourceLoader> ResourceLoaders { get; } = [];
80+
public List<IResourceLoader> ResourceLoaders { get; } = [];
7981

8082
/// <summary>
8183
/// Gets the configured filter registry, or <c>null</c> if no filters have been registered.

src/FlexRender.Core/FlexRender.Core.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
<InternalsVisibleTo Include="FlexRender.Skia.Render" />
1313
<InternalsVisibleTo Include="FlexRender.Svg.Render" />
1414
<InternalsVisibleTo Include="FlexRender.ImageSharp.Render" />
15-
<InternalsVisibleTo Include="FlexRender.Playground" />
1615
</ItemGroup>
1716

1817
</Project>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace FlexRender.Layout;
2+
3+
/// <summary>
4+
/// Diagnostic data attached to a <see cref="LayoutNode"/> for debugging text layout.
5+
/// </summary>
6+
/// <param name="IntrinsicWidth">Intrinsic width from IntrinsicMeasurer (before scaling).</param>
7+
/// <param name="ShapedWidth">Shaped width from TextShaper at final font size.</param>
8+
/// <param name="ContentWidth">Final content width used in layout calculation.</param>
9+
/// <param name="ResolvedTypeface">Resolved typeface family name.</param>
10+
public sealed record LayoutDiagnostics(
11+
float IntrinsicWidth,
12+
float ShapedWidth,
13+
float ContentWidth,
14+
string? ResolvedTypeface = null);

src/FlexRender.Core/Layout/LayoutEngine.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ public LayoutEngine(ResourceLimits limits)
5353
/// </summary>
5454
public ITextShaper? TextShaper { get; set; }
5555

56+
/// <summary>
57+
/// When true, populates <see cref="LayoutNode.Diagnostics"/> with text measurement
58+
/// details (intrinsic width, shaped width, content width). Defaults to false.
59+
/// </summary>
60+
public bool EnableDiagnostics { get; set; }
61+
5662
/// <summary>
5763
/// Base font size in pixels used for em resolution and as fallback when text elements
5864
/// don't specify an explicit size. Must match the renderer's base font size for
@@ -587,9 +593,10 @@ private LayoutNode LayoutTextElement(TextElement text, LayoutContext context)
587593
node.ComputedLineHeight = computedLineHeight;
588594
node.Baseline = padding.Top + border.Top.Width + textBaseline;
589595
node.ComputedFontSize = resolvedFontSize;
590-
node.DiagContentWidth = contentWidth;
591-
node.DiagIntrinsicWidth = diagIntrinsicW;
592-
node.DiagShapedWidth = diagShapedW;
596+
if (EnableDiagnostics)
597+
{
598+
node.Diagnostics = new LayoutDiagnostics(diagIntrinsicW, diagShapedW, contentWidth);
599+
}
593600
return node;
594601
}
595602

@@ -875,9 +882,7 @@ private float ComputeFitContentFontSize(FlexElement flex, LayoutContext innerCon
875882
// Floor to 0.1px to avoid rounding errors where text barely exceeds container width
876883
// due to non-linear font scaling (hinting, glyph rounding)
877884
var computed = refSize * availableWidth / totalMeasured;
878-
var floored = MathF.Floor(computed * 10f) / 10f;
879-
880-
return floored;
885+
return MathF.Floor(computed * 10f) / 10f;
881886
}
882887

883888
private float MeasureContentWidth(TemplateElement element, LayoutContext context)

src/FlexRender.Core/Layout/LayoutNode.cs

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,26 +62,10 @@ public sealed class LayoutNode
6262
public float ComputedFontSize { get; set; }
6363

6464
/// <summary>
65-
/// Diagnostic: intrinsic width from IntrinsicMeasurer (before scaling).
66-
/// Only populated for text elements during layout when diagnostics are enabled.
65+
/// Optional diagnostic data populated during layout for debugging purposes.
66+
/// Only populated when <see cref="LayoutEngine.EnableDiagnostics"/> is true.
6767
/// </summary>
68-
public float DiagIntrinsicWidth { get; set; }
69-
70-
/// <summary>
71-
/// Diagnostic: shaped width from TextShaper at final font size.
72-
/// Only populated for text elements during layout when diagnostics are enabled.
73-
/// </summary>
74-
public float DiagShapedWidth { get; set; }
75-
76-
/// <summary>
77-
/// Diagnostic: final content width used in layout calculation.
78-
/// </summary>
79-
public float DiagContentWidth { get; set; }
80-
81-
/// <summary>
82-
/// Diagnostic: resolved typeface family name from FontManager.
83-
/// </summary>
84-
public string? DiagResolvedTypeface { get; set; }
68+
public LayoutDiagnostics? Diagnostics { get; set; }
8569

8670
/// <summary>Right edge (X + Width).</summary>
8771
public float Right => X + Width;

src/FlexRender.Playground/FlexRender.Playground.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,4 @@
2626
<NativeFileReference Include="$(NuGetPackageRoot)skiasharp.nativeassets.webassembly/3.119.2/buildTransitive/netstandard1.0/libSkiaSharp.a/3.1.56/st/*.a" />
2727
</ItemGroup>
2828

29-
<!-- Embedded default font for WASM (no system fonts available) -->
30-
<ItemGroup>
31-
<EmbeddedResource Include="Fonts/Inter-Regular.ttf" LogicalName="Inter-Regular.ttf" />
32-
</ItemGroup>
3329
</Project>

src/FlexRender.Playground/MemoryResourceLoader.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Concurrent;
12
using FlexRender.Abstractions;
23

34
namespace FlexRender.Playground;
@@ -8,7 +9,9 @@ namespace FlexRender.Playground;
89
/// </summary>
910
internal sealed class MemoryResourceLoader : IResourceLoader
1011
{
11-
private readonly Dictionary<string, byte[]> _resources = new(StringComparer.OrdinalIgnoreCase);
12+
private const int MaxResourceSize = 10 * 1024 * 1024; // 10 MB per resource
13+
14+
private readonly ConcurrentDictionary<string, byte[]> _resources = new(StringComparer.OrdinalIgnoreCase);
1215

1316
/// <inheritdoc />
1417
/// <remarks>Priority 10 ensures uploaded files override all other loaders.</remarks>
@@ -59,6 +62,9 @@ public void AddResource(string name, byte[] data)
5962
ArgumentNullException.ThrowIfNull(name);
6063
ArgumentNullException.ThrowIfNull(data);
6164

65+
if (data.Length > MaxResourceSize)
66+
throw new ArgumentException($"Resource exceeds maximum size of {MaxResourceSize / 1024 / 1024} MB.", nameof(data));
67+
6268
_resources[NormalizePath(name)] = data;
6369
}
6470

@@ -70,15 +76,15 @@ public void RemoveResource(string name)
7076
{
7177
ArgumentNullException.ThrowIfNull(name);
7278

73-
_resources.Remove(NormalizePath(name));
79+
_resources.TryRemove(NormalizePath(name), out _);
7480
}
7581

7682
/// <summary>
7783
/// Removes all stored resources.
7884
/// </summary>
7985
public void Clear()
8086
{
81-
_resources.Clear();
87+
_resources.Clear(); // ConcurrentDictionary.Clear is thread-safe
8288
}
8389

8490
/// <summary>

0 commit comments

Comments
 (0)