From 00ca268092aafb1bfe4ca70cea4ac27b708a6779 Mon Sep 17 00:00:00 2001 From: EliotJones Date: Sun, 20 Jul 2025 12:18:05 -0500 Subject: [PATCH] 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 --- .../InlineImages/BeginInlineImageData.cs | 121 +++++++------ ...ReflectionGraphicsStateOperationFactory.cs | 167 ++++++------------ 2 files changed, 113 insertions(+), 175 deletions(-) diff --git a/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/BeginInlineImageData.cs b/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/BeginInlineImageData.cs index 80490768..fdf7f076 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/BeginInlineImageData.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/BeginInlineImageData.cs @@ -1,65 +1,64 @@ -namespace UglyToad.PdfPig.Graphics.Operations.InlineImages -{ - using System; - using System.Collections.Generic; - using System.IO; - using Tokens; - using UglyToad.PdfPig.Writer; +namespace UglyToad.PdfPig.Graphics.Operations.InlineImages; - /// - /// - /// Begin the image data for an inline image object. - /// - public class BeginInlineImageData : IGraphicsStateOperation - { - /// - /// The symbol for this operation in a stream. - /// - public const string Symbol = "ID"; - - /// - public string Operator => Symbol; - - /// - /// The key-value pairs which specify attributes of the following image. - /// - public IReadOnlyDictionary Dictionary { get; } - - /// - /// Create a new . - /// - public BeginInlineImageData(IReadOnlyDictionary dictionary) - { - Dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary)); - } - - /// - public void Run(IOperationContext operationContext) - { - operationContext.SetInlineImageProperties(Dictionary); - } - - /// - public void Write(Stream stream) +using System; +using System.Collections.Generic; +using System.IO; +using Tokens; +using Writer; + +/// +/// +/// Begin the image data for an inline image object. +/// +public class BeginInlineImageData : IGraphicsStateOperation +{ + /// + /// The symbol for this operation in a stream. + /// + public const string Symbol = "ID"; + + /// + public string Operator => Symbol; + + /// + /// The key-value pairs which specify attributes of the following image. + /// + public IReadOnlyDictionary Dictionary { get; } + + /// + /// Create a new . + /// + public BeginInlineImageData(IReadOnlyDictionary dictionary) + { + Dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary)); + } + + /// + public void Run(IOperationContext operationContext) + { + operationContext.SetInlineImageProperties(Dictionary); + } + + /// + public void Write(Stream stream) + { + var tokenWriter = TokenWriter.Instance; + foreach (var item in Dictionary) { - var tokenWriter = TokenWriter.Instance; - foreach (var item in Dictionary) - { - var name = item.Key; - var value = item.Value; + var name = item.Key; + var value = item.Value; - stream.WriteText($"{name} "); - tokenWriter.WriteToken(value, stream); - stream.WriteNewLine(); - } - stream.WriteText(Symbol); - stream.WriteNewLine(); - } - - /// - public override string ToString() - { - return Symbol; - } - } + stream.WriteText($"{name} "); + tokenWriter.WriteToken(value, stream); + stream.WriteNewLine(); + } + stream.WriteText(Symbol); + stream.WriteNewLine(); + } + + /// + public override string ToString() + { + return Symbol; + } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Graphics/ReflectionGraphicsStateOperationFactory.cs b/src/UglyToad.PdfPig/Graphics/ReflectionGraphicsStateOperationFactory.cs index e9a47bea..019f9c0f 100644 --- a/src/UglyToad.PdfPig/Graphics/ReflectionGraphicsStateOperationFactory.cs +++ b/src/UglyToad.PdfPig/Graphics/ReflectionGraphicsStateOperationFactory.cs @@ -20,7 +20,6 @@ namespace UglyToad.PdfPig.Graphics #endif using System.Collections.Generic; using System.Linq; - using System.Reflection; using Tokens; /// @@ -38,7 +37,7 @@ namespace UglyToad.PdfPig.Graphics // private } - private static readonly IReadOnlyDictionary operations = + private static readonly IReadOnlyDictionary Operations = new Dictionary { { SetStrokeColorAdvanced.Symbol, typeof(SetStrokeColorAdvanced) }, @@ -415,10 +414,14 @@ namespace UglyToad.PdfPig.Graphics var errorMessageScn = string.Join(", ", operands.Select(x => x.ToString())); throw new PdfDocumentFormatException($"Attempted to set a stroke color space (SCN) with invalid arguments: [{errorMessageScn}]"); case SetStrokeColorDeviceCmyk.Symbol: - return new SetStrokeColorDeviceCmyk(OperandToDouble(operands[0]), - OperandToDouble(operands[1]), - OperandToDouble(operands[2]), - OperandToDouble(operands[3])); + var setStrokeColorCmykArgs = GetExpectedDoubles(SetNonStrokeColorDeviceCmyk.Symbol, + operands, + 4); + return new SetStrokeColorDeviceCmyk( + setStrokeColorCmykArgs[0], + setStrokeColorCmykArgs[1], + setStrokeColorCmykArgs[2], + setStrokeColorCmykArgs[3]); case SetStrokeColorDeviceGray.Symbol: return new SetStrokeColorDeviceGray(OperandToDouble(operands[0])); case SetStrokeColorDeviceRgb.Symbol: @@ -462,128 +465,64 @@ namespace UglyToad.PdfPig.Graphics var array = operands.ToArray(); 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; } - 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 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... - var constructor = constructors[0]; - - if (constructor.IsPrivate) + for (var i = 0; i < resultCount; i++) { - 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; - - var arguments = new List(); - - 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().Select(x => x.Data).ToArray()); - offset++; - continue; - } - - var array = new List(); - 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; + private static string PrintOperands(IEnumerable operands) + { + return "[" + string.Join(", ", operands.Select(x => x.ToString())) + "]"; } } } \ No newline at end of file