merge pull request #58 from uglytoad/colors

adds colors to letters and prepares code to add colors to paths.
This commit is contained in:
Eliot Jones
2019-08-13 20:50:06 +01:00
committed by GitHub
43 changed files with 976 additions and 169 deletions

View File

@@ -5,7 +5,7 @@
using PdfPig.Graphics;
using PdfPig.IO;
using PdfPig.Tokens;
using UglyToad.PdfPig.Core;
using PdfPig.Core;
internal class TestOperationContext : IOperationContext
{
@@ -17,21 +17,19 @@
public TextMatrices TextMatrices { get; set; }
= new TextMatrices();
public TransformationMatrix CurrentTransformationMatrix
{
get { return GetCurrentState().CurrentTransformationMatrix; }
}
public TransformationMatrix CurrentTransformationMatrix => GetCurrentState().CurrentTransformationMatrix;
public PdfPath CurrentPath { get; set; }
public IColorspaceContext ColorspaceContext { get; } = new ColorspaceContext();
public IColorSpaceContext ColorSpaceContext { get; }
public PdfPoint CurrentPosition { get; set; }
public TestOperationContext()
{
StateStack.Push(new CurrentGraphicsState());
CurrentPath = new PdfPath(CurrentTransformationMatrix);
CurrentPath = new PdfPath();
ColorSpaceContext = new ColorSpaceContext(GetCurrentState);
}
public CurrentGraphicsState GetCurrentState()
@@ -80,39 +78,4 @@
{
}
}
public class TestColorspaceContext : IColorspaceContext
{
public void SetStrokingColorspace(NameToken colorspace)
{
}
public void SetNonStrokingColorspace(NameToken colorspace)
{
}
public void SetStrokingColorGray(decimal gray)
{
}
public void SetStrokingColorRgb(decimal r, decimal g, decimal b)
{
}
public void SetStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k)
{
}
public void SetNonStrokingColorGray(decimal gray)
{
}
public void SetNonStrokingColorRgb(decimal r, decimal g, decimal b)
{
}
public void SetNonStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k)
{
}
}
}

View File

@@ -28,6 +28,27 @@
}
}
[Theory]
[MemberData(nameof(GetAllDocuments))]
public void CanUseStreamForFirstPage(string documentName)
{
// Add the full path back on, we removed it so we could see it in the test explorer.
documentName = Path.Combine(DocumentFolder.Value, documentName);
var bytes = File.ReadAllBytes(documentName);
using (var memoryStream = new MemoryStream(bytes))
using (var document = PdfDocument.Open(memoryStream, new ParsingOptions { UseLenientParsing = false }))
{
for (var i = 0; i < document.NumberOfPages; i++)
{
var page = document.GetPage(i + 1);
Assert.NotNull(page.ExperimentalAccess.GetAnnotations().ToList());
}
}
}
[Theory]
[MemberData(nameof(GetAllDocuments))]
public void CanTokenizeAllAccessibleObjects(string documentName)

View File

