/// <summary> /// Paints the fragment /// </summary> /// <param name="g">the device to draw to</param> protected override void PaintImp(RGraphics g) { var offset = HtmlContainer != null ? HtmlContainer.ScrollOffset : RPoint.Empty; var rect = new RRect(Bounds.X + offset.X, Bounds.Y + offset.Y, Bounds.Width, Bounds.Height); if (rect.Height > 2 && RenderUtils.IsColorVisible(ActualBackgroundColor)) { g.DrawRectangle(g.GetSolidBrush(ActualBackgroundColor), rect.X, rect.Y, rect.Width, rect.Height); } var b1 = g.GetSolidBrush(ActualBorderTopColor); BordersDrawHandler.DrawBorder(Border.Top, g, this, b1, rect); if (rect.Height > 1) { var b2 = g.GetSolidBrush(ActualBorderLeftColor); BordersDrawHandler.DrawBorder(Border.Left, g, this, b2, rect); var b3 = g.GetSolidBrush(ActualBorderRightColor); BordersDrawHandler.DrawBorder(Border.Right, g, this, b3, rect); var b4 = g.GetSolidBrush(ActualBorderBottomColor); BordersDrawHandler.DrawBorder(Border.Bottom, g, this, b4, rect); } }
/// <summary> /// Applies special vertical alignment for table-cells /// </summary> /// <param name="g"></param> /// <param name="cell"></param> public static void ApplyCellVerticalAlignment(RGraphics g, CssBox cell) { ArgChecker.AssertArgNotNull(g, "g"); ArgChecker.AssertArgNotNull(cell, "cell"); if (cell.VerticalAlign == CssConstants.Top || cell.VerticalAlign == CssConstants.Baseline) return; double cellbot = cell.ClientBottom; double bottom = cell.GetMaximumBottom(cell, 0f); double dist = 0f; if (cell.VerticalAlign == CssConstants.Bottom) { dist = cellbot - bottom; } else if (cell.VerticalAlign == CssConstants.Middle) { dist = (cellbot - bottom) / 2; } foreach (CssBox b in cell.Boxes) { b.OffsetTop(dist); } //float top = cell.ClientTop; //float bottom = cell.ClientBottom; //bool middle = cell.VerticalAlign == CssConstants.Middle; //foreach (LineBox line in cell.LineBoxes) //{ // for (int i = 0; i < line.RelatedBoxes.Count; i++) // { // double diff = bottom - line.RelatedBoxes[i].Rectangles[line].Bottom; // if (middle) diff /= 2f; // RectangleF r = line.RelatedBoxes[i].Rectangles[line]; // line.RelatedBoxes[i].Rectangles[line] = new RectangleF(r.X, r.Y + diff, r.Width, r.Height); // } // foreach (BoxWord word in line.Words) // { // double gap = word.Top - top; // word.Top = bottom - gap - word.Height; // } //} }
/// <summary> /// 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> /// Clip the region the graphics will draw on by the overflow style of the containing block.<br/> /// Recursively travel up the tree to find containing block that has overflow style set to hidden. if not /// block found there will be no clipping and null will be returned. /// </summary> /// <param name="g">the graphics to clip</param> /// <param name="box">the box that is rendered to get containing blocks</param> /// <returns>true - was clipped, false - not clipped</returns> public static bool ClipGraphicsByOverflow(RGraphics g, CssBox box) { var containingBlock = box.ContainingBlock; while (true) { if (containingBlock.Overflow == CssConstants.Hidden) { var prevClip = g.GetClip(); var rect = box.ContainingBlock.ClientRectangle; rect.X -= 2; // TODO:a find better way to fix it rect.Width += 2; rect.Offset(box.HtmlContainer.ScrollOffset); rect.Intersect(prevClip); g.PushClip(rect); return true; } var cBlock = containingBlock.ContainingBlock; if (cBlock == containingBlock) return false; containingBlock = cBlock; } }
/// <summary> /// 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> /// Analyzes the Table and assigns values to this CssTable object. /// To be called from the constructor /// </summary> private void Layout(RGraphics g) { MeasureWords(_tableBox, g); // get the table boxes into the proper fields AssignBoxKinds(); // Insert EmptyBoxes for vertical cell spanning. InsertEmptyBoxes(); // Determine Row and Column Count, and ColumnWidths var availCellSpace = CalculateCountAndWidth(); DetermineMissingColumnWidths(availCellSpace); // Check for minimum sizes (increment widths if necessary) EnforceMinimumSize(); // While table width is larger than it should, and width is reducible EnforceMaximumSize(); // Ensure there's no padding _tableBox.PaddingLeft = _tableBox.PaddingTop = _tableBox.PaddingRight = _tableBox.PaddingBottom = "0"; //Actually layout cells! LayoutCells(g); }
/// <summary> /// /// </summary> /// <param name="g"></param> /// <param name="tableBox"> </param> public static void PerformLayout(RGraphics g, CssBox tableBox) { ArgChecker.AssertArgNotNull(g, "g"); ArgChecker.AssertArgNotNull(tableBox, "tableBox"); try { var table = new CssLayoutEngineTable(tableBox); table.Layout(g); } catch (Exception ex) { tableBox.HtmlContainer.ReportError(HtmlRenderErrorType.Layout, "Failed table layout", ex); } }
/// <summary> /// 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> /// Paints the fragment /// </summary> /// <param name="g">the device to draw to</param> protected override void PaintImp(RGraphics g) { 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(); }
public override double GetWhitespaceWidth(RGraphics graphics) { if (_whitespaceWidth < 0) { _whitespaceWidth = graphics.MeasureString(" ", this).Width; } return _whitespaceWidth; }
/// <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> /// 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(RGraphics g, CssBox blockbox, CssBox box, double limitRight, double linespacing, double startx, ref CssLineBox line, ref double curx, ref double cury, ref double maxRight, ref double 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) { double leftspacing = b.Position != CssConstants.Absolute ? b.ActualMarginLeft + b.ActualBorderLeftWidth + b.ActualPaddingLeft : 0; double 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 && (b.WhiteSpace != CssConstants.PreWrap || !word.IsSpaces)) || 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 RRect(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> /// Applies vertical alignment to the linebox /// </summary> /// <param name="g"></param> /// <param name="lineBox"></param> private static void ApplyVerticalAlignment(RGraphics g, CssLineBox lineBox) { double baseline = Single.MinValue; foreach (var box in lineBox.Rectangles.Keys) { baseline = Math.Max(baseline, lineBox.Rectangles[box].Top); } var boxes = new List<CssBox>(lineBox.Rectangles.Keys); foreach (CssBox box in boxes) { //Important notes on http://www.w3.org/TR/CSS21/tables.html#height-layout switch (box.VerticalAlign) { case CssConstants.Sub: lineBox.SetBaseLine(g, box, baseline + lineBox.Rectangles[box].Height * .2f); break; case CssConstants.Super: lineBox.SetBaseLine(g, box, baseline - lineBox.Rectangles[box].Height * .2f); break; case CssConstants.TextTop: break; case CssConstants.TextBottom: break; case CssConstants.Top: break; case CssConstants.Bottom: break; case CssConstants.Middle: break; default: //case: baseline lineBox.SetBaseLine(g, box, baseline); break; } } }
/// <summary> /// Applies right alignment to the text on the linebox /// </summary> /// <param name="g"></param> /// <param name="line"></param> private static void ApplyRightAlignment(RGraphics g, CssLineBox line) { if (line.Words.Count == 0) return; CssRect lastWord = line.Words[line.Words.Count - 1]; double right = line.OwnerBox.ActualRight - line.OwnerBox.ActualPaddingRight - line.OwnerBox.ActualBorderRightWidth; double diff = right - lastWord.Right - lastWord.OwnerBox.ActualBorderRightWidth - lastWord.OwnerBox.ActualPaddingRight; if (diff > 0) { foreach (CssRect word in line.Words) { word.Left += diff; } foreach (CssBox b in line.Rectangles.Keys) { RRect r = b.Rectangles[line]; b.Rectangles[line] = new RRect(r.X + diff, r.Y, r.Width, r.Height); } } }
/// <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> /// 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)); return g.GetSolidBrush(backColor); } return g.GetSolidBrush(CssUtils.DefaultSelectionBackcolor); }
/// <summary> /// Creates line boxes for the specified blockbox /// </summary> /// <param name="g"></param> /// <param name="blockBox"></param> public static void CreateLineBoxes(RGraphics g, CssBox blockBox) { ArgChecker.AssertArgNotNull(g, "g"); ArgChecker.AssertArgNotNull(blockBox, "blockBox"); blockBox.LineBoxes.Clear(); double limitRight = blockBox.ActualRight - blockBox.ActualPaddingRight - blockBox.ActualBorderRightWidth; //Get the start x and y of the blockBox double startx = blockBox.Location.X + blockBox.ActualPaddingLeft - 0 + blockBox.ActualBorderLeftWidth; double starty = blockBox.Location.Y + blockBox.ActualPaddingTop - 0 + blockBox.ActualBorderTopWidth; double curx = startx + blockBox.ActualTextIndent; double cury = starty; //Reminds the maximum bottom reached double maxRight = startx; double maxBottom = starty; //First line box CssLineBox line = new CssLineBox(blockBox); //Flow words and boxes FlowBox(g, blockBox, blockBox, limitRight, 0, startx, ref line, ref curx, ref cury, ref maxRight, ref maxBottom); // if width is not restricted we need to lower it to the actual width if (blockBox.ActualRight >= 90999) { blockBox.ActualRight = maxRight + blockBox.ActualPaddingRight + blockBox.ActualBorderRightWidth; } //Gets the rectangles for each line-box foreach (var linebox in blockBox.LineBoxes) { ApplyAlignment(g, linebox); ApplyRightToLeft(blockBox, linebox); BubbleRectangles(blockBox, linebox); linebox.AssignRectanglesToBoxes(); } blockBox.ActualBottom = maxBottom + blockBox.ActualPaddingBottom + blockBox.ActualBorderBottomWidth; // handle limiting block height when overflow is hidden if (blockBox.Height != null && blockBox.Height != CssConstants.Auto && blockBox.Overflow == CssConstants.Hidden && blockBox.ActualBottom - blockBox.Location.Y > blockBox.ActualHeight) { blockBox.ActualBottom = blockBox.Location.Y + blockBox.ActualHeight; } }
/// <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> /// 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> /// 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> /// 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> /// 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> /// 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> /// 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> /// Recursively measures words inside the box /// </summary> /// <param name="box">the box to measure</param> /// <param name="g">Device to use</param> private static void MeasureWords(CssBox box, RGraphics g) { if (box != null) { foreach (var childBox in box.Boxes) { childBox.MeasureWordsSize(g); MeasureWords(childBox, g); } } }
/// <summary> /// 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> /// Layout the cells by the calculated table layout /// </summary> /// <param name="g"></param> private void LayoutCells(RGraphics g) { double startx = Math.Max(_tableBox.ClientLeft + GetHorizontalSpacing(), 0); double starty = Math.Max(_tableBox.ClientTop + GetVerticalSpacing(), 0); double cury = starty; double maxRight = startx; double maxBottom = 0f; int currentrow = 0; for (int i = 0; i < _allRows.Count; i++) { var row = _allRows[i]; double 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); double width = GetCellWidth(columnIndex, cell); cell.Location = new RPoint(curx, cury); cell.Size = new RSize(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++; } maxRight = Math.Max(maxRight, _tableBox.Location.X + _tableBox.ActualWidth); _tableBox.ActualRight = maxRight + GetHorizontalSpacing() + _tableBox.ActualBorderRightWidth; _tableBox.ActualBottom = Math.Max(maxBottom, starty) + GetVerticalSpacing() + _tableBox.ActualBorderBottomWidth; }
/// <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); } }