Changed to return value container for available cap value to prevent long range enumerations.

This commit is contained in:
Eugene Wang
2025-11-20 13:18:21 -05:00
parent 36c7dd11ca
commit 8949b36833
7 changed files with 229 additions and 147 deletions

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<!--change these in each release--> <!--change these in each release-->
<VersionPrefix>4.0.0.0</VersionPrefix> <VersionPrefix>4.0.0.0</VersionPrefix>
<VersionSuffix>alpha.16</VersionSuffix> <VersionSuffix>alpha.17</VersionSuffix>
<!--keep it the same until major # changes--> <!--keep it the same until major # changes-->
<AssemblyVersion>4.0.0.0</AssemblyVersion> <AssemblyVersion>4.0.0.0</AssemblyVersion>

View File

@@ -4,101 +4,97 @@ using System.Collections.Generic;
namespace NTwain.Caps namespace NTwain.Caps
{ {
/// <summary>
///
/// </summary>
/// <typeparam name="TValue"></typeparam>
public class CapReader<TValue> where TValue : struct
{
protected readonly TwainAppSession _twain;
public CapReader(TwainAppSession twain, CAP cap, float introducedVersion = 1)
{
_twain = twain;
Cap = cap;
Introduced = introducedVersion;
}
public CAP Cap { get; }
/// <summary> /// <summary>
/// When this was introduced in TWAIN. ///
/// </summary> /// </summary>
public float Introduced { get; } /// <typeparam name="TValue"></typeparam>
public class CapReader<TValue> where TValue : struct
/// <summary>
/// The STS result from the most recent call with this cap wrapper.
/// </summary>
public STS LastSTS { get; protected set; }
TWQC? _qc;
public TWQC Supports
{ {
get protected readonly TwainAppSession _twain;
{
if (!_qc.HasValue) _qc = _twain.QueryCapSupport(Cap);
return _qc.Value;
}
}
public IList<TValue> Get() public CapReader(TwainAppSession twain, CAP cap, float introducedVersion = 1)
{ {
LastSTS = _twain.GetCapValues(Cap, out IList<TValue> values); _twain = twain;
if (LastSTS.IsSuccess) Cap = cap;
{ Introduced = introducedVersion;
return values; }
};
return Array.Empty<TValue>();
}
public IList<TValue> GetCurrent() public CAP Cap { get; }
{
LastSTS = _twain.GetCapCurrent(Cap, out List<TValue> value);
if (LastSTS.IsSuccess)
{
return value;
};
return Array.Empty<TValue>();
}
public IList<TValue> GetDefault() /// <summary>
{ /// When this was introduced in TWAIN.
LastSTS = _twain.GetCapDefault(Cap, out List<TValue> value); /// </summary>
if (LastSTS.IsSuccess) public float Introduced { get; }
{
return value;
};
return Array.Empty<TValue>();
}
public string? GetLabel() /// <summary>
{ /// The STS result from the most recent call with this cap wrapper.
LastSTS = _twain.GetCapLabel(Cap, out string? value); /// </summary>
if (LastSTS.IsSuccess) public STS LastSTS { get; protected set; }
{
return value;
};
return default;
}
public string? GetHelp() TWQC? _qc;
{ public TWQC Supports
LastSTS = _twain.GetCapHelp(Cap, out string? value); {
if (LastSTS.IsSuccess) get
{ {
return value; if (!_qc.HasValue) _qc = _twain.QueryCapSupport(Cap);
}; return _qc.Value;
return default; }
} }
public IList<string> GetLabelEnum() public ValueContainer<TValue> Get()
{ {
LastSTS = _twain.GetCapLabelEnum(Cap, out IList<string> value); LastSTS = _twain.GetCapValues(Cap, out ValueContainer<TValue> value);
if (LastSTS.IsSuccess) return value;
{ }
return value;
}; public IList<TValue> GetCurrent()
return Array.Empty<string>(); {
LastSTS = _twain.GetCapCurrent(Cap, out List<TValue> value);
if (LastSTS.IsSuccess)
{
return value;
}
return Array.Empty<TValue>();
}
public IList<TValue> GetDefault()
{
LastSTS = _twain.GetCapDefault(Cap, out List<TValue> value);
if (LastSTS.IsSuccess)
{
return value;
}
return Array.Empty<TValue>();
}
public string? GetLabel()
{
LastSTS = _twain.GetCapLabel(Cap, out string? value);
if (LastSTS.IsSuccess)
{
return value;
}
return default;
}
public string? GetHelp()
{
LastSTS = _twain.GetCapHelp(Cap, out string? value);
if (LastSTS.IsSuccess)
{
return value;
}
return default;
}
public IList<string> GetLabelEnum()
{
LastSTS = _twain.GetCapLabelEnum(Cap, out IList<string> value);
if (LastSTS.IsSuccess)
{
return value;
}
return Array.Empty<string>();
}
} }
}
} }

