diff --git a/src/NTwain/Caps/ValueContainer.cs b/src/NTwain/Caps/ValueContainer.cs index 238adcc..bd8ab98 100644 --- a/src/NTwain/Caps/ValueContainer.cs +++ b/src/NTwain/Caps/ValueContainer.cs @@ -1,10 +1,11 @@ using NTwain.Data; +using System; +using System.Collections; using System.Collections.Generic; -using System.Linq; namespace NTwain.Caps; -public record ValueContainer where TValue : struct +public record ValueContainer { public TWON ContainerType { get; set; } @@ -20,7 +21,7 @@ public record ValueContainer where TValue : struct { return ContainerType switch { - TWON.ONEVALUE => OneValue.HasValue ? ToEnumerable(OneValue.Value) : [], + TWON.ONEVALUE => ToEnumerable(OneValue), TWON.ARRAY => ArrayValue ?? [], TWON.ENUMERATION => EnumValue?.Items ?? [], TWON.RANGE => RangeValue != null ? GenerateRangeValues(RangeValue) : [], @@ -28,22 +29,27 @@ public record ValueContainer where TValue : struct }; } - private IEnumerable ToEnumerable(TValue value) + private IEnumerable ToEnumerable(TValue? value) { + if (value == null) yield break; yield return value; } private IEnumerable GenerateRangeValues(RangeValue range) { - var de = new DynamicEnumerator(range.Min, range.Max, range.Step); + var dynamicType = typeof(DynamicEnumerator<>); + var genericType = dynamicType.MakeGenericType(typeof(TValue)); + + var de = Activator.CreateInstance(genericType, range.Min, range.Max, range.Step) as IEnumerator; + if (de == null) yield break; while (de.MoveNext()) { - yield return de.Current; + yield return (TValue)de.Current; } } } -public record EnumValue where TValue : struct +public record EnumValue { public TValue[] Items { get; set; } = []; @@ -52,7 +58,7 @@ public record EnumValue where TValue : struct public int DefaultIndex { get; set; } } -public record RangeValue where TValue : struct +public record RangeValue { public TValue Min { get; set; } diff --git a/src/NTwain/Data/TWAINH_EXTRAS.cs b/src/NTwain/Data/TWAINH_EXTRAS.cs index af61a6f..5ea43cd 100644 --- a/src/NTwain/Data/TWAINH_EXTRAS.cs +++ b/src/NTwain/Data/TWAINH_EXTRAS.cs @@ -292,6 +292,17 @@ namespace NTwain.Data return ((IEnumerable)this).GetEnumerator(); } } + /// + /// A more dotnet-friendly representation of with boxed values. + /// + public partial class RangeBoxed + { + public object MinValue; + public object MaxValue; + public object StepSize; + public object DefaultValue; + public object CurrentValue; + } partial struct TW_FIX32 : IEquatable, IConvertible { diff --git a/src/NTwain/Data/ValueReader.cs b/src/NTwain/Data/ValueReader.cs index 0b5ab63..385641c 100644 --- a/src/NTwain/Data/ValueReader.cs +++ b/src/NTwain/Data/ValueReader.cs @@ -87,7 +87,6 @@ namespace NTwain.Data /// /// Reads a boxed one value out of a cap. This can only be done once if memory is freed. /// - /// /// /// /// @@ -414,6 +413,52 @@ namespace NTwain.Data } } + public static RangeBoxed ReadRangeBoxed(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true) + { + var retVal = new RangeBoxed(); + + if (cap.ConType != TWON.RANGE || cap.hContainer == IntPtr.Zero) return retVal; + + var lockedPtr = memMgr.Lock(cap.hContainer); + + try + { + TWTY itemType; + // Mac has a level of indirection and a different structure (ick)... + if (TWPlatform.IsMacOSX) + { + itemType = (TWTY)Marshal.ReadInt32(lockedPtr); + lockedPtr += 4; + } + else + { + // Windows or the 2.4+ Linux DSM... + itemType = (TWTY)Marshal.ReadInt16(lockedPtr); + lockedPtr += 2; + } + retVal.MinValue = ReadTWTYDataBoxed(lockedPtr, itemType, 0); + lockedPtr += 4; + retVal.MaxValue = ReadTWTYDataBoxed(lockedPtr, itemType, 0); + lockedPtr += 4; + retVal.StepSize = ReadTWTYDataBoxed(lockedPtr, itemType, 0); + lockedPtr += 4; + retVal.CurrentValue = ReadTWTYDataBoxed(lockedPtr, itemType, 0); + lockedPtr += 4; + retVal.DefaultValue = ReadTWTYDataBoxed(lockedPtr, itemType, 0); + lockedPtr += 4; + return retVal; + } + finally + { + if (lockedPtr != IntPtr.Zero) memMgr.Unlock(cap.hContainer); + if (freeMemory) + { + memMgr.Free(cap.hContainer); + cap.hContainer = IntPtr.Zero; + } + } + } + public static Range ReadRange(this ref TW_CAPABILITY cap, IMemoryManager memMgr, bool freeMemory = true) where TValue : struct { var retVal = new Range(); diff --git a/src/NTwain/TwainAppSession.Caps.cs b/src/NTwain/TwainAppSession.Caps.cs index 84edf02..c81758c 100644 --- a/src/NTwain/TwainAppSession.Caps.cs +++ b/src/NTwain/TwainAppSession.Caps.cs @@ -101,6 +101,46 @@ namespace NTwain return sts; } + /// + /// Gets a CAP's current value as boxed values. This is a simplified version that doesn't require + /// manual reading, but may or may not work. + /// + /// + /// + /// + public STS GetCapCurrentBoxed(CAP cap, out List value) + { + value = new List(); + var sts = GetCapCurrent(cap, out TW_CAPABILITY twcap); + if (sts.RC == TWRC.SUCCESS) + { + switch (twcap.ConType) + { + case TWON.ONEVALUE: + var read = twcap.ReadOneValueBoxed(this); + if (read != null) value.Add(read); + break; + case TWON.ENUMERATION: + var twenum = twcap.ReadEnumerationBoxed(this); + if (twenum.Items != null && twenum.CurrentIndex < twenum.Items.Length) + { + value.Add(twenum.Items[twenum.CurrentIndex]); + } + break; + case TWON.RANGE: + value.Add(twcap.ReadRangeBoxed(this).CurrentValue); + break; + case TWON.ARRAY: + var twarr = twcap.ReadArrayBoxed(this); + if (twarr != null && twarr.Count > 0) value.AddRange(twarr); + break; + default: + twcap.Free(this); break; + } + } + return sts; + } + /// /// Gets a CAP's raw default value. /// Caller will need to manually read and free the memory. @@ -155,6 +195,46 @@ namespace NTwain return sts; } + /// + /// Gets a CAP's default value. This is a simplified version that doesn't require + /// manual reading, but may or may not work. + /// + /// + /// + /// + public STS GetCapDefaultBoxed(CAP cap, out List value) + { + value = new List(); + var sts = GetCapDefault(cap, out TW_CAPABILITY twcap); + if (sts.RC == TWRC.SUCCESS) + { + switch (twcap.ConType) + { + case TWON.ONEVALUE: + var read = twcap.ReadOneValueBoxed(this); + if (read != null) value.Add(read); + break; + case TWON.ENUMERATION: + var twenum = twcap.ReadEnumerationBoxed(this); + if (twenum.Items != null && twenum.DefaultIndex < twenum.Items.Length) + { + value.Add(twenum.Items[twenum.DefaultIndex]); + } + break; + case TWON.RANGE: + value.Add(twcap.ReadRangeBoxed(this).DefaultValue); + break; + case TWON.ARRAY: + var twarr = twcap.ReadArrayBoxed(this); + if (twarr != null && twarr.Count > 0) value.AddRange(twarr); + break; + default: + twcap.Free(this); break; + } + } + return sts; + } + /// /// Gets a CAP's raw supported values. /// Caller will need to manually read and free the memory. @@ -226,6 +306,63 @@ namespace NTwain return sts; } + + /// + /// Gets a CAP's supported values. This is a simplified version that doesn't require + /// manual reading, but may or may not work. + /// + /// + /// + /// + public STS GetCapValuesBoxed(CAP cap, out ValueContainer value) + { + value = new ValueContainer { ContainerType = TWON.DONTCARE }; + var sts = GetCapValues(cap, out TW_CAPABILITY twcap); + if (sts.RC == TWRC.SUCCESS) + { + value.ContainerType = twcap.ConType; + switch (twcap.ConType) + { + case TWON.ONEVALUE: + value.OneValue = twcap.ReadOneValueBoxed(this); + break; + case TWON.ENUMERATION: + var twenum = twcap.ReadEnumerationBoxed(this); + if (twenum.Items != null) + { + value.EnumValue = new EnumValue + { + CurrentIndex = twenum.CurrentIndex, + DefaultIndex = twenum.DefaultIndex, + Items = twenum.Items + }; + } + break; + case TWON.RANGE: + var range = twcap.ReadRangeBoxed(this); + value.RangeValue = new RangeValue + { + Min = range.MinValue, + Max = range.MaxValue, + Step = range.StepSize, + DefaultValue = range.DefaultValue, + CurrentValue = range.CurrentValue + }; + break; + case TWON.ARRAY: + var twarr = twcap.ReadArrayBoxed(this); + if (twarr != null) + { + value.ArrayValue = twarr; + } + break; + default: + twcap.Free(this); break; + } + } + return sts; + } + /// /// Gets a CAP's help text (description). /// This may not work due to unclear spec.