@@ -27,6 +27,39 @@
}
}
[Fact]
public void LettersHaveCorrectColors()
{
using (var document = PdfDocument.Open(GetFilename(), new ParsingOptions
{
UseLenientParsing = false
}))
{
var page = document.GetPage(1);
// Pinkish.
var (r, g , b) = page.Letters[0].Color.ToRGBValues();
Assert.Equal(1, r);
Assert.Equal(0.914m, g);
Assert.Equal(0.765m, b);
// White.
(r, g, b) = page.Letters[37].Color.ToRGBValues();
Assert.Equal(1, r);
Assert.Equal(1, g);
Assert.Equal(1, b);
// Blackish.
(r, g, b) = page.Letters[76].Color.ToRGBValues();
Assert.Equal(0.137m, r);
Assert.Equal(0.122m, g);
Assert.Equal(0.125m, b);
}
}
[Fact]
public void Page1HasCorrectWords()
{

View File

@@ -20,6 +20,11 @@
foreach (var type in types)
{
if (type.FullName == null)
{
continue;
}
// Skip coverage measuring instrumentation classes.
if (type.FullName.StartsWith("Coverlet", StringComparison.OrdinalIgnoreCase))
{
@@ -81,6 +86,13 @@
"UglyToad.PdfPig.Geometry.PdfPoint",
"UglyToad.PdfPig.Geometry.PdfLine",
"UglyToad.PdfPig.Geometry.PdfRectangle",
"UglyToad.PdfPig.Graphics.Colors.CMYKColor",
"UglyToad.PdfPig.Graphics.Colors.ColorSpace",
"UglyToad.PdfPig.Graphics.Colors.ColorSpaceExtensions",
"UglyToad.PdfPig.Graphics.Colors.ColorSpaceFamily",
"UglyToad.PdfPig.Graphics.Colors.GrayColor",
"UglyToad.PdfPig.Graphics.Colors.IColor",
"UglyToad.PdfPig.Graphics.Colors.RGBColor",
"UglyToad.PdfPig.Graphics.Core.LineCapStyle",
"UglyToad.PdfPig.Graphics.Core.LineDashPattern",
"UglyToad.PdfPig.Graphics.Core.LineJoinStyle",
@@ -88,7 +100,7 @@
"UglyToad.PdfPig.Graphics.Core.TextRenderingMode",
"UglyToad.PdfPig.Graphics.CurrentFontState",
"UglyToad.PdfPig.Graphics.CurrentGraphicsState",
"UglyToad.PdfPig.Graphics.IColorspaceContext",
"UglyToad.PdfPig.Graphics.IColorSpaceContext",
"UglyToad.PdfPig.Graphics.IOperationContext",
"UglyToad.PdfPig.Graphics.Operations.ClippingPaths.ModifyClippingByEvenOddIntersect",
"UglyToad.PdfPig.Graphics.Operations.ClippingPaths.ModifyClippingByNonZeroWindingIntersect",
@@ -293,6 +305,11 @@
foreach (var type in types)
{
var symbol = type.GetField("Symbol", BindingFlags.Public | BindingFlags.Static);
if (symbol == null)
{
continue;
}
symbols.Add(symbol.GetValue(null).ToString());
}

View File

@@ -1,2 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CIE/@EntryIndexedValue">CIE</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CMYK/@EntryIndexedValue">CMYK</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ICC/@EntryIndexedValue">ICC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RGB/@EntryIndexedValue">RGB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XY/@EntryIndexedValue">XY</s:String></wpf:ResourceDictionary>

View File

@@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Content
{
using Geometry;
using Graphics.Colors;
/// <summary>
/// A glyph or combination of glyphs (characters) drawn by a PDF content stream.
@@ -54,6 +55,11 @@
/// </summary>
public string FontName { get; }
/// <summary>
/// The color of the letter.
/// </summary>
public IColor Color { get; }
/// <summary>
/// The size of the font in points. This is not ready for public consumption as the calculation is incorrect.
/// </summary>
@@ -62,27 +68,27 @@
/// <summary>
/// Create a new letter to represent some text drawn by the Tj operator.
/// </summary>
internal Letter(string value, PdfRectangle glyphRectangle, PdfPoint startBaseLine, PdfPoint endBaseLine, decimal width, decimal fontSize, string fontName, decimal pointSize)
internal Letter(string value, PdfRectangle glyphRectangle,
PdfPoint startBaseLine,
PdfPoint endBaseLine,
decimal width,
decimal fontSize,
string fontName,
IColor color,
decimal pointSize)
{
Value = value;
GlyphRectangle = glyphRectangle;
FontSize = fontSize;
FontName = fontName;
PointSize = pointSize;
Width = width;
StartBaseLine = startBaseLine;
EndBaseLine = endBaseLine;
Width = width;
FontSize = fontSize;
FontName = fontName;
Color = color ?? GrayColor.Black;
PointSize = pointSize;
TextDirection = GetTextDirection();
}
/// <summary>
/// Produces a string representation of the letter and its position.
/// </summary>
public override string ToString()
{
return $"{Value} {Location} {FontName} {PointSize}";
}
private TextDirection GetTextDirection()
{
if (System.Math.Abs(StartBaseLine.Y - EndBaseLine.Y) < 10e-5m)
@@ -91,17 +97,29 @@
{
return TextDirection.Rotate180;
}
return TextDirection.Horizontal;
}
else if (System.Math.Abs(StartBaseLine.X - EndBaseLine.X) < 10e-5m)
if (System.Math.Abs(StartBaseLine.X - EndBaseLine.X) < 10e-5m)
{
if (StartBaseLine.Y > EndBaseLine.Y)
{
return TextDirection.Rotate90;
}
return TextDirection.Rotate270;
}
return TextDirection.Unknown;
}
/// <summary>
/// Produces a string representation of the letter and its position.
/// </summary>
public override string ToString()
{
return $"{Value} {Location} {FontName} {PointSize}";
}
}
}

View File

@@ -1,9 +0,0 @@
namespace UglyToad.PdfPig.Core
{
using System.IO;
internal interface ICosStreamWriter
{
void WriteToPdfStream(BinaryWriter output);
}
}

View File

@@ -18,7 +18,7 @@
/// <summary>
/// The current path.
/// </summary>
public PdfPath Path { get; } = new PdfPath(Core.TransformationMatrix.Identity);
public PdfPath Path { get; } = new PdfPath();
/// <summary>
/// The current location of the active point.

View File

@@ -22,7 +22,7 @@
public bool IsFlexing { get; set; }
[NotNull]
public PdfPath Path { get; private set; } = new PdfPath(Core.TransformationMatrix.Identity);
public PdfPath Path { get; private set; } = new PdfPath();
public PdfPoint CurrentPosition { get; set; }

View File

@@ -5,7 +5,6 @@ namespace UglyToad.PdfPig.Geometry
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Core;
/// <summary>
/// A path in a PDF document, used by glyphs and page content. Can contain multiple sub-paths.
@@ -20,21 +19,10 @@ namespace UglyToad.PdfPig.Geometry
public IReadOnlyList<IPathCommand> Commands => commands;
private PdfPoint? currentPosition;
private readonly TransformationMatrix currentTransformationMatrix;
/// <summary>
/// Create a new <see cref="PdfPath"/>.
/// </summary>
/// <param name="transformationMatrix">The transformation to apply to all points in this path.</param>
public PdfPath(TransformationMatrix transformationMatrix)
{
currentTransformationMatrix = transformationMatrix;
}
internal void MoveTo(decimal x, decimal y)
{
currentPosition = currentTransformationMatrix.Transform(new PdfPoint(x, y));
currentPosition = new PdfPoint(x, y);
commands.Add(new Move(currentPosition.Value));
}
@@ -42,12 +30,13 @@ namespace UglyToad.PdfPig.Geometry
{
if (currentPosition.HasValue)
{
var to = currentTransformationMatrix.Transform(new PdfPoint(x, y));
var to = new PdfPoint(x, y);
commands.Add(new Line(currentPosition.Value, to));
currentPosition = to;
}
else
{
// TODO: probably the wrong behaviour here, maybe line starts from (0, 0)?
MoveTo(x, y);
}
}
@@ -58,9 +47,9 @@ namespace UglyToad.PdfPig.Geometry
{
if (currentPosition.HasValue)
{
var to = currentTransformationMatrix.Transform(new PdfPoint(x3, y3));
var to = new PdfPoint(x3, y3);
commands.Add(new BezierCurve(currentPosition.Value,
currentTransformationMatrix.Transform(new PdfPoint(x1, y1)), currentTransformationMatrix.Transform(new PdfPoint(x2, y2)), to));
new PdfPoint(x1, y1), new PdfPoint(x2, y2), to));
currentPosition = to;
}
else
@@ -460,7 +449,7 @@ namespace UglyToad.PdfPig.Geometry
internal void Rectangle(decimal x, decimal y, decimal width, decimal height)
{
currentPosition = currentTransformationMatrix.Transform(new PdfPoint(x, y));
currentPosition = new PdfPoint(x, y);
LineTo(x + width, y);
LineTo(x + width, y + height);
LineTo(x, y + height);

View File

@@ -0,0 +1,160 @@
namespace UglyToad.PdfPig.Graphics
{
using System;
using Colors;
using Tokens;
internal class ColorSpaceContext : IColorSpaceContext
{
private readonly Func<CurrentGraphicsState> currentStateFunc;
public ColorSpace CurrentStrokingColorSpace { get; private set; } = ColorSpace.DeviceGray;
public ColorSpace CurrentNonStrokingColorSpace { get; private set; } = ColorSpace.DeviceGray;
public ColorSpaceContext(Func<CurrentGraphicsState> currentStateFunc)
{
this.currentStateFunc = currentStateFunc ?? throw new ArgumentNullException(nameof(currentStateFunc));
}
public void SetStrokingColorspace(NameToken colorspace)
{
if (colorspace.TryMapToColorSpace(out var colorspaceActual))
{
CurrentStrokingColorSpace = colorspaceActual;
switch (colorspaceActual)
{
case ColorSpace.DeviceGray:
currentStateFunc().CurrentStrokingColor = GrayColor.Black;
break;
case ColorSpace.DeviceRGB:
currentStateFunc().CurrentStrokingColor = RGBColor.Black;
break;
case ColorSpace.DeviceCMYK:
currentStateFunc().CurrentStrokingColor = CMYKColor.Black;
break;
default:
currentStateFunc().CurrentStrokingColor = GrayColor.Black;
break;
}
}
else
{
CurrentStrokingColorSpace = ColorSpace.DeviceGray;
currentStateFunc().CurrentStrokingColor = GrayColor.Black;
}
}
public void SetNonStrokingColorspace(NameToken colorspace)
{
if (colorspace.TryMapToColorSpace(out var colorspaceActual))
{
CurrentNonStrokingColorSpace = colorspaceActual;
switch (colorspaceActual)
{
case ColorSpace.DeviceGray:
currentStateFunc().CurrentNonStrokingColor = GrayColor.Black;
break;
case ColorSpace.DeviceRGB:
currentStateFunc().CurrentNonStrokingColor = RGBColor.Black;
break;
case ColorSpace.DeviceCMYK:
currentStateFunc().CurrentNonStrokingColor = CMYKColor.Black;
break;
default:
currentStateFunc().CurrentNonStrokingColor = GrayColor.Black;
break;
}
}
else
{
CurrentNonStrokingColorSpace = ColorSpace.DeviceGray;
currentStateFunc().CurrentNonStrokingColor = GrayColor.Black;
}
}
public void SetStrokingColorGray(decimal gray)
{
CurrentStrokingColorSpace = ColorSpace.DeviceGray;
if (gray == 0)
{
currentStateFunc().CurrentStrokingColor = GrayColor.Black;
}
else if (gray == 1)
{
currentStateFunc().CurrentStrokingColor = GrayColor.White;
}
else
{
currentStateFunc().CurrentStrokingColor = new GrayColor(gray);
}
}
public void SetStrokingColorRgb(decimal r, decimal g, decimal b)
{
CurrentStrokingColorSpace = ColorSpace.DeviceRGB;
if (r == 0 && g == 0 && b == 0)
{
currentStateFunc().CurrentStrokingColor = RGBColor.Black;
}
else if (r == 1 && g == 1 && b == 1)
{
currentStateFunc().CurrentStrokingColor = RGBColor.White;
}
else
{
currentStateFunc().CurrentStrokingColor = new RGBColor(r, g, b);
}
}
public void SetStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k)
{
CurrentStrokingColorSpace = ColorSpace.DeviceCMYK;
currentStateFunc().CurrentStrokingColor = new CMYKColor(c, m, y, k);
}
public void SetNonStrokingColorGray(decimal gray)
{
CurrentNonStrokingColorSpace = ColorSpace.DeviceGray;
if (gray == 0)
{
currentStateFunc().CurrentNonStrokingColor = GrayColor.Black;
}
else if (gray == 1)
{
currentStateFunc().CurrentNonStrokingColor = GrayColor.White;
}
else
{
currentStateFunc().CurrentNonStrokingColor = new GrayColor(gray);
}
}
public void SetNonStrokingColorRgb(decimal r, decimal g, decimal b)
{
CurrentNonStrokingColorSpace = ColorSpace.DeviceRGB;
if (r == 0 && g == 0 && b == 0)
{
currentStateFunc().CurrentNonStrokingColor = RGBColor.Black;
}
else if (r == 1 && g == 1 && b == 1)
{
currentStateFunc().CurrentNonStrokingColor = RGBColor.White;
}
else
{
currentStateFunc().CurrentNonStrokingColor = new RGBColor(r, g, b);
}
}
public void SetNonStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k)
{
CurrentNonStrokingColorSpace = ColorSpace.DeviceCMYK;
currentStateFunc().CurrentNonStrokingColor = new CMYKColor(c, m, y, k);
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
namespace UglyToad.PdfPig.Graphics.Colors
{
/// <summary>
/// A color with cyan, magenta, yellow and black (K) components.
/// </summary>
public class CMYKColor : IColor, IEquatable<CMYKColor>
{
/// <summary>
/// CMYK Black value (0, 0, 0, 1).
/// </summary>
public static IColor Black { get; } = new CMYKColor(0, 0, 0, 1);
/// <summary>
/// CMYK White value (all 0).
/// </summary>
public static IColor White { get; } = new CMYKColor(0, 0, 0, 0);
/// <inheritdoc/>
public ColorSpace ColorSpace { get; } = ColorSpace.DeviceCMYK;
/// <summary>
/// The cyan value.
/// </summary>
public decimal C { get; }
/// <summary>
/// The magenta value.
/// </summary>
public decimal M { get; }
/// <summary>
/// The yellow value.
/// </summary>
public decimal Y { get; }
/// <summary>
/// The black value.
/// </summary>
public decimal K { get; }
/// <summary>
/// Create a new <see cref="CMYKColor"/>.
/// </summary>
public CMYKColor(decimal c, decimal m, decimal y, decimal k)
{
C = c;
M = m;
Y = y;
K = k;
}
/// <inheritdoc/>
public (decimal r, decimal g, decimal b) ToRGBValues()
{
return ((1 - C) * (1 - K),
(1 - M) * (1 - K),
(1 - Y) * (1 - K));
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return Equals(obj as CMYKColor);
}
/// <inheritdoc />
public bool Equals(CMYKColor other)
{
return other != null &&
C == other.C &&
M == other.M &&
Y == other.Y &&
K == other.K;
}
/// <inheritdoc />
public override int GetHashCode()
{
var hashCode = -492570696;
hashCode = hashCode * -1521134295 + C.GetHashCode();
hashCode = hashCode * -1521134295 + M.GetHashCode();
hashCode = hashCode * -1521134295 + Y.GetHashCode();
hashCode = hashCode * -1521134295 + K.GetHashCode();
return hashCode;
}
/// <summary>
/// Equals.
/// </summary>
public static bool operator ==(CMYKColor color1, CMYKColor color2) => EqualityComparer<CMYKColor>.Default.Equals(color1, color2);
/// <summary>
/// Not Equals.
/// </summary>
public static bool operator !=(CMYKColor color1, CMYKColor color2) => !(color1 == color2);
/// <inheritdoc />
public override string ToString()
{
return $"CMYK: ({C}, {M}, {Y}, {K})";
}
}
}

View File

@@ -0,0 +1,74 @@
namespace UglyToad.PdfPig.Graphics.Colors
{
/// <summary>
/// Color values in a PDF are interpreted according to the current color space.
/// Color spaces enable a PDF to specify abstract colors in a device independent way.
/// </summary>
public enum ColorSpace
{
/// <summary>
/// Grayscale. Controls the intensity of achromatic light on a scale from black to white.
/// </summary>
DeviceGray = 0,
/// <summary>
/// RGB. Controls the intensities of red, green and blue light.
/// </summary>
DeviceRGB = 1,
/// <summary>
/// CMYK. Controls the concentrations of cyan, magenta, yellow and black (K) inks.
/// </summary>
DeviceCMYK = 2,
/// <summary>
/// CIE (Commission Internationale de l'Éclairage) colorspace.
/// Specifies color related to human visual perception with the aim of producing consistent color on different output devices.
/// CalGray - Special case of the CIE colorspace using a single channel (A) and a single transformation.
/// A represents the gray component of a calibrated gray space in the range 0 to 1.
/// </summary>
CalGray = 3,
/// <summary>
/// CIE (Commission Internationale de l'Éclairage) colorspace.
/// Specifies color related to human visual perception with the aim of producing consistent color on different output devices.
/// CalRGB - A CIE ABC color space with a single transformation.
/// A, B and C represent red, green and blue color values in the range 0 to 1.
/// </summary>
CalRGB = 4,
/// <summary>
/// CIE (Commission Internationale de l'Éclairage) colorspace.
/// Specifies color related to human visual perception with the aim of producing consistent color on different output devices.
/// Lab - A CIE ABC color space with two transforms. A, B and C represent the L*, a* and b*
/// components of a CIE 1976 L*a*b* space. The range of A (L*) is 0 to 100.
/// The range of B (a*) and C (b*) are defined by the Range of the color space.
/// </summary>
Lab = 5,
/// <summary>
/// ICC (International Color Consortium) colorspace.
/// ICC - Colorspace specified by a sequence of bytes which are interpreted according to the
/// ICC specification.
/// </summary>
ICCBased = 6,
/// <summary>
/// An Indexed color space allows a PDF content stream to use small integers as indices into a color map or color table of arbitrary colors in some other space.
/// A PDF consumer application treats each sample value as an index into the color table and uses the color value it finds there.
/// </summary>
Indexed = 7,
/// <summary>
/// Enables a PDF content stream to paint an area with a pattern rather than a single color.
/// The pattern may be either a tiling pattern (type 1) or a shading pattern (type 2).
/// </summary>
Pattern = 8,
/// <summary>
/// Provides a means for specifying the use of additional colorants or for isolating the control of individual color components of
/// a device color space for a subtractive device.
/// When such a space is the current color space, the current color is a single-component value, called a tint,
/// that controls the application of the given colorant or color components only.
/// </summary>
Separation = 9,
/// <summary>
/// Can contain an arbitrary number of color components. They provide greater flexibility than is possible with standard device color
/// spaces such as <see cref="DeviceCMYK"/> or with individual <see cref="Separation"/> color spaces.
/// For example, it is possible to create a DeviceN color space consisting of only the cyan, magenta, and yellow color components,
/// with the black component excluded.
/// </summary>
DeviceN = 10
}
}

View File

@@ -0,0 +1,130 @@
namespace UglyToad.PdfPig.Graphics.Colors
{
using System;
using Tokens;
/// <summary>
/// Provides utility extension methods for dealing with <see cref="ColorSpace"/>s.
/// </summary>
public static class ColorSpaceExtensions
{
/// <summary>
/// Gets the corresponding <see cref="ColorSpaceFamily"/> for a given <see cref="ColorSpace"/>.
/// </summary>
public static ColorSpaceFamily GetFamily(this ColorSpace colorSpace)
{
switch (colorSpace)
{
case ColorSpace.DeviceGray:
case ColorSpace.DeviceRGB:
case ColorSpace.DeviceCMYK:
return ColorSpaceFamily.Device;
case ColorSpace.CalGray:
case ColorSpace.CalRGB:
case ColorSpace.Lab:
case ColorSpace.ICCBased:
return ColorSpaceFamily.CIEBased;
case ColorSpace.Indexed:
case ColorSpace.Pattern:
case ColorSpace.Separation:
case ColorSpace.DeviceN:
return ColorSpaceFamily.Special;
default:
throw new ArgumentException($"Unrecognized colorspace: {colorSpace}.");
}
}
/// <summary>
/// Maps from a <see cref="NameToken"/> to the corresponding <see cref="ColorSpace"/> if one exists.
/// </summary>
public static bool TryMapToColorSpace(this NameToken name, out ColorSpace colorspace)
{
colorspace = ColorSpace.DeviceGray;
if (name.Data == NameToken.Devicegray.Data)
{
colorspace = ColorSpace.DeviceGray;
}
else if (name.Data == NameToken.Devicergb.Data)
{
colorspace = ColorSpace.DeviceRGB;
}
else if (name.Data == NameToken.Devicecmyk.Data)
{
colorspace = ColorSpace.DeviceCMYK;
}
else if (name.Data == NameToken.Calgray.Data)
{
colorspace = ColorSpace.CalGray;
}
else if (name.Data == NameToken.Calrgb.Data)
{
colorspace = ColorSpace.CalRGB;
}
else if (name.Data == NameToken.Lab.Data)
{
colorspace = ColorSpace.Lab;
}
else if (name.Data == NameToken.Iccbased.Data)
{
colorspace = ColorSpace.ICCBased;
}
else if (name.Data == NameToken.Indexed.Data)
{
colorspace = ColorSpace.Indexed;
}
else if (name.Data == NameToken.Pattern.Data)
{
colorspace = ColorSpace.Pattern;
}
else if (name.Data == NameToken.Separation.Data)
{
colorspace = ColorSpace.Separation;
}
else if (name.Data == NameToken.Devicen.Data)
{
colorspace = ColorSpace.DeviceN;
}
else
{
return false;
}
return true;
}
/// <summary>
/// Gets the corresponding <see cref="NameToken"/> for a given <see cref="ColorSpace"/>.
/// </summary>
public static NameToken ToNameToken(this ColorSpace colorSpace)
{
switch (colorSpace)
{
case ColorSpace.DeviceGray:
return NameToken.Devicegray;
case ColorSpace.DeviceRGB:
return NameToken.Devicergb;
case ColorSpace.DeviceCMYK:
return NameToken.Devicecmyk;
case ColorSpace.CalGray:
return NameToken.Calgray;
case ColorSpace.CalRGB:
return NameToken.Calrgb;
case ColorSpace.Lab:
return NameToken.Lab;
case ColorSpace.ICCBased:
return NameToken.Iccbased;
case ColorSpace.Indexed:
return NameToken.Indexed;
case ColorSpace.Pattern:
return NameToken.Pattern;
case ColorSpace.Separation:
return NameToken.Separation;
case ColorSpace.DeviceN:
return NameToken.Devicen;
default:
throw new ArgumentException($"Unrecognized colorspace: {colorSpace}.");
}
}
}
}

View File

@@ -0,0 +1,26 @@
namespace UglyToad.PdfPig.Graphics.Colors
{
/// <summary>
/// <see cref="ColorSpace"/>s can be classified into colorspace families.
/// <see cref="ColorSpace"/>s within the same family share general characteristics.
/// </summary>
public enum ColorSpaceFamily
{
/// <summary>
/// Device colorspaces directly specify colors or shades of gray that the output device
/// should produce.
/// </summary>
Device,
/// <summary>
/// CIE-based color spaces are based on an international standard for color specification created by
/// the Commission Internationale de l'Éclairage (International Commission on Illumination) (CIE).
/// These spaces specify colors in a way that is independent of the characteristics of any particular output device.
/// </summary>
CIEBased,
/// <summary>
/// Special color spaces add features or properties to an underlying color space.
/// They include facilities for patterns, color mapping, separations, and high-fidelity and multitone color.
/// </summary>
Special
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
namespace UglyToad.PdfPig.Graphics.Colors
{
/// <summary>
/// A grayscale color with a single gray component.
/// </summary>
public class GrayColor : IColor, IEquatable<GrayColor>
{
/// <summary>
/// Gray Black value (0).
/// </summary>
public static GrayColor Black { get; } = new GrayColor(0);
/// <summary>
/// Gray White value (1).
/// </summary>
public static GrayColor White { get; } = new GrayColor(1);
/// <inheritdoc/>
public ColorSpace ColorSpace { get; } = ColorSpace.DeviceGray;
/// <summary>
/// The gray value between 0 and 1.
/// </summary>
public decimal Gray { get; }
/// <summary>
/// Create a new <see cref="GrayColor"/>.
/// </summary>
public GrayColor(decimal gray)
{
Gray = gray;
}
/// <inheritdoc/>
public (decimal r, decimal g, decimal b) ToRGBValues()
{
return (Gray, Gray, Gray);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return Equals(obj as GrayColor);
}
/// <inheritdoc />
public bool Equals(GrayColor other)
{
return other != null &&
Gray == other.Gray;
}
/// <inheritdoc />
public override int GetHashCode() => Gray.GetHashCode();
/// <summary>
/// Equals.
/// </summary>
public static bool operator ==(GrayColor color1, GrayColor color2) => EqualityComparer<GrayColor>.Default.Equals(color1, color2);
/// <summary>
/// Not Equals.
/// </summary>
public static bool operator !=(GrayColor color1, GrayColor color2) => !(color1 == color2);
/// <inheritdoc />
public override string ToString()
{
return $"Gray: {Gray}";
}
}
}

View File

@@ -0,0 +1,18 @@
namespace UglyToad.PdfPig.Graphics.Colors
{
/// <summary>
/// A color used for text or paths in a PDF.
/// </summary>
public interface IColor
{
/// <summary>
/// The colorspace used for this color.
/// </summary>
ColorSpace ColorSpace { get; }
/// <summary>
/// The color as RGB values (between 0 and 1).
/// </summary>
(decimal r, decimal g, decimal b) ToRGBValues();
}
}

View File

@@ -0,0 +1,98 @@
namespace UglyToad.PdfPig.Graphics.Colors
{
using System;
using System.Collections.Generic;
/// <summary>
/// A color with red, green and blue components.
/// </summary>
public class RGBColor : IColor, IEquatable<RGBColor>
{
/// <summary>
/// RGB Black value (all 0).
/// </summary>
public static RGBColor Black = new RGBColor(0, 0, 0);
/// <summary>
/// RGB White value (all 1).
/// </summary>
public static RGBColor White = new RGBColor(1, 1, 1);
/// <inheritdoc/>
public ColorSpace ColorSpace { get; } = ColorSpace.DeviceRGB;
/// <summary>
/// The red value between 0 and 1.
/// </summary>
public decimal R { get; }
/// <summary>
/// The green value between 0 and 1.
/// </summary>
public decimal G { get; }
/// <summary>
/// The blue value between 0 and 1.
/// </summary>
public decimal B { get; }
/// <summary>
/// Create a new <see cref="RGBColor"/>.
/// </summary>
/// <param name="r">The red value between 0 and 1.</param>
/// <param name="g">The green value between 0 and 1.</param>
/// <param name="b">The blue value between 0 and 1.</param>
public RGBColor(decimal r, decimal g, decimal b)
{
R = r;
G = g;
B = b;
}
/// <inheritdoc/>
public (decimal r, decimal g, decimal b) ToRGBValues() => (R, G, B);
/// <inheritdoc />
public override bool Equals(object obj)
{
if (obj is RGBColor color)
{
return Equals(color);
}
return false;
}
/// <inheritdoc />
/// <summary>
/// Whether 2 RGB colors are equal across all channels.
/// </summary>
public bool Equals(RGBColor other)
{
return other != null &&
R == other.R &&
G == other.G &&
B == other.B;
}
/// <inheritdoc />
public override int GetHashCode() => (R, G, B).GetHashCode();
/// <summary>
/// Equals.
/// </summary>
public static bool operator ==(RGBColor color1, RGBColor color2) =>
EqualityComparer<RGBColor>.Default.Equals(color1, color2);
/// <summary>
/// Not Equals.
/// </summary>
public static bool operator !=(RGBColor color1, RGBColor color2) => !(color1 == color2);
/// <inheritdoc />
public override string ToString()
{
return $"RGB: ({R}, {G}, {B})";
}
}
}

View File

@@ -1,39 +0,0 @@
namespace UglyToad.PdfPig.Graphics
{
using Tokens;
internal class ColorspaceContext : IColorspaceContext
{
public void SetStrokingColorspace(NameToken colorspace)
{
}
public void SetNonStrokingColorspace(NameToken colorspace)
{
}
public void SetStrokingColorGray(decimal gray)
{
}
public void SetStrokingColorRgb(decimal r, decimal g, decimal b)
{
}
public void SetStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k)
{
}
public void SetNonStrokingColorGray(decimal gray)
{
}
public void SetNonStrokingColorRgb(decimal r, decimal g, decimal b)
{
}
public void SetNonStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k)
{
}
}
}

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Colors;
using Content;
using Core;
using Fonts;
@@ -39,7 +40,7 @@
public PdfPath CurrentPath { get; private set; }
public IColorspaceContext ColorspaceContext { get; } = new ColorspaceContext();
public IColorSpaceContext ColorSpaceContext { get; }
public PdfPoint CurrentPosition { get; set; }
@@ -66,6 +67,7 @@
this.xObjectFactory = xObjectFactory;
this.log = log;
graphicsStack.Push(new CurrentGraphicsState());
ColorSpaceContext = new ColorSpaceContext(GetCurrentState);
}
public PageContent Process(IReadOnlyList<IGraphicsStateOperation> operations)
@@ -165,11 +167,27 @@
.Transform(TextMatrices.TextMatrix
.Transform(renderingMatrix
.Transform(boundingBox.GlyphBounds)));
var transformedPdfBounds = rotation.Rotate(transformationMatrix)
.Transform(TextMatrices.TextMatrix
.Transform(renderingMatrix.Transform(new PdfRectangle(0, 0, boundingBox.Width, 0))));
ShowGlyph(font, transformedGlyphBounds, transformedPdfBounds.BottomLeft, transformedPdfBounds.BottomRight, transformedPdfBounds.Width, unicode, fontSize, pointSize);
// If the text rendering mode calls for filling, the current nonstroking color in the graphics state is used;
// if it calls for stroking, the current stroking color is used.
// In modes that perform both filling and stroking, the effect is as if each glyph outline were filled and then stroked in separate operations.
// TODO: expose color as something more advanced
var color = currentState.FontState.TextRenderingMode != TextRenderingMode.Stroke
? currentState.CurrentNonStrokingColor
: currentState.CurrentStrokingColor;
ShowGlyph(font, transformedGlyphBounds,
transformedPdfBounds.BottomLeft,
transformedPdfBounds.BottomRight,
transformedPdfBounds.Width,
unicode,
fontSize,
color,
pointSize);
decimal tx, ty;
if (font.IsVertical)
@@ -270,7 +288,7 @@
public void BeginSubpath()
{
CurrentPath = new PdfPath(CurrentTransformationMatrix);
CurrentPath = new PdfPath();
}
public void StrokePath(bool close)
@@ -336,9 +354,23 @@
TextMatrices.TextMatrix = newMatrix;
}
private void ShowGlyph(IFont font, PdfRectangle glyphRectangle, PdfPoint startBaseLine, PdfPoint endBaseLine, decimal width, string unicode, decimal fontSize, decimal pointSize)
private void ShowGlyph(IFont font, PdfRectangle glyphRectangle,
PdfPoint startBaseLine,
PdfPoint endBaseLine,
decimal width,
string unicode,
decimal fontSize,
IColor color,
decimal pointSize)
{
var letter = new Letter(unicode, glyphRectangle, startBaseLine, endBaseLine, width, fontSize, font.Name.Data, pointSize);
var letter = new Letter(unicode, glyphRectangle,
startBaseLine,
endBaseLine,
width,
fontSize,
font.Name.Data,
color,
pointSize);
Letters.Add(letter);
}

