mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-02-04 05:35:44 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a12a75bde | ||
|
|
d1917eac0c | ||
|
|
09830f0ea2 | ||
|
|
d34e0684fb | ||
|
|
482eca3278 | ||
|
|
5c8ac16c83 | ||
|
|
29382cf0f4 | ||
|
|
53204cc8d6 | ||
|
|
6cf550c517 | ||
|
|
7bf107af4c | ||
|
|
adf2785a5a | ||
|
|
cae44df00a | ||
|
|
8ba1c69573 | ||
|
|
56031c2fd4 | ||
|
|
ad44802d30 | ||
|
|
64ca088e61 | ||
|
|
f4777565a2 | ||
|
|
eb1423e757 | ||
|
|
596b251511 | ||
|
|
e186315935 | ||
|
|
cba9a5120d |
@@ -2,6 +2,7 @@ using Bunit;
|
||||
using Bunit.JSInterop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
@@ -54,7 +55,7 @@ namespace Radzen.Blazor.Tests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void RadzenPager_Renders_Summary() {
|
||||
public async Task RadzenPager_Renders_Summary() {
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
|
||||
@@ -64,7 +65,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<int>(p => p.Count, 100);
|
||||
parameters.Add<bool>(p => p.ShowPagingSummary, true);
|
||||
});
|
||||
await component.Instance.GoToPage(2);
|
||||
await component.InvokeAsync(() => component.Instance.GoToPage(2));
|
||||
component.Render();
|
||||
|
||||
Assert.Contains(@$"rz-pager-summary", component.Markup);
|
||||
@@ -111,7 +112,7 @@ namespace Radzen.Blazor.Tests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void RadzenPager_First_And_Prev_Buttons_Are_Disabled_When_On_The_First_Page()
|
||||
public async Task RadzenPager_First_And_Prev_Buttons_Are_Disabled_When_On_The_First_Page()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
@@ -123,7 +124,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<bool>(p => p.ShowPagingSummary, true);
|
||||
});
|
||||
|
||||
await component.Instance.GoToPage(0);
|
||||
await component.InvokeAsync(() => component.Instance.GoToPage(0));
|
||||
component.Render();
|
||||
|
||||
var firstPageButton = component.Find("a.rz-pager-first");
|
||||
@@ -134,7 +135,7 @@ namespace Radzen.Blazor.Tests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void RadzenPager_Last_And_Next_Buttons_Are_Disabled_When_On_The_Last_Page()
|
||||
public async Task RadzenPager_Last_And_Next_Buttons_Are_Disabled_When_On_The_Last_Page()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
@@ -146,7 +147,7 @@ namespace Radzen.Blazor.Tests
|
||||
parameters.Add<bool>(p => p.ShowPagingSummary, true);
|
||||
});
|
||||
|
||||
await component.Instance.GoToPage(9);
|
||||
await component.InvokeAsync(() => component.Instance.GoToPage(9));
|
||||
component.Render();
|
||||
|
||||
var lastPageButton = component.Find("a.rz-pager-last");
|
||||
|
||||
@@ -508,7 +508,7 @@ namespace Radzen
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
public object? GetItemOrValueFromProperty(object? item, string property)
|
||||
public virtual object? GetItemOrValueFromProperty(object? item, string property)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
|
||||
@@ -17,6 +17,12 @@ public class GroupRowRenderEventArgs
|
||||
/// </summary>
|
||||
public Group? Group { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this group row is expandable.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if expandable; otherwise, <c>false</c>.</value>
|
||||
public bool Expandable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this group row is expanded.
|
||||
/// </summary>
|
||||
|
||||
@@ -41,6 +41,65 @@ namespace Radzen
|
||||
return source.Provider.CreateQuery(selectExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects each element of a sequence into a collection of property values.
|
||||
/// </summary>
|
||||
public static IQueryable Select(this IQueryable source, IEnumerable<string> propertyNames)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
ArgumentNullException.ThrowIfNull(propertyNames);
|
||||
|
||||
var parameter = Expression.Parameter(source.ElementType, "x");
|
||||
|
||||
var bindings = new List<MemberBinding>();
|
||||
var allProperties = source.ElementType.GetProperties();
|
||||
|
||||
foreach (var property in allProperties.Where(p => propertyNames.Contains(p.Name)))
|
||||
{
|
||||
bindings.Add(Expression.Bind(property, Expression.Property(parameter, property)));
|
||||
}
|
||||
|
||||
var body = Expression.MemberInit(Expression.New(source.ElementType), bindings);
|
||||
|
||||
var delegateType = typeof(Func<,>).MakeGenericType(source.ElementType, source.ElementType);
|
||||
|
||||
var lambda = Expression.Lambda(delegateType, body, parameter);
|
||||
|
||||
var selectExpression = Expression.Call(typeof(Queryable),
|
||||
nameof(Queryable.Select), [source.ElementType, source.ElementType], source.Expression,
|
||||
Expression.Quote(lambda));
|
||||
|
||||
return source.Provider.CreateQuery(selectExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects each element of a sequence into a collection of property values.
|
||||
/// </summary>
|
||||
public static IQueryable<T> Select<T>(this IQueryable<T> source, IEnumerable<string> propertyNames)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
ArgumentNullException.ThrowIfNull(propertyNames);
|
||||
|
||||
var parameter = Expression.Parameter(typeof(T), "x");
|
||||
|
||||
var bindings = new List<MemberBinding>();
|
||||
var allProperties = typeof(T).GetProperties();
|
||||
|
||||
foreach (var property in allProperties.Where(p => propertyNames.Contains(p.Name)))
|
||||
{
|
||||
bindings.Add(Expression.Bind(property, Expression.Property(parameter, property)));
|
||||
}
|
||||
|
||||
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
|
||||
|
||||
var lambda = Expression.Lambda<Func<T, T>>(body, parameter);
|
||||
|
||||
var selectExpression = Expression.Call(typeof(Queryable),
|
||||
nameof(Queryable.Select), [typeof(T), typeof(T)], source.Expression,
|
||||
Expression.Quote(lambda));
|
||||
|
||||
return source.Provider.CreateQuery<T>(selectExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence.
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageId>Radzen.Blazor</PackageId>
|
||||
<Product>Radzen.Blazor</Product>
|
||||
<Version>8.7.0</Version>
|
||||
<Version>8.7.5</Version>
|
||||
<Copyright>Radzen Ltd.</Copyright>
|
||||
<Authors>Radzen Ltd.</Authors>
|
||||
<Description>Radzen Blazor is the most sophisticated free UI component library for Blazor, featuring 100+ native components including DataGrid, Scheduler, Charts, and advanced theming with full support for Material Design and Fluent UI.</Description>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
@@ -93,22 +95,6 @@ namespace Radzen.Blazor
|
||||
_ => false
|
||||
};
|
||||
|
||||
internal readonly struct BarcodeRect
|
||||
{
|
||||
public readonly double X;
|
||||
public readonly double Y;
|
||||
public readonly double Width;
|
||||
public readonly double Height;
|
||||
|
||||
public BarcodeRect(double x, double y, double width, double height)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the barcode value to encode.
|
||||
/// </summary>
|
||||
@@ -275,894 +261,20 @@ namespace Radzen.Blazor
|
||||
if (vbWidth <= 0) vbWidth = 1;
|
||||
return (geometry.bars, vbWidth, checksumText, null);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class RadzenBarcodeEncoder
|
||||
{
|
||||
// Code 128 patterns (0..106). Each entry is 6 digits (bar/space/bar/space/bar/space) module widths.
|
||||
// Stop code (106) is 7 digits in the spec (includes a final bar). We keep it as 7 digits and handle it.
|
||||
static readonly string[] Code128Patterns = new[]
|
||||
/// <summary>
|
||||
/// Returns the SVG markup of the rendered QR code as a string.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{String}"/> representing the asynchronous operation. The task result contains the SVG markup of the QR code.
|
||||
/// </returns>
|
||||
public async Task<string> ToSvg()
|
||||
{
|
||||
"212222","222122","222221","121223","121322","131222","122213","122312","132212","221213",
|
||||
"221312","231212","112232","122132","122231","113222","123122","123221","223211","221132",
|
||||
"221231","213212","223112","312131","311222","321122","321221","312212","322112","322211",
|
||||
"212123","212321","232121","111323","131123","131321","112313","132113","132311","211313",
|
||||
"231113","231311","112133","112331","132131","113123","113321","133121","313121","211331",
|
||||
"231131","213113","213311","213131","311123","311321","331121","312113","312311","332111",
|
||||
"314111","221411","431111","111224","111422","121124","121421","141122","141221","112214",
|
||||
"112412","122114","122411","142112","142211","241211","221114","413111","241112","134111",
|
||||
"111242","121142","121241","114212","124112","124211","411212","421112","421211","212141",
|
||||
"214121","412121","111143","111341","131141","114113","114311","411113","411311","113141",
|
||||
"114131","311141","411131","211412","211214","211232","2331112"
|
||||
};
|
||||
|
||||
public static IReadOnlyList<int> EncodeCode128B(string value) => EncodeCode128B(value, out _);
|
||||
|
||||
public static IReadOnlyList<int> EncodeCode128B(string value, out int checksum)
|
||||
{
|
||||
// Code 128 subset B supports ASCII 32..127 (inclusive). We treat 127 as DEL.
|
||||
var codes = new List<int>(value.Length + 3);
|
||||
const int startB = 104;
|
||||
const int stop = 106;
|
||||
|
||||
codes.Add(startB);
|
||||
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
if (JSRuntime != null)
|
||||
{
|
||||
var ch = value[i];
|
||||
int ascii = ch;
|
||||
if (ascii < 32 || ascii > 127)
|
||||
{
|
||||
throw new ArgumentException($"Code128B supports ASCII 32..127. Invalid character: U+{ascii:X4}.");
|
||||
}
|
||||
|
||||
// In Code128B, code value is ascii - 32
|
||||
codes.Add(ascii - 32);
|
||||
return await JSRuntime.InvokeAsync<string>("Radzen.outerHTML", Element);
|
||||
}
|
||||
|
||||
int checksumValue = codes[0];
|
||||
for (int i = 1; i < codes.Count; i++)
|
||||
{
|
||||
checksumValue += codes[i] * i;
|
||||
}
|
||||
checksumValue %= 103;
|
||||
|
||||
// expose checksum (0..102)
|
||||
checksum = checksumValue;
|
||||
|
||||
codes.Add(checksumValue);
|
||||
codes.Add(stop);
|
||||
|
||||
// Convert codes to module pattern widths, alternating bar/space.
|
||||
// Most codes are 6 digits; stop is 7 digits.
|
||||
var modules = new List<int>(codes.Count * 6);
|
||||
foreach (var code in codes)
|
||||
{
|
||||
var p = Code128Patterns[code];
|
||||
for (int i = 0; i < p.Length; i++)
|
||||
{
|
||||
modules.Add(p[i] - '0');
|
||||
}
|
||||
}
|
||||
|
||||
// Code 128 requires a 2-module termination bar after the stop pattern.
|
||||
// Many pattern tables omit it because it can be represented by extending the
|
||||
// final bar of the stop pattern by 2 modules.
|
||||
if (modules.Count > 0)
|
||||
{
|
||||
modules[^1] += 2;
|
||||
}
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
static readonly Dictionary<char, string> Code39Map = new Dictionary<char, string>()
|
||||
{
|
||||
// Each pattern is 9 elements (bar/space alternating, starting with bar).
|
||||
// 'n' = narrow (1), 'w' = wide (2). We expand to digits.
|
||||
['0'] = "nnnwwnwnn",
|
||||
['1'] = "wnnwnnnnw",
|
||||
['2'] = "nnwwnnnnw",
|
||||
['3'] = "wnwwnnnnn",
|
||||
['4'] = "nnnwwnnnw",
|
||||
['5'] = "wnnwwnnnn",
|
||||
['6'] = "nnwwwnnnn",
|
||||
['7'] = "nnnwnnwnw",
|
||||
['8'] = "wnnwnnwnn",
|
||||
['9'] = "nnwwnnwnn",
|
||||
['A'] = "wnnnnwnnw",
|
||||
['B'] = "nnwnnwnnw",
|
||||
['C'] = "wnwnnwnnn",
|
||||
['D'] = "nnnnwwnnw",
|
||||
['E'] = "wnnnwwnnn",
|
||||
['F'] = "nnwnwwnnn",
|
||||
['G'] = "nnnnnwwnw",
|
||||
['H'] = "wnnnnwwnn",
|
||||
['I'] = "nnwnnwwnn",
|
||||
['J'] = "nnnnwwwnn",
|
||||
['K'] = "wnnnnnnww",
|
||||
['L'] = "nnwnnnnww",
|
||||
['M'] = "wnwnnnnwn",
|
||||
['N'] = "nnnnwnnww",
|
||||
['O'] = "wnnnwnnwn",
|
||||
['P'] = "nnwnwnnwn",
|
||||
['Q'] = "nnnnnnwww",
|
||||
['R'] = "wnnnnnwwn",
|
||||
['S'] = "nnwnnnwwn",
|
||||
['T'] = "nnnnwnwwn",
|
||||
['U'] = "wwnnnnnnw",
|
||||
['V'] = "nwwnnnnnw",
|
||||
['W'] = "wwwnnnnnn",
|
||||
['X'] = "nwnnwnnnw",
|
||||
['Y'] = "wwnnwnnnn",
|
||||
['Z'] = "nwwnwnnnn",
|
||||
['-'] = "nwnnnnwnw",
|
||||
['.'] = "wwnnnnwnn",
|
||||
[' '] = "nwwnnnwnn",
|
||||
['$'] = "nwnwnwnnn",
|
||||
['/'] = "nwnwnnnwn",
|
||||
['+'] = "nwnnnwnwn",
|
||||
['%'] = "nnnwnwnwn",
|
||||
['*'] = "nwnnwnwnn", // start/stop
|
||||
};
|
||||
|
||||
public static IReadOnlyList<int> EncodeCode39(string value)
|
||||
{
|
||||
// Code39 traditionally uses uppercase
|
||||
var text = value.ToUpperInvariant();
|
||||
|
||||
foreach (var ch in text)
|
||||
{
|
||||
if (!Code39Map.ContainsKey(ch))
|
||||
{
|
||||
throw new ArgumentException($"Code39 does not support character '{ch}'.");
|
||||
}
|
||||
}
|
||||
|
||||
// Start + data + stop, inter-character gap (narrow space) between characters.
|
||||
var full = "*" + text + "*";
|
||||
var modules = new List<int>(full.Length * 10);
|
||||
|
||||
for (int idx = 0; idx < full.Length; idx++)
|
||||
{
|
||||
var pat = Code39Map[full[idx]];
|
||||
for (int i = 0; i < pat.Length; i++)
|
||||
{
|
||||
modules.Add(pat[i] == 'w' ? 2 : 1);
|
||||
}
|
||||
|
||||
// Inter-character gap (narrow space) except after last char.
|
||||
if (idx != full.Length - 1)
|
||||
{
|
||||
modules.Add(1);
|
||||
}
|
||||
}
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<int> EncodeItf(string value)
|
||||
{
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (digits.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("ITF requires numeric input.");
|
||||
}
|
||||
if (digits.Length % 2 != 0)
|
||||
{
|
||||
// pad with leading zero (common behavior)
|
||||
digits = "0" + digits;
|
||||
}
|
||||
|
||||
const int narrow = 1;
|
||||
const int wide = 3;
|
||||
|
||||
static string Pat(char d) => d switch
|
||||
{
|
||||
'0' => "nnwwn",
|
||||
'1' => "wnnnw",
|
||||
'2' => "nwnnw",
|
||||
'3' => "wwnnn",
|
||||
'4' => "nnwnw",
|
||||
'5' => "wnwnn",
|
||||
'6' => "nwwnn",
|
||||
'7' => "nnnww",
|
||||
'8' => "wnnwn",
|
||||
'9' => "nwnwn",
|
||||
_ => throw new ArgumentException("ITF requires numeric input.")
|
||||
};
|
||||
|
||||
var widths = new List<int>(digits.Length * 10 + 16);
|
||||
|
||||
// Start: narrow bar, narrow space, narrow bar, narrow space (1010)
|
||||
widths.Add(narrow);
|
||||
widths.Add(narrow);
|
||||
widths.Add(narrow);
|
||||
widths.Add(narrow);
|
||||
|
||||
for (int i = 0; i < digits.Length; i += 2)
|
||||
{
|
||||
var a = Pat(digits[i]);
|
||||
var b = Pat(digits[i + 1]);
|
||||
|
||||
for (int j = 0; j < 5; j++)
|
||||
{
|
||||
widths.Add(a[j] == 'w' ? wide : narrow); // bar
|
||||
widths.Add(b[j] == 'w' ? wide : narrow); // space
|
||||
}
|
||||
}
|
||||
|
||||
// Stop: wide bar, narrow space, narrow bar (1101)
|
||||
widths.Add(wide);
|
||||
widths.Add(narrow);
|
||||
widths.Add(narrow);
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<int> EncodeCodabar(string value)
|
||||
{
|
||||
// Wikipedia table mapping (bars: 1=wide, spaces: 0=wide) for the standard symbol set.
|
||||
// Ensure start/stop are present; default to A ... B if missing.
|
||||
var raw = (value ?? string.Empty).Trim().ToUpperInvariant();
|
||||
if (raw.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Codabar requires a non-empty value.");
|
||||
}
|
||||
|
||||
bool HasStartStop(string s)
|
||||
{
|
||||
if (s.Length < 2) return false;
|
||||
bool isStart = s[0] == 'A' || s[0] == 'B' || s[0] == 'C' || s[0] == 'D';
|
||||
bool isStop = s[s.Length - 1] == 'A' || s[s.Length - 1] == 'B' || s[s.Length - 1] == 'C' || s[s.Length - 1] == 'D';
|
||||
return isStart && isStop;
|
||||
}
|
||||
|
||||
var text = HasStartStop(raw) ? raw : $"A{raw}B";
|
||||
|
||||
static (string spaceBits, string barBits) Map(char ch) => ch switch
|
||||
{
|
||||
'0' => ("001", "0001"),
|
||||
'1' => ("001", "0010"),
|
||||
'2' => ("010", "0001"),
|
||||
'3' => ("100", "1000"),
|
||||
'4' => ("001", "0100"),
|
||||
'5' => ("001", "1000"),
|
||||
'6' => ("100", "0001"),
|
||||
'7' => ("100", "0010"),
|
||||
'8' => ("100", "0100"),
|
||||
'9' => ("010", "1000"),
|
||||
'-' => ("010", "0010"),
|
||||
'$' => ("010", "0100"),
|
||||
'.' => ("000", "0001"),
|
||||
'/' => ("000", "0010"),
|
||||
':' => ("000", "0100"),
|
||||
'+' => ("000", "1000"),
|
||||
'A' => ("011", "0100"),
|
||||
'B' => ("110", "0001"),
|
||||
'C' => ("011", "0001"),
|
||||
'D' => ("011", "0010"),
|
||||
_ => throw new ArgumentException($"Codabar does not support character '{ch}'.")
|
||||
};
|
||||
|
||||
const int narrow = 1;
|
||||
const int wide = 3;
|
||||
|
||||
var widths = new List<int>(text.Length * 8);
|
||||
|
||||
for (int idx = 0; idx < text.Length; idx++)
|
||||
{
|
||||
var ch = text[idx];
|
||||
var (spaceBits, barBits) = Map(ch);
|
||||
|
||||
// Bars: 4 bits, 1=wide
|
||||
int BarWidth(int pos) => barBits[pos] == '1' ? wide : narrow;
|
||||
// Spaces: 3 bits, 0=wide (per wikipedia mapping table)
|
||||
int SpaceWidth(int pos) => spaceBits[pos] == '0' ? wide : narrow;
|
||||
|
||||
widths.Add(BarWidth(0));
|
||||
widths.Add(SpaceWidth(0));
|
||||
widths.Add(BarWidth(1));
|
||||
widths.Add(SpaceWidth(1));
|
||||
widths.Add(BarWidth(2));
|
||||
widths.Add(SpaceWidth(2));
|
||||
widths.Add(BarWidth(3));
|
||||
|
||||
// Inter-character narrow space (except after last char).
|
||||
if (idx != text.Length - 1)
|
||||
{
|
||||
widths.Add(narrow);
|
||||
}
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
static readonly string[] EanL = new[]
|
||||
{
|
||||
"0001101","0011001","0010011","0111101","0100011","0110001","0101111","0111011","0110111","0001011"
|
||||
};
|
||||
static readonly string[] EanG = new[]
|
||||
{
|
||||
"0100111","0110011","0011011","0100001","0011101","0111001","0000101","0010001","0001001","0010111"
|
||||
};
|
||||
static readonly string[] EanR = new[]
|
||||
{
|
||||
"1110010","1100110","1101100","1000010","1011100","1001110","1010000","1000100","1001000","1110100"
|
||||
};
|
||||
static readonly string[] Ean13Parity = new[]
|
||||
{
|
||||
"LLLLLL","LLGLGG","LLGGLG","LLGGGL","LGLLGG","LGGLLG","LGGGLL","LGLGLG","LGLGGL","LGGLGL"
|
||||
};
|
||||
|
||||
static int ComputeEanCheckDigit(string digitsWithoutCheck)
|
||||
{
|
||||
// digitsWithoutCheck is 7/11/12 digits depending on symbology.
|
||||
int sum = 0;
|
||||
bool weight3 = true;
|
||||
for (int i = digitsWithoutCheck.Length - 1; i >= 0; i--)
|
||||
{
|
||||
int d = digitsWithoutCheck[i] - '0';
|
||||
sum += weight3 ? d * 3 : d;
|
||||
weight3 = !weight3;
|
||||
}
|
||||
int mod = sum % 10;
|
||||
return (10 - mod) % 10;
|
||||
}
|
||||
|
||||
public static string EncodeEan13(string value, out string checksumText)
|
||||
{
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (digits.Length != 12 && digits.Length != 13)
|
||||
{
|
||||
throw new ArgumentException("EAN-13 requires 12 or 13 digits.");
|
||||
}
|
||||
|
||||
if (digits.Length == 12)
|
||||
{
|
||||
var check = ComputeEanCheckDigit(digits);
|
||||
digits += check.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
var expected = ComputeEanCheckDigit(digits[..12]);
|
||||
if (digits[12] - '0' != expected)
|
||||
{
|
||||
throw new ArgumentException("Invalid EAN-13 check digit.");
|
||||
}
|
||||
}
|
||||
|
||||
checksumText = digits[^1].ToString();
|
||||
|
||||
int first = digits[0] - '0';
|
||||
var parity = Ean13Parity[first];
|
||||
|
||||
var sb = new StringBuilder(95);
|
||||
sb.Append("101");
|
||||
// digits 2..7 (index 1..6)
|
||||
for (int i = 1; i <= 6; i++)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
sb.Append(parity[i - 1] == 'G' ? EanG[d] : EanL[d]);
|
||||
}
|
||||
sb.Append("01010");
|
||||
for (int i = 7; i <= 12; i++)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
sb.Append(EanR[d]);
|
||||
}
|
||||
sb.Append("101");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string EncodeUpcA(string value, out string checksumText)
|
||||
{
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (digits.Length != 11 && digits.Length != 12)
|
||||
{
|
||||
throw new ArgumentException("UPC-A requires 11 or 12 digits.");
|
||||
}
|
||||
|
||||
if (digits.Length == 11)
|
||||
{
|
||||
var check = ComputeEanCheckDigit(digits);
|
||||
digits += check.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
var expected = ComputeEanCheckDigit(digits[..11]);
|
||||
if (digits[11] - '0' != expected)
|
||||
{
|
||||
throw new ArgumentException("Invalid UPC-A check digit.");
|
||||
}
|
||||
}
|
||||
|
||||
checksumText = digits[^1].ToString();
|
||||
|
||||
var sb = new StringBuilder(95);
|
||||
sb.Append("101");
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
sb.Append(EanL[d]);
|
||||
}
|
||||
sb.Append("01010");
|
||||
for (int i = 6; i < 12; i++)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
sb.Append(EanR[d]);
|
||||
}
|
||||
sb.Append("101");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string EncodeEan8(string value, out string checksumText)
|
||||
{
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (digits.Length != 7 && digits.Length != 8)
|
||||
{
|
||||
throw new ArgumentException("EAN-8 requires 7 or 8 digits.");
|
||||
}
|
||||
|
||||
if (digits.Length == 7)
|
||||
{
|
||||
var check = ComputeEanCheckDigit(digits);
|
||||
digits += check.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
var expected = ComputeEanCheckDigit(digits[..7]);
|
||||
if (digits[7] - '0' != expected)
|
||||
{
|
||||
throw new ArgumentException("Invalid EAN-8 check digit.");
|
||||
}
|
||||
}
|
||||
|
||||
checksumText = digits[^1].ToString();
|
||||
|
||||
var sb = new StringBuilder(67);
|
||||
sb.Append("101");
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
sb.Append(EanL[d]);
|
||||
}
|
||||
sb.Append("01010");
|
||||
for (int i = 4; i < 8; i++)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
sb.Append(EanR[d]);
|
||||
}
|
||||
sb.Append("101");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string EncodeIsbnAsEan13(string value, out string checksumText)
|
||||
{
|
||||
var raw = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
|
||||
if (raw.Length == 10)
|
||||
{
|
||||
// ISBN-10 -> EAN-13: 978 + first 9 digits + EAN check
|
||||
var core = raw[..9];
|
||||
if (!core.All(char.IsDigit)) throw new ArgumentException("Invalid ISBN-10.");
|
||||
return EncodeEan13("978" + core, out checksumText);
|
||||
}
|
||||
if (raw.Length == 13)
|
||||
{
|
||||
if (!raw.All(char.IsDigit)) throw new ArgumentException("Invalid ISBN-13.");
|
||||
return EncodeEan13(raw, out checksumText);
|
||||
}
|
||||
|
||||
throw new ArgumentException("ISBN requires 10 or 13 characters.");
|
||||
}
|
||||
|
||||
public static string EncodeIssnAsEan13(string value, out string checksumText)
|
||||
{
|
||||
// ISSN EAN-13: 977 + first 7 digits + 00 + EAN check
|
||||
var raw = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
|
||||
if (raw.Length != 8) throw new ArgumentException("ISSN requires 8 characters.");
|
||||
var core = raw[..7];
|
||||
if (!core.All(char.IsDigit)) throw new ArgumentException("Invalid ISSN.");
|
||||
return EncodeEan13("977" + core + "00", out checksumText);
|
||||
}
|
||||
|
||||
public static (IReadOnlyList<RadzenBarcode.BarcodeRect> bars, double vbWidth) EncodePharmacode(string value, double barHeight, int quietZone)
|
||||
{
|
||||
// Pharmacode one-track: numbers 3..131070
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (!int.TryParse(digits, NumberStyles.None, CultureInfo.InvariantCulture, out var n))
|
||||
{
|
||||
throw new ArgumentException("Pharmacode requires a numeric value.");
|
||||
}
|
||||
if (n < 3 || n > 131070)
|
||||
{
|
||||
throw new ArgumentException("Pharmacode value must be in range 3..131070.");
|
||||
}
|
||||
|
||||
var bars = new List<(int width, bool isWide)>();
|
||||
while (n > 0)
|
||||
{
|
||||
if (n % 2 == 0)
|
||||
{
|
||||
bars.Add((2, true));
|
||||
n = (n - 2) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
bars.Add((1, false));
|
||||
n = (n - 1) / 2;
|
||||
}
|
||||
}
|
||||
bars.Reverse();
|
||||
|
||||
double x = Math.Max(0, quietZone);
|
||||
var rects = new List<RadzenBarcode.BarcodeRect>(bars.Count);
|
||||
foreach (var b in bars)
|
||||
{
|
||||
rects.Add(new RadzenBarcode.BarcodeRect(x, 0, b.width, barHeight));
|
||||
x += b.width + 1; // 1 module gap
|
||||
}
|
||||
|
||||
var vbWidth = x + Math.Max(0, quietZone);
|
||||
if (vbWidth <= 0) vbWidth = 1;
|
||||
return (rects, vbWidth);
|
||||
}
|
||||
|
||||
// POSTNET digit encoding from Wikipedia (weights 7,4,2,1,0). 1=full bar, 0=half bar.
|
||||
static readonly Dictionary<char, string> PostnetDigitBits = new Dictionary<char, string>()
|
||||
{
|
||||
['0'] = "11000",
|
||||
['1'] = "00011",
|
||||
['2'] = "00101",
|
||||
['3'] = "00110",
|
||||
['4'] = "01001",
|
||||
['5'] = "01010",
|
||||
['6'] = "01100",
|
||||
['7'] = "10001",
|
||||
['8'] = "10010",
|
||||
['9'] = "10100",
|
||||
};
|
||||
|
||||
public static (IReadOnlyList<RadzenBarcode.BarcodeRect> bars, double vbWidth) EncodePostnet(string value, double barHeight, int quietZone, out string checksumText)
|
||||
{
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if ((digits.Length != 5 && digits.Length != 9 && digits.Length != 11))
|
||||
{
|
||||
throw new ArgumentException("POSTNET requires 5, 9, or 11 digits (ZIP / ZIP+4 / Delivery Point).");
|
||||
}
|
||||
|
||||
int sum = digits.Sum(ch => ch - '0');
|
||||
int check = (10 - (sum % 10)) % 10;
|
||||
checksumText = check.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
var payload = digits + checksumText;
|
||||
|
||||
double fullH = barHeight;
|
||||
double halfH = barHeight / 2.0;
|
||||
double halfY = fullH - halfH;
|
||||
|
||||
double x = Math.Max(0, quietZone);
|
||||
var rects = new List<RadzenBarcode.BarcodeRect>();
|
||||
|
||||
// Start frame bar (full)
|
||||
rects.Add(new RadzenBarcode.BarcodeRect(x, 0, 1, fullH));
|
||||
x += 2; // bar(1) + space(1)
|
||||
|
||||
foreach (var ch in payload)
|
||||
{
|
||||
var bits = PostnetDigitBits[ch];
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
bool full = bits[i] == '1';
|
||||
rects.Add(full
|
||||
? new RadzenBarcode.BarcodeRect(x, 0, 1, fullH)
|
||||
: new RadzenBarcode.BarcodeRect(x, halfY, 1, halfH));
|
||||
x += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop frame bar (full)
|
||||
rects.Add(new RadzenBarcode.BarcodeRect(x, 0, 1, fullH));
|
||||
x += 1;
|
||||
|
||||
var vbWidth = x + Math.Max(0, quietZone);
|
||||
if (vbWidth <= 0) vbWidth = 1;
|
||||
return (rects, vbWidth);
|
||||
}
|
||||
|
||||
// RM4SCC patterns and symbol matrix from Wikipedia:
|
||||
// Top patterns (values 1..6) and Bottom patterns (values 1..6) are:
|
||||
// 1=0011, 2=0101, 3=0110, 4=1001, 5=1010, 6=1100
|
||||
static readonly string[] Rm4Patterns = new[] { "0011", "0101", "0110", "1001", "1010", "1100" };
|
||||
|
||||
// Matrix indexed by [topValue-1, bottomValue-1]
|
||||
static readonly char[,] Rm4Matrix = new char[6, 6]
|
||||
{
|
||||
{ '0', '1', '2', '3', '4', '5' },
|
||||
{ '6', '7', '8', '9', 'A', 'B' },
|
||||
{ 'C', 'D', 'E', 'F', 'G', 'H' },
|
||||
{ 'I', 'J', 'K', 'L', 'M', 'N' },
|
||||
{ 'O', 'P', 'Q', 'R', 'S', 'T' },
|
||||
{ 'U', 'V', 'W', 'X', 'Y', 'Z' }
|
||||
};
|
||||
|
||||
static readonly Dictionary<char, (string top, string bottom)> Rm4CharToBits = BuildRm4CharToBits();
|
||||
|
||||
static Dictionary<char, (string top, string bottom)> BuildRm4CharToBits()
|
||||
{
|
||||
var dict = new Dictionary<char, (string top, string bottom)>();
|
||||
for (int r = 0; r < 6; r++)
|
||||
{
|
||||
for (int c = 0; c < 6; c++)
|
||||
{
|
||||
dict[Rm4Matrix[r, c]] = (Rm4Patterns[r], Rm4Patterns[c]);
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static (IReadOnlyList<RadzenBarcode.BarcodeRect> bars, double vbWidth) EncodeRm4scc(string value, double barHeight, int quietZone, out string checksumText)
|
||||
{
|
||||
var text = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
throw new ArgumentException("RM4SCC requires alphanumeric input.");
|
||||
}
|
||||
|
||||
// Only symbols present in the Wikipedia table (0-9, A-Z).
|
||||
foreach (var ch in text)
|
||||
{
|
||||
if (!(ch is >= '0' and <= '9') && !(ch is >= 'A' and <= 'Z'))
|
||||
{
|
||||
throw new ArgumentException($"RM4SCC does not support character '{ch}'.");
|
||||
}
|
||||
}
|
||||
|
||||
// Compute checksum per Wikipedia: sum top values and bottom values separately, mod 6, 0 => 6.
|
||||
int sumTop = 0;
|
||||
int sumBottom = 0;
|
||||
foreach (var ch in text)
|
||||
{
|
||||
if (!Rm4CharToBits.TryGetValue(ch, out var bits))
|
||||
{
|
||||
throw new ArgumentException($"RM4SCC does not support character '{ch}'.");
|
||||
}
|
||||
int t = Array.IndexOf(Rm4Patterns, bits.top) + 1;
|
||||
int b = Array.IndexOf(Rm4Patterns, bits.bottom) + 1;
|
||||
sumTop += t;
|
||||
sumBottom += b;
|
||||
}
|
||||
|
||||
int topVal = sumTop % 6;
|
||||
topVal = topVal == 0 ? 6 : topVal;
|
||||
int bottomVal = sumBottom % 6;
|
||||
bottomVal = bottomVal == 0 ? 6 : bottomVal;
|
||||
|
||||
var checkChar = Rm4Matrix[topVal - 1, bottomVal - 1];
|
||||
|
||||
checksumText = checkChar.ToString();
|
||||
|
||||
// Encode start + data + checksum + stop.
|
||||
// Start/stop are single bars; we use ascender for start and descender for stop.
|
||||
double h = barHeight;
|
||||
double third = h / 3.0;
|
||||
double trackerY = third;
|
||||
double trackerH = third;
|
||||
|
||||
RadzenBarcode.BarcodeRect BarRect(double x, bool top, bool bottom)
|
||||
{
|
||||
return (top, bottom) switch
|
||||
{
|
||||
(false, false) => new RadzenBarcode.BarcodeRect(x, trackerY, 1, trackerH), // tracker
|
||||
(true, false) => new RadzenBarcode.BarcodeRect(x, 0, 1, trackerY + trackerH), // ascender (top + tracker)
|
||||
(false, true) => new RadzenBarcode.BarcodeRect(x, trackerY, 1, h - trackerY), // descender (tracker + bottom)
|
||||
(true, true) => new RadzenBarcode.BarcodeRect(x, 0, 1, h), // full
|
||||
};
|
||||
}
|
||||
|
||||
double xPos = Math.Max(0, quietZone);
|
||||
var rects = new List<RadzenBarcode.BarcodeRect>();
|
||||
|
||||
// Start bar (ascender)
|
||||
rects.Add(BarRect(xPos, top: true, bottom: false));
|
||||
xPos += 2;
|
||||
|
||||
void AddSymbol(char ch)
|
||||
{
|
||||
var (topBits, bottomBits) = Rm4CharToBits[ch];
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
bool top = topBits[i] == '1';
|
||||
bool bottom = bottomBits[i] == '1';
|
||||
rects.Add(BarRect(xPos, top, bottom));
|
||||
xPos += 2;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ch in text) AddSymbol(ch);
|
||||
AddSymbol(checkChar);
|
||||
|
||||
// Stop bar (descender)
|
||||
rects.Add(BarRect(xPos, top: false, bottom: true));
|
||||
xPos += 1;
|
||||
|
||||
var vbWidth = xPos + Math.Max(0, quietZone);
|
||||
if (vbWidth <= 0) vbWidth = 1;
|
||||
return (rects, vbWidth);
|
||||
}
|
||||
|
||||
public static string EncodeMsiPlessey(string value, out string checksumText)
|
||||
{
|
||||
var digits = new string(value.Where(char.IsDigit).ToArray());
|
||||
if (digits.Length == 0) throw new ArgumentException("Plessey (MSI) requires numeric input.");
|
||||
|
||||
// Mod 10 (Luhn) check digit (common)
|
||||
int check = ComputeLuhnCheckDigit(digits);
|
||||
checksumText = check.ToString(CultureInfo.InvariantCulture);
|
||||
digits += checksumText;
|
||||
|
||||
// MSI map from Wikipedia.
|
||||
static string DigitMap(char d) => d switch
|
||||
{
|
||||
'0' => "100100100100",
|
||||
'1' => "100100100110",
|
||||
'2' => "100100110100",
|
||||
'3' => "100100110110",
|
||||
'4' => "100110100100",
|
||||
'5' => "100110100110",
|
||||
'6' => "100110110100",
|
||||
'7' => "100110110110",
|
||||
'8' => "110100100100",
|
||||
'9' => "110100100110",
|
||||
_ => throw new ArgumentException("MSI requires numeric input.")
|
||||
};
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("110"); // start
|
||||
foreach (var ch in digits) sb.Append(DigitMap(ch));
|
||||
sb.Append("1001"); // stop
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
static int ComputeLuhnCheckDigit(string digits)
|
||||
{
|
||||
int sum = 0;
|
||||
bool dbl = true;
|
||||
for (int i = digits.Length - 1; i >= 0; i--)
|
||||
{
|
||||
int d = digits[i] - '0';
|
||||
if (dbl)
|
||||
{
|
||||
d *= 2;
|
||||
if (d > 9) d -= 9;
|
||||
}
|
||||
sum += d;
|
||||
dbl = !dbl;
|
||||
}
|
||||
return (10 - (sum % 10)) % 10;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<int> EncodeTelepen(string value, out string checksumText)
|
||||
{
|
||||
// Telepen algorithm per Wikipedia: even parity bytes, little-endian bit order, modulo-127 checksum.
|
||||
var bytes = Encoding.ASCII.GetBytes(value ?? string.Empty);
|
||||
|
||||
int sum = 0;
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
if (bytes[i] > 0x7F) throw new ArgumentException("Telepen supports ASCII only.");
|
||||
sum = (sum + bytes[i]) % 127;
|
||||
}
|
||||
|
||||
int check = (127 - sum) % 127;
|
||||
checksumText = check.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
// Build payload: start '_' + data + checksum byte + stop 'z'
|
||||
var payload = new List<byte>(bytes.Length + 3) { (byte)'_' };
|
||||
payload.AddRange(bytes);
|
||||
payload.Add((byte)check);
|
||||
payload.Add((byte)'z');
|
||||
|
||||
// Build bit stream LSB-first with even parity bit as MSB.
|
||||
var bitStream = new List<int>(payload.Count * 8);
|
||||
foreach (var b0 in payload)
|
||||
{
|
||||
int b = b0 & 0x7F;
|
||||
int ones = CountBits(b);
|
||||
int parityBit = (ones % 2 == 0) ? 0 : 1; // make total even
|
||||
int byteWithParity = b | (parityBit << 7);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
bitStream.Add((byteWithParity >> i) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Encode bit stream into bar/space widths (narrow=1, wide=3).
|
||||
// We produce alternating bar/space widths list, starting with bar.
|
||||
const int narrow = 1;
|
||||
const int wide = 3;
|
||||
var widths = new List<int>();
|
||||
|
||||
int idx = 0;
|
||||
while (idx < bitStream.Count)
|
||||
{
|
||||
if (bitStream[idx] == 1)
|
||||
{
|
||||
// "1" => narrow bar, narrow space
|
||||
widths.Add(narrow);
|
||||
widths.Add(narrow);
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// starts with 0
|
||||
if (idx + 1 < bitStream.Count && bitStream[idx + 1] == 0)
|
||||
{
|
||||
// "00" => wide bar, narrow space
|
||||
widths.Add(wide);
|
||||
widths.Add(narrow);
|
||||
idx += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (idx + 2 < bitStream.Count && bitStream[idx] == 0 && bitStream[idx + 1] == 1 && bitStream[idx + 2] == 0)
|
||||
{
|
||||
// "010" => wide bar, wide space
|
||||
widths.Add(wide);
|
||||
widths.Add(wide);
|
||||
idx += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
// General block 0 1^k 0 with k>=2
|
||||
if (idx + 3 >= bitStream.Count || bitStream[idx] != 0 || bitStream[idx + 1] != 1)
|
||||
{
|
||||
throw new ArgumentException("Invalid Telepen bit stream.");
|
||||
}
|
||||
|
||||
int j = idx + 1;
|
||||
while (j < bitStream.Count && bitStream[j] == 1) j++;
|
||||
if (j >= bitStream.Count || bitStream[j] != 0)
|
||||
{
|
||||
throw new ArgumentException("Invalid Telepen bit stream.");
|
||||
}
|
||||
|
||||
int k = j - (idx + 1); // number of 1s
|
||||
if (k < 2) throw new ArgumentException("Invalid Telepen bit stream.");
|
||||
|
||||
// leading "01" => narrow bar, wide space
|
||||
widths.Add(narrow);
|
||||
widths.Add(wide);
|
||||
|
||||
// middle extra 1s (k-2) => narrow bar, narrow space
|
||||
for (int m = 0; m < k - 2; m++)
|
||||
{
|
||||
widths.Add(narrow);
|
||||
widths.Add(narrow);
|
||||
}
|
||||
|
||||
// trailing "10" => narrow bar, wide space
|
||||
widths.Add(narrow);
|
||||
widths.Add(wide);
|
||||
|
||||
idx = j + 1;
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
static int CountBits(int v)
|
||||
{
|
||||
int c = 0;
|
||||
while (v != 0)
|
||||
{
|
||||
c += v & 1;
|
||||
v >>= 1;
|
||||
}
|
||||
return c;
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
1227
Radzen.Blazor/RadzenBarcodeEncoder.cs
Normal file
1227
Radzen.Blazor/RadzenBarcodeEncoder.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -892,7 +893,7 @@ namespace Radzen.Blazor
|
||||
}
|
||||
}
|
||||
|
||||
void ToggleColumns()
|
||||
async Task ToggleColumns()
|
||||
{
|
||||
if (selectedColumns == null)
|
||||
{
|
||||
@@ -906,8 +907,13 @@ namespace Radzen.Blazor
|
||||
c.SetVisible(selected.Contains(c));
|
||||
}
|
||||
|
||||
PickedColumnsChanged.InvokeAsync(new DataGridPickedColumnsChangedEventArgs<TItem>() { Columns = selected });
|
||||
await PickedColumnsChanged.InvokeAsync(new DataGridPickedColumnsChangedEventArgs<TItem>() { Columns = selected });
|
||||
SaveSettings();
|
||||
|
||||
if (QueryOnlyVisibleColumns)
|
||||
{
|
||||
await Reload();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1627,6 +1633,13 @@ namespace Radzen.Blazor
|
||||
[Parameter]
|
||||
public int ColumnsPickerMaxSelectedLabels { get; set; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether only visible columns are included in the query.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if only visible columns are included; otherwise, <c>false</c>.</value>
|
||||
[Parameter]
|
||||
public bool QueryOnlyVisibleColumns { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column picker all columns text.
|
||||
/// </summary>
|
||||
@@ -2016,7 +2029,8 @@ namespace Radzen.Blazor
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
return QueryOnlyVisibleColumns ? view
|
||||
.Select(allColumns.Where(c => c.GetVisible() && !string.IsNullOrEmpty(c.Property)).Select(c => c.Property)) : view;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2496,7 +2510,7 @@ namespace Radzen.Blazor
|
||||
|
||||
internal Tuple<GroupRowRenderEventArgs, IReadOnlyDictionary<string, object>> GroupRowAttributes(RadzenDataGridGroupRow<TItem> item)
|
||||
{
|
||||
var args = new Radzen.GroupRowRenderEventArgs() { Group = item.Group, FirstRender = firstRender };
|
||||
var args = new Radzen.GroupRowRenderEventArgs() { Group = item.Group, FirstRender = firstRender, Expandable = item.GroupResult.Count > 0 };
|
||||
|
||||
if (GroupRowRender != null)
|
||||
{
|
||||
@@ -3576,6 +3590,59 @@ namespace Radzen.Blazor
|
||||
}
|
||||
}
|
||||
|
||||
if (expandedItems != null)
|
||||
{
|
||||
expandedItems.Clear();
|
||||
}
|
||||
|
||||
if (editedItems != null)
|
||||
{
|
||||
editedItems.Clear();
|
||||
}
|
||||
|
||||
if (editContexts != null)
|
||||
{
|
||||
editContexts.Clear();
|
||||
}
|
||||
|
||||
if (childData != null)
|
||||
{
|
||||
childData.Clear();
|
||||
}
|
||||
|
||||
if (selectedItems != null)
|
||||
{
|
||||
selectedItems.Clear();
|
||||
}
|
||||
|
||||
if (rowSpans != null)
|
||||
{
|
||||
rowSpans.Clear();
|
||||
}
|
||||
|
||||
if (columns != null)
|
||||
{
|
||||
columns.Clear();
|
||||
}
|
||||
|
||||
if (allPickableColumns != null)
|
||||
{
|
||||
allPickableColumns.Clear();
|
||||
}
|
||||
|
||||
if (allColumns != null)
|
||||
{
|
||||
allColumns.Clear();
|
||||
}
|
||||
|
||||
if (childColumns != null)
|
||||
{
|
||||
childColumns.Clear();
|
||||
}
|
||||
|
||||
_value = null;
|
||||
Data = null;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -1310,7 +1310,7 @@ namespace Radzen.Blazor
|
||||
|
||||
internal bool HasCustomFilter()
|
||||
{
|
||||
return GetFilterOperator() == FilterOperator.Custom && GetCustomFilterExpression() != null;
|
||||
return GetFilterOperator() == FilterOperator.Custom && !string.IsNullOrEmpty(GetCustomFilterExpression());
|
||||
}
|
||||
|
||||
internal bool HasActiveFilter()
|
||||
|
||||
@@ -26,9 +26,12 @@
|
||||
}
|
||||
<td class="rz-col-icon">
|
||||
<span class="rz-column-title"></span>
|
||||
<a id="@(Grid.GridId() + Group.GetHashCode())" aria-label=@Grid.ExpandGroupAriaLabel @onclick:preventDefault="true" @onclick="@(_ => Grid.ExpandGroupItem(this, rowArgs?.Item1.Expanded ?? false))">
|
||||
<span class="@(Grid.ExpandedGroupItemStyle(this, Grid.allGroupsExpanded != null ? Grid.allGroupsExpanded : rowArgs?.Item1.Expanded ?? false))"></span>
|
||||
</a>
|
||||
@if (rowArgs?.Item1.Expandable == true)
|
||||
{
|
||||
<a id="@(Grid.GridId() + Group.GetHashCode())" aria-label=@Grid.ExpandGroupAriaLabel @onclick:preventDefault="true" @onclick="@(_ => Grid.ExpandGroupItem(this, rowArgs?.Item1.Expanded))">
|
||||
<span class="@(Grid.ExpandedGroupItemStyle(this, Grid.allGroupsExpanded != null ? Grid.allGroupsExpanded : rowArgs?.Item1.Expanded))"></span>
|
||||
</a>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
<td colspan="@(TotalColumnCount + (Grid?.Groups.Count ?? 0) - 1 - Group.Level + ((Grid?.Template != null && Grid?.ShowExpandColumn == true) ? 1 : 0))">
|
||||
|
||||
@@ -100,7 +100,10 @@ namespace Radzen.Blazor
|
||||
if (AllowFiltering && key.Length == 1 && JSRuntime != null)
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", SearchID);
|
||||
await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, key);
|
||||
if (JSRuntime is not IJSInProcessRuntime)
|
||||
{
|
||||
await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, key);
|
||||
}
|
||||
}
|
||||
|
||||
await OnKeyPress(args, false);
|
||||
|
||||
@@ -349,6 +349,7 @@ namespace Radzen.Blazor
|
||||
skip = page * PageSize;
|
||||
await InvokeAsync(Reload);
|
||||
await PageChanged.InvokeAsync(new PagerEventArgs() { Skip = skip, Top = PageSize, PageIndex = CurrentPage });
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
int n = modules.GetLength(0);
|
||||
int vb = n + 8; // quiet zone
|
||||
|
||||
var backgroundFill = GetSvgFillParts(Background);
|
||||
|
||||
// --- Center Image math (viewBox units == modules incl. quiet zone) ---
|
||||
bool hasImage = !string.IsNullOrWhiteSpace(Image);
|
||||
// Clamp percent for scan reliability (5%..60% of QR inner box)
|
||||
@@ -32,14 +34,14 @@
|
||||
height="@Size"
|
||||
@ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" id="@GetId()">
|
||||
<!-- Background -->
|
||||
<rect x="0" y="0" width="@vb" height="@vb" fill="@Background" />
|
||||
<rect x="0" y="0" width="@vb" height="@vb" fill="@backgroundFill.Color" fill-opacity="@Format(backgroundFill.Opacity)" />
|
||||
|
||||
@if (modules is not null)
|
||||
{
|
||||
<!-- finder patterns / eyes -->
|
||||
@DrawEye(4, 4, EyeShapeTopLeft ?? EyeShape, EyeColorTopLeft ?? EyeColor ?? Foreground)
|
||||
@DrawEye(vb - 11, 4, EyeShapeTopRight ?? EyeShape, EyeColorTopRight ?? EyeColor ?? Foreground)
|
||||
@DrawEye(4, vb - 11, EyeShapeBottomLeft ?? EyeShape, EyeColorBottomLeft ?? EyeColor ?? Foreground)
|
||||
@DrawEye(4, 4, EyeShapeTopLeft ?? EyeShape, EyeColorTopLeft ?? EyeColor ?? Foreground, $"{GetId()}-eye-0")
|
||||
@DrawEye(vb - 11, 4, EyeShapeTopRight ?? EyeShape, EyeColorTopRight ?? EyeColor ?? Foreground, $"{GetId()}-eye-1")
|
||||
@DrawEye(4, vb - 11, EyeShapeBottomLeft ?? EyeShape, EyeColorBottomLeft ?? EyeColor ?? Foreground, $"{GetId()}-eye-2")
|
||||
|
||||
<!-- data modules (skip eye regions) -->
|
||||
@for (var r = 0; r < n; r++)
|
||||
@@ -95,7 +97,7 @@
|
||||
{
|
||||
// Draw a 7x7 eye whose top-left screen coordinate (including quiet zone) is (x,y).
|
||||
// NOTE: x,y are *SVG units* (already offset by quiet zone).
|
||||
private RenderFragment DrawEye(double x, double y, QRCodeEyeShape shape, string color)
|
||||
private RenderFragment DrawEye(double x, double y, QRCodeEyeShape shape, string color, string maskId)
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
@@ -104,10 +106,14 @@
|
||||
{
|
||||
case QRCodeEyeShape.Rounded:
|
||||
{
|
||||
<!-- Outer 7x7 rounded ring -->
|
||||
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" rx="@Format(1.25)" ry="@Format(1.25)" fill="@color" />
|
||||
<!-- Inner hole (background) -->
|
||||
<rect x="@Format(x + 1)" y="@Format(y + 1)" width="@Format(5)" height="@Format(5)" rx="@Format(0.25)" ry="@Format(0.25)" fill="@Background" />
|
||||
<defs>
|
||||
<mask id="@maskId" maskUnits="userSpaceOnUse">
|
||||
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" rx="@Format(1.25)" ry="@Format(1.25)" fill="white" />
|
||||
<rect x="@Format(x + 1)" y="@Format(y + 1)" width="@Format(5)" height="@Format(5)" rx="@Format(0.25)" ry="@Format(0.25)" fill="black" />
|
||||
</mask>
|
||||
</defs>
|
||||
<!-- Outer 7x7 rounded ring with transparent hole -->
|
||||
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" rx="@Format(1.25)" ry="@Format(1.25)" fill="@color" mask="url(#@maskId)" />
|
||||
<!-- Pupil 3x3 rounded -->
|
||||
<rect x="@Format(x + 2)" y="@Format(y + 2)" width="@Format(3)" height="@Format(3)" rx="@Format(0.75)" ry="@Format(0.75)" fill="@color" />
|
||||
}
|
||||
@@ -115,9 +121,14 @@
|
||||
|
||||
case QRCodeEyeShape.Framed:
|
||||
{
|
||||
<!-- Bold square frame (thickness ~1.2) -->
|
||||
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="@color" />
|
||||
<rect x="@Format(x + 1.2)" y="@Format(y + 1.2)" width="@Format(7 - 2 * 1.2)" height="@Format(7 - 2 * 1.2)" fill="@Background" />
|
||||
<defs>
|
||||
<mask id="@maskId" maskUnits="userSpaceOnUse">
|
||||
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="white" />
|
||||
<rect x="@Format(x + 1.2)" y="@Format(y + 1.2)" width="@Format(7 - 2 * 1.2)" height="@Format(7 - 2 * 1.2)" fill="black" />
|
||||
</mask>
|
||||
</defs>
|
||||
<!-- Bold square frame (thickness ~1.2) with transparent hole -->
|
||||
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="@color" mask="url(#@maskId)" />
|
||||
<!-- Pupil -->
|
||||
<rect x="@Format(x + 2)" y="@Format(y + 2)" width="@Format(3)" height="@Format(3)" fill="@color" />
|
||||
}
|
||||
@@ -126,9 +137,14 @@
|
||||
case QRCodeEyeShape.Square:
|
||||
default:
|
||||
{
|
||||
<!-- Classic square: 7x7 outer, 5x5 inner (background), 3x3 pupil -->
|
||||
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="@color" />
|
||||
<rect x="@Format(x + 1)" y="@Format(y + 1)" width="@Format(5)" height="@Format(5)" fill="@Background" />
|
||||
<defs>
|
||||
<mask id="@maskId" maskUnits="userSpaceOnUse">
|
||||
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="white" />
|
||||
<rect x="@Format(x + 1)" y="@Format(y + 1)" width="@Format(5)" height="@Format(5)" fill="black" />
|
||||
</mask>
|
||||
</defs>
|
||||
<!-- Classic square: 7x7 outer with transparent 5x5 hole -->
|
||||
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="@color" mask="url(#@maskId)" />
|
||||
<rect x="@Format(x + 2)" y="@Format(y + 2)" width="@Format(3)" height="@Format(3)" fill="@color" />
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using Radzen.Blazor.Rendering;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Radzen.Blazor
|
||||
{
|
||||
@@ -88,19 +89,19 @@ namespace Radzen.Blazor
|
||||
/// <value>The size in CSS units. Default is "100%".</value>
|
||||
[Parameter] public string Size { get; set; } = "100%";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the QR code modules (the dark squares/dots).
|
||||
/// Supports any valid CSS color. Use high contrast with background for best scanability.
|
||||
/// </summary>
|
||||
/// <value>The foreground color. Default is "#000" (black).</value>
|
||||
[Parameter] public string Foreground { get; set; } = "#000";
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the QR code modules (the dark squares/dots).
|
||||
/// Supports any valid CSS color. Use high contrast with background for best scanability.
|
||||
/// </summary>
|
||||
/// <value>The foreground color. Default is "#000" (black).</value>
|
||||
[Parameter] public string Foreground { get; set; } = "#000";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the background color of the QR code.
|
||||
/// Should contrast well with the foreground color for reliable scanning.
|
||||
/// </summary>
|
||||
/// <value>The background color. Default is "#FFF" (white).</value>
|
||||
[Parameter] public string Background { get; set; } = "#FFF";
|
||||
/// <summary>
|
||||
/// Gets or sets the background color of the QR code.
|
||||
/// Should contrast well with the foreground color for reliable scanning.
|
||||
/// </summary>
|
||||
/// <value>The background color. Default is "#FFF" (white).</value>
|
||||
[Parameter] public string Background { get; set; } = "#FFF";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the visual shape of the QR code modules (data squares).
|
||||
@@ -156,6 +157,28 @@ namespace Radzen.Blazor
|
||||
/// </summary>
|
||||
protected override string GetComponentCssClass() => "rz-qrcode";
|
||||
private static string Format(double v) => v.ToString(CultureInfo.InvariantCulture);
|
||||
private static (string Color, double Opacity) GetSvgFillParts(string? color)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(color))
|
||||
{
|
||||
return ("none", 1);
|
||||
}
|
||||
|
||||
if (string.Equals(color, "transparent", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ("rgb(0, 0, 0)", 0);
|
||||
}
|
||||
|
||||
var rgb = RGB.Parse(color);
|
||||
if (rgb == null)
|
||||
{
|
||||
return (color, 1);
|
||||
}
|
||||
|
||||
var opacity = Math.Clamp(rgb.Alpha, 0, 1);
|
||||
var fill = $"rgb({Format(rgb.Red)}, {Format(rgb.Green)}, {Format(rgb.Blue)})";
|
||||
return (fill, opacity);
|
||||
}
|
||||
private static bool IsFinderCell(int r, int c, int n)
|
||||
{
|
||||
bool inTL = r < 7 && c < 7;
|
||||
@@ -163,6 +186,21 @@ namespace Radzen.Blazor
|
||||
bool inBL = r >= n - 7 && c < 7;
|
||||
return inTL || inTR || inBL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the SVG markup of the rendered QR code as a string.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{String}"/> representing the asynchronous operation. The task result contains the SVG markup of the QR code.
|
||||
/// </returns>
|
||||
public async Task<string> ToSvg()
|
||||
{
|
||||
if (JSRuntime != null)
|
||||
{
|
||||
return await JSRuntime.InvokeAsync<string>("Radzen.outerHTML", Element);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Radzen.Blazor.Rendering;
|
||||
using System.Text;
|
||||
|
||||
namespace Radzen.Blazor;
|
||||
@@ -29,7 +30,10 @@ public enum RadzenQREcc
|
||||
High
|
||||
}
|
||||
|
||||
internal static class RadzenQREncoder
|
||||
/// <summary>
|
||||
/// Provides QR encoding utilities for UTF-8 strings and raw bytes.
|
||||
/// </summary>
|
||||
public static class RadzenQREncoder
|
||||
{
|
||||
/// <summary>Encode a UTF-8 string into a QR module matrix.</summary>
|
||||
public static bool[,] EncodeUtf8(string value, RadzenQREcc ecc, int minVersion = 1, int maxVersion = 40)
|
||||
@@ -93,6 +97,8 @@ internal static class RadzenQREncoder
|
||||
/// <summary>Encode raw bytes into a QR module matrix.</summary>
|
||||
public static bool[,] EncodeBytes(byte[] data, RadzenQREcc ecc = RadzenQREcc.Medium, int minVersion = 1, int maxVersion = 40)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(data);
|
||||
|
||||
if (minVersion < 1 || maxVersion > 40 || minVersion > maxVersion)
|
||||
throw new ArgumentOutOfRangeException(nameof(minVersion), "Version range must be within 1..40");
|
||||
|
||||
@@ -152,24 +158,93 @@ internal static class RadzenQREncoder
|
||||
}
|
||||
|
||||
/// <summary>Render a module matrix into an SVG string with a 4-module quiet zone.</summary>
|
||||
public static string ToSvg(bool[,] modules, int moduleSize = 8, string foreground = "#000000", string background = "#FFFFFF")
|
||||
public static string ToSvg(
|
||||
bool[,] modules,
|
||||
int moduleSize = 8,
|
||||
string foreground = "#000000",
|
||||
string background = "#FFFFFF",
|
||||
QRCodeModuleShape moduleShape = QRCodeModuleShape.Square,
|
||||
QRCodeEyeShape eyeShape = QRCodeEyeShape.Square,
|
||||
QRCodeEyeShape? eyeShapeTopLeft = null,
|
||||
QRCodeEyeShape? eyeShapeTopRight = null,
|
||||
QRCodeEyeShape? eyeShapeBottomLeft = null,
|
||||
string? eyeColor = null,
|
||||
string? eyeColorTopLeft = null,
|
||||
string? eyeColorTopRight = null,
|
||||
string? eyeColorBottomLeft = null,
|
||||
string? image = null,
|
||||
string imageBackground = "#FFF")
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(modules);
|
||||
ArgumentNullException.ThrowIfNull(foreground);
|
||||
ArgumentNullException.ThrowIfNull(background);
|
||||
ArgumentNullException.ThrowIfNull(imageBackground);
|
||||
|
||||
int n = modules.GetLength(0);
|
||||
int vb = n + 8; // 4 modules of quiet zone on each side
|
||||
int px = vb * moduleSize;
|
||||
|
||||
var sb = new StringBuilder(n * n + 1024);
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{px}\" height=\"{px}\" viewBox=\"0 0 {vb} {vb}\" shape-rendering=\"crispEdges\">");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"0\" y=\"0\" width=\"{vb}\" height=\"{vb}\" fill=\"{background}\"/>");
|
||||
var backgroundFill = GetSvgFillParts(background);
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"0\" y=\"0\" width=\"{vb}\" height=\"{vb}\" fill=\"{backgroundFill.Color}\" fill-opacity=\"{Format(backgroundFill.Opacity)}\"/>");
|
||||
|
||||
var baseEyeColor = eyeColor ?? foreground;
|
||||
AppendEye(sb, 4, 4, eyeShapeTopLeft ?? eyeShape, eyeColorTopLeft ?? baseEyeColor, "eye-mask-0");
|
||||
AppendEye(sb, vb - 11, 4, eyeShapeTopRight ?? eyeShape, eyeColorTopRight ?? baseEyeColor, "eye-mask-1");
|
||||
AppendEye(sb, 4, vb - 11, eyeShapeBottomLeft ?? eyeShape, eyeColorBottomLeft ?? baseEyeColor, "eye-mask-2");
|
||||
|
||||
for (int r = 0; r < n; r++)
|
||||
{
|
||||
for (int c = 0; c < n; c++)
|
||||
{
|
||||
if (modules[r, c])
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{c + 4}\" y=\"{r + 4}\" width=\"1\" height=\"1\" fill=\"{foreground}\"/>");
|
||||
if (!modules[r, c]) continue;
|
||||
if (IsFinderCell(r, c, n)) continue;
|
||||
|
||||
var x = c + 4;
|
||||
var y = r + 4;
|
||||
|
||||
if (moduleShape == QRCodeModuleShape.Circle)
|
||||
{
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<circle cx=\"{Format(x + 0.5)}\" cy=\"{Format(y + 0.5)}\" r=\"0.5\" fill=\"{foreground}\"/>");
|
||||
}
|
||||
else if (moduleShape == QRCodeModuleShape.Rounded)
|
||||
{
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"1\" height=\"1\" rx=\"0.25\" ry=\"0.25\" fill=\"{foreground}\"/>");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"1\" height=\"1\" fill=\"{foreground}\"/>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(image))
|
||||
{
|
||||
const double imageSizePercent = 20;
|
||||
const double imagePaddingModules = 1.0;
|
||||
const double imageCornerRadius = 0.75;
|
||||
const double imageBackgroundOpacity = 1.0;
|
||||
|
||||
double pct = Math.Clamp(imageSizePercent, 5, 60);
|
||||
double boxModules = Math.Max(5, Math.Round(n * (pct / 100.0)));
|
||||
double pad = Math.Max(0, imagePaddingModules);
|
||||
|
||||
double cutoutW = boxModules + 2 * pad;
|
||||
double cutoutH = boxModules + 2 * pad;
|
||||
|
||||
double centerX = vb / 2.0;
|
||||
double centerY = vb / 2.0;
|
||||
|
||||
double cutoutX = centerX - cutoutW / 2.0;
|
||||
double cutoutY = centerY - cutoutH / 2.0;
|
||||
double imgX = centerX - boxModules / 2.0;
|
||||
double imgY = centerY - boxModules / 2.0;
|
||||
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(cutoutX)}\" y=\"{Format(cutoutY)}\" width=\"{Format(cutoutW)}\" height=\"{Format(cutoutH)}\" rx=\"{Format(imageCornerRadius)}\" ry=\"{Format(imageCornerRadius)}\" fill=\"{imageBackground}\" fill-opacity=\"{Format(Math.Clamp(imageBackgroundOpacity, 0, 1))}\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<image x=\"{Format(imgX)}\" y=\"{Format(imgY)}\" width=\"{Format(boxModules)}\" height=\"{Format(boxModules)}\" preserveAspectRatio=\"xMidYMid meet\" href=\"{image}\"/>");
|
||||
}
|
||||
|
||||
sb.Append("</svg>");
|
||||
return sb.ToString();
|
||||
}
|
||||
@@ -1065,4 +1140,69 @@ internal static class RadzenQREncoder
|
||||
for (int i = len - 1; i >= 0; i--) this.Add((val >> i) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsFinderCell(int r, int c, int n)
|
||||
{
|
||||
bool inTL = r < 7 && c < 7;
|
||||
bool inTR = r < 7 && c >= n - 7;
|
||||
bool inBL = r >= n - 7 && c < 7;
|
||||
return inTL || inTR || inBL;
|
||||
}
|
||||
|
||||
private static void AppendEye(StringBuilder sb, double x, double y, QRCodeEyeShape shape, string color, string maskId)
|
||||
{
|
||||
switch (shape)
|
||||
{
|
||||
case QRCodeEyeShape.Rounded:
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<defs><mask id=\"{maskId}\" maskUnits=\"userSpaceOnUse\">");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" rx=\"1.25\" ry=\"1.25\" fill=\"white\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" rx=\"0.25\" ry=\"0.25\" fill=\"black\"/>");
|
||||
sb.Append("</mask></defs>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" rx=\"1.25\" ry=\"1.25\" fill=\"{color}\" mask=\"url(#{maskId})\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 2)}\" y=\"{Format(y + 2)}\" width=\"3\" height=\"3\" rx=\"0.75\" ry=\"0.75\" fill=\"{color}\"/>");
|
||||
break;
|
||||
case QRCodeEyeShape.Framed:
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<defs><mask id=\"{maskId}\" maskUnits=\"userSpaceOnUse\">");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"white\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1.2)}\" y=\"{Format(y + 1.2)}\" width=\"{Format(7 - 2 * 1.2)}\" height=\"{Format(7 - 2 * 1.2)}\" fill=\"black\"/>");
|
||||
sb.Append("</mask></defs>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\" mask=\"url(#{maskId})\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 2)}\" y=\"{Format(y + 2)}\" width=\"3\" height=\"3\" fill=\"{color}\"/>");
|
||||
break;
|
||||
case QRCodeEyeShape.Square:
|
||||
default:
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<defs><mask id=\"{maskId}\" maskUnits=\"userSpaceOnUse\">");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"white\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" fill=\"black\"/>");
|
||||
sb.Append("</mask></defs>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\" mask=\"url(#{maskId})\"/>");
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 2)}\" y=\"{Format(y + 2)}\" width=\"3\" height=\"3\" fill=\"{color}\"/>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static (string Color, double Opacity) GetSvgFillParts(string? color)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(color))
|
||||
{
|
||||
return ("none", 1);
|
||||
}
|
||||
|
||||
if (string.Equals(color, "transparent", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ("rgb(0, 0, 0)", 0);
|
||||
}
|
||||
|
||||
var rgb = RGB.Parse(color);
|
||||
if (rgb == null)
|
||||
{
|
||||
return (color, 1);
|
||||
}
|
||||
|
||||
var opacity = Math.Clamp(rgb.Alpha, 0, 1);
|
||||
var fill = $"rgb({Format(rgb.Red)}, {Format(rgb.Green)}, {Format(rgb.Blue)})";
|
||||
return (fill, opacity);
|
||||
}
|
||||
|
||||
private static string Format(double v) => v.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<Line class="rz-line" X1="@x1" Y1="@y1" X2="@x1" Y2="@y2" Stroke="@YAxis.Stroke" StrokeWidth="@YAxis.StrokeWidth" LineType="@YAxis.LineType" />
|
||||
@for (var idx = start; idx <= end; idx += step)
|
||||
{
|
||||
var value = Chart?.ValueScale?.Value(idx) ?? 0;
|
||||
var value = Chart?.ValueScale?.Value(idx);
|
||||
var y = Chart?.ValueScale?.Scale(idx) ?? 0;
|
||||
var text = YAxis != null && Chart?.ValueScale != null ? YAxis.Format(Chart.ValueScale, value) : "";
|
||||
var text = value != null && YAxis != null && Chart?.ValueScale != null ? YAxis.Format(Chart.ValueScale, value) : "";
|
||||
|
||||
if (YAxis?.Ticks?.Template != null)
|
||||
{
|
||||
@@ -18,7 +18,7 @@
|
||||
@YAxis.Ticks.Template(context)
|
||||
</ValueAxisTick>
|
||||
}
|
||||
else
|
||||
else if (!String.IsNullOrEmpty(text))
|
||||
{
|
||||
<ValueAxisTick X="@x1" Y="@y" Text="@text" Stroke="@(YAxis?.Ticks?.Stroke ?? YAxis?.Stroke)" StrokeWidth="@(YAxis?.Ticks?.StrokeWidth ?? 0)" LineType="@(YAxis?.Ticks?.LineType ?? LineType.Solid)"/>
|
||||
}
|
||||
|
||||
@@ -38,6 +38,17 @@ window.Radzen = {
|
||||
}
|
||||
};
|
||||
},
|
||||
downloadFile: function (fileName, data, mimeType) {
|
||||
const blob = new Blob([data], { type: mimeType });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
a.click();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
},
|
||||
mask: function (id, mask, pattern, characterPattern) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) {
|
||||
@@ -2315,6 +2326,12 @@ window.Radzen = {
|
||||
var rect = el.getBoundingClientRect();
|
||||
return { left: rect.left, top: rect.top, width: rect.width, height: rect.height };
|
||||
},
|
||||
outerHTML: function (arg) {
|
||||
var el = arg instanceof Element || arg instanceof HTMLDocument
|
||||
? arg
|
||||
: document.getElementById(arg);
|
||||
return el ? el.outerHTML : '';
|
||||
},
|
||||
endDrag: function (ref) {
|
||||
document.removeEventListener('mousemove', ref.mouseMoveHandler);
|
||||
document.removeEventListener('mouseup', ref.mouseUpHandler);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<RadzenRow Gap="2rem">
|
||||
<RadzenColumn Size="12" SizeMD="6">
|
||||
<RadzenStack AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Style="height: 100%;">
|
||||
<RadzenBarcode Value="@value"
|
||||
<RadzenBarcode @ref="barCode" Value="@value"
|
||||
Type="@type"
|
||||
Foreground="@foreground"
|
||||
Background="@background"
|
||||
@@ -79,6 +81,11 @@
|
||||
<RadzenTextArea @bind-Value="@valueStyle" class="rz-w-100" Rows="3" />
|
||||
</RadzenFormField>
|
||||
|
||||
<RadzenButton Text="Save SVG"
|
||||
Icon="download"
|
||||
ButtonStyle="ButtonStyle.Primary"
|
||||
Click="@(_ => SaveSvg())" />
|
||||
|
||||
<RadzenText TextStyle="TextStyle.Caption">
|
||||
Tip: some types require numeric-only input (EAN/UPC/ITF/MSI/POSTNET/Pharmacode). Changing the type auto-fills a valid sample value.
|
||||
</RadzenText>
|
||||
@@ -87,6 +94,7 @@
|
||||
</RadzenRow>
|
||||
|
||||
@code {
|
||||
RadzenBarcode barCode;
|
||||
RadzenBarcodeType type = RadzenBarcodeType.Code128;
|
||||
IEnumerable<RadzenBarcodeType> types = Enum.GetValues<RadzenBarcodeType>();
|
||||
|
||||
@@ -123,6 +131,19 @@
|
||||
_ => value
|
||||
};
|
||||
}
|
||||
|
||||
async Task SaveSvg(bool custom = false)
|
||||
{
|
||||
var svg = custom ? RadzenBarcodeEncoder.ToSvg(
|
||||
type,
|
||||
value,
|
||||
barHeight: barHeight,
|
||||
quietZoneModules: quietZone,
|
||||
foreground: foreground,
|
||||
background: background) : await barCode.ToSvg();
|
||||
|
||||
await JS.InvokeVoidAsync("Radzen.downloadFile", "barcode.svg", svg, "image/svg+xml;charset=utf-8");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
ColumnWidth="300px"
|
||||
AllowColumnPicking="true"
|
||||
PickedColumnsChanged="@PickedColumnsChanged"
|
||||
ColumnsPickerAllowFiltering="true"
|
||||
>
|
||||
ColumnsPickerAllowFiltering="true" QueryOnlyVisibleColumns="true">
|
||||
<Columns>
|
||||
<RadzenDataGridColumn
|
||||
Property=@nameof(Employee.EmployeeID)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
Enable default column picker by setting the <strong>AllowColumnPicking</strong> grid property to true.
|
||||
You can disable picking for specific columns by setting their <strong>Pickable</strong> property to false.
|
||||
The example below also sets <strong>ColumnsPickerAllowFiltering</strong> on the grid to make picking columns easier.
|
||||
Use <strong>QueryOnlyVisibleColumns</strong> to tell the grid to only include currently visible columns in the IQueryable query.
|
||||
</RadzenText>
|
||||
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P" class="rz-pb-4">
|
||||
Documentation:
|
||||
|
||||
@@ -85,6 +85,8 @@
|
||||
{
|
||||
args.Expanded = allGroupsExpanded != null ? allGroupsExpanded : false;
|
||||
}
|
||||
|
||||
args.Expandable = args.Group.Data.Key != "Vice President, Sales";
|
||||
}
|
||||
|
||||
void OnGroupRowExpand(Group group)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<RadzenRow Gap="2rem" AlignItems="AlignItems.Start">
|
||||
<RadzenColumn Size="12" SizeMD="6">
|
||||
<RadzenStack AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Style="height: 100%;">
|
||||
<RadzenQRCode Value="Radzen Blazor"
|
||||
<RadzenQRCode @ref="qrCode" Value="Radzen Blazor"
|
||||
Foreground="@foreground"
|
||||
Background="@background"
|
||||
EyeColor="@eyeColor"
|
||||
@@ -109,11 +110,18 @@
|
||||
</RadzenFormField>
|
||||
</RadzenStack>
|
||||
</RadzenFieldset>
|
||||
|
||||
<RadzenButton Text="Save SVG"
|
||||
Icon="download"
|
||||
ButtonStyle="ButtonStyle.Primary"
|
||||
Click="@(_ => SaveSvg())" />
|
||||
</RadzenStack>
|
||||
</RadzenColumn>
|
||||
</RadzenRow>
|
||||
|
||||
@code {
|
||||
RadzenQRCode qrCode;
|
||||
|
||||
string foreground = "#0f62fe";
|
||||
string background = "#eef4ff";
|
||||
|
||||
@@ -158,4 +166,28 @@
|
||||
bottomLeftEyeColor = null;
|
||||
imageBackground = "#FFFFFF";
|
||||
}
|
||||
}
|
||||
|
||||
async Task SaveSvg(bool custom = false)
|
||||
{
|
||||
const string value = "Radzen Blazor";
|
||||
var modules = RadzenQREncoder.EncodeUtf8(value, RadzenQREcc.Quartile);
|
||||
var svg = custom ? RadzenQREncoder.ToSvg(
|
||||
modules,
|
||||
moduleSize: 8,
|
||||
foreground: foreground,
|
||||
background: background,
|
||||
moduleShape: moduleShape,
|
||||
eyeShape: eyeShape,
|
||||
eyeShapeTopLeft: topLeftEyeShape,
|
||||
eyeShapeTopRight: topRightEyeShape,
|
||||
eyeShapeBottomLeft: bottomLeftEyeShape,
|
||||
eyeColor: eyeColor,
|
||||
eyeColorTopLeft: topLeftEyeColor,
|
||||
eyeColorTopRight: topRightEyeColor,
|
||||
eyeColorBottomLeft: bottomLeftEyeColor,
|
||||
image: imageUrl,
|
||||
imageBackground: imageBackground) : await qrCode.ToSvg();
|
||||
|
||||
await JS.InvokeVoidAsync("Radzen.downloadFile", "qrcode.svg", svg, "image/svg+xml;charset=utf-8");
|
||||
}
|
||||
}
|
||||
@@ -2025,15 +2025,6 @@ namespace RadzenBlazorDemos
|
||||
Description = "This example demonstrates different color schemes, custom colors and styling of Radzen Blazor Chart component.",
|
||||
Tags = new [] { "chart", "graph", "styling" }
|
||||
},
|
||||
new Example
|
||||
{
|
||||
Name = "Spider Chart",
|
||||
Path = "spider-chart",
|
||||
Title = "Blazor Spider Chart Component | Free UI Components by Radzen",
|
||||
Description = "Radzen Blazor Spider Chart for displaying multivariate data in a radial format.",
|
||||
Tags = new [] { "spider", "radar", "chart", "multivariate", "radial", "web" },
|
||||
New = true
|
||||
},
|
||||
}
|
||||
},
|
||||
new Example
|
||||
@@ -2045,6 +2036,16 @@ namespace RadzenBlazorDemos
|
||||
Tags = new [] { "chart", "sparkline" }
|
||||
},
|
||||
new Example
|
||||
{
|
||||
Name = "Spider Chart",
|
||||
Path = "spider-chart",
|
||||
Title = "Blazor Spider Chart Component | Free UI Components by Radzen",
|
||||
Description = "Radzen Blazor Spider Chart for displaying multivariate data in a radial format.",
|
||||
Tags = new [] { "spider", "radar", "chart", "multivariate", "radial", "web" },
|
||||
Icon = "\ueb39",
|
||||
New = true
|
||||
},
|
||||
new Example
|
||||
{
|
||||
Name = "Arc Gauge",
|
||||
Path = "arc-gauge",
|
||||
|
||||
Reference in New Issue
Block a user