/**
* Writes a text line to the document. It takes care of all the attributes.
* <P>
* Before entering the line position must have been established and the
* <CODE>text</CODE> argument must be in text object scope (<CODE>beginText()</CODE>).
* @param line the line to be written
* @param text the <CODE>PdfContentByte</CODE> where the text will be written to
* @param graphics the <CODE>PdfContentByte</CODE> where the graphics will be written to
* @param currentValues the current font and extra spacing values
* @param ratio
* @throws DocumentException on error
*/
internal float WriteLineToContent(PdfLine line, PdfContentByte text, PdfContentByte graphics, Object[] currentValues, float ratio) {
PdfFont currentFont = (PdfFont)(currentValues[0]);
float lastBaseFactor = (float)currentValues[1];
//PdfChunk chunkz;
int numberOfSpaces;
int lineLen;
bool isJustified;
float hangingCorrection = 0;
float hScale = 1;
float lastHScale = float.NaN;
float baseWordSpacing = 0;
float baseCharacterSpacing = 0;
float glueWidth = 0;
float lastX = text.XTLM + line.OriginalWidth;
numberOfSpaces = line.NumberOfSpaces;
lineLen = line.GetLineLengthUtf32();
// does the line need to be justified?
isJustified = line.HasToBeJustified() && (numberOfSpaces != 0 || lineLen > 1);
int separatorCount = line.GetSeparatorCount();
if (separatorCount > 0) {
glueWidth = line.WidthLeft / separatorCount;
}
else if (isJustified && separatorCount == 0) {
if (line.NewlineSplit && line.WidthLeft >= (lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1))) {
if (line.RTL) {
text.MoveText(line.WidthLeft - lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1), 0);
}
baseWordSpacing = ratio * lastBaseFactor;
baseCharacterSpacing = lastBaseFactor;
}
else {
float width = line.WidthLeft;
PdfChunk last = line.GetChunk(line.Size - 1);
if (last != null) {
String s = last.ToString();
char c;
if (s.Length > 0 && hangingPunctuation.IndexOf((c = s[s.Length - 1])) >= 0) {
float oldWidth = width;
width += last.Font.Width(c) * 0.4f;
hangingCorrection = width - oldWidth;
}
}
float baseFactor = width / (ratio * numberOfSpaces + lineLen - 1);
baseWordSpacing = ratio * baseFactor;
baseCharacterSpacing = baseFactor;
lastBaseFactor = baseFactor;
}
}
else if (line.alignment == Element.ALIGN_LEFT || line.alignment == Element.ALIGN_UNDEFINED) {
lastX -= line.WidthLeft;
}
int lastChunkStroke = line.LastStrokeChunk;
int chunkStrokeIdx = 0;
float xMarker = text.XTLM;
float baseXMarker = xMarker;
float yMarker = text.YTLM;
bool adjustMatrix = false;
float tabPosition = 0;
// looping over all the chunks in 1 line
foreach (PdfChunk chunk in line) {
if (IsTagged(writer) && chunk.accessibleElement != null) {
text.OpenMCBlock(chunk.accessibleElement);
}
BaseColor color = chunk.Color;
float fontSize = chunk.Font.Size;
float ascender;
float descender;
if (chunk.IsImage())
{
ascender = chunk.Height();
descender = 0;
}
else
{
ascender = chunk.Font.Font.GetFontDescriptor(BaseFont.ASCENT, fontSize);
descender = chunk.Font.Font.GetFontDescriptor(BaseFont.DESCENT, fontSize);
}
hScale = 1;
if (chunkStrokeIdx <= lastChunkStroke) {
float width;
if (isJustified) {
width = chunk.GetWidthCorrected(baseCharacterSpacing, baseWordSpacing);
}
else {
width = chunk.Width();
}
if (chunk.IsStroked()) {
PdfChunk nextChunk = line.GetChunk(chunkStrokeIdx + 1);
if (chunk.IsSeparator()) {
width = glueWidth;
Object[] sep = (Object[])chunk.GetAttribute(Chunk.SEPARATOR);
IDrawInterface di = (IDrawInterface)sep[0];
bool vertical = (bool)sep[1];
if (vertical) {
di.Draw(graphics, baseXMarker, yMarker + descender, baseXMarker + line.OriginalWidth, ascender - descender, yMarker);
}
else {
di.Draw(graphics, xMarker, yMarker + descender, xMarker + width, ascender - descender, yMarker);
}
}
if (chunk.IsTab()) {
if (chunk.IsAttribute(Chunk.TABSETTINGS))
{
TabStop tabStop = chunk.TabStop;
if (tabStop != null) {
tabPosition = tabStop.Position + baseXMarker;
if (tabStop.Leader != null)
tabStop.Leader.Draw(graphics, xMarker, yMarker + descender, tabPosition, ascender - descender, yMarker);
}
else {
tabPosition = xMarker;
}
} else {
//Keep deprecated tab logic for backward compatibility...
Object[] tab = (Object[])chunk.GetAttribute(Chunk.TAB);
IDrawInterface di = (IDrawInterface)tab[0];
tabPosition = (float)tab[1] + (float)tab[3];
if (tabPosition > xMarker)
di.Draw(graphics, xMarker, yMarker + descender, tabPosition, ascender - descender, yMarker);
}
float tmp = xMarker;
xMarker = tabPosition;
tabPosition = tmp;
}
if (chunk.IsAttribute(Chunk.BACKGROUND)) {
bool inText = graphics.InText;
if (inText && IsTagged(writer)) {
graphics.EndText();
}
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.IsAttribute(Chunk.BACKGROUND))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
Object[] bgr = (Object[])chunk.GetAttribute(Chunk.BACKGROUND);
graphics.SetColorFill((BaseColor)bgr[0]);
float[] extra = (float[])bgr[1];
graphics.Rectangle(xMarker - extra[0],
yMarker + descender - extra[1] + chunk.TextRise,
width - subtract + extra[0] + extra[2],
ascender - descender + extra[1] + extra[3]);
graphics.Fill();
graphics.SetGrayFill(0);
if (inText && IsTagged(writer)) {
graphics.BeginText(true);
}
}
if (chunk.IsAttribute(Chunk.UNDERLINE) && !chunk.IsNewlineSplit()) {
bool inText = graphics.InText;
if (inText && IsTagged(writer)) {
graphics.EndText();
}
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.IsAttribute(Chunk.UNDERLINE))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
Object[][] unders = (Object[][])chunk.GetAttribute(Chunk.UNDERLINE);
BaseColor scolor = null;
for (int k = 0; k < unders.Length; ++k) {
Object[] obj = unders[k];
scolor = (BaseColor)obj[0];
float[] ps = (float[])obj[1];
if (scolor == null)
scolor = color;
if (scolor != null)
graphics.SetColorStroke(scolor);
graphics.SetLineWidth(ps[0] + fontSize * ps[1]);
float shift = ps[2] + fontSize * ps[3];
int cap2 = (int)ps[4];
if (cap2 != 0)
graphics.SetLineCap(cap2);
graphics.MoveTo(xMarker, yMarker + shift);
graphics.LineTo(xMarker + width - subtract, yMarker + shift);
graphics.Stroke();
if (scolor != null)
graphics.ResetGrayStroke();
if (cap2 != 0)
graphics.SetLineCap(0);
}
graphics.SetLineWidth(1);
if (inText && IsTagged(writer)) {
graphics.BeginText(true);
}
}
if (chunk.IsAttribute(Chunk.ACTION))
{
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.IsAttribute(Chunk.ACTION))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
PdfAnnotation annot = null;
if (chunk.IsImage()) {
annot = new PdfAnnotation(writer, xMarker, yMarker + chunk.ImageOffsetY, xMarker + width - subtract, yMarker + chunk.ImageHeight + chunk.ImageOffsetY, (PdfAction)chunk.GetAttribute(Chunk.ACTION));
}
else {
annot = new PdfAnnotation(writer, xMarker, yMarker + descender + chunk.TextRise, xMarker + width - subtract, yMarker + ascender + chunk.TextRise, (PdfAction)chunk.GetAttribute(Chunk.ACTION));
}
text.AddAnnotation(annot, true);
if (IsTagged(writer) && chunk.accessibleElement != null) {
int structParent = GetStructParentIndex(annot);
annot.Put(PdfName.STRUCTPARENT, new PdfNumber(structParent));
PdfStructureElement strucElem;
structElements.TryGetValue(chunk.accessibleElement.ID, out strucElem);
if (strucElem != null) {
PdfArray kArray = strucElem.GetAsArray(PdfName.K);
if (kArray == null) {
kArray = new PdfArray();
PdfObject k = strucElem.Get(PdfName.K);
if (k != null) {
kArray.Add(k);
}
strucElem.Put(PdfName.K, kArray);
}
PdfDictionary dict = new PdfDictionary();
dict.Put(PdfName.TYPE, PdfName.OBJR);
dict.Put(PdfName.OBJ, annot.IndirectReference);
kArray.Add(dict);
writer.StructureTreeRoot.SetAnnotationMark(structParent, strucElem.Reference);
}
}
}
if (chunk.IsAttribute(Chunk.REMOTEGOTO)) {
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.IsAttribute(Chunk.REMOTEGOTO))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
Object[] obj = (Object[])chunk.GetAttribute(Chunk.REMOTEGOTO);
String filename = (String)obj[0];
if (obj[1] is String)
RemoteGoto(filename, (String)obj[1], xMarker, yMarker + descender + chunk.TextRise, xMarker + width - subtract, yMarker + ascender + chunk.TextRise);
else
RemoteGoto(filename, (int)obj[1], xMarker, yMarker + descender + chunk.TextRise, xMarker + width - subtract, yMarker + ascender + chunk.TextRise);
}
if (chunk.IsAttribute(Chunk.LOCALGOTO)) {
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.IsAttribute(Chunk.LOCALGOTO))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
LocalGoto((String)chunk.GetAttribute(Chunk.LOCALGOTO), xMarker, yMarker, xMarker + width - subtract, yMarker + fontSize);
}
if (chunk.IsAttribute(Chunk.LOCALDESTINATION)) {
/*float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.IsAttribute(Chunk.LOCALDESTINATION))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;*/
LocalDestination((String)chunk.GetAttribute(Chunk.LOCALDESTINATION), new PdfDestination(PdfDestination.XYZ, xMarker, yMarker + fontSize, 0));
}
if (chunk.IsAttribute(Chunk.GENERICTAG)) {
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.IsAttribute(Chunk.GENERICTAG))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
Rectangle rect = new Rectangle(xMarker, yMarker, xMarker + width - subtract, yMarker + fontSize);
IPdfPageEvent pev = writer.PageEvent;
if (pev != null)
pev.OnGenericTag(writer, this, rect, (String)chunk.GetAttribute(Chunk.GENERICTAG));
}
if (chunk.IsAttribute(Chunk.PDFANNOTATION)) {
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.IsAttribute(Chunk.PDFANNOTATION))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
PdfAnnotation annot = PdfFormField.ShallowDuplicate((PdfAnnotation)chunk.GetAttribute(Chunk.PDFANNOTATION));
annot.Put(PdfName.RECT, new PdfRectangle(xMarker, yMarker + descender, xMarker + width - subtract, yMarker + ascender));
text.AddAnnotation(annot, true);
}
float[] paramsx = (float[])chunk.GetAttribute(Chunk.SKEW);
object hs = chunk.GetAttribute(Chunk.HSCALE);
if (paramsx != null || hs != null) {
float b = 0, c = 0;
if (paramsx != null) {
b = paramsx[0];
c = paramsx[1];
}
if (hs != null)
hScale = (float)hs;
text.SetTextMatrix(hScale, b, c, 1, xMarker, yMarker);
}
if (!isJustified)
{
if (chunk.IsAttribute(Chunk.WORD_SPACING))
{
float ws = (float)chunk.GetAttribute(Chunk.WORD_SPACING);
text.SetWordSpacing(ws);
}
}
if (chunk.IsAttribute(Chunk.CHAR_SPACING)) {
float cs = (float) chunk.GetAttribute(Chunk.CHAR_SPACING);
text.SetCharacterSpacing(cs);
}
if (chunk.IsImage()) {
Image image = chunk.Image;
width = chunk.ImageWidth;
float[] matrix = image.GetMatrix(chunk.ImageScalePercentage);
matrix[Image.CX] = xMarker + chunk.ImageOffsetX - matrix[Image.CX];
matrix[Image.CY] = yMarker + chunk.ImageOffsetY - matrix[Image.CY];
graphics.AddImage(image, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
text.MoveText(xMarker + lastBaseFactor + chunk.ImageWidth - text.XTLM, 0);
}
}
xMarker += width;
++chunkStrokeIdx;
}
if (!chunk.IsImage() && chunk.Font.CompareTo(currentFont) != 0) {
currentFont = chunk.Font;
text.SetFontAndSize(currentFont.Font, currentFont.Size);
}
float rise = 0;
Object[] textRender = (Object[])chunk.GetAttribute(Chunk.TEXTRENDERMODE);
int tr = 0;
float strokeWidth = 1;
BaseColor strokeColor = null;
object fr = chunk.GetAttribute(Chunk.SUBSUPSCRIPT);
if (textRender != null) {
tr = (int)textRender[0] & 3;
if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL)
text.SetTextRenderingMode(tr);
if (tr == PdfContentByte.TEXT_RENDER_MODE_STROKE || tr == PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE) {
strokeWidth = (float)textRender[1];
if (strokeWidth != 1)
text.SetLineWidth(strokeWidth);
strokeColor = (BaseColor)textRender[2];
if (strokeColor == null)
strokeColor = color;
if (strokeColor != null)
text.SetColorStroke(strokeColor);
}
}
if (fr != null)
rise = (float)fr;
if (color != null)
text.SetColorFill(color);
if (rise != 0)
text.SetTextRise(rise);
if (chunk.IsImage()) {
adjustMatrix = true;
}
else if (chunk.IsHorizontalSeparator()) {
PdfTextArray array = new PdfTextArray();
array.Add(-glueWidth * 1000f / chunk.Font.Size / hScale);
text.ShowText(array);
}
else if (chunk.IsTab() && tabPosition != xMarker)
{
PdfTextArray array = new PdfTextArray();
array.Add((tabPosition - xMarker) * 1000f / chunk.Font.Size / hScale);
text.ShowText(array);
}
// If it is a CJK chunk or Unicode TTF we will have to simulate the
// space adjustment.
else if (isJustified && numberOfSpaces > 0 && chunk.IsSpecialEncoding()) {
if (hScale != lastHScale) {
lastHScale = hScale;
text.SetWordSpacing(baseWordSpacing / hScale);
text.SetCharacterSpacing(baseCharacterSpacing / hScale + text.CharacterSpacing);
}
String s = chunk.ToString();
int idx = s.IndexOf(' ');
if (idx < 0)
text.ShowText(s);
else {
float spaceCorrection = - baseWordSpacing * 1000f / chunk.Font.Size / hScale;
PdfTextArray textArray = new PdfTextArray(s.Substring(0, idx));
int lastIdx = idx;
while ((idx = s.IndexOf(' ', lastIdx + 1)) >= 0) {
textArray.Add(spaceCorrection);
textArray.Add(s.Substring(lastIdx, idx - lastIdx));
lastIdx = idx;
}
textArray.Add(spaceCorrection);
textArray.Add(s.Substring(lastIdx));
text.ShowText(textArray);
}
}
else {
if (isJustified && hScale != lastHScale) {
lastHScale = hScale;
text.SetWordSpacing(baseWordSpacing / hScale);
text.SetCharacterSpacing(baseCharacterSpacing / hScale + text.CharacterSpacing);
}
text.ShowText(chunk.ToString());
}
if (rise != 0)
text.SetTextRise(0);
if (color != null)
text.ResetRGBColorFill();
if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL)
text.SetTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
if (strokeColor != null)
text.ResetRGBColorStroke();
if (strokeWidth != 1)
text.SetLineWidth(1);
if (chunk.IsAttribute(Chunk.SKEW) || chunk.IsAttribute(Chunk.HSCALE)) {
adjustMatrix = true;
text.SetTextMatrix(xMarker, yMarker);
}
if (chunk.IsAttribute(Chunk.CHAR_SPACING)) {
text.SetCharacterSpacing(baseCharacterSpacing);
}
if (chunk.IsAttribute(Chunk.WORD_SPACING)) {
text.SetWordSpacing(baseWordSpacing);
}
if (IsTagged(writer) && chunk.accessibleElement != null) {
text.CloseMCBlock(chunk.accessibleElement);
}
}
if (isJustified) {
text.SetWordSpacing(0);
text.SetCharacterSpacing(0);
if (line.NewlineSplit)
lastBaseFactor = 0;
}
if (adjustMatrix)
text.MoveText(baseXMarker - text.XTLM, 0);
currentValues[0] = currentFont;
currentValues[1] = lastBaseFactor;
return lastX;
}