View File

@@ -0,0 +1,66 @@
using NTwain.Data;
using System.Collections.Generic;
using System.Linq;
namespace NTwain.Caps;
public record ValueContainer<TValue> where TValue : struct
{
public TWON ContainerType { get; set; }
public TValue? OneValue { get; set; }
public IList<TValue>? ArrayValue { get; set; }
public EnumValue<TValue>? EnumValue { get; set; }
public RangeValue<TValue>? RangeValue { get; set; }
public IEnumerable<TValue> GetValues()
{
return ContainerType switch
{
TWON.ONEVALUE => OneValue.HasValue ? ToEnumerable(OneValue.Value) : [],
TWON.ARRAY => ArrayValue ?? [],
TWON.ENUMERATION => EnumValue?.Items ?? [],
TWON.RANGE => RangeValue != null ? GenerateRangeValues(RangeValue) : [],
_ => [],
};
}
private IEnumerable<TValue> ToEnumerable(TValue value)
{
yield return value;
}
private IEnumerable<TValue> GenerateRangeValues(RangeValue<TValue> range)
{
var de = new DynamicEnumerator<TValue>(range.Min, range.Max, range.Step);
while (de.MoveNext())
{
yield return de.Current;
}
}
}
public record EnumValue<TValue> where TValue : struct
{
public TValue[] Items { get; set; } = [];
public int CurrentIndex { get; set; }
public int DefaultIndex { get; set; }
}
public record RangeValue<TValue> where TValue : struct
{
public TValue Min { get; set; }
public TValue Max { get; set; }
public TValue Step { get; set; }
public TValue DefaultValue;
public TValue CurrentValue;
}

View File

@@ -0,0 +1,48 @@
using System.Collections.Generic;
namespace NTwain.Data;
// dynamic is a cheap hack to sidestep the compiler restrictions if I know TValue is numeric
class DynamicEnumerator<TValue> : IEnumerator<TValue> where TValue : struct
{
private readonly TValue _min;
private readonly TValue _max;
private readonly TValue _step;
private TValue _cur;
bool started = false;
public DynamicEnumerator(TValue min, TValue max, TValue step)
{
_min = min;
_max = max;
_step = step;
_cur = min;
}
public TValue Current => _cur;
object System.Collections.IEnumerator.Current => this.Current;
public void Dispose() { }
public bool MoveNext()
{
if (!started)
{
started = true;
return true;
}
var next = _cur + (dynamic)_step;
if (next == _cur || next < _min || next > _max) return false;
_cur = next;
return true;
}
public void Reset()
{
_cur = _min;
started = false;
}
}

View File

@@ -2590,7 +2590,9 @@ namespace NTwain.Data
ICONID = 962, ICONID = 962,
DSMID = 461, DSMID = 461,
DSMCODEID = 63 DSMCODEID = 63,
DONTCARE = 0xffff
} }
///// <summary> ///// <summary>

View File

