/// <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> /// Measure the size of the html by performing layout under the given restrictions. /// </summary> /// <param name="g">the graphics to use</param> /// <param name="htmlContainer">the html to calculate the layout for</param> /// <param name="minSize">the minimal size of the rendered html (zero - not limit the width/height)</param> /// <param name="maxSize">the maximum size of the rendered html, if not zero and html cannot be layout within the limit it will be clipped (zero - not limit the width/height)</param> /// <returns>return: the size of the html to be rendered within the min/max limits</returns> public static RSize MeasureHtmlByRestrictions(RGraphics g, HtmlContainerInt htmlContainer, RSize minSize, RSize maxSize) { // first layout without size restriction to know html actual size htmlContainer.PerformLayout(g); if (maxSize.Width > 0 && maxSize.Width < htmlContainer.ActualSize.Width) { // to allow the actual size be smaller than max we need to set max size only if it is really larger htmlContainer.MaxSize = new RSize(maxSize.Width, 0); htmlContainer.PerformLayout(g); } // restrict the final size by min/max var finalWidth = Math.Max(maxSize.Width > 0 ? Math.Min(maxSize.Width, (int)htmlContainer.ActualSize.Width) : (int)htmlContainer.ActualSize.Width, minSize.Width); // if the final width is larger than the actual we need to re-layout so the html can take the full given width. if (finalWidth > htmlContainer.ActualSize.Width) { htmlContainer.MaxSize = new RSize(finalWidth, 0); htmlContainer.PerformLayout(g); } var finalHeight = Math.Max(maxSize.Height > 0 ? Math.Min(maxSize.Height, (int)htmlContainer.ActualSize.Height) : (int)htmlContainer.ActualSize.Height, minSize.Height); return new RSize(finalWidth, finalHeight); }
/// <summary> /// Paints the fragment /// </summary> /// <param name="g">the device to draw to</param> protected override void PaintImp(RGraphics g) { // load image iff it is in visible rectangle if (_imageLoadHandler == null) { _imageLoadHandler = new ImageLoadHandler(HtmlContainer, OnLoadImageComplete); _imageLoadHandler.LoadImage(GetAttribute("src"), HtmlTag != null ? HtmlTag.Attributes : null); } var rect = CommonUtils.GetFirstValueOrDefault(Rectangles); RPoint offset = HtmlContainer.ScrollOffset; rect.Offset(offset); var clipped = RenderUtils.ClipGraphicsByOverflow(g, this); PaintBackground(g, rect, true, true); BordersDrawHandler.DrawBoxBorders(g, this, rect, true, true); RRect r = _imageWord.Rectangle; r.Offset(offset); r.Height -= ActualBorderTopWidth + ActualBorderBottomWidth + ActualPaddingTop + ActualPaddingBottom; r.Y += ActualBorderTopWidth + ActualPaddingTop; r.X = Math.Floor(r.X); r.Y = Math.Floor(r.Y); if (_imageWord.Image != null) { if (r.Width > 0 && r.Height > 0) { if (_imageWord.ImageRectangle == RRect.Empty) g.DrawImage(_imageWord.Image, r); else g.DrawImage(_imageWord.Image, r, _imageWord.ImageRectangle); if (_imageWord.Selected) { g.DrawRectangle(GetSelectionBackBrush(g, true), _imageWord.Left + offset.X, _imageWord.Top + offset.Y, _imageWord.Width + 2, DomUtils.GetCssLineBoxByWord(_imageWord).LineHeight); } } } else if (_imageLoadingComplete) { if (_imageLoadingComplete && r.Width > 19 && r.Height > 19) { RenderUtils.DrawImageErrorIcon(g, HtmlContainer, r); } } else { RenderUtils.DrawImageLoadingIcon(g, HtmlContainer, r); if (r.Width > 19 && r.Height > 19) { g.DrawRectangle(g.GetPen(RColor.LightGray), r.X, r.Y, r.Width, r.Height); } } if (clipped) g.PopClip(); }
public override double GetWhitespaceWidth(RGraphics graphics) { if (_whitespaceWidth < 0) { _whitespaceWidth = graphics.MeasureString(" ", this).Width; } return _whitespaceWidth; }
/// <summary> /// Gets the white space width of the specified box /// </summary> /// <param name="g"></param> /// <param name="box"></param> /// <returns></returns> public static double WhiteSpace(RGraphics g, CssBoxProperties box) { double w = box.ActualFont.GetWhitespaceWidth(g); if (!(String.IsNullOrEmpty(box.WordSpacing) || box.WordSpacing == CssConstants.Normal)) { w += CssValueParser.ParseLength(box.WordSpacing, 0, box, true); } return w; }
/// <summary> /// Measures the bounds of box and children, recursively.<br/> /// Performs layout of the DOM structure creating lines by set bounds restrictions. /// </summary> /// <param name="g">Device context to use</param> protected override void PerformLayoutImp(RGraphics g) { if (Display == CssConstants.None) return; RectanglesReset(); var prevSibling = DomUtils.GetPreviousSibling(this); double left = ContainingBlock.Location.X + ContainingBlock.ActualPaddingLeft + ActualMarginLeft + ContainingBlock.ActualBorderLeftWidth; double top = (prevSibling == null && ParentBox != null ? ParentBox.ClientTop : ParentBox == null ? Location.Y : 0) + MarginTopCollapse(prevSibling) + (prevSibling != null ? prevSibling.ActualBottom + prevSibling.ActualBorderBottomWidth : 0); Location = new RPoint(left, top); ActualBottom = top; //width at 100% (or auto) double minwidth = GetMinimumWidth(); double width = ContainingBlock.Size.Width - ContainingBlock.ActualPaddingLeft - ContainingBlock.ActualPaddingRight - ContainingBlock.ActualBorderLeftWidth - ContainingBlock.ActualBorderRightWidth - ActualMarginLeft - ActualMarginRight - ActualBorderLeftWidth - ActualBorderRightWidth; //Check width if not auto if (Width != CssConstants.Auto && !string.IsNullOrEmpty(Width)) { width = CssValueParser.ParseLength(Width, width, this); } if (width < minwidth || width >= 9999) width = minwidth; double height = ActualHeight; if (height < 1) { height = Size.Height + ActualBorderTopWidth + ActualBorderBottomWidth; } if (height < 1) { height = 2; } if (height <= 2 && ActualBorderTopWidth < 1 && ActualBorderBottomWidth < 1) { BorderTopStyle = BorderBottomStyle = CssConstants.Solid; BorderTopWidth = "1px"; BorderBottomWidth = "1px"; } Size = new RSize(width, height); ActualBottom = Location.Y + ActualPaddingTop + ActualPaddingBottom + height; }
/// <summary> /// Perform the layout of the html container by given size restrictions returning the final size.<br/> /// The layout can be effected by the HTML content in the <paramref name="htmlContainer"/> if <paramref name="autoSize"/> or /// <paramref name="autoSizeHeightOnly"/> is set to true.<br/> /// Handle minimum and maximum size restrictions.<br/> /// Handle auto size and auto size for height only. if <paramref name="autoSize"/> is true <paramref name="autoSizeHeightOnly"/> /// is ignored.<br/> /// </summary> /// <param name="g">the graphics used for layout</param> /// <param name="htmlContainer">the html container to layout</param> /// <param name="size">the current size</param> /// <param name="minSize">the min size restriction - can be empty for no restriction</param> /// <param name="maxSize">the max size restriction - can be empty for no restriction</param> /// <param name="autoSize">if to modify the size (width and height) by html content layout</param> /// <param name="autoSizeHeightOnly">if to modify the height by html content layout</param> public static RSize Layout(RGraphics g, HtmlContainerInt htmlContainer, RSize size, RSize minSize, RSize maxSize, bool autoSize, bool autoSizeHeightOnly) { if (autoSize) htmlContainer.MaxSize = new RSize(0, 0); else if (autoSizeHeightOnly) htmlContainer.MaxSize = new RSize(size.Width, 0); else htmlContainer.MaxSize = size; htmlContainer.PerformLayout(g); RSize newSize = size; if (autoSize || autoSizeHeightOnly) { if (autoSize) { if (maxSize.Width > 0 && maxSize.Width < htmlContainer.ActualSize.Width) { // to allow the actual size be smaller than max we need to set max size only if it is really larger htmlContainer.MaxSize = maxSize; htmlContainer.PerformLayout(g); } else if (minSize.Width > 0 && minSize.Width > htmlContainer.ActualSize.Width) { // if min size is larger than the actual we need to re-layout so all 100% layouts will be correct htmlContainer.MaxSize = new RSize(minSize.Width, 0); htmlContainer.PerformLayout(g); } newSize = htmlContainer.ActualSize; } else if (Math.Abs(size.Height - htmlContainer.ActualSize.Height) > 0.01) { var prevWidth = size.Width; // make sure the height is not lower than min if given newSize.Height = minSize.Height > 0 && minSize.Height > htmlContainer.ActualSize.Height ? minSize.Height : htmlContainer.ActualSize.Height; // handle if changing the height of the label affects the desired width and those require re-layout if (Math.Abs(prevWidth - size.Width) > 0.01) return Layout(g, htmlContainer, size, minSize, maxSize, false, true); } } return newSize; }
/// <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> /// 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> /// Paints the background of the box /// </summary> /// <param name="g">the device to draw into</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> protected void PaintBackground(RGraphics g, RRect rect, bool isFirst, bool isLast) { if (rect.Width > 0 && rect.Height > 0) { RBrush brush = null; if (BackgroundGradient != CssConstants.None) { brush = g.GetLinearGradientBrush(rect, ActualBackgroundColor, ActualBackgroundGradient, ActualBackgroundGradientAngle); } else if (RenderUtils.IsColorVisible(ActualBackgroundColor)) { brush = g.GetSolidBrush(ActualBackgroundColor); } if (brush != null) { // TODO:a handle it correctly (tables background) // if (isLast) // rectangle.Width -= ActualWordSpacing + CssUtils.GetWordEndWhitespace(ActualFont); RGraphicsPath roundrect = null; if (IsRounded) { roundrect = RenderUtils.GetRoundRect(g, rect, ActualCornerNw, ActualCornerNe, ActualCornerSe, ActualCornerSw); } Object prevMode = null; if (HtmlContainer != null && !HtmlContainer.AvoidGeometryAntialias && IsRounded) { prevMode = g.SetAntiAliasSmoothingMode(); } if (roundrect != null) { g.DrawPath(brush, roundrect); } else { g.DrawRectangle(brush, Math.Ceiling(rect.X), Math.Ceiling(rect.Y), rect.Width, rect.Height); } g.ReturnPreviousSmoothingMode(prevMode); if (roundrect != null) roundrect.Dispose(); brush.Dispose(); } if (_imageLoadHandler != null && _imageLoadHandler.Image != null && isFirst) { BackgroundImageDrawHandler.DrawBackgroundImage(g, this, _imageLoadHandler, rect); } } }
/// <summary> /// Assigns words its width and height /// </summary> /// <param name="g"></param> internal virtual void MeasureWordsSize(RGraphics g) { if (!_wordsSizeMeasured) { if (BackgroundImage != CssConstants.None && _imageLoadHandler == null) { _imageLoadHandler = new ImageLoadHandler(HtmlContainer, OnImageLoadComplete); _imageLoadHandler.LoadImage(BackgroundImage, HtmlTag != null ? HtmlTag.Attributes : null); } MeasureWordSpacing(g); if (Words.Count > 0) { foreach (var boxWord in Words) { boxWord.Width = boxWord.Text != "\n" ? g.MeasureString(boxWord.Text, ActualFont).Width : 0; boxWord.Height = ActualFont.Height; } } _wordsSizeMeasured = true; } }
/// <summary> /// Paints the fragment /// </summary> /// <param name="g">Device context to use</param> public void Paint(RGraphics g) { try { if (Display != CssConstants.None && Visibility == CssConstants.Visible) { // don't call paint if the rectangle of the box is not in visible rectangle bool visible = Rectangles.Count == 0; if (!visible) { var clip = g.GetClip(); var rect = ContainingBlock.ClientRectangle; rect.X -= 2; rect.Width += 2; rect.Offset(new RPoint(-HtmlContainer.Location.X, -HtmlContainer.Location.Y)); rect.Offset(HtmlContainer.ScrollOffset); clip.Intersect(rect); if (clip != RRect.Empty) visible = true; } if (visible) PaintImp(g); } } catch (Exception ex) { HtmlContainer.ReportError(HtmlRenderErrorType.Paint, "Exception in box paint", ex); } }
/// <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> /// Paints the fragment /// </summary> /// <param name="g">the device to draw to</param> protected virtual void PaintImp(RGraphics g) { if (Display != CssConstants.None && (Display != CssConstants.TableCell || EmptyCells != CssConstants.Hide || !IsSpaceOrEmpty)) { var clipped = RenderUtils.ClipGraphicsByOverflow(g, this); var areas = Rectangles.Count == 0 ? new List<RRect>(new[] { Bounds }) : new List<RRect>(Rectangles.Values); RRect[] rects = areas.ToArray(); RPoint offset = HtmlContainer.ScrollOffset; for (int i = 0; i < rects.Length; i++) { var actualRect = rects[i]; actualRect.Offset(offset); PaintBackground(g, actualRect, i == 0, i == rects.Length - 1); BordersDrawHandler.DrawBoxBorders(g, this, actualRect, i == 0, i == rects.Length - 1); } PaintWords(g, offset); for (int i = 0; i < rects.Length; i++) { var actualRect = rects[i]; actualRect.Offset(offset); PaintDecoration(g, actualRect, i == 0, i == rects.Length - 1); } // split paint to handle z-order foreach (CssBox b in Boxes) { if (b.Position != CssConstants.Absolute) b.Paint(g); } foreach (CssBox b in Boxes) { if (b.Position == CssConstants.Absolute) b.Paint(g); } if (clipped) g.PopClip(); if (_listItemBox != null) { _listItemBox.Paint(g); } } }
/// <summary> /// Draw the background image at the required location repeating it over the X and Y axis.<br/> /// Adjust location to left-top if starting location doesn't include all the range (adjusted to center or bottom/right). /// </summary> private static void DrawRepeat(RGraphics g, ImageLoadHandler imageLoadHandler, RRect rectangle, RRect srcRect, RRect destRect, RSize imgSize) { while (destRect.X > rectangle.X) destRect.X -= imgSize.Width; while (destRect.Y > rectangle.Y) destRect.Y -= imgSize.Height; using (var brush = g.GetTextureBrush(imageLoadHandler.Image, srcRect, destRect.Location)) { g.DrawRectangle(brush, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); } }
/// <summary> /// Render the html using the given device. /// </summary> /// <param name="g">the device to use to render</param> public void PerformPaint(RGraphics g) { ArgChecker.AssertArgNotNull(g, "g"); if (MaxSize.Height > 0) { g.PushClip(new RRect(_location.X, _location.Y, Math.Min(_maxSize.Width, PageSize.Width), Math.Min(_maxSize.Height, PageSize.Height))); } else { g.PushClip(new RRect(MarginLeft, MarginTop, PageSize.Width, PageSize.Height)); } if (_root != null) { _root.Paint(g); } g.PopClip(); }
/// <summary> /// Creates a rounded rectangle using the specified corner radius<br/> /// NW-----NE /// | | /// | | /// SW-----SE /// </summary> /// <param name="g">the device to draw into</param> /// <param name="rect">Rectangle to round</param> /// <param name="nwRadius">Radius of the north east corner</param> /// <param name="neRadius">Radius of the north west corner</param> /// <param name="seRadius">Radius of the south east corner</param> /// <param name="swRadius">Radius of the south west corner</param> /// <returns>GraphicsPath with the lines of the rounded rectangle ready to be painted</returns> public static RGraphicsPath GetRoundRect(RGraphics g, RRect rect, double nwRadius, double neRadius, double seRadius, double swRadius) { var path = g.GetGraphicsPath(); path.Start(rect.Left + nwRadius, rect.Top); path.LineTo(rect.Right - neRadius, rect.Y); if (neRadius > 0f) path.ArcTo(rect.Right, rect.Top + neRadius, neRadius, RGraphicsPath.Corner.TopRight); path.LineTo(rect.Right, rect.Bottom - seRadius); if (seRadius > 0f) path.ArcTo(rect.Right - seRadius, rect.Bottom, seRadius, RGraphicsPath.Corner.BottomRight); path.LineTo(rect.Left + swRadius, rect.Bottom); if (swRadius > 0f) path.ArcTo(rect.Left, rect.Bottom - swRadius, swRadius, RGraphicsPath.Corner.BottomLeft); path.LineTo(rect.Left, rect.Top + nwRadius); if (nwRadius > 0f) path.ArcTo(rect.Left + nwRadius, rect.Top, nwRadius, RGraphicsPath.Corner.TopLeft); return path; }
/// <summary> /// Assigns words its width and height /// </summary> /// <param name="g">the device to use</param> internal override void MeasureWordsSize(RGraphics g) { if (!_wordsSizeMeasured) { if (_imageLoadHandler == null && (HtmlContainer.AvoidAsyncImagesLoading || HtmlContainer.AvoidImagesLateLoading)) { _imageLoadHandler = new ImageLoadHandler(HtmlContainer, OnLoadImageComplete); if (this.Content != null && this.Content != CssConstants.Normal) _imageLoadHandler.LoadImage(this.Content, HtmlTag != null ? HtmlTag.Attributes : null); else _imageLoadHandler.LoadImage(GetAttribute("src"), HtmlTag != null ? HtmlTag.Attributes : null); } MeasureWordSpacing(g); _wordsSizeMeasured = true; } CssLayoutEngine.MeasureImageSize(_imageWord); }
/// <summary> /// Draw image failed to load icon. /// </summary> /// <param name="g">the device to draw into</param> /// <param name="htmlContainer"></param> /// <param name="r">the rectangle to draw icon in</param> public static void DrawImageErrorIcon(RGraphics g, HtmlContainerInt htmlContainer, RRect r) { g.DrawRectangle(g.GetPen(RColor.LightGray), r.Left + 2, r.Top + 2, 15, 15); var image = htmlContainer.Adapter.GetLoadingFailedImage(); g.DrawImage(image, new RRect(r.Left + 3, r.Top + 3, image.Width, image.Height)); }
/// <summary> /// Paints the fragment /// </summary> /// <param name="g">the device to draw to</param> protected override void PaintImp(RGraphics g) { if (_videoImageUrl != null && _imageLoadHandler == null) { _imageLoadHandler = new ImageLoadHandler(HtmlContainer, OnLoadImageComplete); _imageLoadHandler.LoadImage(_videoImageUrl, HtmlTag != null ? HtmlTag.Attributes : null); } var rects = CommonUtils.GetFirstValueOrDefault(Rectangles); RPoint offset = HtmlContainer != null ? HtmlContainer.ScrollOffset : RPoint.Empty; rects.Offset(offset); var clipped = RenderUtils.ClipGraphicsByOverflow(g, this); PaintBackground(g, rects, true, true); BordersDrawHandler.DrawBoxBorders(g, this, rects, true, true); var word = Words[0]; var tmpRect = word.Rectangle; tmpRect.Offset(offset); tmpRect.Height -= ActualBorderTopWidth + ActualBorderBottomWidth + ActualPaddingTop + ActualPaddingBottom; tmpRect.Y += ActualBorderTopWidth + ActualPaddingTop; tmpRect.X = Math.Floor(tmpRect.X); tmpRect.Y = Math.Floor(tmpRect.Y); var rect = tmpRect; DrawImage(g, offset, rect); DrawTitle(g, rect); DrawPlay(g, rect); if (clipped) g.PopClip(); }
/// <summary> /// Measures the bounds of box and children, recursively.<br/> /// Performs layout of the DOM structure creating lines by set bounds restrictions.<br/> /// </summary> /// <param name="g">Device context to use</param> protected virtual void PerformLayoutImp(RGraphics g) { if (Display != CssConstants.None) { RectanglesReset(); MeasureWordsSize(g); } if (IsBlock || Display == CssConstants.ListItem || Display == CssConstants.Table || Display == CssConstants.InlineTable || Display == CssConstants.TableCell) { // Because their width and height are set by CssTable if (Display != CssConstants.TableCell && Display != CssConstants.Table) { double width = ContainingBlock.Size.Width - ContainingBlock.ActualPaddingLeft - ContainingBlock.ActualPaddingRight - ContainingBlock.ActualBorderLeftWidth - ContainingBlock.ActualBorderRightWidth; if (Width != CssConstants.Auto && !string.IsNullOrEmpty(Width)) { width = CssValueParser.ParseLength(Width, width, this); } Size = new RSize(width, Size.Height); // must be separate because the margin can be calculated by percentage of the width Size = new RSize(width - ActualMarginLeft - ActualMarginRight, Size.Height); } if (Display != CssConstants.TableCell) { var prevSibling = DomUtils.GetPreviousSibling(this); double left = ContainingBlock.Location.X + ContainingBlock.ActualPaddingLeft + ActualMarginLeft + ContainingBlock.ActualBorderLeftWidth; double top = (prevSibling == null && ParentBox != null ? ParentBox.ClientTop : ParentBox == null ? Location.Y : 0) + MarginTopCollapse(prevSibling) + (prevSibling != null ? prevSibling.ActualBottom + prevSibling.ActualBorderBottomWidth : 0); Location = new RPoint(left, top); ActualBottom = top; } //If we're talking about a table here.. if (Display == CssConstants.Table || Display == CssConstants.InlineTable) { CssLayoutEngineTable.PerformLayout(g, this); } else { //If there's just inline boxes, create LineBoxes if (DomUtils.ContainsInlinesOnly(this)) { ActualBottom = Location.Y; CssLayoutEngine.CreateLineBoxes(g, this); //This will automatically set the bottom of this block } else if (_boxes.Count > 0) { foreach (var childBox in Boxes) { childBox.PerformLayout(g); } ActualRight = CalculateActualRight(); ActualBottom = MarginBottomCollapse(); } } } else { var prevSibling = DomUtils.GetPreviousSibling(this); if (prevSibling != null) { if (Location == RPoint.Empty) Location = prevSibling.Location; ActualBottom = prevSibling.ActualBottom; } } ActualBottom = Math.Max(ActualBottom, Location.Y + ActualHeight); CreateListItemBox(g); var actualWidth = Math.Max(GetMinimumWidth() + GetWidthMarginDeep(this), Size.Width < 90999 ? ActualRight - HtmlContainer.Root.Location.X : 0); HtmlContainer.ActualSize = CommonUtils.Max(HtmlContainer.ActualSize, new RSize(actualWidth, ActualBottom - HtmlContainer.Root.Location.Y)); }
/// <summary> /// Draw video image over the iframe if found. /// </summary> private void DrawImage(RGraphics g, RPoint offset, RRect rect) { if (_imageWord.Image != null) { if (rect.Width > 0 && rect.Height > 0) { if (_imageWord.ImageRectangle == RRect.Empty) g.DrawImage(_imageWord.Image, rect); else g.DrawImage(_imageWord.Image, rect, _imageWord.ImageRectangle); if (_imageWord.Selected) { g.DrawRectangle(GetSelectionBackBrush(g, true), _imageWord.Left + offset.X, _imageWord.Top + offset.Y, _imageWord.Width + 2, DomUtils.GetCssLineBoxByWord(_imageWord).LineHeight); } } } else if (_isVideo && !_imageLoadingComplete) { RenderUtils.DrawImageLoadingIcon(g, HtmlContainer, rect); if (rect.Width > 19 && rect.Height > 19) { g.DrawRectangle(g.GetPen(RColor.LightGray), rect.X, rect.Y, rect.Width, rect.Height); } } }
/// <summary> /// Paint all the words in the box. /// </summary> /// <param name="g">the device to draw into</param> /// <param name="offset">the current scroll offset to offset the words</param> private void PaintWords(RGraphics g, RPoint offset) { if (Width.Length > 0) { var isRtl = Direction == CssConstants.Rtl; foreach (var word in Words) { if (!word.IsLineBreak) { var wordPoint = new RPoint(word.Left + offset.X, word.Top + offset.Y); if (word.Selected) { // handle paint selected word background and with partial word selection var wordLine = DomUtils.GetCssLineBoxByWord(word); var left = word.SelectedStartOffset > -1 ? word.SelectedStartOffset : (wordLine.Words[0] != word && word.HasSpaceBefore ? -ActualWordSpacing : 0); var padWordRight = word.HasSpaceAfter && !wordLine.IsLastSelectedWord(word); var width = word.SelectedEndOffset > -1 ? word.SelectedEndOffset : word.Width + (padWordRight ? ActualWordSpacing : 0); var rect = new RRect(word.Left + offset.X + left, word.Top + offset.Y, width - left, wordLine.LineHeight); g.DrawRectangle(GetSelectionBackBrush(g, false), rect.X, rect.Y, rect.Width, rect.Height); if (HtmlContainer.SelectionForeColor != RColor.Empty && (word.SelectedStartOffset > 0 || word.SelectedEndIndexOffset > -1)) { g.PushClipExclude(rect); g.DrawString(word.Text, ActualFont, ActualColor, wordPoint, new RSize(word.Width, word.Height), isRtl); g.PopClip(); g.PushClip(rect); g.DrawString(word.Text, ActualFont, GetSelectionForeBrush(), wordPoint, new RSize(word.Width, word.Height), isRtl); g.PopClip(); } else { g.DrawString(word.Text, ActualFont, GetSelectionForeBrush(), wordPoint, new RSize(word.Width, word.Height), isRtl); } } else { // g.DrawRectangle(HtmlContainer.Adapter.GetPen(RColor.Black), wordPoint.X, wordPoint.Y, word.Width - 1, word.Height - 1); g.DrawString(word.Text, ActualFont, ActualColor, wordPoint, new RSize(word.Width, word.Height), isRtl); } } } } }
/// <summary> /// Draw video title on top of the iframe if found. /// </summary> private void DrawTitle(RGraphics g, RRect rect) { if (_videoTitle != null && _imageWord.Width > 40 && _imageWord.Height > 40) { var font = HtmlContainer.Adapter.GetFont("Arial", 9f, RFontStyle.Regular); g.DrawRectangle(g.GetSolidBrush(RColor.FromArgb(160, 0, 0, 0)), rect.Left, rect.Top, rect.Width, ActualFont.Height + 7); var titleRect = new RRect(rect.Left + 3, rect.Top + 3, rect.Width - 6, rect.Height - 6); g.DrawString(_videoTitle, font, RColor.WhiteSmoke, titleRect.Location, RSize.Empty, false); } }
/// <summary> /// Measures the bounds of box and children, recursively.<br/> /// Performs layout of the DOM structure creating lines by set bounds restrictions. /// </summary> /// <param name="g">Device context to use</param> public void PerformLayout(RGraphics g) { try { PerformLayoutImp(g); } catch (Exception ex) { HtmlContainer.ReportError(HtmlRenderErrorType.Layout, "Exception in box layout", ex); } }
/// <summary> /// Draw play over the iframe if we found link url. /// </summary> private void DrawPlay(RGraphics g, RRect rect) { if (_isVideo && _imageWord.Width > 70 && _imageWord.Height > 50) { var prevMode = g.SetAntiAliasSmoothingMode(); var size = new RSize(60, 40); var left = rect.Left + (rect.Width - size.Width) / 2; var top = rect.Top + (rect.Height - size.Height) / 2; g.DrawRectangle(g.GetSolidBrush(RColor.FromArgb(160, 0, 0, 0)), left, top, size.Width, size.Height); RPoint[] points = { new RPoint(left + size.Width / 3f + 1,top + 3 * size.Height / 4f), new RPoint(left + size.Width / 3f + 1, top + size.Height / 4f), new RPoint(left + 2 * size.Width / 3f + 1, top + size.Height / 2f) }; g.DrawPolygon(g.GetSolidBrush(RColor.White), points); g.ReturnPreviousSmoothingMode(prevMode); } }
/// <summary> /// Get brush for selection background depending if it has external and if alpha is required for images. /// </summary> /// <param name="g"></param> /// <param name="forceAlpha">used for images so they will have alpha effect</param> protected RBrush GetSelectionBackBrush(RGraphics g, bool forceAlpha) { var backColor = HtmlContainer.SelectionBackColor; if (backColor != RColor.Empty) { if (forceAlpha && backColor.A > 180) return g.GetSolidBrush(RColor.FromArgb(180, backColor.R, backColor.G, backColor.B)); else return g.GetSolidBrush(backColor); } else { return g.GetSolidBrush(CssUtils.DefaultSelectionBackcolor); } }
/// <summary> /// Assigns words its width and height /// </summary> /// <param name="g">the device to use</param> internal override void MeasureWordsSize(RGraphics g) { if (!_wordsSizeMeasured) { MeasureWordSpacing(g); _wordsSizeMeasured = true; } CssLayoutEngine.MeasureImageSize(_imageWord); }
/// <summary> /// Paints the text decoration (underline/strike-through/over-line) /// </summary> /// <param name="g">the device to draw into</param> /// <param name="rectangle"> </param> /// <param name="isFirst"> </param> /// <param name="isLast"> </param> protected void PaintDecoration(RGraphics g, RRect rectangle, bool isFirst, bool isLast) { if (string.IsNullOrEmpty(TextDecoration) || TextDecoration == CssConstants.None) return; double y = 0f; if (TextDecoration == CssConstants.Underline) { y = Math.Round(rectangle.Top + ActualFont.UnderlineOffset); } else if (TextDecoration == CssConstants.LineThrough) { y = rectangle.Top + rectangle.Height / 2f; } else if (TextDecoration == CssConstants.Overline) { y = rectangle.Top; } y -= ActualPaddingBottom - ActualBorderBottomWidth; double x1 = rectangle.X; if (isFirst) x1 += ActualPaddingLeft + ActualBorderLeftWidth; double x2 = rectangle.Right; if (isLast) x2 -= ActualPaddingRight + ActualBorderRightWidth; var pen = g.GetPen(ActualColor); pen.Width = 1; pen.DashStyle = RDashStyle.Solid; g.DrawLine(pen, x1, y, x2, y); }
/// <summary> /// Measures the bounds of box and children, recursively. /// </summary> /// <param name="g">Device context to draw</param> public void PerformLayout(RGraphics g) { ArgChecker.AssertArgNotNull(g, "g"); _actualSize = RSize.Empty; if (_root != null) { // if width is not restricted we set it to large value to get the actual later _root.Size = new RSize(_maxSize.Width > 0 ? _maxSize.Width : 99999, 0); _root.Location = _location; _root.PerformLayout(g); if (_maxSize.Width <= 0.1) { // in case the width is not restricted we need to double layout, first will find the width so second can layout by it (center alignment) _root.Size = new RSize((int)Math.Ceiling(_actualSize.Width), 0); _actualSize = RSize.Empty; _root.PerformLayout(g); } if (!_loadComplete) { _loadComplete = true; EventHandler handler = LoadComplete; if (handler != null) handler(this, EventArgs.Empty); } } }