mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-15 19:54:52 +08:00
add colors to letters based on current font and graphics state
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
using System.Collections.Generic;
|
||||
using PdfPig.Geometry;
|
||||
using PdfPig.Graphics;
|
||||
using PdfPig.Graphics.Colors;
|
||||
using PdfPig.IO;
|
||||
using PdfPig.Tokens;
|
||||
using PdfPig.Core;
|
||||
@@ -22,7 +21,7 @@
|
||||
|
||||
public PdfPath CurrentPath { get; set; }
|
||||
|
||||
public IColorSpaceContext ColorSpaceContext { get; } = new ColorSpaceContext();
|
||||
public IColorSpaceContext ColorSpaceContext { get; }
|
||||
|
||||
public PdfPoint CurrentPosition { get; set; }
|
||||
|
||||
@@ -30,6 +29,7 @@
|
||||
{
|
||||
StateStack.Push(new CurrentGraphicsState());
|
||||
CurrentPath = new PdfPath(CurrentTransformationMatrix);
|
||||
ColorSpaceContext = new ColorSpaceContext(GetCurrentState);
|
||||
}
|
||||
|
||||
public CurrentGraphicsState GetCurrentState()
|
||||
@@ -78,47 +78,4 @@
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class TestColorSpaceContext : IColorSpaceContext
|
||||
{
|
||||
public ColorSpace CurrentStrokingColorSpace { get; } = ColorSpace.DeviceGray;
|
||||
|
||||
public ColorSpace CurrentNonStrokingColorSpace { get; } = ColorSpace.DeviceGray;
|
||||
|
||||
public IColor CurrentStrokingColor { get; } = GrayColor.Black;
|
||||
|
||||
public IColor CurrentNonStrokingColor { get; } = GrayColor.Black;
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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()
|
||||
{
|
||||
|
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,15 +1,21 @@
|
||||
namespace UglyToad.PdfPig.Graphics
|
||||
{
|
||||
using System;
|
||||
using Colors;
|
||||
using Tokens;
|
||||
|
||||
internal class ColorSpaceContext : IColorSpaceContext
|
||||
{
|
||||
public ColorSpace CurrentStrokingColorSpace { get; private set; } = ColorSpace.DeviceGray;
|
||||
public ColorSpace CurrentNonStrokingColorSpace { get; private set; } = ColorSpace.DeviceGray;
|
||||
private readonly Func<CurrentGraphicsState> currentStateFunc;
|
||||
|
||||
public IColor CurrentStrokingColor { get; private set; } = GrayColor.Black;
|
||||
public IColor CurrentNonStrokingColor { get; private set; } = GrayColor.Black;
|
||||
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)
|
||||
{
|
||||
@@ -19,23 +25,23 @@
|
||||
switch (colorspaceActual)
|
||||
{
|
||||
case ColorSpace.DeviceGray:
|
||||
CurrentStrokingColor = GrayColor.Black;
|
||||
currentStateFunc().CurrentStrokingColor = GrayColor.Black;
|
||||
break;
|
||||
case ColorSpace.DeviceRGB:
|
||||
CurrentStrokingColor = RGBColor.Black;
|
||||
currentStateFunc().CurrentStrokingColor = RGBColor.Black;
|
||||
break;
|
||||
case ColorSpace.DeviceCMYK:
|
||||
CurrentStrokingColor = CMYKColor.Black;
|
||||
currentStateFunc().CurrentStrokingColor = CMYKColor.Black;
|
||||
break;
|
||||
default:
|
||||
CurrentStrokingColor = GrayColor.Black;
|
||||
currentStateFunc().CurrentStrokingColor = GrayColor.Black;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentStrokingColorSpace = ColorSpace.DeviceGray;
|
||||
CurrentStrokingColor = GrayColor.Black;
|
||||
currentStateFunc().CurrentStrokingColor = GrayColor.Black;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,60 +53,60 @@
|
||||
switch (colorspaceActual)
|
||||
{
|
||||
case ColorSpace.DeviceGray:
|
||||
CurrentNonStrokingColor = GrayColor.Black;
|
||||
currentStateFunc().CurrentNonStrokingColor = GrayColor.Black;
|
||||
break;
|
||||
case ColorSpace.DeviceRGB:
|
||||
CurrentNonStrokingColor = RGBColor.Black;
|
||||
currentStateFunc().CurrentNonStrokingColor = RGBColor.Black;
|
||||
break;
|
||||
case ColorSpace.DeviceCMYK:
|
||||
CurrentNonStrokingColor = CMYKColor.Black;
|
||||
currentStateFunc().CurrentNonStrokingColor = CMYKColor.Black;
|
||||
break;
|
||||
default:
|
||||
CurrentNonStrokingColor = GrayColor.Black;
|
||||
currentStateFunc().CurrentNonStrokingColor = GrayColor.Black;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentNonStrokingColorSpace = ColorSpace.DeviceGray;
|
||||
CurrentNonStrokingColor = GrayColor.Black;
|
||||
currentStateFunc().CurrentNonStrokingColor = GrayColor.Black;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStrokingColorGray(decimal gray)
|
||||
{
|
||||
CurrentStrokingColorSpace = ColorSpace.DeviceGray;
|
||||
CurrentStrokingColor = new GrayColor(gray);
|
||||
currentStateFunc().CurrentStrokingColor = new GrayColor(gray);
|
||||
}
|
||||
|
||||
public void SetStrokingColorRgb(decimal r, decimal g, decimal b)
|
||||
{
|
||||
CurrentStrokingColorSpace = ColorSpace.DeviceRGB;
|
||||
CurrentStrokingColor = new RGBColor(r, g, b);
|
||||
currentStateFunc().CurrentStrokingColor = new RGBColor(r, g, b);
|
||||
}
|
||||
|
||||
public void SetStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k)
|
||||
{
|
||||
CurrentStrokingColorSpace = ColorSpace.DeviceCMYK;
|
||||
CurrentStrokingColor = new CMYKColor(c, m, y, k);
|
||||
currentStateFunc().CurrentStrokingColor = new CMYKColor(c, m, y, k);
|
||||
}
|
||||
|
||||
public void SetNonStrokingColorGray(decimal gray)
|
||||
{
|
||||
CurrentNonStrokingColorSpace = ColorSpace.DeviceGray;
|
||||
CurrentNonStrokingColor = new GrayColor(gray);
|
||||
currentStateFunc().CurrentNonStrokingColor = new GrayColor(gray);
|
||||
}
|
||||
|
||||
public void SetNonStrokingColorRgb(decimal r, decimal g, decimal b)
|
||||
{
|
||||
CurrentNonStrokingColorSpace = ColorSpace.DeviceRGB;
|
||||
CurrentNonStrokingColor = new RGBColor(r, g, b);
|
||||
currentStateFunc().CurrentNonStrokingColor = new RGBColor(r, g, b);
|
||||
}
|
||||
|
||||
public void SetNonStrokingColorCmyk(decimal c, decimal m, decimal y, decimal k)
|
||||
{
|
||||
CurrentNonStrokingColorSpace = ColorSpace.DeviceCMYK;
|
||||
CurrentNonStrokingColor = new CMYKColor(c, m, y, k);
|
||||
currentStateFunc().CurrentNonStrokingColor = new CMYKColor(c, m, y, k);
|
||||
}
|
||||
}
|
||||
}
|
@@ -45,9 +45,15 @@
|
||||
/// <inheritdoc/>
|
||||
public (decimal r, decimal g, decimal b) ToRGBValues()
|
||||
{
|
||||
return ((255 * (1 - C) * (1 - K)) / 255m,
|
||||
(255 * (1 - M) * (1 - K)) / 255m,
|
||||
(255 * (1 - Y) * (1 - K)) / 255m);
|
||||
return ((1 - C) * (1 - K),
|
||||
(1 - M) * (1 - K),
|
||||
(1 - Y) * (1 - K));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"CMYK: ({C}, {M}, {Y}, {K})";
|
||||
}
|
||||
}
|
||||
}
|
@@ -29,5 +29,11 @@
|
||||
{
|
||||
return (Gray, Gray, Gray);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Gray: {Gray}";
|
||||
}
|
||||
}
|
||||
}
|
@@ -41,5 +41,11 @@
|
||||
{
|
||||
return (R, G, B);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"RGB: ({R}, {G}, {B})";
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
|
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -17,17 +17,7 @@
|
||||
/// The <see cref="ColorSpace"/> used for non-stroking operations.
|
||||
/// </summary>
|
||||
ColorSpace CurrentNonStrokingColorSpace { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IColor"/> used for stroking operations.
|
||||
/// </summary>
|
||||
IColor CurrentStrokingColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IColor"/> used for non-stroking operations.
|
||||
/// </summary>
|
||||
IColor CurrentNonStrokingColor { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set the current color space to use for stroking operations.
|
||||
/// </summary>
|
||||
|
@@ -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()));
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user