// ===========================================================================
// ©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.Collections.Generic;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Diagnostics;
using System.Runtime.InteropServices;
using WebSupergoo.ABCpdf14;
using WebSupergoo.ABCpdf14.Atoms;
using WebSupergoo.ABCpdf14.Objects;
using DocPage = WebSupergoo.ABCpdf14.Objects.Page;
#if CONTENT_VALIDATION
using System.Windows.Forms;
#endif
namespace WebSupergoo.ABCpdf14.Drawing {
#region PDFContent
/// <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>
internal class PDFContent {
public sealed class DocumentCache {
[StructLayout(LayoutKind.Auto)]
private struct ReferencedImage {
public WeakReference Image;
public int ImageID;
public ReferencedImage(Image image, int imageID) {
Image = new WeakReference(image);
ImageID = imageID;
}
}
private Dictionary<string, Text.FontMetrics> _fontMetrics
= new Dictionary<string, Text.FontMetrics>(14);
private LinkedList<ReferencedImage> _images = new LinkedList<ReferencedImage>();
public void Clear() {
_images.Clear();
}
public Text.FontMetrics RegisterFontMetric(BuiltInFont font) {
Text.FontMetrics metrics;
if (!_fontMetrics.TryGetValue(font.BuiltInName, out metrics)) {
metrics = font.FontMetrics;
_fontMetrics.Add(font.BuiltInName, metrics);
}
return metrics;
}
public int GetReferencedImageID(Image image) {
LinkedListNode<ReferencedImage> node = _images.First;
while (node != null) {
object obj = node.Value.Image.Target;
if (obj == null) {
LinkedListNode<ReferencedImage> nextNode = node.Next;
_images.Remove(node);
node = nextNode;
} else {
if (obj == image)
return node.Value.ImageID;
node = node.Next;
}
}
return 0;
}
public void AddReferencedImage(Image image, int imageID) {
_images.AddFirst(new ReferencedImage(image, imageID));
}
}
#region Constructors
public PDFContent() { }
public PDFContent(Doc doc): this(doc, new DocumentCache()) { }
public PDFContent(Doc doc, DocumentCache docCache) {
mDoc = doc;
mDocCache = docCache;
}
#endregion
internal Doc Doc { get { return mDoc; } }
internal System.Text.StringBuilder Contents { get { return mContents; } }
/// <summary>The ID of the object containing the resources dictionary.</summary>
protected virtual int ResourcesObjectID {
get {
int id = mDoc.Page;
if(id == 0)
mDoc.Page = id = mDoc.AddPage();
return id;
}
}
#region PDF content methods
public int GetReferencedImageID(Image image) {
return mDocCache.GetReferencedImageID(image);
}
public void AddReferencedImage(Image image, int imageID) {
mDocCache.AddReferencedImage(image, imageID);
}
/// <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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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 virtual void Arc(double start, double end, double cx, double cy, double rx, double ry, double angle, bool inMove) {
angle = angle / 180 * Math.PI;
start = start / 180 * Math.PI;
end = end / 180 * Math.PI;
// for efficiency reasons it might be a good idea to suppress the calculation of the 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]);
}
}
/// <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) { // idempotent
foreach (ExtGState gstate in enumerable) {
string path = "/Resources/ExtGState/" + gstate.Name + ":Ref";
if (mDoc.GetInfoInt(ResourcesObjectID, path) == 0) {
int theID = mDoc.AddObject(gstate.PdfObject);
mDoc.SetInfo(ResourcesObjectID, path, theID);
}
}
}
/// <summary>Write content to the doc.</summary>
public virtual bool AddToDoc() { // idempotent
if (mDoc.Page == 0)
mDoc.Page = mDoc.AddPage();
if (mContents.Length <= 0)
return false;
WriteAllGStates();
mDoc.SetInfo(mDoc.FrameRect(), "stream", mContents.ToString());
mContents.Clear();
ShowErrors();
return true;
}
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...";
Debug.Fail(errors);
}
#endif
}
#region ExtGState
/// <summary>Extended graphic state.</summary>
private class ExtGState {
public string Name = "";
public string PdfObject = "";
}
#endregion
/// <summary>Set nonstroking alpha constant.</summary>
/// <param name="ca">Nonstroking alpha constant</param>
public virtual 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 virtual 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 virtual 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 virtual 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 Do pdf command.</summary>
/// <returns>Name of the Do command</returns>
private string GetNextXObjectName() {
for (int i = mXObjNumber; ; i++) {
string objName = "Iabc" + i.ToString(NumberFormatInfo.InvariantInfo);
string test = mDoc.GetInfo(ResourcesObjectID, "/Resources/XObject/" + objName);
if (test == "") {
mXObjNumber = i + 1;
return objName;
}
}
}
/// <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;
}
}
}
/// <summary>Find or add a resource if it is not already there.</summary>
/// <param name="type">The type of the resource eg Font or XObject</param>
/// <param name="id">The ID of the resource</param>
/// <returns>The name of the resource</returns>
private string FindOrAddResource(string type, int id) {
DocPage page = (DocPage)mDoc.ObjectSoup[ResourcesObjectID];
IDictionary<string, Atom> map = page.GetResourceMap(page.ID, type);
if (map != null) {
foreach(KeyValuePair<string, Atom> pair in map) {
RefAtom theRef = pair.Value as RefAtom;
if(theRef != null && theRef.ID == id)
return pair.Key;
}
}
return page.AddResource(mDoc.ObjectSoup[id], "XObject", "Iabc" + id.ToString(NumberFormatInfo.InvariantInfo));
}
/// <summary>Set rendering intent.</summary>
/// <param name="intent">Rendering intent</param>
public virtual void SetRenderingIntent(string intent) {
mContents.Append(" /").Append(intent).Append(" ri");
#if CONTENT_VALIDATION
Validate("ri");
#endif
}
/// <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 = mDoc.GetInfoInt(imageID , "XObject");
if (imageID == 0)
throw new ArgumentException("Invalid image ID.");
string doImageCommand = FindOrAddResource("XObject", imageID);
mContents.Append(" /").Append(doImageCommand).Append(" Do");
#if CONTENT_VALIDATION
Validate("Do");
#endif
}
/// <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 = FindOrAddResource("XObject", xobjID.ID);
mContents.Append(" /").Append(doCommand).Append(" Do");
#if CONTENT_VALIDATION
Validate("Do");
#endif
}
/// <summary>Begin text object.</summary>
public virtual void BeginText() {
mContents.Append(" BT");
#if CONTENT_VALIDATION
Validate("BT");
#endif
}
/// <summary>End text object.</summary>
public virtual void EndText() {
mContents.Append(" ET");
#if CONTENT_VALIDATION
Validate("ET");
#endif
}
/// <summary>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) {
string fontCommand = FindOrAddResource("Font", fontObjID);
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) {
double iv = (double)decimal.Truncate((decimal)v);
mContents.AppendFormat(NumberFormatInfo.InvariantInfo, " {0:" + mFloatFormat + "}", v);
}
#endregion
#region Declare variables
/// <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 XObject Do command (e.g. Iabc7 for mXObjNumber = 7).</summary>
private int mXObjNumber = 0;
/// <summary>Last used number for graphic state command (e.g. GS7 for mGSNumber = 7).</summary>
private int mGSNumber = 0;
/// <summary>Parent doc.</summary>
private Doc mDoc;
/// <summary>Document cache.</summary>
private DocumentCache mDocCache;
/// <summary>Pdf content string.</summary>
private 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", "Do", "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
#endregion
}
#endregion
}