View File

@@ -1,6 +1,7 @@
// ReSharper disable RedundantDefaultMemberInitializer
namespace UglyToad.PdfPig.Graphics
{
using Colors;
using Core;
using PdfPig.Core;
@@ -96,6 +97,16 @@ namespace UglyToad.PdfPig.Graphics
/// </summary>
public decimal Smoothness { get; set; } = 0;
/// <summary>
/// The current active stroking color for paths.
/// </summary>
public IColor CurrentStrokingColor { get; set; }
/// <summary>
/// The current active non-stroking color for text and fill.
/// </summary>
public IColor CurrentNonStrokingColor { get; set; }
#endregion
/// <inheritdoc />
@@ -118,7 +129,9 @@ namespace UglyToad.PdfPig.Graphics
NonStrokingOverprint = NonStrokingOverprint,
OverprintMode = OverprintMode,
Smoothness = Smoothness,
StrokeAdjustment = StrokeAdjustment
StrokeAdjustment = StrokeAdjustment,
CurrentStrokingColor = CurrentStrokingColor,
CurrentNonStrokingColor = CurrentNonStrokingColor
};
}
}

View File

@@ -1,12 +1,23 @@
namespace UglyToad.PdfPig.Graphics
{
using Colors;
using Tokens;
/// <summary>
/// Methods for manipulating and retrieving the current color state for a PDF content stream.
/// </summary>
public interface IColorspaceContext
public interface IColorSpaceContext
{
/// <summary>
/// The <see cref="ColorSpace"/> used for stroking operations.
/// </summary>
ColorSpace CurrentStrokingColorSpace { get; }
/// <summary>
/// The <see cref="ColorSpace"/> used for non-stroking operations.
/// </summary>
ColorSpace CurrentNonStrokingColorSpace { get; }
/// <summary>
/// Set the current color space to use for stroking operations.
/// </summary>
@@ -39,7 +50,7 @@
/// <param name="y">Yellow - A number between 0 (minimum concentration) and 1 (maximum concentration).</param>
/// <param name="k">Black - A number between 0 (minimum concentration) and 1 (maximum concentration).</param>
void SetStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k);
/// <summary>
/// Set the nonstroking color space to DeviceGray and set the gray level to use for nonstroking operations.
/// </summary>

View File

@@ -21,7 +21,7 @@
/// <summary>
/// The active colorspaces for this content stream.
/// </summary>
IColorspaceContext ColorspaceContext { get; }
IColorSpaceContext ColorSpaceContext { get; }
/// <summary>
/// The current position.

View File

@@ -52,10 +52,13 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.CurrentPath.BezierCurveTo(ControlPoint1.X, ControlPoint1.Y,
ControlPoint2.X, ControlPoint2.Y,
End.X, End.Y);
operationContext.CurrentPosition = End;
var controlPoint1Transform = operationContext.CurrentTransformationMatrix.Transform(ControlPoint1);
var controlPoint2Transform = operationContext.CurrentTransformationMatrix.Transform(ControlPoint2);
var endTransform = operationContext.CurrentTransformationMatrix.Transform(End);
operationContext.CurrentPath.BezierCurveTo(controlPoint1Transform.X, controlPoint1Transform.Y,
controlPoint2Transform.X, controlPoint2Transform.Y,
endTransform.X, endTransform.Y);
operationContext.CurrentPosition = endTransform;
}
/// <inheritdoc />

