// ===========================================================================
// ©2013-2026 WebSupergoo. All rights reserved.
//
// This source code is for use exclusively with the ABCpdf product with
// which it is distributed, under the terms of the license for that
// product. Details can be found at
//
// http://www.websupergoo.com/
//
// This copyright notice must not be deleted and must be reproduced alongside
// any sections of code extracted from this module.
// ===========================================================================
#if DEBUG
#define CONTENT_VALIDATION
#endif
using System;
using System.Globalization;
using WebSupergoo.ABCpdf14;
using WebSupergoo.ABCpdf14.Atoms;
using WebSupergoo.ABCpdf14.Objects;
using System.Collections.Generic;
using System.Text.RegularExpressions;
#if CONTENT_VALIDATION
using System.Windows.Forms;
#endif
namespace AdvancedGraphics
{
/// <summary>
/// PDFContent class is a simple constructor of the pdf content.
/// It provides easy access to the most of the commands described in the PDF Reference.
/// </summary>
public class PDFContent
{
public PDFContent(Doc theDoc)
{
mDoc = theDoc;
}
/// <summary>The ID of the object containing the resources dictionary.</summary>
protected virtual int ResourcesObjectID {
get {
if(mDoc.PageCount == 0)
mDoc.Page = mDoc.AddPage();
return mDoc.Page;
}
}
/// <summary>Begin new subpath.</summary>
/// <param name="x">x coordinate</param>
/// <param name="y">y coordinate</param>
public virtual void Move(double x, double y)
{
AppendParameter(x);
AppendParameter(y);
mContents.Append(" m");
#if CONTENT_VALIDATION
Validate("m");
#endif
}
/// <summary>Append straight line segment to path.</summary>
/// <param name="x">x coordinate </param>
/// <param name="y">y coordinate </param>
public virtual void Line(double x, double y)
{
AppendParameter(x);
AppendParameter(y);
mContents.Append(" l");
#if CONTENT_VALIDATION
Validate("l");
#endif
}
/// <summary>Append rectangle to path.</summary>
/// <param name="x">Bottom left x coordinate</param>
/// <param name="y">Bottom left y coordinate</param>
/// <param name="w">Width</param>
/// <param name="h">Height</param>
public virtual void Rect(double x, double y, double w, double h)
{
AppendParameter(x);
AppendParameter(y);
AppendParameter(w);
AppendParameter(h);
mContents.Append(" re");
#if CONTENT_VALIDATION
Validate("re");
#endif
}
/// <summary>Append curved segment to path (three control points).</summary>
/// <param name="x1">x coordinate (1st control point)</param>
/// <param name="y1">y coordinate (1st control point)</param>
/// <param name="x2">x coordinate (2nd control point)</param>
/// <param name="y2">y coordinate (2nd control point)</param>
/// <param name="x3">x coordinate (3rd control point)</param>
/// <param name="y3">y coordinate (3rd control point)</param>
public virtual void Bezier(double x1, double y1, double x2, double y2, double x3, double y3)
{
AppendParameter(x1);
AppendParameter(y1);
AppendParameter(x2);
AppendParameter(y2);
AppendParameter(x3);
AppendParameter(y3);
mContents.Append(" c");
#if CONTENT_VALIDATION
Validate("c");
#endif
}
/// <summary>Close subpath.</summary>
public void Close() {
mContents.Append(" h");
}
/// <summary>Stroke path.</summary>
public virtual void Stroke() {
mContents.Append(" S");
#if CONTENT_VALIDATION
Validate("S");
#endif
}
/// <summary>Fill path using nonzero winding number rule.</summary>
public virtual void Fill() {
mContents.Append(" f");
#if CONTENT_VALIDATION
Validate("f");
#endif
}
/// <summary>Fill path using even-odd rule.</summary>
public virtual void FillEvenOddRule()
{
mContents.Append(" f*");
#if CONTENT_VALIDATION
Validate("f");
#endif
}
/// <summary>Set clipping path using nonzero winding number rule.</summary>
public virtual void Clip() {
mContents.Append(" W n");
#if CONTENT_VALIDATION
Validate("W");
#endif
}
/// <summary>Set clipping path using even-odd rule.</summary>
public virtual void ClipEvenOddRule()
{
mContents.Append(" W* n");
#if CONTENT_VALIDATION
Validate("W");
#endif
}
/// <summary>Save graphics state.</summary>
public virtual void SaveState() {
mContents.Append(" q");
#if CONTENT_VALIDATION
Validate("q");
#endif
}
/// <summary>Restore graphics state.</summary>
public virtual void RestoreState()
{
mContents.Append(" Q");
#if CONTENT_VALIDATION
Validate("Q");
#endif
}
/// <summary>Set line width.</summary>
/// <param name="v">Line width</param>
public virtual void SetLineWidth(double v)
{
AppendParameter(v);
mContents.Append(" w");
#if CONTENT_VALIDATION
Validate("w");
#endif
}
/// <summary>The line cap for the ends of any lines to be stroked.</summary>
public enum LineCap
{
Butt,
Round,
ProjectingSquare
}
/// <summary>Set line cap style.</summary>
/// <param name="v">Line cap style</param>
public virtual void SetLineCap(int v)
{
AppendParameter(v);
mContents.Append(" J");
#if CONTENT_VALIDATION
Validate("J");
#endif
}
/// <summary>The line join for the shape of joints between connected segments of a path.</summary>
public enum LineJoin
{
Miter,
Round,
Bevel
}
/// <summary>Set line join style.</summary>
/// <param name="v">Line join style</param>
public virtual void SetLineJoin(int v)
{
AppendParameter(v);
mContents.Append(" j");
#if CONTENT_VALIDATION
Validate("j");
#endif
}
/// <summary>Set miter limit.</summary>
/// <param name="v">Miter limit</param>
public virtual void SetMiterLimit(double v)
{
AppendParameter(v);
mContents.Append(" M");
#if CONTENT_VALIDATION
Validate("M");
#endif
}
/// <summary>Set line dash pattern.</summary>
/// <param name="dashPattern">Dash pattern</param>
public void LineDash(string dashPattern)
{
mContents.Append(" ").Append(dashPattern).Append(" d");
#if CONTENT_VALIDATION
Validate("d");
#endif
}
/// <summary>Set gray level for stroking operations.</summary>
/// <param name="w">Level of gray color</param>
public void SetGrayStrokeColor(double w)
{
AppendParameter(w);
mContents.Append(" G");
#if CONTENT_VALIDATION
Validate("G");
#endif
}
/// <summary>Set gray level for nonstroking operations.</summary>
/// <param name="w">Level of gray color</param>
public void SetGrayNonStrokeColor(double w)
{
AppendParameter(w);
mContents.Append(" g");
#if CONTENT_VALIDATION
Validate("g");
#endif
}
/// <summary>Set RGB color for stroking operations.</summary>
/// <param name="r">Level of red color</param>
/// <param name="g">Level of green color </param>
/// <param name="b">Level of blue color</param>
public void SetRGBStrokeColor(double r, double g, double b)
{
AppendParameter(r);
AppendParameter(g);
AppendParameter(b);
mContents.Append(" RG");
#if CONTENT_VALIDATION
Validate("RG");
#endif
}
/// <summary>Set RGB color for nonstroking operations.</summary>
/// <param name="r">Level of red color</param>
/// <param name="g">Level of green color </param>
/// <param name="b">Level of blue color</param>
public void SetRGBNonStrokeColor(double r, double g, double b)
{
AppendParameter(r);
AppendParameter(g);
AppendParameter(b);
mContents.Append(" rg");
#if CONTENT_VALIDATION
Validate("rg");
#endif
}
/// <summary>Set CMYK color for stroking operations.</summary>
/// <param name="c">Level of cyan color</param>
/// <param name="m">Level of magenta color</param>
/// <param name="y">Level of yellow color</param>
/// <param name="k">Level of black color</param>
public void SetCMYKStrokeColor(double c, double m, double y, double k)
{
AppendParameter(c);
AppendParameter(m);
AppendParameter(y);
AppendParameter(k);
mContents.Append(" K");
#if CONTENT_VALIDATION
Validate("K");
#endif
}
/// <summary>Set CMYK color for nonstroking operations.</summary>
/// <param name="c">Level of cyan color</param>
/// <param name="m">Level of magenta color</param>
/// <param name="y">Level of yellow color</param>
/// <param name="k">Level of black color</param>
public void SetCMYKNonStrokeColor(double c, double m, double y, double k)
{
AppendParameter(c);
AppendParameter(m);
AppendParameter(y);
AppendParameter(k);
mContents.Append(" k");
#if CONTENT_VALIDATION
Validate("k");
#endif
}
/// <summary>Concatenate matrix to current transformation matrix.</summary>
/// <param name="a">Transformation matrix parameter</param>
/// <param name="b">Transformation matrix parameter</param>
/// <param name="c">Transformation matrix parameter</param>
/// <param name="d">Transformation matrix parameter</param>
/// <param name="h">Transformation matrix parameter</param>
/// <param name="v">Transformation matrix parameter</param>
public virtual void Transform(double a, double b, double c, double d, double h, double v)
{
AppendParameter(a);
AppendParameter(b);
AppendParameter(c);
AppendParameter(d);
AppendParameter(h);
AppendParameter(v);
mContents.Append(" cm");
#if CONTENT_VALIDATION
Validate("cm");
#endif
}
/// <summary>Append arc segment to path.</summary>
/// <param name="start">Start angle of the arc in degrees</param>
/// <param name="end">End angle of the arc in degrees</param>
/// <param name="cx">Horizontal center of the arc</param>
/// <param name="cy">Vertical center of the arc</param>
/// <param name="rx">Horizontal radius of the arc</param>
/// <param name="ry">Vertical radius of the arc</param>
/// <param name="angle">Rotate angle</param>
/// <param name="inMove">If true, move to the first point of the arc</param>
public void Arc(double start, double end, double cx, double cy, double rx, double ry, double angle, bool inMove)
{
angle = angle * Math.PI / 180;
start = start * Math.PI / 180;
end = end * Math.PI / 180;
// for efficiency reasons we should probably suppress calculation of bounds rect here
const int theN = 8;
const int theNum = (theN * 3) - 2; // start and end only have one control point
double delta = (end - start) / (theN - 1);
double kv = 4 * (1 - Math.Cos(delta / 2)) / (3 * Math.Sin(delta / 2));
double[] x = new double[theNum];
double[] y = new double[theNum];
double ca = 0, sa = 0, tx = 0, ty = 0;
int i = 0, n = 0;
// make points
for (i = 0; i < theN; i++)
{
// establish point
n = i * 3;
ca = Math.Cos((i * delta) + start);
sa = Math.Sin((i * delta) + start);
x[n] = rx * ca;
y[n] = ry * sa;
sa = kv * rx * sa;
ca = kv * ry * ca;
// establish control points
if ((n + 1) < theNum) x[n + 1] = x[n] - sa;
if ((n + 1) < theNum) y[n + 1] = y[n] + ca;
if (n > 0) x[n - 1] = x[n] + sa;
if (n > 0) y[n - 1] = y[n] - ca;
}
// translate and rotate
ca = Math.Cos(angle);
sa = Math.Sin(angle);
for (i = 0; i < theNum; i++)
{
tx = (x[i] * ca) - (y[i] * sa) + cx;
ty = (x[i] * sa) + (y[i] * ca) + cy;
x[i] = tx; y[i] = ty;
}
// draw ellipse
if (inMove) Move(x[0], y[0]);
for (i = 0; i < (theN - 1); i++)
{
n = i * 3;
Bezier(x[n + 1], y[n + 1], x[n + 2], y[n + 2], x[n + 3], y[n + 3]);
}
#if CONTENT_VALIDATION
Validate("c");
#endif
}
/// <summary>Append ellipse to path. You may wish to close the path after callling this method.</summary>
/// <param name="x">Bottom left x coordinate</param>
/// <param name="y">Bottom left y coordinate</param>
/// <param name="w">Width</param>
/// <param name="h">Height</param>
internal void Ellipse(double x1, double y1, double w1, double h1) {
double x = x1 + w1, cy = y1 + h1 / 2;
double y = y1 + h1, cx = x1 + w1 / 2;
const double SquareRoot2 = 1.4142135623731d;
const double ctrlDist = (SquareRoot2 - 1) * 2 / 3;
double ctrlX = ctrlDist * w1, ctrlY = ctrlDist * h1;
Move(x, cy);
Bezier(x, cy + ctrlY, cx + ctrlX, y, cx, y);
Bezier(cx - ctrlX, y, x1, cy + ctrlY, x1, cy);
Bezier(x1, cy - ctrlY, cx - ctrlX, y1, cx, y1);
Bezier(cx + ctrlX, y1, x, cy - ctrlY, x, cy);
}
/// <summary>Append pdf content.</summary>
/// <param name="theContent">The content</param>
public virtual void AddContent(PDFContent theContent)
{
mContents.Append(theContent.mContents.ToString());
}
/// <summary>Add graphic state dictionaries to the page resources.</summary>
/// <param name="enumerable">Graphic states</param>
private void WriteGStates(IEnumerable<ExtGState> enumerable)
{
foreach (ExtGState gstate in enumerable) {
int theID = mDoc.AddObject(gstate.PdfObject);
mDoc.SetInfo(ResourcesObjectID, "/Resources*/ExtGState*/" + gstate.Name + ":Ref", theID);
}
}
/// <summary>Write content to the doc.</summary>
public virtual void AddToDoc()
{
if (mDoc.PageCount == 0)
mDoc.Page = mDoc.AddPage();
WriteAllGStates();
mDoc.SetInfo(mDoc.FrameRect(), "stream", mContents.ToString());
ShowErrors();
}
protected void WriteAllGStates()
{
WriteGStates(mNonStrokeExtGStates.Values);
WriteGStates(mStrokeExtGStates.Values);
WriteGStates(mBlendModeExtGStates.Values);
WriteGStates(mArbitraryExtGStates);
}
protected void ShowErrors()
{
#if CONTENT_VALIDATION
if(mErrors.Count > 0) {
string errors = "The content contains following errors:";
int errorsToShow = (mErrors.Count > 20)? 20: mErrors.Count;
for(int i = 0; i< errorsToShow; i++)
errors += "\n" + mErrors[i];
if(errorsToShow < mErrors.Count)
errors += "\n...";
MessageBox.Show(errors, "PDFContent",
MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
#endif
}
/// <summary>Extended graphic state.</summary>
private class ExtGState
{
public string Name = "";
public string PdfObject = "";
}
/// <summary>Set nonstroking alpha constant.</summary>
/// <param name="ca">Nonstroking alpha constant</param>
public void SetNonStrokeAlpha(double ca)
{
string val = ca.ToString(mFloatFormat, NumberFormatInfo.InvariantInfo);
ca = double.Parse(val, NumberFormatInfo.InvariantInfo);
ExtGState gstate;
if ( !mNonStrokeExtGStates.TryGetValue(ca, out gstate) ) {
gstate = new ExtGState();
gstate.Name = GetNextGSName();
gstate.PdfObject = "<< /Type/ExtGState\n /ca "+ val + "\n >>";
mNonStrokeExtGStates.Add(ca, gstate);
}
mContents.Append(" /").Append(gstate.Name).Append(" gs");
#if CONTENT_VALIDATION
Validate("gs");
#endif
}
/// <summary>Set stroking alpha constant.</summary>
/// <param name="CA">Stroking alpha constant</param>
public void SetStrokeAlpha(double CA)
{
string val = CA.ToString(mFloatFormat, NumberFormatInfo.InvariantInfo);
CA = double.Parse(val, NumberFormatInfo.InvariantInfo);
ExtGState gstate;
if ( !mStrokeExtGStates.TryGetValue(CA, out gstate) ) {
gstate = new ExtGState();
gstate.Name = GetNextGSName();
gstate.PdfObject = "<< /Type/ExtGState\n /CA "+ val + "\n >>";
mStrokeExtGStates.Add(CA, gstate);
}
mContents.Append(" /").Append(gstate.Name).Append(" gs");
#if CONTENT_VALIDATION
Validate("gs");
#endif
}
/// <summary>Set graphic state.</summary>
/// <param name="pdfFormattedDict">Graphic State dictionary in pdf native format</param>
public void SetGraphicState(string pdfFormattedDict)
{
ExtGState gstate = new ExtGState();
gstate.Name = GetNextGSName();
gstate.PdfObject = pdfFormattedDict;
mArbitraryExtGStates.Add(gstate);
mContents.Append(" /").Append(gstate.Name).Append(" gs");
#if CONTENT_VALIDATION
Validate("gs");
#endif
}
/// <summary>Set blend mode.</summary>
/// <param name="blendMode">Blend mode</param>
public void SetBlendMode(string blendMode)
{
ExtGState gstate;
if ( !mBlendModeExtGStates.TryGetValue(blendMode, out gstate) ) {
string name = GetNextGSName();
gstate = new ExtGState();
gstate.Name = name;
gstate.PdfObject = "<< /Type/ExtGState\n /BM /"+ blendMode + "\n >>";
mBlendModeExtGStates.Add(blendMode, gstate);
}
mContents.Append(" /").Append(gstate.Name).Append(" gs");
#if CONTENT_VALIDATION
Validate("gs");
#endif
}
/// <summary>Get next available name for gs pdf command.</summary>
/// <returns>Name of the gs command</returns>
private string GetNextGSName()
{
for (int i = mGSNumber; ; i++)
{
string gsName = "GS" + i.ToString(NumberFormatInfo.InvariantInfo);
string test = mDoc.GetInfo(ResourcesObjectID, "/Resources/ExtGState/" + gsName);
if (test == "")
{
mGSNumber = i + 1;
return gsName;
}
}
}
private string FindResourceName(string path, int id, out DictAtom outDict)
{
string resourcesStr = mDoc.GetInfo(ResourcesObjectID, path);
DictAtom resourcesDict = Atom.FromString(resourcesStr) as DictAtom;
outDict = resourcesDict;
if(resourcesDict != null) {
foreach(KeyValuePair<string, Atom> pair in resourcesDict) {
RefAtom theRef = pair.Value as RefAtom;
if(theRef != null && theRef.ID == id)
return pair.Key;
}
}
return "";
}
/// <summary>Get the name for Do pdf command.</summary>
/// <param name="id">The ID of the XObject</param>
/// <returns>Name of the Do command</returns>
private string GetXObjectName(int id)
{
const string path = "/Resources/XObject";
DictAtom xobjDict;
string name = FindResourceName(path + "*", id, out xobjDict);
if(name != "")
return name;
if(xobjDict == null)
xobjDict = new DictAtom();
string idStr = id.ToString(NumberFormatInfo.InvariantInfo);
Atom xobjRef = Atom.FromString(idStr + " 0 R");
name = "Iabc" + idStr;
string test = mDoc.GetInfo(ResourcesObjectID, path + "/" + name);
if(test == "") {
xobjDict.Add(name, xobjRef);
mDoc.SetInfo(ResourcesObjectID, path, xobjDict.ToString());
return name;
}
for(int i = mXObjNumber; ; ++i) {
name = "Iabc" + i.ToString(NumberFormatInfo.InvariantInfo);
test = mDoc.GetInfo(ResourcesObjectID, path + "/" + name);
if(test == "") {
xobjDict.Add(name, xobjRef);
mDoc.SetInfo(ResourcesObjectID, path, xobjDict.ToString());
mXObjNumber = i + 1;
return name;
}
}
}
/// <summary>Set rendering intent.</summary>
/// <param name="intent">Rendering intent</param>
public void SetRenderingIntent(string intent)
{
mContents.Append(" /").Append(intent).Append(" ri");
}
/// <summary>Add image to the pdf content.</summary>
/// <param name="imageID">ID of the image returned by Doc.AddImage method</param>
public virtual void DoImage(int imageID)
{
imageID = int.Parse(mDoc.GetInfo(imageID , "XObject"));
string doImageCommand = GetXObjectName(imageID);
mContents.Append(" /").Append(doImageCommand).Append(" Do");
}
/// <summary>Add image to the pdf content.</summary>
/// <param name="imageID">ID of the image returned by Doc.AddImage method</param>
public virtual void DoImage(string name) {
mContents.Append(" /").Append(name).Append(" Do");
}
/// <summary>Add image to the pdf page without drawing it.</summary>
/// <param name="image">Image to be added to the page</param>
public virtual string AddImage(XImage image) {
int id = mDoc.AddImage(image);
var stm = (StreamObject)mDoc.ObjectSoup[id];
id = int.Parse(mDoc.GetInfo(id, "XObject"));
string name = GetXObjectName(id);
stm.ClearData();
return name;
}
/// <summary>Add Form XObject to the pdf content.</summary>
/// <param name="xobjID">ID of the Form XObject returned by PDFBoundedContent.WriteToFormXObject method</param>
public virtual void DoFormXObject(PDFFormXObjectID xobjID)
{
string doCommand = GetXObjectName(xobjID.ID);
mContents.Append(" /").Append(doCommand).Append(" Do");
}
/// <summary>Begin text object.</summary>
public virtual void BeginText()
{
mContents.Append(" BT");
#if CONTENT_VALIDATION
Validate("BT");
#endif
}
/// <summary>End text object.</summary>
public void EndText()
{
mContents.Append(" ET");
#if CONTENT_VALIDATION
Validate("ET");
#endif
}
/// <summary>Text rendering mode.</summary>
public enum TextRenderingMode
{
FillText,
StrokeText,
FillThenStrokeText,
Invisible,
FillTextAndAddForClipping,
StrokeTextAndAddForClipping,
FillThenStrokeTextAndAddForClipping,
AddForClipping
}
/// <summary>Set text rendering mode.</summary>
/// <param name="mode">Text rendering mode</param>
public virtual void SetTextRenderingMode(TextRenderingMode mode)
{
AppendParameter((double)mode);
mContents.Append(" Tr");
#if CONTENT_VALIDATION
Validate("Tr");
#endif
}
/// <summary>Set character spacing.</summary>
/// <param name="tc">Character spacing</param>
public virtual void SetCharacterSpacing(double tc)
{
AppendParameter(tc);
mContents.Append(" Tc");
#if CONTENT_VALIDATION
Validate("Tc");
#endif
}
/// <summary>Set Word Spacing.</summary>
/// <param name="tw">Word spacing</param>
public virtual void SetWordSpacing(double tw)
{
AppendParameter(tw);
mContents.Append(" Tw");
#if CONTENT_VALIDATION
Validate("Tw");
#endif
}
/// <summary>Set horizontal text scaling.</summary>
/// <param name="th">Horizontal text scaling</param>
public virtual void SetHorizontalScaling(double th)
{
AppendParameter(th);
mContents.Append(" Tz");
#if CONTENT_VALIDATION
Validate("Tz");
#endif
}
/// <summary>Set text leading.</summary>
/// <param name="tl">Text leading</param>
public virtual void SetTextLeading(double tl)
{
AppendParameter(tl);
mContents.Append(" TL");
#if CONTENT_VALIDATION
Validate("TL");
#endif
}
/// <summary>Set text rise.</summary>
/// <param name="ts">Text rise</param>
public virtual void SetTextRise(double ts)
{
AppendParameter(ts);
mContents.Append(" Ts");
#if CONTENT_VALIDATION
Validate("Ts");
#endif
}
/// <summary>Show text.</summary>
/// <param name="text">Text string</param>
public virtual void ShowTextString(string text)
{
mContents.AppendFormat(" ({0}) Tj", mPdfString.Replace(text, @"\$&"));
#if CONTENT_VALIDATION
Validate("Tj");
#endif
}
/// <summary>Set text font and size.</summary>
/// <param name="fontObjID">Font object ID</param>
/// <param name="fontSize">Font size</param>
public virtual void SetFont(int fontObjID, double fontSize)
{
const string path = "/Resources/Font";
DictAtom fontDict;
string fontCommand = FindResourceName(path + "*", fontObjID, out fontDict);
if (fontCommand == "")
{
if (fontDict == null)
fontDict = new DictAtom();
Atom fontRef = Atom.FromString(fontObjID.ToString(NumberFormatInfo.InvariantInfo) + " 0 R");
fontCommand = "Fabc" + fontObjID.ToString(NumberFormatInfo.InvariantInfo);
fontDict.Add(fontCommand, fontRef);
mDoc.SetInfo(ResourcesObjectID, path, fontDict.ToString());
}
if ( fontCommand != "" )
{
mContents.AppendFormat(" /{0} {1} Tf", fontCommand, fontSize);
#if CONTENT_VALIDATION
Validate("Tf");
#endif
}
}
/// <summary>Set text matrix and text line matrix.</summary>
/// <param name="a">Text matrix parameter</param>
/// <param name="b">Text matrix parameter</param>
/// <param name="c">Text matrix parameter</param>
/// <param name="d">Text matrix parameter</param>
/// <param name="e">Text matrix parameter</param>
/// <param name="f">Text matrix parameter</param>
public virtual void SetTextMatrix(double a, double b, double c, double d, double e, double f)
{
AppendParameter(a);
AppendParameter(b);
AppendParameter(c);
AppendParameter(d);
AppendParameter(e);
AppendParameter(f);
mContents.Append(" Tm");
#if CONTENT_VALIDATION
Validate("Tm");
#endif
}
/// <summary>Move to start of next text line.</summary>
public virtual void NextLine()
{
mContents.Append(" T*");
#if CONTENT_VALIDATION
Validate("T*");
#endif
}
/// <summary>Move text position.</summary>
/// <param name="tx">x offset from the start of the current line</param>
/// <param name="ty">y offset from the start of the current line</param>
public virtual void TextMove(double tx, double ty)
{
AppendParameter(tx);
AppendParameter(ty);
mContents.Append(" Td");
#if CONTENT_VALIDATION
Validate("Td");
#endif
}
private void AppendParameter(double v)
{
mContents.AppendFormat(NumberFormatInfo.InvariantInfo, " {0:" + mFloatFormat + "}", v);
}
/// <summary>The PDF floating-point format.</summary>
internal const string mFloatFormat = "0.#####";
/// <summary>The PDF string escape.</summary>
private static readonly Regex mPdfString = new Regex(@"[\\\(\)]",
RegexOptions.Compiled | RegexOptions.Singleline
| RegexOptions.CultureInvariant);
/// <summary>Last used number for graphic state command (e.g. GS7 for mGSNumber = 7).</summary>
private int mGSNumber = 0;
/// <summary>Last used number for XObject Do command (e.g. Iabc7 for mXObjNumber = 7).</summary>
private int mXObjNumber = 0;
/// <summary>Parent doc.</summary>
protected Doc mDoc;
/// <summary>Pdf content string.</summary>
internal System.Text.StringBuilder mContents = new System.Text.StringBuilder();
/// <summary>Binary tree of used extended graphic states for stroke color transparency.</summary>
private SortedDictionary<double, ExtGState> mStrokeExtGStates = new SortedDictionary<double, ExtGState>();
/// <summary>Binary tree of used extended graphic states for nonstroke color transparency.</summary>
private SortedDictionary<double, ExtGState> mNonStrokeExtGStates = new SortedDictionary<double, ExtGState>();
/// <summary>Hashtable of used extended graphic states for blending modes.</summary>
private Dictionary<string, ExtGState> mBlendModeExtGStates = new Dictionary<string, ExtGState>();
/// <summary>List of used ExtGStates.</summary>
private List<ExtGState> mArbitraryExtGStates = new List<ExtGState>();
#if CONTENT_VALIDATION
/// <summary>Pdf command validation.</summary>
/// <param name="command">Pdf command</param>
private void Validate(string command)
{
int generalOperation = Array.BinarySearch(mGeneralOps, command);
int textOperation = Array.BinarySearch(mTextOps, command);
if (generalOperation > 0 && mBeginTextOpen)
mErrors.Add("Illegal operation '" + command + "' inside text object");
else if (textOperation > 0 && !mBeginTextOpen )
mErrors.Add("Illegal operation '" + command + "' outside text object");
else
{
switch (command)
{
case "q":
mOpenQCount++;
break;
case "Q":
mOpenQCount--;
if (mOpenQCount < 0)
mErrors.Add("Invalid restore");
break;
case "BT":
mBeginTextOpen = true;
break;
case "ET":
mBeginTextOpen = false;
break;
}
}
}
/// <summary>Array of pdf commands.</summary>
private static readonly string[] mGeneralOps = {"b", "B", "b*", "B*", "c", "d", "f",
"F", "f*", "g", "G", "h", "i", "j", "J", "k", "l", "m",
"M", "n", "re", "rg", "RG", "ri", "s", "S", "v", "w", "W", "W*",
"y"};
/// <summary>Array of pdf commands used for text output.</summary>
private static readonly string[] mTextOps = {"T*", "Tc", "Td", "TD", "Tf", "Tj", "TJ", "TL",
"Tm", "Tr", "Ts", "Tw", "Tz"};
/// <summary>Number of open "q" operators.</summary>
private int mOpenQCount = 0;
/// <summary>True if Begin text operator wasn't closed by End text operator.</summary>
private bool mBeginTextOpen = false;
/// <summary>List of errors in pdf content.</summary>
private List<string> mErrors = new List<string>();
#endif
}
}