@@ -284,58 +284,13 @@ namespace NTwain.Data
if (MinValue is not IConvertible) if (MinValue is not IConvertible)
throw new NotSupportedException($"The value type {typeof(TValue).Name} is not supported for range enumeration."); throw new NotSupportedException($"The value type {typeof(TValue).Name} is not supported for range enumeration.");
return new DynamicEnumerator(MinValue, MaxValue, StepSize); return new DynamicEnumerator<TValue>(MinValue, MaxValue, StepSize);
} }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{ {
return ((IEnumerable<TValue>)this).GetEnumerator(); return ((IEnumerable<TValue>)this).GetEnumerator();
} }
// dynamic is a cheap hack to sidestep the compiler restrictions if I know TValue is numeric
class DynamicEnumerator : IEnumerator<TValue>
{
private readonly TValue _min;
private readonly TValue _max;
private readonly TValue _step;
private TValue _cur;
bool started = false;
public DynamicEnumerator(TValue min, TValue max, TValue step)
{
_min = min;
_max = max;
_step = step;
_cur = min;
}
public TValue Current => _cur;
object System.Collections.IEnumerator.Current => this.Current;
public void Dispose() { }
public bool MoveNext()
{
if (!started)
{
started = true;
return true;
}
var next = _cur + (dynamic)_step;
if (next == _cur || next < _min || next > _max) return false;
_cur = next;
return true;
}
public void Reset()
{
_cur = _min;
started = false;
}
}
} }
partial struct TW_FIX32 : IEquatable<TW_FIX32>, IConvertible partial struct TW_FIX32 : IEquatable<TW_FIX32>, IConvertible

View File

@@ -1,7 +1,6 @@
using NTwain.Caps; using NTwain.Caps;
using NTwain.Data; using NTwain.Data;
using NTwain.Triplets; using NTwain.Triplets;
using NTwain.Triplets.ControlDATs;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -178,31 +177,47 @@ namespace NTwain
/// <param name="cap"></param> /// <param name="cap"></param>
/// <param name="values"></param> /// <param name="values"></param>
/// <returns></returns> /// <returns></returns>
public STS GetCapValues<TValue>(CAP cap, out IList<TValue> values) where TValue : struct public STS GetCapValues<TValue>(CAP cap, out ValueContainer<TValue> value) where TValue : struct
{ {
values = new List<TValue>(); value = new ValueContainer<TValue> { ContainerType = TWON.DONTCARE };
var sts = GetCapValues(cap, out TW_CAPABILITY twcap); var sts = GetCapCurrent(cap, out TW_CAPABILITY twcap);
if (sts.RC == TWRC.SUCCESS) if (sts.RC == TWRC.SUCCESS)
{ {
value.ContainerType = twcap.ConType;
switch (twcap.ConType) switch (twcap.ConType)
{ {
case TWON.ONEVALUE: case TWON.ONEVALUE:
values.Add(twcap.ReadOneValue<TValue>(this)); value.OneValue = twcap.ReadOneValue<TValue>(this);
break; break;
case TWON.ENUMERATION: case TWON.ENUMERATION:
var twenum = twcap.ReadEnumeration<TValue>(this); var twenum = twcap.ReadEnumeration<TValue>(this);
if (twenum.Items != null && twenum.Items.Length > 0) if (twenum.Items != null)
((List<TValue>)values).AddRange(twenum.Items); {
value.EnumValue = new EnumValue<TValue>
{
CurrentIndex = twenum.CurrentIndex,
DefaultIndex = twenum.DefaultIndex,
Items = twenum.Items
};
}
break; break;
case TWON.RANGE: case TWON.RANGE:
// This can be slow var range = twcap.ReadRange<TValue>(this);
var twrange = twcap.ReadRange<TValue>(this); value.RangeValue = new RangeValue<TValue>
((List<TValue>)values).AddRange(twrange); {
Min = range.MinValue,
Max = range.MaxValue,
Step = range.StepSize,
DefaultValue = range.DefaultValue,
CurrentValue = range.CurrentValue
};
break; break;
case TWON.ARRAY: case TWON.ARRAY:
var twarr = twcap.ReadArray<TValue>(this); var twarr = twcap.ReadArray<TValue>(this);
if (twarr != null && twarr.Count > 0) if (twarr != null)
((List<TValue>)values).AddRange(twarr); {
value.ArrayValue = twarr;
}
break; break;
default: default:
twcap.Free(this); break; twcap.Free(this); break;