View File

@@ -44,12 +44,14 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.CurrentPath.BezierCurveTo(ControlPoint1.X, ControlPoint1.Y,
End.X,
End.Y,
End.X,
End.Y);
operationContext.CurrentPosition = End;
var controlPoint1Transform = operationContext.CurrentTransformationMatrix.Transform(ControlPoint1);
var endTransform = operationContext.CurrentTransformationMatrix.Transform(End);
operationContext.CurrentPath.BezierCurveTo(controlPoint1Transform.X, controlPoint1Transform.Y,
endTransform.X,
endTransform.Y,
endTransform.X,
endTransform.Y);
operationContext.CurrentPosition = endTransform;
}
/// <inheritdoc />

View File

@@ -51,7 +51,8 @@
public void Run(IOperationContext operationContext)
{
operationContext.BeginSubpath();
operationContext.CurrentPath.Rectangle(LowerLeft.X, LowerLeft.Y, Width, Height);
var lowerLeftTransform = operationContext.CurrentTransformationMatrix.Transform(LowerLeft);
operationContext.CurrentPath.Rectangle(lowerLeftTransform.X, lowerLeftTransform.Y, Width, Height);
}
/// <inheritdoc />

View File

@@ -44,13 +44,15 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
var controlPoint2Transform = operationContext.CurrentTransformationMatrix.Transform(ControlPoint2);
var endTransform = operationContext.CurrentTransformationMatrix.Transform(End);
operationContext.CurrentPath.BezierCurveTo(operationContext.CurrentPosition.X,
operationContext.CurrentPosition.Y,
ControlPoint2.X,
ControlPoint2.Y,
End.X,
End.Y);
operationContext.CurrentPosition = End;
controlPoint2Transform.X,
controlPoint2Transform.Y,
endTransform.X,
endTransform.Y);
operationContext.CurrentPosition = endTransform;
}
/// <inheritdoc />

