move last uncovered operators to switch statement

in order to remove reflection from the core content stream operators
construction we ensure all types covered by the operations dictionary
have corresponding switch statement support. this moves the remaining
2 operators to the switch statement. fix for #1062
This commit is contained in:
EliotJones 2025-07-20 12:18:05 -05:00 committed by BobLd
parent 813d3baa18
commit 00ca268092
2 changed files with 113 additions and 175 deletions

View File

@ -1,65 +1,64 @@
namespace UglyToad.PdfPig.Graphics.Operations.InlineImages namespace UglyToad.PdfPig.Graphics.Operations.InlineImages;
{
using System;
using System.Collections.Generic;
using System.IO;
using Tokens;
using UglyToad.PdfPig.Writer;
/// <inheritdoc /> using System;
/// <summary> using System.Collections.Generic;
/// Begin the image data for an inline image object. using System.IO;
/// </summary> using Tokens;
public class BeginInlineImageData : IGraphicsStateOperation using Writer;
{
/// <summary> /// <inheritdoc />
/// The symbol for this operation in a stream. /// <summary>
/// </summary> /// Begin the image data for an inline image object.
public const string Symbol = "ID"; /// </summary>
public class BeginInlineImageData : IGraphicsStateOperation
/// <inheritdoc /> {
public string Operator => Symbol; /// <summary>
/// The symbol for this operation in a stream.
/// <summary> /// </summary>
/// The key-value pairs which specify attributes of the following image. public const string Symbol = "ID";
/// </summary>
public IReadOnlyDictionary<NameToken, IToken> Dictionary { get; } /// <inheritdoc />
public string Operator => Symbol;
/// <summary>
/// Create a new <see cref="BeginInlineImageData"/>. /// <summary>
/// </summary> /// The key-value pairs which specify attributes of the following image.
public BeginInlineImageData(IReadOnlyDictionary<NameToken, IToken> dictionary) /// </summary>
{ public IReadOnlyDictionary<NameToken, IToken> Dictionary { get; }
Dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
} /// <summary>
/// Create a new <see cref="BeginInlineImageData"/>.
/// <inheritdoc /> /// </summary>
public void Run(IOperationContext operationContext) public BeginInlineImageData(IReadOnlyDictionary<NameToken, IToken> dictionary)
{ {
operationContext.SetInlineImageProperties(Dictionary); Dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
} }
/// <inheritdoc /> /// <inheritdoc />
public void Write(Stream stream) public void Run(IOperationContext operationContext)
{
operationContext.SetInlineImageProperties(Dictionary);
}
/// <inheritdoc />
public void Write(Stream stream)
{
var tokenWriter = TokenWriter.Instance;
foreach (var item in Dictionary)
{ {
var tokenWriter = TokenWriter.Instance; var name = item.Key;
foreach (var item in Dictionary) var value = item.Value;
{
var name = item.Key;
var value = item.Value;
stream.WriteText($"{name} "); stream.WriteText($"{name} ");
tokenWriter.WriteToken(value, stream); tokenWriter.WriteToken(value, stream);
stream.WriteNewLine(); stream.WriteNewLine();
} }
stream.WriteText(Symbol); stream.WriteText(Symbol);
stream.WriteNewLine(); stream.WriteNewLine();
} }
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return Symbol; return Symbol;
} }
}
} }

View File

