// "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); } } }