View File

@@ -35,8 +35,9 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.CurrentPath.LineTo(End.X, End.Y);
operationContext.CurrentPosition = End;
var endPoint = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(End.X, End.Y));
operationContext.CurrentPath.LineTo(endPoint.X, endPoint.Y);
operationContext.CurrentPosition = endPoint;
}
/// <inheritdoc />

View File

@@ -36,8 +36,9 @@
public void Run(IOperationContext operationContext)
{
operationContext.BeginSubpath();
operationContext.CurrentPosition = Point;
operationContext.CurrentPath.LineTo(Point.X, Point.Y);
var pointTransform = operationContext.CurrentTransformationMatrix.Transform(Point);
operationContext.CurrentPosition = pointTransform;
operationContext.CurrentPath.MoveTo(pointTransform.X, pointTransform.Y);
}
/// <inheritdoc />

View File

@@ -35,6 +35,20 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
switch (Operands.Count)
{
case 1:
operationContext.ColorSpaceContext.SetNonStrokingColorGray(Operands[0]);
break;
case 3:
operationContext.ColorSpaceContext.SetNonStrokingColorRgb(Operands[0], Operands[1], Operands[2]);
break;
case 4:
operationContext.ColorSpaceContext.SetNonStrokingColorCmyk(Operands[0], Operands[1], Operands[2], Operands[3]);
break;
default:
return;
}
}
/// <inheritdoc />

