/// <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> /// 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> /// 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; if (!box.IsFixed) rect.Offset(box.HtmlContainer.ScrollOffset); rect.Intersect(prevClip); g.PushClip(rect); return true; } else { var cBlock = containingBlock.ContainingBlock; if (cBlock == containingBlock) return false; containingBlock = cBlock; } } }
/// <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> /// 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> /// Check if the given location is inside the given box deep.<br/> /// Check inner boxes and all lines that the given box spans to. /// </summary> /// <param name="box">the box to check</param> /// <param name="location">the location to check</param> /// <returns>true - location inside the box, false - otherwise</returns> public static bool IsInBox(CssBox box, RPoint location) { foreach (var line in box.Rectangles) { if (line.Value.Contains(location)) return true; } foreach (var childBox in box.Boxes) { if (IsInBox(childBox, location)) return true; } return false; }
/// <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> /// 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> /// Get the <paramref name="min"/> and <paramref name="maxSum"/> of the box words content and <paramref name="paddingSum"/>.<br/> /// </summary> /// <param name="box">the box to calculate for</param> /// <param name="min">the width that allows for each word to fit (width of the longest word)</param> /// <param name="maxSum">the max width a single line of words can take without wrapping</param> /// <param name="paddingSum">the total amount of padding the content has </param> /// <param name="marginSum"></param> /// <returns></returns> private static void GetMinMaxSumWords(CssBox box, ref double min, ref double maxSum, ref double paddingSum, ref double marginSum) { double? oldSum = null; // not inline (block) boxes start a new line so we need to reset the max sum if (box.Display != CssConstants.Inline && box.Display != CssConstants.TableCell && box.WhiteSpace != CssConstants.NoWrap) { oldSum = maxSum; maxSum = marginSum; } // add the padding paddingSum += box.ActualBorderLeftWidth + box.ActualBorderRightWidth + box.ActualPaddingRight + box.ActualPaddingLeft; // for tables the padding also contains the spacing between cells if (box.Display == CssConstants.Table) paddingSum += CssLayoutEngineTable.GetTableSpacing(box); if (box.Words.Count > 0) { // calculate the min and max sum for all the words in the box foreach (CssRect word in box.Words) { maxSum += word.FullWidth + (word.HasSpaceBefore ? word.OwnerBox.ActualWordSpacing : 0); min = Math.Max(min, word.Width); } // remove the last word padding if (box.Words.Count > 0 && !box.Words[box.Words.Count - 1].HasSpaceAfter) maxSum -= box.Words[box.Words.Count - 1].ActualWordSpacing; } else { // recursively on all the child boxes for (int i = 0; i < box.Boxes.Count; i++) { CssBox childBox = box.Boxes[i]; marginSum += childBox.ActualMarginLeft + childBox.ActualMarginRight; //maxSum += childBox.ActualMarginLeft + childBox.ActualMarginRight; GetMinMaxSumWords(childBox, ref min, ref maxSum, ref paddingSum, ref marginSum); marginSum -= childBox.ActualMarginLeft + childBox.ActualMarginRight; } } // max sum is max of all the lines in the box if (oldSum.HasValue) { maxSum = Math.Max(maxSum, oldSum.Value); } }
/// <summary> /// Gets the longest word (in width) inside the box, deeply. /// </summary> /// <param name="box"></param> /// <param name="maxWidth"> </param> /// <param name="maxWidthWord"> </param> /// <returns></returns> private static void GetMinimumWidth_LongestWord(CssBox box, ref double maxWidth, ref CssRect maxWidthWord) { if (box.Words.Count > 0) { foreach (CssRect cssRect in box.Words) { if (cssRect.Width > maxWidth) { maxWidth = cssRect.Width; maxWidthWord = cssRect; } } } else { foreach (CssBox childBox in box.Boxes) GetMinimumWidth_LongestWord(childBox, ref maxWidth, ref maxWidthWord); } }
/// <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> /// 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; } else { foreach (CssBox bb in b.Boxes) { CssRect w = FirstWordOccourence(bb, line); if (w != null) { return w; } } return null; } }
/// <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> /// 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); } else if (tag.Name == HtmlConstants.Iframe) { return new CssBoxFrame(parent, tag); } else if (tag.Name == HtmlConstants.Hr) { return new CssBoxHr(parent, tag); } else { return new CssBox(parent, tag); } }
/// <summary> /// Create new css block box. /// </summary> /// <returns>the new block box</returns> public static CssBox CreateBlock() { var box = new CssBox(null, null); box.Display = CssConstants.Block; return box; }
/// <summary> /// Get the total margin value (left and right) from the given box to the given end box.<br/> /// </summary> /// <param name="box">the box to start calculation from.</param> /// <returns>the total margin</returns> private static double GetWidthMarginDeep(CssBox box) { double sum = 0f; if (box.Size.Width > 90999 || (box.ParentBox != null && box.ParentBox.Size.Width > 90999)) { while (box != null) { sum += box.ActualMarginLeft + box.ActualMarginRight; box = box.ParentBox; } } return sum; }
/// <summary> /// Creates the <see cref="_listItemBox"/> /// </summary> /// <param name="g"></param> private void CreateListItemBox(RGraphics g) { if (Display == CssConstants.ListItem && ListStyleType != CssConstants.None) { if (_listItemBox == null) { _listItemBox = new CssBox(null, null); _listItemBox.InheritStyle(this); _listItemBox.Display = CssConstants.Inline; _listItemBox.HtmlContainer = HtmlContainer; if (ListStyleType.Equals(CssConstants.Disc, StringComparison.InvariantCultureIgnoreCase)) { _listItemBox.Text = new SubString("•"); } else if (ListStyleType.Equals(CssConstants.Circle, StringComparison.InvariantCultureIgnoreCase)) { _listItemBox.Text = new SubString("o"); } else if (ListStyleType.Equals(CssConstants.Square, StringComparison.InvariantCultureIgnoreCase)) { _listItemBox.Text = new SubString("♠"); } else if (ListStyleType.Equals(CssConstants.Decimal, StringComparison.InvariantCultureIgnoreCase)) { _listItemBox.Text = new SubString(GetIndexForList().ToString(CultureInfo.InvariantCulture) + "."); } else if (ListStyleType.Equals(CssConstants.DecimalLeadingZero, StringComparison.InvariantCultureIgnoreCase)) { _listItemBox.Text = new SubString(GetIndexForList().ToString("00", CultureInfo.InvariantCulture) + "."); } else { _listItemBox.Text = new SubString(CommonUtils.ConvertToAlphaNumber(GetIndexForList(), ListStyleType) + "."); } _listItemBox.ParseToWords(); _listItemBox.PerformLayoutImp(g); _listItemBox.Size = new RSize(_listItemBox.Words[0].Width, _listItemBox.Words[0].Height); } _listItemBox.Words[0].Left = Location.X - _listItemBox.Size.Width - 5; _listItemBox.Words[0].Top = Location.Y + ActualPaddingTop; // +FontAscent; } }
/// <summary> /// Init. /// </summary> /// <param name="root">the root of the handled html tree</param> public SelectionHandler(CssBox root) { ArgChecker.AssertArgNotNull(root, "root"); _root = root; _contextMenuHandler = new ContextMenuHandler(this, root.HtmlContainer); }
/// <summary> /// Create new css block 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 block boxes visit CSS spec: /// http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level /// </remarks> /// <param name="parent">the box to add the new block 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 block box</returns> public static CssBox CreateBlock(CssBox parent, HtmlTag tag = null, CssBox before = null) { ArgChecker.AssertArgNotNull(parent, "parent"); var newBox = CreateBox(parent, tag, before); newBox.Display = CssConstants.Block; return newBox; }
/// <summary> /// Handle mouse move to handle hover cursor and text selection. /// </summary> /// <param name="parent">the control hosting the html to set cursor and invalidate</param> /// <param name="loc">the location of the mouse on the html</param> public void HandleMouseMove(RControl parent, RPoint loc) { if (_root.HtmlContainer.IsSelectionEnabled && _mouseDownInControl && parent.LeftMouseButton) { if (_mouseDownOnSelectedWord) { // make sure not to start drag-drop on click but when it actually moves as it f***s mouse-up if ((DateTime.Now - _lastMouseDown).TotalMilliseconds > 200) StartDragDrop(parent); } else { HandleSelection(parent, loc, !_isDoubleClickSelect); _inSelection = _selectionStart != null && _selectionEnd != null && (_selectionStart != _selectionEnd || _selectionStartIndex != _selectionEndIndex); } } else { // Handle mouse hover over the html to change the cursor depending if hovering word, link of other. var link = DomUtils.GetLinkBox(_root, loc); if (link != null) { _cursorChanged = true; parent.SetCursorHand(); if (link != _lastLink) { _root.HtmlContainer.HandleLinkHover(parent, loc, link); _lastLink = link; } } else if (_root.HtmlContainer.IsSelectionEnabled) { var word = DomUtils.GetCssBoxWord(_root, loc); _cursorChanged = word != null && !word.IsImage && !(word.Selected && (word.SelectedStartIndex < 0 || word.Left + word.SelectedStartOffset <= loc.X) && (word.SelectedEndOffset < 0 || word.Left + word.SelectedEndOffset >= loc.X)); if (_cursorChanged) parent.SetCursorIBeam(); else parent.SetCursorDefault(); _lastLink = null; } else if (_cursorChanged) { parent.SetCursorDefault(); _lastLink = null; } } }
/// <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> /// Select all the words that are under <paramref name="box"/> DOM hierarchy.<br/> /// </summary> /// <param name="box">the box to start select all at</param> public void SelectAllWords(CssBox box) { foreach (var word in box.Words) { word.Selection = this; } foreach (var childBox in box.Boxes) { SelectAllWords(childBox); } }
/// <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> /// Clear the selection from all the words in the css box recursively. /// </summary> /// <param name="box">the css box to selectionStart clear at</param> private static void ClearSelection(CssBox box) { foreach (var word in box.Words) { word.Selection = null; } foreach (var childBox in box.Boxes) { ClearSelection(childBox); } }
/// <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> /// 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> /// 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> public CssBox(CssBox parentBox, HtmlTag tag) { if (parentBox != null) { _parentBox = parentBox; _parentBox.Boxes.Add(this); } _htmltag = tag; }
/// <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; }