/// <summary> /// Creates a new LineBox /// </summary> public CssLineBox(CssBox ownerBox) { _rects = new Dictionary<CssBox, RRect>(); _relatedBoxes = new List<CssBox>(); _words = new List<CssRect>(); _ownerBox = ownerBox; _ownerBox.LineBoxes.Add(this); }
public CssSpacingBox(CssBox tableBox, ref CssBox extendedBox, int startRow) : base(tableBox, new HtmlTag("none", false, new Dictionary<string, string> { { "colspan", "1" } })) { _extendedBox = extendedBox; Display = CssConstants.None; _startRow = startRow; _endRow = startRow + Int32.Parse(extendedBox.GetAttribute("rowspan", "1")) - 1; }
/// <summary> /// Recursively searches for the parent with the specified HTML Tag name /// </summary> /// <param name="root"></param> /// <param name="tagName"></param> /// <param name="box"></param> public static CssBox FindParent(CssBox root, string tagName, CssBox box) { if (box == null) { return root; } if (box.HtmlTag != null && box.HtmlTag.Name.Equals(tagName, StringComparison.CurrentCultureIgnoreCase)) { return box.ParentBox ?? root; } return FindParent(root, tagName, box.ParentBox); }
/// <summary> /// Check if the given box contains only inline child boxes. /// </summary> /// <param name="box">the box to check</param> /// <returns>true - only inline child boxes, false - otherwise</returns> public static bool ContainsInlinesOnly(CssBox box) { foreach (CssBox b in box.Boxes) { if (!b.IsInline) { return false; } } return true; }
/// <summary> /// Applies special vertical alignment for table-cells /// </summary> /// <param name="g"></param> /// <param name="cell"></param> public static void ApplyCellVerticalAlignment(RGraphics g, CssBox cell) { ArgChecker.AssertArgNotNull(g, "g"); ArgChecker.AssertArgNotNull(cell, "cell"); if (cell.VerticalAlign == CssConstants.Top || cell.VerticalAlign == CssConstants.Baseline) return; double cellbot = cell.ClientBottom; double bottom = cell.GetMaximumBottom(cell, 0f); double dist = 0f; if (cell.VerticalAlign == CssConstants.Bottom) { dist = cellbot - bottom; } else if (cell.VerticalAlign == CssConstants.Middle) { dist = (cellbot - bottom) / 2; } foreach (CssBox b in cell.Boxes) { b.OffsetTop(dist); } //float top = cell.ClientTop; //float bottom = cell.ClientBottom; //bool middle = cell.VerticalAlign == CssConstants.Middle; //foreach (LineBox line in cell.LineBoxes) //{ // for (int i = 0; i < line.RelatedBoxes.Count; i++) // { // double diff = bottom - line.RelatedBoxes[i].Rectangles[line].Bottom; // if (middle) diff /= 2f; // RectangleF r = line.RelatedBoxes[i].Rectangles[line]; // line.RelatedBoxes[i].Rectangles[line] = new RectangleF(r.X, r.Y + diff, r.Width, r.Height); // } // foreach (BoxWord word in line.Words) // { // double gap = word.Top - top; // word.Top = bottom - gap - word.Height; // } //} }
/// <summary> /// Draws all the border of the box with respect to style, width, etc. /// </summary> /// <param name="g">the device to draw into</param> /// <param name="box">the box to draw borders for</param> /// <param name="rect">the bounding rectangle to draw in</param> /// <param name="isFirst">is it the first rectangle of the element</param> /// <param name="isLast">is it the last rectangle of the element</param> public static void DrawBoxBorders(RGraphics g, CssBox box, RRect rect, bool isFirst, bool isLast) { if (rect.Width > 0 && rect.Height > 0) { if (!(string.IsNullOrEmpty(box.BorderTopStyle) || box.BorderTopStyle == CssConstants.None || box.BorderTopStyle == CssConstants.Hidden) && box.ActualBorderTopWidth > 0) { DrawBorder(Border.Top, box, g, rect, isFirst, isLast); } if (isFirst && !(string.IsNullOrEmpty(box.BorderLeftStyle) || box.BorderLeftStyle == CssConstants.None || box.BorderLeftStyle == CssConstants.Hidden) && box.ActualBorderLeftWidth > 0) { DrawBorder(Border.Left, box, g, rect, true, isLast); } if (!(string.IsNullOrEmpty(box.BorderBottomStyle) || box.BorderBottomStyle == CssConstants.None || box.BorderBottomStyle == CssConstants.Hidden) && box.ActualBorderBottomWidth > 0) { DrawBorder(Border.Bottom, box, g, rect, isFirst, isLast); } if (isLast && !(string.IsNullOrEmpty(box.BorderRightStyle) || box.BorderRightStyle == CssConstants.None || box.BorderRightStyle == CssConstants.Hidden) && box.ActualBorderRightWidth > 0) { DrawBorder(Border.Right, box, g, rect, isFirst, true); } } }
/// <summary> /// Draw the background image of the given box in the given rectangle.<br/> /// Handle background-repeat and background-position values. /// </summary> /// <param name="g">the device to draw into</param> /// <param name="box">the box to draw its background image</param> /// <param name="imageLoadHandler">the handler that loads image to draw</param> /// <param name="rectangle">the rectangle to draw image in</param> public static void DrawBackgroundImage(RGraphics g, CssBox box, ImageLoadHandler imageLoadHandler, RRect rectangle) { // image size depends if specific rectangle given in image loader var imgSize = new RSize(imageLoadHandler.Rectangle == RRect.Empty ? imageLoadHandler.Image.Width : imageLoadHandler.Rectangle.Width, imageLoadHandler.Rectangle == RRect.Empty ? imageLoadHandler.Image.Height : imageLoadHandler.Rectangle.Height); // get the location by BackgroundPosition value var location = GetLocation(box.BackgroundPosition, rectangle, imgSize); var srcRect = imageLoadHandler.Rectangle == RRect.Empty ? new RRect(0, 0, imgSize.Width, imgSize.Height) : new RRect(imageLoadHandler.Rectangle.Left, imageLoadHandler.Rectangle.Top, imgSize.Width, imgSize.Height); // initial image destination rectangle var destRect = new RRect(location, imgSize); // need to clip so repeated image will be cut on rectangle var lRectangle = rectangle; lRectangle.Intersect(g.GetClip()); g.PushClip(lRectangle); switch (box.BackgroundRepeat) { case "no-repeat": g.DrawImage(imageLoadHandler.Image, destRect, srcRect); break; case "repeat-x": DrawRepeatX(g, imageLoadHandler, rectangle, srcRect, destRect, imgSize); break; case "repeat-y": DrawRepeatY(g, imageLoadHandler, rectangle, srcRect, destRect, imgSize); break; default: DrawRepeat(g, imageLoadHandler, rectangle, srcRect, destRect, imgSize); break; } g.PopClip(); }
/// <summary> /// Clip the region the graphics will draw on by the overflow style of the containing block.<br/> /// Recursively travel up the tree to find containing block that has overflow style set to hidden. if not /// block found there will be no clipping and null will be returned. /// </summary> /// <param name="g">the graphics to clip</param> /// <param name="box">the box that is rendered to get containing blocks</param> /// <returns>true - was clipped, false - not clipped</returns> public static bool ClipGraphicsByOverflow(RGraphics g, CssBox box) { var containingBlock = box.ContainingBlock; while (true) { if (containingBlock.Overflow == CssConstants.Hidden) { var prevClip = g.GetClip(); var rect = box.ContainingBlock.ClientRectangle; rect.X -= 2; // TODO:a find better way to fix it rect.Width += 2; rect.Offset(box.HtmlContainer.ScrollOffset); rect.Intersect(prevClip); g.PushClip(rect); return true; } var cBlock = containingBlock.ContainingBlock; if (cBlock == containingBlock) return false; containingBlock = cBlock; } }
/// <summary> /// Gets the spanned width of a cell (With of all columns it spans minus one). /// </summary> private double GetSpannedMinWidth(CssBox row, CssBox cell, int realcolindex, int colspan) { double w = 0f; for (int i = realcolindex; i < row.Boxes.Count || i < realcolindex + colspan - 1; i++) { if (i < GetColumnMinWidths().Length) w += GetColumnMinWidths()[i]; } return w; }
/// <summary> /// Gets the cells width, taking colspan and being in the specified column /// </summary> /// <param name="column"></param> /// <param name="b"></param> /// <returns></returns> private double GetCellWidth(int column, CssBox b) { double colspan = Convert.ToSingle(GetColSpan(b)); double sum = 0f; for (int i = column; i < column + colspan; i++) { if (column >= _columnWidths.Length) break; if (_columnWidths.Length <= i) break; sum += _columnWidths[i]; } sum += (colspan - 1) * GetHorizontalSpacing(); return sum; // -b.ActualBorderLeftWidth - b.ActualBorderRightWidth - b.ActualPaddingRight - b.ActualPaddingLeft; }
/// <summary> /// Recursively measures words inside the box /// </summary> /// <param name="box">the box to measure</param> /// <param name="g">Device to use</param> private static void MeasureWords(CssBox box, RGraphics g) { if (box != null) { foreach (var childBox in box.Boxes) { childBox.MeasureWordsSize(g); MeasureWords(childBox, g); } } }
/// <summary> /// Gets the rowspan of the specified box /// </summary> /// <param name="b"></param> private static int GetRowSpan(CssBox b) { string att = b.GetAttribute("rowspan", "1"); int rowspan; if (!int.TryParse(att, out rowspan)) { return 1; } return rowspan; }
/// <summary> /// Gets the colspan of the specified box /// </summary> /// <param name="b"></param> private static int GetColSpan(CssBox b) { string att = b.GetAttribute("colspan", "1"); int colspan; if (!int.TryParse(att, out colspan)) { return 1; } return colspan; }
/// <summary> /// /// </summary> /// <param name="g"></param> /// <param name="tableBox"> </param> public static void PerformLayout(RGraphics g, CssBox tableBox) { ArgChecker.AssertArgNotNull(g, "g"); ArgChecker.AssertArgNotNull(tableBox, "tableBox"); try { var table = new CssLayoutEngineTable(tableBox); table.Layout(g); } catch (Exception ex) { tableBox.HtmlContainer.ReportError(HtmlRenderErrorType.Layout, "Failed table layout", ex); } }
/// <summary> /// Create new css box for the given parent with the given html tag.<br/> /// </summary> /// <param name="tag">the html tag to define the box</param> /// <param name="parent">the box to add the new box to it as child</param> /// <returns>the new box</returns> public static CssBox CreateBox(HtmlTag tag, CssBox parent = null) { ArgChecker.AssertArgNotNull(tag, "tag"); if (tag.Name == HtmlConstants.Img) { return new CssBoxImage(parent, tag); } if (tag.Name == HtmlConstants.Iframe) { return new CssBoxFrame(parent, tag); } if (tag.Name == HtmlConstants.Hr) { return new CssBoxHr(parent, tag); } return new CssBox(parent, tag); }
/// <summary> /// Init. /// </summary> /// <param name="parent">the parent box of this box</param> /// <param name="tag">the html tag data of this box</param> public CssBoxFrame(CssBox parent, HtmlTag tag) : base(parent, tag) { _imageWord = new CssRectImage(this); Words.Add(_imageWord); Uri uri; if (Uri.TryCreate(GetAttribute("src"), UriKind.Absolute, out uri)) { if (uri.Host.IndexOf("youtube.com", StringComparison.InvariantCultureIgnoreCase) > -1) { _isVideo = true; LoadYoutubeDataAsync(uri); } else if (uri.Host.IndexOf("vimeo.com", StringComparison.InvariantCultureIgnoreCase) > -1) { _isVideo = true; LoadVimeoDataAsync(uri); } } if (!_isVideo) { SetErrorBorder(); } }
/// <summary> /// Init. /// </summary> public HoverBoxBlock(CssBox cssBox, CssBlock cssBlock) { _cssBox = cssBox; _cssBlock = cssBlock; }
/// <summary> /// Create new css box for the given parent with the given optional html tag and insert it either /// at the end or before the given optional box.<br/> /// If no html tag is given the box will be anonymous.<br/> /// If no before box is given the new box will be added at the end of parent boxes collection.<br/> /// If before box doesn't exists in parent box exception is thrown.<br/> /// </summary> /// <remarks> /// To learn more about anonymous inline boxes visit: http://www.w3.org/TR/CSS21/visuren.html#anonymous /// </remarks> /// <param name="parent">the box to add the new box to it as child</param> /// <param name="tag">optional: the html tag to define the box</param> /// <param name="before">optional: to insert as specific location in parent box</param> /// <returns>the new box</returns> public static CssBox CreateBox(CssBox parent, HtmlTag tag = null, CssBox before = null) { ArgChecker.AssertArgNotNull(parent, "parent"); var newBox = new CssBox(parent, tag); newBox.InheritStyle(); if (before != null) { newBox.SetBeforeBox(before); } return newBox; }
/// <summary> /// Gets the cell column index checking its position and other cells colspans /// </summary> /// <param name="row"></param> /// <param name="cell"></param> /// <returns></returns> private static int GetCellRealColumnIndex(CssBox row, CssBox cell) { int i = 0; foreach (CssBox b in row.Boxes) { if (b.Equals(cell)) break; i += GetColSpan(b); } return i; }
/// <summary> /// Move all child boxes from <paramref name="fromBox"/> to this box. /// </summary> /// <param name="fromBox">the box to move all its child boxes from</param> public void SetAllBoxes(CssBox fromBox) { foreach (var childBox in fromBox._boxes) childBox._parentBox = this; _boxes.AddRange(fromBox._boxes); fromBox._boxes.Clear(); }
/// <summary> /// Gets the actual horizontal spacing of the table /// </summary> private static double GetHorizontalSpacing(CssBox box) { return box.BorderCollapse == CssConstants.Collapse ? -1f : box.ActualBorderSpacingHorizontal; }
/// <summary> /// Set this box in /// </summary> /// <param name="before"></param> public void SetBeforeBox(CssBox before) { int index = _parentBox.Boxes.IndexOf(before); if (index < 0) throw new Exception("before box doesn't exist on parent"); _parentBox.Boxes.Remove(this); _parentBox.Boxes.Insert(index, this); }
/// <summary> /// Gets the span attribute of the tag of the specified box /// </summary> /// <param name="b"></param> private static int GetSpan(CssBox b) { double f = CssValueParser.ParseNumber(b.GetAttribute("span"), 1); return Math.Max(1, Convert.ToInt32(f)); }
/// <summary> /// Searches for the first word occurrence inside the box, on the specified linebox /// </summary> /// <param name="b"></param> /// <param name="line"> </param> /// <returns></returns> internal CssRect FirstWordOccourence(CssBox b, CssLineBox line) { if (b.Words.Count == 0 && b.Boxes.Count == 0) { return null; } if (b.Words.Count > 0) { foreach (CssRect word in b.Words) { if (line.Words.Contains(word)) { return word; } } return null; } foreach (CssBox bb in b.Boxes) { CssRect w = FirstWordOccourence(bb, line); if (w != null) { return w; } } return null; }
/// <summary> /// Get the table boxes into the proper fields. /// </summary> private void AssignBoxKinds() { foreach (var box in _tableBox.Boxes) { switch (box.Display) { case CssConstants.TableCaption: _caption = box; break; case CssConstants.TableRow: _bodyrows.Add(box); break; case CssConstants.TableRowGroup: foreach (CssBox childBox in box.Boxes) if (childBox.Display == CssConstants.TableRow) _bodyrows.Add(childBox); break; case CssConstants.TableHeaderGroup: if (_headerBox != null) _bodyrows.Add(box); else _headerBox = box; break; case CssConstants.TableFooterGroup: if (_footerBox != null) _bodyrows.Add(box); else _footerBox = box; break; case CssConstants.TableColumn: for (int i = 0; i < GetSpan(box); i++) _columns.Add(box); break; case CssConstants.TableColumnGroup: if (box.Boxes.Count == 0) { int gspan = GetSpan(box); for (int i = 0; i < gspan; i++) { _columns.Add(box); } } else { foreach (CssBox bb in box.Boxes) { int bbspan = GetSpan(bb); for (int i = 0; i < bbspan; i++) { _columns.Add(bb); } } } break; } } if (_headerBox != null) _allRows.AddRange(_headerBox.Boxes); _allRows.AddRange(_bodyrows); if (_footerBox != null) _allRows.AddRange(_footerBox.Boxes); }
/// <summary> /// Gets the maximum bottom of the boxes inside the startBox /// </summary> /// <param name="startBox"></param> /// <param name="currentMaxBottom"></param> /// <returns></returns> internal double GetMaximumBottom(CssBox startBox, double currentMaxBottom) { foreach (var line in startBox.Rectangles.Keys) { currentMaxBottom = Math.Max(currentMaxBottom, startBox.Rectangles[line].Bottom); } foreach (var b in startBox.Boxes) { currentMaxBottom = Math.Max(currentMaxBottom, GetMaximumBottom(b, currentMaxBottom)); } return currentMaxBottom; }
/// <summary> /// Init. /// </summary> /// <param name="tableBox"></param> private CssLayoutEngineTable(CssBox tableBox) { _tableBox = tableBox; }
/// <summary> /// Gets the rectangles where inline box will be drawn. See Remarks for more info. /// </summary> /// <returns>Rectangles where content should be placed</returns> /// <remarks> /// Inline boxes can be split across different LineBoxes, that's why this method /// Delivers a rectangle for each LineBox related to this box, if inline. /// </remarks> /// <summary> /// Inherits inheritable values from parent. /// </summary> internal new void InheritStyle(CssBox box = null, bool everything = false) { base.InheritStyle(box ?? ParentBox, everything); }
/// <summary> /// Get the table cells spacing for all the cells in the table.<br/> /// Used to calculate the spacing the table has in addition to regular padding and borders. /// </summary> /// <param name="tableBox">the table box to calculate the spacing for</param> /// <returns>the calculated spacing</returns> public static double GetTableSpacing(CssBox tableBox) { int count = 0; int columns = 0; foreach (var box in tableBox.Boxes) { if (box.Display == CssConstants.TableColumn) { columns += GetSpan(box); } else if (box.Display == CssConstants.TableRowGroup) { foreach (CssBox cr in tableBox.Boxes) { count++; if (cr.Display == CssConstants.TableRow) columns = Math.Max(columns, cr.Boxes.Count); } } else if (box.Display == CssConstants.TableRow) { count++; columns = Math.Max(columns, box.Boxes.Count); } // limit the amount of rows to process for performance if (count > 30) break; } // +1 columns because padding is between the cell and table borders return (columns + 1) * GetHorizontalSpacing(tableBox); }
/// <summary> /// Creates a new BoxWord which represents an image /// </summary> /// <param name="owner">the CSS box owner of the word</param> public CssRectImage(CssBox owner) : base(owner) { }