View File

@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Colors;
using Tokens;
using Writer;
@@ -53,6 +54,25 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
if (operationContext.ColorSpaceContext.CurrentNonStrokingColorSpace.GetFamily() != ColorSpaceFamily.Device)
{
return;
}
switch (Operands.Count)
{
case 1:
operationContext.ColorSpaceContext.SetNonStrokingColorGray(Operands[0]);
break;
case 3:
operationContext.ColorSpaceContext.SetNonStrokingColorRgb(Operands[0], Operands[1], Operands[2]);
break;
case 4:
operationContext.ColorSpaceContext.SetNonStrokingColorCmyk(Operands[0], Operands[1], Operands[2], Operands[3]);
break;
default:
return;
}
}
/// <inheritdoc />

View File

@@ -54,7 +54,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorspaceContext.SetNonStrokingColorCmyk(C, M, Y, K);
operationContext.ColorSpaceContext.SetNonStrokingColorCmyk(C, M, Y, K);
}
/// <inheritdoc />

View File

@@ -33,7 +33,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorspaceContext.SetNonStrokingColorGray(Gray);
operationContext.ColorSpaceContext.SetNonStrokingColorGray(Gray);
}
/// <inheritdoc />

View File

@@ -47,7 +47,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorspaceContext.SetNonStrokingColorRgb(R, G, B);
operationContext.ColorSpaceContext.SetNonStrokingColorRgb(R, G, B);
}
/// <inheritdoc />