@ -20,7 +20,6 @@ namespace UglyToad.PdfPig.Graphics
#endif #endif
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using Tokens; using Tokens;
/// <summary> /// <summary>
@ -38,7 +37,7 @@ namespace UglyToad.PdfPig.Graphics
// private // private
} }
private static readonly IReadOnlyDictionary<string, Type> operations = private static readonly IReadOnlyDictionary<string, Type> Operations =
new Dictionary<string, Type> new Dictionary<string, Type>
{ {
{ SetStrokeColorAdvanced.Symbol, typeof(SetStrokeColorAdvanced) }, { SetStrokeColorAdvanced.Symbol, typeof(SetStrokeColorAdvanced) },
@ -415,10 +414,14 @@ namespace UglyToad.PdfPig.Graphics
var errorMessageScn = string.Join(", ", operands.Select(x => x.ToString())); var errorMessageScn = string.Join(", ", operands.Select(x => x.ToString()));
throw new PdfDocumentFormatException($"Attempted to set a stroke color space (SCN) with invalid arguments: [{errorMessageScn}]"); throw new PdfDocumentFormatException($"Attempted to set a stroke color space (SCN) with invalid arguments: [{errorMessageScn}]");
case SetStrokeColorDeviceCmyk.Symbol: case SetStrokeColorDeviceCmyk.Symbol:
return new SetStrokeColorDeviceCmyk(OperandToDouble(operands[0]), var setStrokeColorCmykArgs = GetExpectedDoubles(SetNonStrokeColorDeviceCmyk.Symbol,
OperandToDouble(operands[1]), operands,
OperandToDouble(operands[2]), 4);
OperandToDouble(operands[3])); return new SetStrokeColorDeviceCmyk(
setStrokeColorCmykArgs[0],
setStrokeColorCmykArgs[1],
setStrokeColorCmykArgs[2],
setStrokeColorCmykArgs[3]);
case SetStrokeColorDeviceGray.Symbol: case SetStrokeColorDeviceGray.Symbol:
return new SetStrokeColorDeviceGray(OperandToDouble(operands[0])); return new SetStrokeColorDeviceGray(OperandToDouble(operands[0]));
case SetStrokeColorDeviceRgb.Symbol: case SetStrokeColorDeviceRgb.Symbol:
@ -462,128 +465,64 @@ namespace UglyToad.PdfPig.Graphics
var array = operands.ToArray(); var array = operands.ToArray();
return new ShowTextsWithPositioning(array); return new ShowTextsWithPositioning(array);
case BeginInlineImageData.Symbol:
// Should never be encountered because it is handled by the page content parser.
return null;
case EndInlineImage.Symbol:
// Should never be encountered because it is handled by the page content parser.
return null;
case Type3SetGlyphWidth.Symbol:
var t3SetWidthArgs = GetExpectedDoubles(Type3SetGlyphWidth.Symbol, operands, 2);
return new Type3SetGlyphWidth(t3SetWidthArgs[0], t3SetWidthArgs[1]);
case Type3SetGlyphWidthAndBoundingBox.Symbol:
var t3SetWidthAndBbArgs = GetExpectedDoubles(Type3SetGlyphWidthAndBoundingBox.Symbol, operands, 6);
return new Type3SetGlyphWidthAndBoundingBox(
t3SetWidthAndBbArgs[0],
t3SetWidthAndBbArgs[1],
t3SetWidthAndBbArgs[2],
t3SetWidthAndBbArgs[3],
t3SetWidthAndBbArgs[4],
t3SetWidthAndBbArgs[5]);
} }
if (!operations.TryGetValue(op.Data, out Type? operationType)) if (!Operations.TryGetValue(op.Data, out _))
{ {
return null; return null;
} }
var constructors = operationType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); throw new NotImplementedException(
$"No support implemented for content operator {op.Data}");
}
if (constructors.Length == 0) private static double[] GetExpectedDoubles(string operatorSymbol, IReadOnlyList<IToken> operands, int resultCount)
{
var results = new double[resultCount];
if (operands.Count < resultCount)
{ {
throw new InvalidOperationException("No constructors to invoke were found for operation type: " + operationType.FullName); throw new InvalidOperationException(
$"Invalid operands for {operatorSymbol}, needed {resultCount} numbers, got: {PrintOperands(operands)}");
} }
// This only works by luck... for (var i = 0; i < resultCount; i++)
var constructor = constructors[0];
if (constructor.IsPrivate)
{ {
return (IGraphicsStateOperation)operationType.GetField("Value")?.GetValue(null)!; var op = operands[i];
if (op is not NumericToken nt)
{
throw new InvalidOperationException(
$"Invalid operands for {operatorSymbol}, needed {resultCount} numbers, got: {PrintOperands(operands)}");
}
results[i] = nt.Data;
} }
var parameters = constructor.GetParameters(); return results;
}
var offset = 0; private static string PrintOperands(IEnumerable<IToken> operands)
{
var arguments = new List<object>(); return "[" + string.Join(", ", operands.Select(x => x.ToString())) + "]";
foreach (var parameter in parameters)
{
if (offset >= operands.Count)
{
throw new InvalidOperationException($"Fewer operands {operands.Count} found than required ({offset + 1}) for operator: {op.Data}.");
}
if (parameter.ParameterType == typeof(double))
{
if (operands[offset] is NumericToken numeric)
{
arguments.Add(numeric.Data);
}
else
{
throw new InvalidOperationException($"Expected a double parameter for operation type {operationType.FullName}. Instead got: {operands[offset]}");
}
offset++;
}
else if (parameter.ParameterType == typeof(int))
{
if (operands[offset] is NumericToken numeric)
{
arguments.Add(numeric.Int);
}
else
{
throw new InvalidOperationException($"Expected an integer parameter for operation type {operationType.FullName}. Instead got: {operands[offset]}");
}
offset++;
}
else if (parameter.ParameterType == typeof(double[]))
{
if (operands[offset] is ArrayToken arr)
{
arguments.Add(arr.Data.OfType<NumericToken>().Select(x => x.Data).ToArray());
offset++;
continue;
}
var array = new List<double>();
while (offset < operands.Count && operands[offset] is NumericToken numeric)
{
array.Add(numeric.Data);
offset++;
}
arguments.Add(array.ToArray());
}
else if (parameter.ParameterType == typeof(NameToken))
{
if (operands[offset] is NameToken name)
{
arguments.Add(name);
}
else if (operands[offset] is StringToken s)
{
arguments.Add(NameToken.Create(s.Data));
}
else
{
throw new InvalidOperationException($"Expected a NameToken parameter for operation type {operationType.FullName}. Instead got: {operands[offset]}");
}
offset++;
}
else if (parameter.ParameterType == typeof(string))
{
if (operands[offset] is StringToken stringToken)
{
arguments.Add(stringToken.Data);
}
else if (operands[offset] is HexToken hexToken)
{
arguments.Add(hexToken.Data);
}
else
{
throw new InvalidOperationException($"Expected a string parameter for operation type {operationType.FullName}. Instead got: {operands[offset]}");
}
offset++;
}
else
{
throw new NotImplementedException($"Unsupported parameter type {parameter.ParameterType.FullName} for operation type {operationType.FullName}.");
}
}
var result = constructor.Invoke(arguments.ToArray());
return (IGraphicsStateOperation)result;
} }
} }
} }