/// <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>the prev region if clipped, otherwise null</returns> public static RectangleF ClipGraphicsByOverflow(IGraphics 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; // atodo: find better way to fix it rect.Width += 2; rect.Offset(box.HtmlContainer.ScrollOffset); rect.Intersect(prevClip); g.SetClip(rect); return prevClip; } else { var cBlock = containingBlock.ContainingBlock; if (cBlock == containingBlock) return RectangleF.Empty; containingBlock = cBlock; } } }
/// <summary> /// Init. /// </summary> /// <param name="owner">the CSS box owner of the word</param> /// <param name="word">the word chars </param> /// <param name="hasSpaceBefore">was there a whitespace before the word chars (before trim)</param> /// <param name="hasSpaceAfter">was there a whitespace after the word chars (before trim)</param> public CssBoxWord(CssBox owner, string word, bool hasSpaceBefore, bool hasSpaceAfter) { _ownerBox = owner; _word = word; _hasSpaceBefore = hasSpaceBefore; _hasSpaceAfter = hasSpaceAfter; }
/// <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> /// <param name="owner">the CSS box owner of the word</param> /// <param name="text">the word chars </param> /// <param name="hasSpaceBefore">was there a whitespace before the word chars (before trim)</param> /// <param name="hasSpaceAfter">was there a whitespace after the word chars (before trim)</param> public CssRectWord(CssBox owner, string text, bool hasSpaceBefore, bool hasSpaceAfter) : base(owner) { _text = text; _hasSpaceBefore = hasSpaceBefore; _hasSpaceAfter = hasSpaceAfter; }
/// <summary> /// Cascades to the TD's the border spacified in the TABLE tag. /// </summary> /// <param name="table"></param> /// <param name="border"></param> private static void ApplyTableBorder(CssBox table, string border) { SetForAllCells(table, cell => { cell.BorderLeftStyle = cell.BorderTopStyle = cell.BorderRightStyle = cell.BorderBottomStyle = CssConstants.Solid; cell.BorderLeftWidth = cell.BorderTopWidth = cell.BorderRightWidth = cell.BorderBottomWidth = border; }); }
/// <summary> /// Creates a new LineBox /// </summary> public CssLineBox(CssBox ownerBox) { _rects = new Dictionary<CssBox, RectangleF>(); _relatedBoxes = new List<CssBox>(); _words = new List<CssBoxWord>(); _ownerBox = ownerBox; _ownerBox.LineBoxes.Add(this); }
/// <summary> /// Generate html from the given dom tree.<br/> /// Generate all the tyle inside the html. /// </summary> /// <param name="root">the box of the html generate html from</param> /// <param name="styleGen">Optional: controls the way styles are generated when html is generated</param> /// <param name="onlySelected">Optional: true - generate only selected html subset, false - generate all (default - false)</param> /// <returns>generated html</returns> public static string GenerateHtml(CssBox root, HtmlGenerationStyle styleGen = HtmlGenerationStyle.Inline, bool onlySelected = false) { var sb = new StringBuilder(); if (root != null) { WriteHtml(sb, root, 0, styleGen, onlySelected ? CollectSelectedHtmlTags(root) : null); } return sb.ToString(); }
public CssSpacingBox(CssBox tableBox, ref CssBox extendedBox, int startRow) : base(tableBox, new HtmlTag("none",new Dictionary<string, string>{{"colspan","1"}} )) { _extendedBox = extendedBox; Display = CssConstants.None; _startRow = startRow; _endRow = startRow + Int32.Parse(extendedBox.GetAttribute("rowspan", "1")) - 1; }
/// <summary> /// Init. /// </summary> /// <param name="parentBox">optional: the parent of this css box in html</param> /// <param name="tag">optional: the html tag associated with this css box</param> internal CssBox(CssBox parentBox = null, HtmlTag tag = null) { if(parentBox != null) { _parentBox = parentBox; _parentBox.Boxes.Add(this); } _htmltag = tag; }
/// <summary> /// Asigns the given css style blocks to the given css box checking if matching. /// </summary> /// <param name="box">the css box to assign css to</param> /// <param name="cssData">the css data to use to get the matching css blocks</param> /// <param name="className">the class selector to search for css blocks</param> private static void AssignCssBlocks(CssBox box, CssData cssData, string className) { var blocks = cssData.GetCssBlock(className); foreach (var block in blocks) { if (IsBlockAssignableToBox(box, block)) { AssignCssBlock(box, block); } } }
/// <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> /// Asigns the given css style block properties to the given css box. /// </summary> /// <param name="box">the css box to assign css to</param> /// <param name="block">the css block to assign</param> private static void AssignCssBlock(CssBox box, CssBlock block) { foreach (var prop in block.Properties) { var value = prop.Value; if (prop.Value == CssConstants.Inherit && box.ParentBox != null) { value = CssUtils.GetPropertyValue(box.ParentBox, prop.Key); } CssUtils.SetPropertyValue(box, prop.Key, value); } }
/// <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.Display != CssConstants.Inline) { return false; } } return true; }
public CssTable(CssBox tableBox, Graphics g) : this() { if (!(tableBox.Display == CssConstants.Table || tableBox.Display == CssConstants.InlineTable)) throw new ArgumentException("Box is not a table", "tableBox"); _tableBox = tableBox; MeasureWords(tableBox, g); Analyze(g); }
/// <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; } else if (box.HtmlTag != null && box.HtmlTag.Name.Equals(tagName, StringComparison.CurrentCultureIgnoreCase)) { return box.ParentBox ?? root; } else { return FindParent(root, tagName, box.ParentBox); } }
/// <summary> /// Applies special vertical alignment for table-cells /// </summary> /// <param name="g"></param> /// <param name="cell"></param> public static void ApplyCellVerticalAlignment(IGraphics g, CssBox cell) { ArgChecker.AssertArgNotNull(g, "g"); ArgChecker.AssertArgNotNull(cell, "cell"); if (cell.VerticalAlign == CssConstants.Top || cell.VerticalAlign == CssConstants.Baseline) return; float cellbot = cell.ClientBottom; float bottom = cell.GetMaximumBottom(cell, 0f); float 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++) // { // float 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) // { // float gap = word.Top - top; // word.Top = bottom - gap - word.Height; // } //} }
/// <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(IGraphics g, CssBox box, ImageLoadHandler imageLoadHandler, RectangleF rectangle) { // image size depends if specific rectangle given in image loader var imgSize = new Size(imageLoadHandler.Rectangle == Rectangle.Empty ? imageLoadHandler.Image.Width : imageLoadHandler.Rectangle.Width, imageLoadHandler.Rectangle == Rectangle.Empty ? imageLoadHandler.Image.Height : imageLoadHandler.Rectangle.Height); // get the location by BackgroundPosition value var location = GetLocation(box.BackgroundPosition, rectangle, imgSize); var srcRect = imageLoadHandler.Rectangle == Rectangle.Empty ? new Rectangle(0, 0, imgSize.Width, imgSize.Height) : new Rectangle(imageLoadHandler.Rectangle.Left, imageLoadHandler.Rectangle.Top, imgSize.Width, imgSize.Height); // initial image destination rectangle var destRect = new Rectangle(location, imgSize); // need to clip so repeated image will be cut on rectangle var prevClip = g.GetClip(); var lRectangle = rectangle; lRectangle.Intersect(prevClip); g.SetClip(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.SetClip(prevClip); }
/// <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(IGraphics g, CssBox box, RectangleF 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> /// Check if the given box contains only inline child boxes in all subtree. /// </summary> /// <param name="box">the box to check</param> /// <returns>true - only inline child boxes, false - otherwise</returns> private static bool ContainsInlinesOnlyDeep(CssBox box) { foreach (var childBox in box.Boxes) { if (!childBox.IsInline || !ContainsInlinesOnlyDeep(childBox)) { return false; } } return true; }
/// <summary> /// Init. /// </summary> /// <param name="tableBox"></param> private CssLayoutEngineTable(CssBox tableBox) { _tableBox = tableBox; }
/// <summary> /// Applies style to all boxes in the tree.<br/> /// If the html tag has style defined for each apply that style to the css box of the tag.<br/> /// If the html tag has "class" attribute and the class name has style defined apply that style on the tag css box.<br/> /// If the html tag has "style" attribute parse it and apply the parsed style on the tag css box.<br/> /// If the html tag is "style" tag parse it content and add to the css data for all future tags parsing.<br/> /// If the html tag is "link" that point to style data parse it content and add to the css data for all future tags parsing.<br/> /// </summary> /// <param name="box"></param> /// <param name="bridge"> </param> /// <param name="cssData"> </param> /// <param name="cssDataChanged">check if the css data has been modified by the handled html not to change the base css data</param> private static void CascadeStyles(CssBox box, object bridge, ref CssData cssData, ref bool cssDataChanged) { box.InheritStyle(); if (box.HtmlTag != null) { // try assign style using the html element tag AssignCssBlocks(box, cssData, box.HtmlTag.Name); // try assign style using the "class" attribute of the html element if (box.HtmlTag.HasAttribute("class")) { AssignCssBlocks(box, cssData, "." + box.HtmlTag.Attributes["class"]); AssignCssBlocks(box, cssData, box.HtmlTag.Name + "." + box.HtmlTag.Attributes["class"]); } // try assign style using the "id" attribute of the html element if (box.HtmlTag.HasAttribute("id")) { AssignCssBlocks(box, cssData, "#" + box.HtmlTag.Attributes["id"]); } HtmlParser.TranslateAttributes(box.HtmlTag, box); // Check for the style="" attribute if (box.HtmlTag.HasAttribute("style")) { var block = CssParser.ParseCssBlockImp(box.HtmlTag.Name, box.HtmlTag.Attributes["style"]); AssignCssBlock(box, block); } // Check for the <style> tag if (box.HtmlTag.Name.Equals("style", StringComparison.CurrentCultureIgnoreCase) && box.Boxes.Count == 1) { CloneCssData(ref cssData, ref cssDataChanged); CssParser.ParseStyleSheet(cssData, box.Boxes[0].Text); } // Check for the <link rel=stylesheet> tag if (box.HtmlTag.Name.Equals("link", StringComparison.CurrentCultureIgnoreCase) && box.GetAttribute("rel", string.Empty).Equals("stylesheet", StringComparison.CurrentCultureIgnoreCase)) { CloneCssData(ref cssData, ref cssDataChanged); var styleSheet = CssValueParser.GetStyleSheet(box.GetAttribute("href", string.Empty), bridge); CssParser.ParseStyleSheet(cssData, styleSheet); } } foreach (var childBox in box.Boxes) { CascadeStyles(childBox, bridge, ref cssData, ref cssDataChanged); } }
/// <summary> /// Check if the given css block is assignable to the given css box.<br/> /// the block is assignable if it has no hierarchial selectors or if the hierarchy matches.<br/> /// </summary> /// <param name="box">the box to check assign to</param> /// <param name="block">the block to check assign of</param> /// <returns>true - the block is assignable to the box, false - otherwise</returns> private static bool IsBlockAssignableToBox(CssBox box, CssBlock block) { if (block.Selectors != null) { foreach (var selector in block.Selectors) { bool matched = false; while (!matched) { box = box.ParentBox; while (box != null && box.HtmlTag == null) box = box.ParentBox; if (box == null) return false; if (box.HtmlTag.Name == selector.Class) matched = true; if (!matched && box.HtmlTag.HasAttribute("class")) { var className = box.HtmlTag.Attributes["class"]; if (selector.Class == "." + className || selector.Class == box.HtmlTag.Name + "." + className) matched = true; } if (!matched && box.HtmlTag.HasAttribute("id")) { var id = box.HtmlTag.Attributes["id"]; if (selector.Class == "#" + id) matched = true; } if (!matched && selector.DirectParent) return false; } } } return true; }
/// <summary> /// Inherits inheritable values from specified box. /// </summary> /// <param name="everything">Set to true to inherit all CSS properties instead of only the ineritables</param> /// <param name="p">Box to inherit the properties</param> protected void InheritStyle(CssBox p, bool everything) { if (p != null) { _borderSpacing = p._borderSpacing; _borderCollapse = p._borderCollapse; _color = p._color; _emptyCells = p._emptyCells; _whiteSpace = p._whiteSpace; _visibility = p._visibility; _textIndent = p._textIndent; _textAlign = p._textAlign; _verticalAlign = p._verticalAlign; _fontFamily = p._fontFamily; _fontSize = p._fontSize; _fontStyle = p._fontStyle; _fontVariant = p._fontVariant; _fontWeight = p._fontWeight; _listStyleImage = p._listStyleImage; _listStylePosition = p._listStylePosition; _listStyleType = p._listStyleType; _listStyle = p._listStyle; _lineHeight = p._lineHeight; _wordBreak = p.WordBreak; _direction = p._direction; if (everything) { _backgroundColor = p._backgroundColor; _backgroundGradient = p._backgroundGradient; _backgroundGradientAngle = p._backgroundGradientAngle; _backgroundImage = p._backgroundImage; _backgroundPosition = p._backgroundPosition; _backgroundRepeat = p._backgroundRepeat; _borderTopWidth = p._borderTopWidth; _borderRightWidth = p._borderRightWidth; _borderBottomWidth = p._borderBottomWidth; _borderLeftWidth = p._borderLeftWidth; _borderTopColor = p._borderTopColor; _borderRightColor = p._borderRightColor; _borderBottomColor = p._borderBottomColor; _borderLeftColor = p._borderLeftColor; _borderTopStyle = p._borderTopStyle; _borderRightStyle = p._borderRightStyle; _borderBottomStyle = p._borderBottomStyle; _borderLeftStyle = p._borderLeftStyle; _bottom = p._bottom; _cornerNWRadius = p._cornerNWRadius; _cornerNERadius = p._cornerNERadius; _cornerSERadius = p._cornerSERadius; _cornerSWRadius = p._cornerSWRadius; _cornerRadius = p._cornerRadius; _display = p._display; _float = p._float; _height = p._height; _marginBottom = p._marginBottom; _marginLeft = p._marginLeft; _marginRight = p._marginRight; _marginTop = p._marginTop; _left = p._left; _lineHeight = p._lineHeight; _overflow = p._overflow; _paddingLeft = p._paddingLeft; _paddingBottom = p._paddingBottom; _paddingRight = p._paddingRight; _paddingTop = p._paddingTop; _right = p._right; _textDecoration = p._textDecoration; _top = p._top; _position = p._position; _width = p._width; _maxWidth = p._maxWidth; _wordSpacing = p._wordSpacing; } } }
/// <summary> /// Rearrange the DOM of the box to have block box with boxes before the inner block box and after. /// </summary> /// <param name="box">the box that has the problem</param> private static void CorrectBlockInsideInlineImp(CssBox box) { if (box.Boxes.Count > 1) { var leftBlock = CssBox.CreateBlock(box); while (ContainsInlinesOnlyDeep(box.Boxes[0])) box.Boxes[0].ParentBox = leftBlock; leftBlock.SetBeforeBox(box.Boxes[0]); var splitBox = box.Boxes[1]; splitBox.ParentBox = null; CorrectBlockSplitBadBox(box, splitBox, leftBlock); if (box.Boxes.Count > 2) { var rightBox = CssBox.CreateBox(box, null, box.Boxes[2]); while (box.Boxes.Count > 3) box.Boxes[3].ParentBox = rightBox; } box.Display = CssConstants.Block; } else { box.Boxes[0].Display = CssConstants.Block; } }
/// <summary> /// Makes block boxes be among only block boxes and all inline boxes have block parent box.<br/> /// Inline boxes should live in a pool of Inline boxes only so they will define a single block.<br/> /// At the end of this process a block box will have only block siblings and inline box will have /// only inline siblings. /// </summary> /// <param name="box">the current box to correct its sub-tree</param> private static void CorrectInlineBoxesParent(CssBox box) { if (ContainsVariantBoxes(box)) { for (int i = 0; i < box.Boxes.Count; i++) { if (box.Boxes[i].IsInline) { var newbox = CssBox.CreateBlock(box, null, box.Boxes[i++]); while (i < box.Boxes.Count && box.Boxes[i].IsInline) { box.Boxes[i].ParentBox = newbox; } } } } if (!DomUtils.ContainsInlinesOnly(box)) { foreach (var childBox in box.Boxes) { CorrectInlineBoxesParent(childBox); } } }
/// <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) { }
/// <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 CssBoxImage(CssBox parent, HtmlTag tag) : base(parent, tag) { _imageWord = new CssRectImage(this); Words.Add(_imageWord); }
/// <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> /// Recursively flows the content of the box using the inline model /// </summary> /// <param name="g">Device Info</param> /// <param name="blockbox">Blockbox that contains the text flow</param> /// <param name="box">Current box to flow its content</param> /// <param name="limitRight">Maximum reached right</param> /// <param name="linespacing">Space to use between rows of text</param> /// <param name="startx">x starting coordinate for when breaking lines of text</param> /// <param name="line">Current linebox being used</param> /// <param name="curx">Current x coordinate that will be the left of the next word</param> /// <param name="cury">Current y coordinate that will be the top of the next word</param> /// <param name="maxRight">Maximum right reached so far</param> /// <param name="maxbottom">Maximum bottom reached so far</param> private static void FlowBox(Graphics g, CssBox blockbox, CssBox box, float limitRight, float linespacing, float startx, ref CssLineBox line, ref float curx, ref float cury, ref float maxRight, ref float maxbottom) { var startY = cury; box.FirstHostingLineBox = line; var localCurx = curx; var localMaxRight = maxRight; var localmaxbottom = maxbottom; foreach (CssBox b in box.Boxes) { float leftspacing = b.Position != CssConstants.Absolute ? b.ActualMarginLeft + b.ActualBorderLeftWidth + b.ActualPaddingLeft : 0; float rightspacing = b.Position != CssConstants.Absolute ? b.ActualMarginRight + b.ActualBorderRightWidth + b.ActualPaddingRight : 0; b.RectanglesReset(); b.MeasureWordsSize(g); curx += leftspacing; if (b.Words.Count > 0) { // fix word space for first word in inline tag if (!b.Words[0].IsImage && b.Words[0].HasSpaceBefore && b.IsInline) { var sib = DomUtils.GetPreviousContainingBlockSibling(b); if (sib != null && sib.IsInline) { curx += b.ActualWordSpacing; } } foreach (var word in b.Words) { if (maxbottom - cury < box.ActualLineHeight) { maxbottom += box.ActualLineHeight - (maxbottom - cury); } if ((b.WhiteSpace != CssConstants.Nowrap && b.WhiteSpace != CssConstants.Pre && curx + word.Width + rightspacing > limitRight) || word.IsLineBreak) { curx = startx; // handle if line is wrapped for the first text element where parent has left margin\padding if (b == box.Boxes[0] && word == b.Words[0] && !word.IsLineBreak) { curx += box.ActualMarginLeft + box.ActualBorderLeftWidth + box.ActualPaddingLeft; } cury = maxbottom + linespacing; line = new CssLineBox(blockbox); if (word.IsImage || word.Equals(b.FirstWord)) { curx += leftspacing; } } line.ReportExistanceOf(word); word.Left = curx; word.Top = cury; curx = word.Left + word.FullWidth - word.LastMeasureOffset.X / 2; maxRight = Math.Max(maxRight, word.Right); maxbottom = Math.Max(maxbottom, word.Bottom); if (b.Position != CssConstants.Absolute) { } else { word.Left += box.ActualMarginLeft; word.Top += box.ActualMarginTop; } } } else { FlowBox(g, blockbox, b, limitRight, linespacing, startx, ref line, ref curx, ref cury, ref maxRight, ref maxbottom); } curx += rightspacing; } // handle height setting if (maxbottom - startY < box.ActualHeight) { maxbottom += box.ActualHeight - (maxbottom - startY); } // handle box that is only a whitespace if (box.Text != null && box.Text.IsWhitespace() && !box.IsImage && box.IsInline && box.Boxes.Count == 0 && box.Words.Count == 0) { curx += box.ActualWordSpacing; } // hack to support specific absolute possition elements if (box.Position == CssConstants.Absolute) { curx = localCurx; maxRight = localMaxRight; maxbottom = localmaxbottom; AdjustAbsolutePosition(box, 0, 0); } box.LastHostingLineBox = line; }
/// <summary> /// Recursively flows the content of the box using the inline model /// </summary> /// <param name="g">Device Info</param> /// <param name="blockbox">Blockbox that contains the text flow</param> /// <param name="box">Current box to flow its content</param> /// <param name="limitRight">Maximum reached right</param> /// <param name="linespacing">Space to use between rows of text</param> /// <param name="startx">x starting coordinate for when breaking lines of text</param> /// <param name="line">Current linebox being used</param> /// <param name="curx">Current x coordinate that will be the left of the next word</param> /// <param name="cury">Current y coordinate that will be the top of the next word</param> /// <param name="maxRight">Maximum right reached so far</param> /// <param name="maxbottom">Maximum bottom reached so far</param> private static void FlowBox(IGraphics g, CssBox blockbox, CssBox box, float limitRight, float linespacing, float startx, ref CssLineBox line, ref float curx, ref float cury, ref float maxRight, ref float maxbottom) { var startX = curx; var startY = cury; box.FirstHostingLineBox = line; var localCurx = curx; var localMaxRight = maxRight; var localmaxbottom = maxbottom; foreach (CssBox b in box.Boxes) { float leftspacing = b.Position != CssConstants.Absolute ? b.ActualMarginLeft + b.ActualBorderLeftWidth + b.ActualPaddingLeft : 0; float rightspacing = b.Position != CssConstants.Absolute ? b.ActualMarginRight + b.ActualBorderRightWidth + b.ActualPaddingRight : 0; b.RectanglesReset(); b.MeasureWordsSize(g); curx += leftspacing; if (b.Words.Count > 0) { bool wrapNoWrapBox = false; if (b.WhiteSpace == CssConstants.NoWrap && curx > startx) { var boxRight = curx; foreach (var word in b.Words) { boxRight += word.FullWidth; } if (boxRight > limitRight) { wrapNoWrapBox = true; } } if (DomUtils.IsBoxHasWhitespace(b)) { curx += box.ActualWordSpacing; } foreach (var word in b.Words) { if (maxbottom - cury < box.ActualLineHeight) { maxbottom += box.ActualLineHeight - (maxbottom - cury); } if ((b.WhiteSpace != CssConstants.NoWrap && b.WhiteSpace != CssConstants.Pre && curx + word.Width + rightspacing > limitRight) || word.IsLineBreak || wrapNoWrapBox) { wrapNoWrapBox = false; curx = startx; // handle if line is wrapped for the first text element where parent has left margin\padding if (b == box.Boxes[0] && !word.IsLineBreak && (word == b.Words[0] || (box.ParentBox != null && box.ParentBox.IsBlock))) { curx += box.ActualMarginLeft + box.ActualBorderLeftWidth + box.ActualPaddingLeft; } cury = maxbottom + linespacing; line = new CssLineBox(blockbox); if (word.IsImage || word.Equals(b.FirstWord)) { curx += leftspacing; } } line.ReportExistanceOf(word); word.Left = curx; word.Top = cury; curx = word.Left + word.FullWidth; maxRight = Math.Max(maxRight, word.Right); maxbottom = Math.Max(maxbottom, word.Bottom); if (b.Position == CssConstants.Absolute) { word.Left += box.ActualMarginLeft; word.Top += box.ActualMarginTop; } } } else { FlowBox(g, blockbox, b, limitRight, linespacing, startx, ref line, ref curx, ref cury, ref maxRight, ref maxbottom); } curx += rightspacing; } // handle height setting if (maxbottom - startY < box.ActualHeight) { maxbottom += box.ActualHeight - (maxbottom - startY); } // handle width setting if (box.IsInline && 0 <= curx - startX && curx - startX < box.ActualWidth) { // hack for actual width handling curx += box.ActualWidth - (curx - startX); line.Rectangles.Add(box, new RectangleF(startX, startY, box.ActualWidth, box.ActualHeight)); } // handle box that is only a whitespace if (box.Text != null && box.Text.IsWhitespace() && !box.IsImage && box.IsInline && box.Boxes.Count == 0 && box.Words.Count == 0) { curx += box.ActualWordSpacing; } // hack to support specific absolute position elements if (box.Position == CssConstants.Absolute) { curx = localCurx; maxRight = localMaxRight; maxbottom = localmaxbottom; AdjustAbsolutePosition(box, 0, 0); } box.LastHostingLineBox = line; }
/// <summary> /// Recursively flows the content of the box using the inline model /// </summary> /// <param name="g">Device Info</param> /// <param name="blockbox">Blockbox that contains the text flow</param> /// <param name="box">Current box to flow its content</param> /// <param name="maxright">Maximum reached right</param> /// <param name="linespacing">Space to use between rows of text</param> /// <param name="startx">x starting coordinate for when breaking lines of text</param> /// <param name="line">Current linebox being used</param> /// <param name="curx">Current x coordinate that will be the left of the next word</param> /// <param name="cury">Current y coordinate that will be the top of the next word</param> /// <param name="maxbottom">Maximum bottom reached so far</param> private static void FlowBox(Graphics g, CssBox blockbox, CssBox box, float maxright, float linespacing, float startx, ref CssLineBox line, ref float curx, ref float cury, ref float maxbottom) { var startY = cury; box.FirstHostingLineBox = line; foreach (CssBox b in box.Boxes) { float leftspacing = b.ActualMarginLeft + b.ActualBorderLeftWidth + b.ActualPaddingLeft; float rightspacing = b.ActualMarginRight + b.ActualBorderRightWidth + b.ActualPaddingRight; b.RectanglesReset(); b.MeasureWordsSize(g); curx += leftspacing; if (b.Words.Count > 0) { // fix word space for first word in inline tag if (!b.Words[0].IsImage && b.Words[0].HasSpaceBefore && b.Display == CssConstants.Inline) { var sib = DomUtils.GetPreviousContainingBlockSibling(b); if (sib != null && sib.Display == CssConstants.Inline) { curx += b.ActualWordSpacing; } } foreach (var word in b.Words) { if (maxbottom - cury < box.ActualLineHeight) { maxbottom += box.ActualLineHeight - (maxbottom - cury); } if ((b.WhiteSpace != CssConstants.Nowrap && curx + word.Width + rightspacing > maxright) || word.IsLineBreak) { curx = startx; cury = maxbottom + linespacing; line = new CssLineBox(blockbox); if (word.IsImage || word.Equals(b.FirstWord)) { curx += leftspacing; } } // atodo: why? line.ReportExistanceOf(word); word.Left = curx; // -word.LastMeasureOffset.X + 1; word.Top = cury; // - word.LastMeasureOffset.Y; curx = word.Right + (word.IsImage ? b.ActualWordSpacing : 0); // +word.SpacesAfterWidth; maxbottom = Math.Max(maxbottom, word.Bottom); //+ (word.IsImage ? topspacing + bottomspacing : 0)); } // fix word space for last word that is right before inline tag var lastWord = b.Words[b.Words.Count - 1]; if (!lastWord.IsImage && !lastWord.HasSpaceAfter) { var next = DomUtils.GetNextSibling(b); if (next == null || next.Display == CssConstants.Inline) { curx -= b.ActualWordSpacing + CssUtils.GetWordEndWhitespace(b.ActualFont); } } } else { FlowBox(g, blockbox, b, maxright, linespacing, startx, ref line, ref curx, ref cury, ref maxbottom); } curx += rightspacing; } // handle height setting if (maxbottom - startY < box.ActualHeight) { maxbottom += box.ActualHeight - (maxbottom - startY); } // handle box that is only a whitespace if (box.Text == " " && !box.IsImage && box.IsInline && box.Boxes.Count == 0 && box.Words.Count == 0) { curx += box.ActualWordSpacing; } box.LastHostingLineBox = line; }
/// <summary> /// Gets the span attribute of the tag of the specified box /// </summary> /// <param name="b"></param> private static int GetSpan(CssBox b) { float f = CssValueParser.ParseNumber(b.GetAttribute("span"), 1); return(Math.Max(1, Convert.ToInt32(f))); }
/// <summary> /// Check if the given box contains inline and block child boxes. /// </summary> /// <param name="box">the box to check</param> /// <returns>true - has variant child boxes, false - otherwise</returns> private static bool ContainsVariantBoxes(CssBox box) { bool hasBlock = false; bool hasInline = false; for (int i = 0; i < box.Boxes.Count && (!hasBlock || !hasInline); i++) { var isBlock = box.Boxes[i].Display != CssConstants.Inline; hasBlock = hasBlock || isBlock; hasInline = hasInline || !isBlock; } return hasBlock && hasInline; }
/// <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 splitted 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> /// Correct DOM tree if there is block boxes that are inside inline blocks.<br/> /// Need to rearange the tree so block box will be only the child of other block box. /// </summary> /// <param name="box">the current box to correct its sub-tree</param> private static void CorrectBlockInsideInline(CssBox box) { if (DomUtils.ContainsInlinesOnly(box) && !ContainsInlinesOnlyDeep(box)) { CorrectBlockInsideInlineImp(box); } if (!DomUtils.ContainsInlinesOnly(box)) { foreach (var childBox in box.Boxes) { CorrectBlockInsideInline(childBox); } } }
/// <summary> /// Select all the words that are between <paramref name="selectionStart"/> word and <paramref name="selectionEnd"/> word in the DOM hierarchy.<br/> /// </summary> /// <param name="root">the root of the DOM sub-tree the selection is in</param> /// <param name="selectionStart">selection start word limit</param> /// <param name="selectionEnd">selection end word limit</param> private void SelectWordsInRange(CssBox root, CssRect selectionStart, CssRect selectionEnd) { bool inSelection = false; SelectWordsInRange(root, selectionStart, selectionEnd, ref inSelection); }
/// <summary> /// Split bad box that has inline and block boxes into two parts, the left - before the block box /// and right - after the block box. /// </summary> /// <param name="parentBox">the parent box that has the problem</param> /// <param name="badBox">the box to split into different boxes</param> /// <param name="leftBlock">the left block box that is created for the split</param> private static void CorrectBlockSplitBadBox(CssBox parentBox, CssBox badBox, CssBox leftBlock) { var leftbox = CssBox.CreateBox(leftBlock, badBox.HtmlTag); leftbox.InheritStyle(badBox, true); while (badBox.Boxes[0].IsInline && ContainsInlinesOnlyDeep(badBox.Boxes[0])) badBox.Boxes[0].ParentBox = leftbox; var splitBox = badBox.Boxes[0]; if (!ContainsInlinesOnlyDeep(splitBox)) { CorrectBlockSplitBadBox(parentBox, splitBox, leftBlock); splitBox.ParentBox = null; } else { splitBox.ParentBox = parentBox; } if (badBox.Boxes.Count > 0) { CssBox rightBox = null; if (splitBox.ParentBox != null || parentBox.Boxes.Count < 2) { rightBox = CssBox.CreateBox(parentBox, badBox.HtmlTag); rightBox.InheritStyle(badBox, true); if (parentBox.Boxes.Count > 2) rightBox.SetBeforeBox(parentBox.Boxes[1]); splitBox.SetBeforeBox(rightBox); } else if (parentBox.Boxes.Count > 2) { rightBox = parentBox.Boxes[2]; } while (badBox.Boxes.Count > 0) badBox.Boxes[0].ParentBox = rightBox; } else if(splitBox.ParentBox != null && parentBox.Boxes.Count > 1) { splitBox.SetBeforeBox(parentBox.Boxes[1]); } }
/// <summary> /// Select all the words that are between <paramref name="selectionStart"/> word and <paramref name="selectionEnd"/> word in the DOM hierarchy. /// </summary> /// <param name="box">the current traversal node</param> /// <param name="selectionStart">selection start word limit</param> /// <param name="selectionEnd">selection end word limit</param> /// <param name="inSelection">used to know the traversal is currently in selected range</param> /// <returns></returns> private bool SelectWordsInRange(CssBox box, CssRect selectionStart, CssRect selectionEnd, ref bool inSelection) { foreach (var boxWord in box.Words) { if (!inSelection && boxWord == selectionStart) { inSelection = true; } if (inSelection) { boxWord.Selection = this; if (selectionStart == selectionEnd || boxWord == selectionEnd) { return true; } } } foreach (var childBox in box.Boxes) { if (SelectWordsInRange(childBox, selectionStart, selectionEnd, ref inSelection)) { return true; } } return false; }
/// <summary> /// Correct the DOM tree recursively by replacing "br" html boxes with anonymous blocks that respect br spec.<br/> /// If the "br" tag is after inline box then the anon block will have zero height only acting as newline, /// but if it is after block box then it will have min-height of the font size so it will create empty line. /// </summary> /// <param name="box">the current box to correct its sub-tree</param> private static void CorrectLineBreaksBlocks(CssBox box) { int lastBr = -1; CssBox brBox; do { brBox = null; CssBox prevBox = null; for (int i = 0; i < box.Boxes.Count && brBox == null; i++) { if (i > lastBr && box.Boxes[i].HtmlTag != null && box.Boxes[i].HtmlTag.Name == "br") { brBox = box.Boxes[i]; lastBr = i; } else { prevBox = box.Boxes[i]; } } if (brBox != null) { var anonBlock = CssBox.CreateBlock(box, new HtmlTag("br"), brBox); if (prevBox == null || prevBox.Display != CssConstants.Inline) anonBlock.Height = ".9em"; // atodo: check the height to min-height when it is supported brBox.ParentBox = null; } } while (brBox != null); foreach (var childBox in box.Boxes) { CorrectLineBreaksBlocks(childBox); } }
/// <summary> /// Gets the actual horizontal spacing of the table /// </summary> private static float GetHorizontalSpacing(CssBox box) { return(box.BorderCollapse == CssConstants.Collapse ? -1f : box.ActualBorderSpacingHorizontal); }
/// <summary> /// Init. /// </summary> /// <param name="owner">the CSS box owner of the word</param> protected CssRect(CssBox owner) { _ownerBox = owner; }
/// <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 CssBoxHr(CssBox parent, HtmlTag tag) : base(parent, tag) { Display = CssConstants.Block; }
/// <summary> /// Layout the cells by the calculated table layout /// </summary> /// <param name="g"></param> private void LayoutCells(Graphics g) { float startx = Math.Max(_tableBox.ClientLeft + GetHorizontalSpacing(), 0); float starty = Math.Max(_tableBox.ClientTop + GetVerticalSpacing(), 0); float cury = starty; float maxRight = startx; float maxBottom = 0f; int currentrow = 0; for (int i = 0; i < _allRows.Count; i++) { var row = _allRows[i]; float curx = startx; int curCol = 0; for (int j = 0; j < row.Boxes.Count; j++) { CssBox cell = row.Boxes[j]; if (curCol >= _columnWidths.Length) { break; } int rowspan = GetRowSpan(cell); var columnIndex = GetCellRealColumnIndex(row, cell); float width = GetCellWidth(columnIndex, cell); cell.Location = new PointF(curx, cury); cell.Size = new SizeF(width, 0f); cell.PerformLayout(g); //That will automatically set the bottom of the cell //Alter max bottom only if row is cell's row + cell's rowspan - 1 CssSpacingBox sb = cell as CssSpacingBox; if (sb != null) { if (sb.EndRow == currentrow) { maxBottom = Math.Max(maxBottom, sb.ExtendedBox.ActualBottom); } } else if (rowspan == 1) { maxBottom = Math.Max(maxBottom, cell.ActualBottom); } maxRight = Math.Max(maxRight, cell.ActualRight); curCol++; curx = cell.ActualRight + GetHorizontalSpacing(); } foreach (CssBox cell in row.Boxes) { CssSpacingBox spacer = cell as CssSpacingBox; if (spacer == null && GetRowSpan(cell) == 1) { cell.ActualBottom = maxBottom; CssLayoutEngine.ApplyCellVerticalAlignment(g, cell); } else if (spacer != null && spacer.EndRow == currentrow) { spacer.ExtendedBox.ActualBottom = maxBottom; CssLayoutEngine.ApplyCellVerticalAlignment(g, spacer.ExtendedBox); } } cury = maxBottom + GetVerticalSpacing(); currentrow++; } _tableBox.ActualRight = maxRight + GetHorizontalSpacing() + _tableBox.ActualBorderRightWidth; _tableBox.ActualBottom = Math.Max(maxBottom, starty) + GetVerticalSpacing() + _tableBox.ActualBorderBottomWidth; }