View File

@@ -36,7 +36,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorspaceContext.SetNonStrokingColorspace(Name);
operationContext.ColorSpaceContext.SetNonStrokingColorspace(Name);
}
/// <inheritdoc />

View File

@@ -38,13 +38,13 @@
switch (Operands.Count)
{
case 1:
operationContext.ColorspaceContext.SetStrokingColorGray(Operands[0]);
operationContext.ColorSpaceContext.SetStrokingColorGray(Operands[0]);
break;
case 3:
operationContext.ColorspaceContext.SetStrokingColorRgb(Operands[0], Operands[1], Operands[2]);
operationContext.ColorSpaceContext.SetStrokingColorRgb(Operands[0], Operands[1], Operands[2]);
break;
case 4:
operationContext.ColorspaceContext.SetStrokingColorCmyk(Operands[0], Operands[1], Operands[2], Operands[3]);
operationContext.ColorSpaceContext.SetStrokingColorCmyk(Operands[0], Operands[1], Operands[2], Operands[3]);
break;
default:
return;

View File

@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Colors;
using Tokens;
using Writer;
@@ -40,7 +41,7 @@
}
/// <summary>
/// Create a new <see cref="SetStrokeColor"/>.
/// Create a new <see cref="SetStrokeColorAdvanced"/>.
/// </summary>
/// <param name="operands">The color operands.</param>
/// <param name="patternName">The pattern name.</param>
@@ -53,6 +54,25 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
if (operationContext.ColorSpaceContext.CurrentStrokingColorSpace.GetFamily() != ColorSpaceFamily.Device)
{
return;
}
switch (Operands.Count)
{
case 1:
operationContext.ColorSpaceContext.SetStrokingColorGray(Operands[0]);
break;
case 3:
operationContext.ColorSpaceContext.SetStrokingColorRgb(Operands[0], Operands[1], Operands[2]);
break;
case 4:
operationContext.ColorSpaceContext.SetStrokingColorCmyk(Operands[0], Operands[1], Operands[2], Operands[3]);
break;
default:
return;
}
}
/// <inheritdoc />

