// ===========================================================================
// ©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.
// ===========================================================================
using System;
using System.Xml;
using System.Globalization;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections.Generic;
using System.Windows.Forms;
using System.IO;
using WebSupergoo.ABCpdf14;
using WebSupergoo.ABCpdf14.Atoms;
using WebSupergoo.ABCpdf14.Objects;
namespace ABCpdfControls {
/// <summary>
/// Annotated page content.
/// </summary>
internal class AnnotatedContent {
private Doc mDoc;
private NumberFormatInfo mFormatProvider;
private const string kDefaultTextProperties = "0 Tc 0 Tw 100 Tz 0 TL 0 Ts";
public AnnotatedContent(Doc inDoc) {
mDoc = inDoc;
mFormatProvider = new NumberFormatInfo();
mFormatProvider.NumberDecimalSeparator = ".";
mFormatProvider.NumberDecimalDigits = 5;
}
/// <summary>
/// Extract text block information from the xml decription
/// </summary>
/// <param name="node">Xml node corresonding to the text block</param>
/// <returns>Text Block</returns>
private TextBlock ParseTextBlock(XmlNode node) {
NumberFormatInfo theFormatProvider = new NumberFormatInfo();
theFormatProvider.NumberDecimalSeparator = ".";
XmlNode attrNode = null;
float fontSize = 0;
if ((attrNode = node.Attributes["font-size"]) != null) {
fontSize = float.Parse(attrNode.Value, mFormatProvider);
// SVG does not allow negative font size
// so special leading '+' sign indicates negative font size
if (attrNode.Value.Length > 0 && attrNode.Value[0] == '+')
fontSize = -fontSize;
}
string text = "";
float w = 0;
if (node.ChildNodes.Count == 0 || (node.ChildNodes.Count > 0 && node.ChildNodes[0].Name != "tspan")) {
text = node.InnerText;
string lengthStr = node.Attributes["pdf_w1000"].Value;
w = float.Parse(lengthStr, theFormatProvider) / 1000;
}
else {
for (int j = 0; j < node.ChildNodes.Count; j++) {
XmlNode childNode = node.ChildNodes.Item(j);
if (childNode.Name == "tspan") {
text += childNode.InnerText;
string lengthStr = childNode.Attributes["pdf_w1000"].Value;
w += float.Parse(lengthStr, theFormatProvider) / 1000;
if ((attrNode = childNode.Attributes["dx"]) != null)
w += float.Parse(attrNode.Value, theFormatProvider) / fontSize;
}
}
}
if (text == "" || w == 0)
return null;
float h = 1;
GraphicsPath thePath = new GraphicsPath();
thePath.AddRectangle(new RectangleF(0, -(float)0.2*h, w, h));
string textMatrix = node.Attributes["pdf_Trm"].Value;
string[] valuesStr = textMatrix.Split(' ');
Matrix theMatrix = new Matrix(float.Parse(valuesStr[0], theFormatProvider), float.Parse(valuesStr[1], theFormatProvider), float.Parse(valuesStr[2], theFormatProvider), float.Parse(valuesStr[3], theFormatProvider), float.Parse(valuesStr[4], theFormatProvider), float.Parse(valuesStr[5], theFormatProvider));
thePath.Transform(theMatrix);
TextBlock theBlock = new TextBlock(text, thePath, node);
if ((attrNode = node.Attributes["pdf_StreamID"]) != null)
theBlock.StreamId = int.Parse(attrNode.Value);
if ((attrNode = node.Attributes["pdf_StreamOffset"]) != null)
theBlock.StreamOffset = int.Parse(attrNode.Value);
if ((attrNode = node.Attributes["pdf_StreamLength"]) != null)
theBlock.Length = int.Parse(attrNode.Value);
if ((attrNode = node.Attributes["fill"]) != null) {
string colorStr = attrNode.Value;
colorStr = colorStr.Replace("rgb(", "");
colorStr = colorStr.Replace(")", "");
XColor theColor = new XColor();
theColor.String = colorStr;
theBlock.TextColor = theColor.Color;
}
else
theBlock.TextColor = Color.Black;
if ((attrNode = node.Attributes["font-family"]) != null)
theBlock.FontFamily = attrNode.Value;
theBlock.FontSize = fontSize;
if ((attrNode = node.Attributes["pdf_Op"]) != null)
theBlock.PDFOperator = attrNode.Value;
if ((attrNode = node.Attributes["pdf_Tf"]) != null)
theBlock.FontOperator = attrNode.Value;
if ((attrNode = node.Attributes["pdf_Ts"]) != null)
theBlock.TextRise = float.Parse(attrNode.Value, mFormatProvider);
if ((attrNode = node.Attributes["pdf_Tw"]) != null)
theBlock.WordSpacing = float.Parse(attrNode.Value, mFormatProvider);
if ((attrNode = node.Attributes["pdf_Tc"]) != null)
theBlock.CharacterSpacing = float.Parse(attrNode.Value, mFormatProvider);
if ((attrNode = node.Attributes["pdf_TL"]) != null)
theBlock.TextLeading = float.Parse(attrNode.Value, mFormatProvider);
if ((attrNode = node.Attributes["pdf_Tz"]) != null)
theBlock.HorizontalScaling = float.Parse(attrNode.Value, mFormatProvider);
if ((attrNode = node.Attributes["pdf_nonStroke"]) != null)
theBlock.TextColorOperator = attrNode.Value;
theBlock.TextRenderingMatrix = theMatrix;
return theBlock;
}
/// <summary>
/// Returns Text Blocks from the page
/// </summary>
/// <param name="mDoc">Document</param>
/// <returns>ArrayList containing text blocks</returns>
public List<TextBlock> Parse(Doc mDoc) {
List<TextBlock> theBlocks = new List<TextBlock>();
mDoc.Flatten();
string content = mDoc.GetText("SVG+");
StringReader xmlReader = new StringReader(content);
XmlDocument xmlDoc = new XmlDocument();
try {
xmlDoc.Load(xmlReader);
}
catch (Exception e) {
MessageBox.Show("Error deconstructing SVG: " + e.Message);
}
theBlocks.Clear();
XmlNode svg = xmlDoc["svg"];
XmlNode node = svg != null && svg.FirstChild != null? svg.FirstChild: svg;
while (node!=svg) {
if (node.Name == "text") {
TextBlock theBlock = ParseTextBlock(node);
if (theBlock != null)
theBlocks.Add(theBlock);
}
if (node.FirstChild != null)
node = node.FirstChild;
else if (node.NextSibling != null)
node = node.NextSibling;
else {
do {
node = node.ParentNode;
} while (node != svg && node.NextSibling == null);
if (node != svg)
node = node.NextSibling;
}
}
return theBlocks;
}
public void UpdateTextBlock(TextBlock inBlock, string inText) {
if (inBlock != null && inBlock.StreamId != 0 && inBlock.StreamOffset > 0 && inBlock.Length > 0) {
StreamObject streamObject = (StreamObject)mDoc.ObjectSoup[inBlock.StreamId];
streamObject.Decompress();
string stream = streamObject.GetText();
stream = stream.Remove(inBlock.StreamOffset, inBlock.Length);
string oldFontPath = "/Resources/Font/"+inBlock.FontOperator+":Ref";
int oldFontId = mDoc.GetInfoInt(inBlock.StreamId, oldFontPath);
if(oldFontId == 0)
oldFontId = mDoc.GetInfoInt(mDoc.Page, oldFontPath);
int fontId;
string updatedString = UnicodeToFontEncoding(mDoc, oldFontId,
inBlock.FontFamily?? "", inText, out fontId) + " Tj";
string fontOp = fontId==0? "": GetFontOperator(inBlock.StreamId, fontId);
if (fontOp != "")
updatedString = string.Format(mFormatProvider,
" /{0} {1:0.#####} Tf\r\n{2}\r\n/{3} {1:0.#####} Tf",
fontOp, inBlock.FontSize, updatedString, inBlock.FontOperator);
stream = stream.Insert(inBlock.StreamOffset, updatedString);
streamObject.SetText(stream);
inBlock.Length = updatedString.Length;
}
}
private string UnicodeToFontEncoding(Doc inDoc, int inFontId, string inFont,
string inText, out int outFontId)
{
string theText;
FontObject fontObj = inFontId==0? null: inDoc.ObjectSoup[inFontId] as FontObject;
if (fontObj != null) {
int count;
theText = fontObj.EncodeText(inText,
FontObject.EncodingSupport.NoneIfAnyNotSupported, out count);
if (count >= inText.Length) {
outFontId = 0;
return theText;
}
if (inFont != "") {
inFontId = mDoc.AddFont(inFont);
fontObj = inFontId==0? null: inDoc.ObjectSoup[inFontId] as FontObject;
if (fontObj != null) {
theText = fontObj.EncodeText(inText,
FontObject.EncodingSupport.NoneIfAnyNotSupported, out count);
if (count >= inText.Length) {
outFontId = inFontId;
return theText;
}
}
mDoc.Delete(inFontId);
}
}
outFontId = 0;
theText = inText.Replace("\\", "\\\\");
theText = theText.Replace("(", "\\(");
theText = theText.Replace(")", "\\)");
return theText;
}
public string GetFontOperator(int resObjId, int fontID) {
string fontCommand = "";
string fontStr = mDoc.GetInfo(resObjId, "/Resources/Font*");
DictAtom fontDict = Atom.FromString(fontStr) as DictAtom;
if(fontDict==null) {
resObjId = mDoc.Page;
fontStr = mDoc.GetInfo(resObjId, "/Resources/Font*");
fontDict = Atom.FromString(fontStr) as DictAtom;
}
if (fontDict != null) {
foreach (KeyValuePair<string, Atom> pair in fontDict) {
RefAtom refAtom = pair.Value as RefAtom;
if (refAtom != null && refAtom.ID == fontID) {
fontCommand = pair.Key;
break;
}
}
}
if (fontCommand == "") {
if (fontDict == null)
fontDict = new DictAtom();
Atom fontRef = Atom.FromString(fontID.ToString()+" 0 R");
fontCommand = "Fabc"+fontID.ToString();
fontDict.Add(fontCommand, fontRef);
mDoc.SetInfo(resObjId, "/Resources*/Font", fontDict.ToString());
}
return fontCommand;
}
public void UpdateTextBlock(TextBlock inBlock, string inText, Color inColor, string font) {
if (inBlock != null && inBlock.StreamId != 0 && inBlock.StreamOffset > 0 && inBlock.Length > 0) {
StreamObject streamObject = (StreamObject)mDoc.ObjectSoup[inBlock.StreamId];
streamObject.Decompress();
string stream = streamObject.GetText();
stream = stream.Remove(inBlock.StreamOffset, inBlock.Length);
int fontId = 0;
string fontOp = "";
if (font != "") {
fontId = mDoc.AddFont(font);
if (fontId != 0)
fontOp = GetFontOperator(inBlock.StreamId, fontId);
}
string updatedString = inBlock.PDFOperator;
bool sameText = inBlock.Text == inText;
if (!sameText || fontId != 0) {
string oldFontPath = "/Resources/Font/"+inBlock.FontOperator+":Ref";
int oldFontId = mDoc.GetInfoInt(inBlock.StreamId, oldFontPath);
if (oldFontId == 0)
oldFontId = mDoc.GetInfoInt(mDoc.Page, oldFontPath);
string oldEncoding = "";
if (sameText && oldFontId != 0)
oldEncoding = mDoc.GetInfo(oldFontId, "/Encoding*:Name");
if (!sameText || oldEncoding == ""
|| oldEncoding != mDoc.GetInfo(fontId, "/Encoding*:Name"))
{
updatedString = UnicodeToFontEncoding(mDoc,
fontId==0? oldFontId: fontId,
fontId!=0 || inBlock.FontFamily==null? "": inBlock.FontFamily,
inText, out fontId) + " Tj";
if (fontId != 0)
fontOp = GetFontOperator(inBlock.StreamId, fontId);
}
}
if (inBlock.TextColor != inColor) {
string oldColorOP = inBlock.TextColorOperator?? string.Format(mFormatProvider,
"{0:0.#####} {1:0.#####} {2:0.#####} rg",
inBlock.TextColor.R / 255.0, inBlock.TextColor.G / 255.0, inBlock.TextColor.B / 255.0);
updatedString = string.Format(mFormatProvider,
" {0:0.#####} {1:0.#####} {2:0.#####} rg\r\n{3}\r\n{4}",
inColor.R / 255.0, inColor.G / 255.0, inColor.B / 255.0, updatedString, oldColorOP);
}
if (fontOp != "")
updatedString = string.Format(mFormatProvider,
" /{0} {1:0.#####} Tf\r\n{2}\r\n/{3} {1:0.#####} Tf",
fontOp, inBlock.FontSize, updatedString, inBlock.FontOperator);
stream = stream.Insert(inBlock.StreamOffset, updatedString);
streamObject.SetText(stream);
inBlock.Length = updatedString.Length;
}
}
//public string GetTextPropertiesString(TextBlock inBlock) {
// return " " + inBlock.TextRise.ToString(mFormatProvider) + " Ts " +
// inBlock.WordSpacing.ToString(mFormatProvider) + " Tw " +
// inBlock.CharacterSpacing.ToString(mFormatProvider) + " Tc " +
// inBlock.TextLeading.ToString(mFormatProvider) + " TL " +
// inBlock.HorizontalScaling.ToString(mFormatProvider) + " Tz ";
//}
public void DeleteTextBlock(TextBlock inBlock) {
if (inBlock != null && inBlock.StreamId != 0 && inBlock.StreamOffset > 0 && inBlock.Length > 0) {
StreamObject streamObject = (StreamObject)mDoc.ObjectSoup[inBlock.StreamId];
streamObject.Decompress();
string stream = streamObject.GetText();
if (inBlock.StreamOffset + inBlock.Length < stream.Length){
stream = stream.Remove(inBlock.StreamOffset, inBlock.Length);
streamObject.SetText(stream);
inBlock.Length = 0;
}
}
}
public void MoveTextBlock(TextBlock inBlock, int xOffset, int yOffset) {
Matrix moveMatrix = new Matrix(1, 0, 0, 1, xOffset, yOffset);
TransformTextBlock(inBlock, moveMatrix);
}
public void TransformTextBlock(TextBlock inBlock, Matrix ctm) {
if (inBlock != null && inBlock.StreamId != 0 && inBlock.StreamOffset > 0 && inBlock.Length > 0) {
DeleteTextBlock(inBlock);
string colorOP = inBlock.TextColorOperator?? string.Format(mFormatProvider,
"{0:0.#####} {1:0.#####} {2:0.#####} rg",
inBlock.TextColor.R / 255.0, inBlock.TextColor.G / 255.0, inBlock.TextColor.B / 255.0);
inBlock.TextRenderingMatrix.Multiply(ctm, MatrixOrder.Append);
float[] elements = inBlock.TextRenderingMatrix.Elements;
// not using GetTextPropertiesString(inBlock)
string textBlockStr = string.Format(mFormatProvider,
"q\r\nBT\r\n"+kDefaultTextProperties
+ "\r\n{0}\r\n{1:0.#####} {2:0.#####} {3:0.#####} {4:0.#####} {5:0.#####} {6:0.#####} Tm"
+ "\r\n/{7} 1 Tf\r\n{8}\r\nET\r\nQ",
colorOP, elements[0], elements[1], elements[2], elements[3], elements[4], elements[5],
inBlock.FontOperator, inBlock.PDFOperator);
mDoc.SetInfo(mDoc.FrameRect(), "stream", textBlockStr);
}
}
}
}