// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using System;
using System.Collections.Generic;
using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
namespace TheArtOfDev.HtmlRenderer.Core.Dom
{
///
/// Represents a line of text.
///
///
/// To learn more about line-boxes see CSS spec:
/// http://www.w3.org/TR/CSS21/visuren.html
///
internal sealed class CssLineBox
{
#region Fields and Consts
private readonly List _words;
private readonly CssBox _ownerBox;
private readonly Dictionary _rects;
private readonly List _relatedBoxes;
#endregion
///
/// Creates a new LineBox
///
public CssLineBox(CssBox ownerBox)
{
_rects = new Dictionary();
_relatedBoxes = new List();
_words = new List();
_ownerBox = ownerBox;
_ownerBox.LineBoxes.Add(this);
}
///
/// Gets a list of boxes related with the linebox.
/// To know the words of the box inside this linebox, use the method.
///
public List RelatedBoxes
{
get { return _relatedBoxes; }
}
///
/// Gets the words inside the linebox
///
public List Words
{
get { return _words; }
}
///
/// Gets the owner box
///
public CssBox OwnerBox
{
get { return _ownerBox; }
}
///
/// Gets a List of rectangles that are to be painted on this linebox
///
public Dictionary Rectangles
{
get { return _rects; }
}
///
/// Get the height of this box line (the max height of all the words)
///
public double LineHeight
{
get
{
double height = 0;
foreach (var rect in _rects)
{
height = Math.Max(height, rect.Value.Height);
}
return height;
}
}
///
/// Get the bottom of this box line (the max bottom of all the words)
///
public double LineBottom
{
get
{
double bottom = 0;
foreach (var rect in _rects)
{
bottom = Math.Max(bottom, rect.Value.Bottom);
}
return bottom;
}
}
///
/// Lets the linebox add the word an its box to their lists if necessary.
///
///
internal void ReportExistanceOf(CssRect word)
{
if (!Words.Contains(word))
{
Words.Add(word);
}
if (!RelatedBoxes.Contains(word.OwnerBox))
{
RelatedBoxes.Add(word.OwnerBox);
}
}
///
/// Return the words of the specified box that live in this linebox
///
///
///
internal List WordsOf(CssBox box)
{
List r = new List();
foreach (CssRect word in Words)
if (word.OwnerBox.Equals(box))
r.Add(word);
return r;
}
///
/// Updates the specified rectangle of the specified box.
///
///
///
///
///
///
internal void UpdateRectangle(CssBox box, double x, double y, double r, double b)
{
double leftspacing = box.ActualBorderLeftWidth + box.ActualPaddingLeft;
double rightspacing = box.ActualBorderRightWidth + box.ActualPaddingRight;
double topspacing = box.ActualBorderTopWidth + box.ActualPaddingTop;
double bottomspacing = box.ActualBorderBottomWidth + box.ActualPaddingTop;
if ((box.FirstHostingLineBox != null && box.FirstHostingLineBox.Equals(this)) || box.IsImage)
x -= leftspacing;
if ((box.LastHostingLineBox != null && box.LastHostingLineBox.Equals(this)) || box.IsImage)
r += rightspacing;
if (!box.IsImage)
{
y -= topspacing;
b += bottomspacing;
}
if (!Rectangles.ContainsKey(box))
{
Rectangles.Add(box, RRect.FromLTRB(x, y, r, b));
}
else
{
RRect f = Rectangles[box];
Rectangles[box] = RRect.FromLTRB(
Math.Min(f.X, x), Math.Min(f.Y, y),
Math.Max(f.Right, r), Math.Max(f.Bottom, b));
}
if (box.ParentBox != null && box.ParentBox.IsInline)
{
UpdateRectangle(box.ParentBox, x, y, r, b);
}
}
internal void SetPageLocation(double yLastPageRef, int iPage, PageList _pagelist)
{
}
///
/// Copies the rectangles to their specified box
///
internal void AssignRectanglesToBoxes()
{
foreach (CssBox b in Rectangles.Keys)
{
b.Rectangles.Add(this, Rectangles[b]);
}
}
///
/// Sets the baseline of the words of the specified box to certain height
///
/// Device info
/// box to check words
/// baseline
internal void SetBaseLine(RGraphics g, CssBox b, double baseline)
{
//TODO: Aqui me quede, checar poniendo "by the" con un font-size de 3em
List ws = WordsOf(b);
if (!Rectangles.ContainsKey(b))
return;
RRect r = Rectangles[b];
//Save top of words related to the top of rectangle
double gap = 0f;
if (ws.Count > 0)
{
gap = ws[0].Top - r.Top;
}
else
{
CssRect firstw = b.FirstWordOccourence(b, this);
if (firstw != null)
{
gap = firstw.Top - r.Top;
}
}
//New top that words will have
//float newtop = baseline - (Height - OwnerBox.FontDescent - 3); //OLD
double newtop = baseline; // -GetBaseLineHeight(b, g); //OLD
if (b.ParentBox != null &&
b.ParentBox.Rectangles.ContainsKey(this) &&
r.Height < b.ParentBox.Rectangles[this].Height)
{
//Do this only if rectangle is shorter than parent's
double recttop = newtop - gap;
RRect newr = new RRect(r.X, recttop, r.Width, r.Height);
Rectangles[b] = newr;
b.OffsetRectangle(this, gap);
}
foreach (var word in ws)
{
if (!word.IsImage)
word.Top = newtop;
}
}
///
/// Check if the given word is the last selected word in the line.
/// It can either be the last word in the line or the next word has no selection.
///
/// the word to check
///
public bool IsLastSelectedWord(CssRect word)
{
for (int i = 0; i < _words.Count - 1; i++)
{
if (_words[i] == word)
{
return !_words[i + 1].Selected;
}
}
return true;
}
///
/// Returns the words of the linebox
///
///
public override string ToString()
{
string[] ws = new string[Words.Count];
for (int i = 0; i < ws.Length; i++)
{
ws[i] = Words[i].Text;
}
return string.Join(" ", ws);
}
}
}