View File

@@ -54,7 +54,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorspaceContext.SetStrokingColorCmyk(C, M, Y, K);
operationContext.ColorSpaceContext.SetStrokingColorCmyk(C, M, Y, K);
}
/// <inheritdoc />

View File

@@ -33,7 +33,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorspaceContext.SetStrokingColorGray(Gray);
operationContext.ColorSpaceContext.SetStrokingColorGray(Gray);
}
/// <inheritdoc />

View File

@@ -47,7 +47,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorspaceContext.SetStrokingColorRgb(R, G, B);
operationContext.ColorSpaceContext.SetStrokingColorRgb(R, G, B);
}
/// <inheritdoc />

View File

@@ -36,7 +36,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ColorspaceContext.SetStrokingColorspace(Name);
operationContext.ColorSpaceContext.SetStrokingColorspace(Name);
}
/// <inheritdoc />

View File

@@ -126,11 +126,11 @@ namespace UglyToad.PdfPig.Graphics
case SetNonStrokeColorAdvanced.Symbol:
if (operands[operands.Count - 1] is NameToken scnLowerPatternName)
{
return new SetStrokeColorAdvanced(operands.Take(operands.Count - 1).Select(x => ((NumericToken)x).Data).ToList(), scnLowerPatternName);
return new SetNonStrokeColorAdvanced(operands.Take(operands.Count - 1).Select(x => ((NumericToken)x).Data).ToList(), scnLowerPatternName);
}
else if (operands.All(x => x is NumericToken))
{
return new SetStrokeColorAdvanced(operands.Select(x => ((NumericToken)x).Data).ToList());
return new SetNonStrokeColorAdvanced(operands.Select(x => ((NumericToken)x).Data).ToList());
}
var errorMessageScnLower = string.Join(", ", operands.Select(x => x.ToString()));

View File

@@ -5,6 +5,7 @@
using Content;
using Core;
using Geometry;
using Graphics.Colors;
using Graphics.Operations;
using Graphics.Operations.General;
using Graphics.Operations.PathConstruction;
@@ -258,7 +259,9 @@
var documentSpace = textMatrix.Transform(renderingMatrix.Transform(fontMatrix.Transform(rect)));
var letter = new Letter(c.ToString(), documentSpace, advanceRect.BottomLeft, advanceRect.BottomRight, width, fontSize, font.Name, fontSize);
var letter = new Letter(c.ToString(), documentSpace, advanceRect.BottomLeft, advanceRect.BottomRight, width, fontSize, font.Name,
GrayColor.Black,
fontSize);
letters.Add(letter);
var tx = advanceRect.Width * horizontalScaling;