/// <summary> /// Gets the baseline Height of the rectangle /// </summary> /// <param name="g"></param> /// <returns></returns> public float GetBaseLineHeight(Box b, Graphics g) { var f = b.ActualFont; var ff = f.FontFamily; var s = f.Style; return f.GetHeight(g)*ff.GetCellAscent(s)/ff.GetLineSpacing(s); }
/// <summary> /// Creates a new LineBox /// </summary> public LineBox(Box ownerBox) { Rectangles = new Dictionary<Box, RectangleF>(); RelatedBoxes = new List<Box>(); Words = new List<BoxWord>(); OwnerBox = ownerBox; OwnerBox.LineBoxes.Add(this); }
public Table(Box tableBox, Graphics g) : this() { if (!(tableBox.Display == Constants.Table || tableBox.Display == Constants.InlineTable)) throw new ArgumentException("Box is not a table", "tableBox"); TableBox = tableBox; MeasureWords(tableBox, g); Analyze(g); }
public AnonymousBlockBox(Box parent, Box insertBefore) : this(parent) { var index = parent.Boxes.IndexOf(insertBefore); if (index < 0) { throw new Exception("insertBefore box doesn't exist on parent"); } parent.Boxes.Remove(this); parent.Boxes.Insert(index, this); }
/// <summary> /// Applies special vertical alignment for table-cells /// </summary> /// <param name="g"></param> /// <param name="cell"></param> public static void ApplyCellVerticalAlignment(Graphics g, Box cell) { if (cell.VerticalAlign == Constants.Top || cell.VerticalAlign == Constants.Baseline) return; var celltop = cell.ClientTop; var cellbot = cell.ClientBottom; var bottom = cell.GetMaximumBottom(cell, 0f); var dist = 0f; if (cell.VerticalAlign == Constants.Bottom) { dist = cellbot - bottom; } else if (cell.VerticalAlign == Constants.Middle) { dist = (cellbot - bottom)/2; } foreach (var b in cell.Boxes) { b.OffsetTop(dist); } //float top = cell.ClientTop; //float bottom = cell.ClientBottom; //bool middle = cell.VerticalAlign == CssConstants.Middle; //foreach (LineBox line in cell.LineBoxes) //{ // for (int i = 0; i < line.RelatedBoxes.Count; i++) // { // float diff = bottom - line.RelatedBoxes[i].Rectangles[line].Bottom; // if (middle) diff /= 2f; // RectangleF r = line.RelatedBoxes[i].Rectangles[line]; // line.RelatedBoxes[i].Rectangles[line] = new RectangleF(r.X, r.Y + diff, r.Width, r.Height); // } // foreach (BoxWord word in line.Words) // { // float gap = word.Top - top; // word.Top = bottom - gap - word.Height; // } //} }
/// <summary> /// Parses a border value in CSS style; e.g. 1px, 1, thin, thick, medium /// </summary> /// <param name="borderValue"></param> /// <param name="b"></param> /// <returns></returns> public static float GetActualBorderWidth(string borderValue, Box b) { if (string.IsNullOrEmpty(borderValue)) { return GetActualBorderWidth(Constants.Medium, b); } switch (borderValue) { case Constants.Thin: return 1f; case Constants.Medium: return 2f; case Constants.Thick: return 4f; default: return Math.Abs(ParseLength(borderValue, 1, b)); } }
/// <summary> /// Creates line boxes for the specified blockbox /// </summary> /// <param name="g"></param> /// <param name="blockBox"></param> public static void CreateLineBoxes(Graphics g, Box blockBox) { blockBox.LineBoxes.Clear(); var maxRight = blockBox.ActualRight - blockBox.ActualPaddingRight - blockBox.ActualBorderRightWidth; //Get the start x and y of the blockBox var startx = blockBox.Location.X + blockBox.ActualPaddingLeft - 0 + blockBox.ActualBorderLeftWidth; //TODO: Check for floats var starty = blockBox.Location.Y + blockBox.ActualPaddingTop - 0 + blockBox.ActualBorderTopWidth; var curx = startx + blockBox.ActualTextIndent; var cury = starty; //Reminds the maximum bottom reached var maxBottom = starty; //Extra amount of spacing that should be applied to lines when breaking them. var lineSpacing = 0f; //First line box var line = new LineBox(blockBox); //Flow words and boxes FlowBox(g, blockBox, blockBox, maxRight, lineSpacing, startx, ref line, ref curx, ref cury, ref maxBottom); //Gets the rectangles foreach linebox foreach (var linebox in blockBox.LineBoxes) { BubbleRectangles(blockBox, linebox); linebox.AssignRectanglesToBoxes(); ApplyAlignment(g, linebox); if (blockBox.Direction == Constants.Rtl) ApplyRightToLeft(linebox); //linebox.DrawRectangles(g); } blockBox.ActualBottom = maxBottom + blockBox.ActualPaddingBottom + blockBox.ActualBorderBottomWidth; }
internal Box(Box parentBox, Tag tag) : this(parentBox) { HtmlTag = tag; }
/// <summary> /// Gets the result of collapsing the vertical margins of the two boxes /// </summary> /// <param name="a">Superior box (checks for margin-bottom)</param> /// <param name="b">Inferior box (checks for margin-top)</param> /// <returns>Maximum of margins</returns> private float MarginCollapse(Box a, Box b) { return Math.Max( a?.ActualMarginBottom ?? 0, b?.ActualMarginTop ?? 0); }
/// <summary> /// Gets the previous sibling of this box. /// </summary> /// <returns>Box before this one on the tree. Null if its the first</returns> private Box GetPreviousSibling(Box b) { if (b.ParentBox == null) { return null; //This is initial containing block } var index = b.ParentBox.Boxes.IndexOf(this); if (index < 0) throw new Exception("Box doesn't exist on parent's Box list"); if (index == 0) return null; //This is the first sibling. var diff = 1; var sib = b.ParentBox.Boxes[index - diff]; while ((sib.Display == Constants.None || sib.Position == Constants.Absolute) && index - diff - 1 >= 0) { sib = b.ParentBox.Boxes[index - ++diff]; } return sib.Display == Constants.None ? null : sib; }
/// <summary> /// Gets the longest word (in width) inside the box, deeply. /// </summary> /// <param name="b"></param> /// <param name="maxw"></param> /// <param name="word"></param> /// <returns></returns> private void GetMinimumWidth_LongestWord(Box b, ref float maxw, ref BoxWord word) { if (b.Words.Count > 0) { foreach (var w in b.Words) { if (!(w.FullWidth > maxw)) continue; maxw = w.FullWidth; word = w; } } else { foreach (var bb in b.Boxes) GetMinimumWidth_LongestWord(bb, ref maxw, ref word); } }
/// <summary> /// Bubbles up the padding from the starting box /// </summary> /// <param name="box"></param> /// <param name="endbox"></param> /// <param name="sum"></param> /// <returns></returns> private void GetMinimumWidth_BubblePadding(Box box, Box endbox, ref float sum) { //float padding = box.ActualMarginLeft + box.ActualBorderLeftWidth + box.ActualPaddingLeft + // box.ActualMarginRight + box.ActualBorderRightWidth + box.ActualPaddingRight; var padding = box.ActualBorderLeftWidth + box.ActualPaddingLeft + box.ActualBorderRightWidth + box.ActualPaddingRight; sum += padding; if (!box.Equals(endbox)) { GetMinimumWidth_BubblePadding(box.ParentBox, endbox, ref sum); } }
/// <summary> /// Gets the longest word (in width) inside the box, deeply. /// </summary> /// <param name="b"></param> /// <param name="g"></param> /// <param name="sum"></param> /// <param name="paddingsum"></param> /// <returns></returns> private void GetFullWidth_WordsWith(Box b, Graphics g, ref float sum, ref float paddingsum) { if (b.Display != Constants.Inline) { sum = 0; } paddingsum += b.ActualBorderLeftWidth + b.ActualBorderRightWidth + b.ActualPaddingRight + b.ActualPaddingLeft; if (b.Words.Count > 0) { sum += b.Words.Sum(word => word.FullWidth); } else { foreach (var bb in b.Boxes) { GetFullWidth_WordsWith(bb, g, ref sum, ref paddingsum); } } }
/// <summary> /// Creates the corner to place with the borders /// </summary> /// <returns></returns> private static GraphicsPath CreateCorner(Box b, RectangleF r, int cornerIndex) { var corner = new GraphicsPath(); var outer = RectangleF.Empty; var inner = RectangleF.Empty; float start1 = 0; float start2 = 0; switch (cornerIndex) { case 1: outer = new RectangleF(r.Left, r.Top, b.ActualCornerNW, b.ActualCornerNW); inner = RectangleF.FromLTRB(outer.Left + b.ActualBorderLeftWidth, outer.Top + b.ActualBorderTopWidth, outer.Right, outer.Bottom); start1 = 180; start2 = 270; break; case 2: outer = new RectangleF(r.Right - b.ActualCornerNE, r.Top, b.ActualCornerNE, b.ActualCornerNE); inner = RectangleF.FromLTRB(outer.Left, outer.Top + b.ActualBorderTopWidth, outer.Right - b.ActualBorderRightWidth, outer.Bottom); outer.X -= outer.Width; inner.X -= inner.Width; start1 = -90; start2 = 0; break; case 3: outer = RectangleF.FromLTRB(r.Right - b.ActualCornerSE, r.Bottom - b.ActualCornerSE, r.Right, r.Bottom); inner = new RectangleF(outer.Left, outer.Top, outer.Width - b.ActualBorderRightWidth, outer.Height - b.ActualBorderBottomWidth); outer.X -= outer.Width; outer.Y -= outer.Height; inner.X -= inner.Width; inner.Y -= inner.Height; start1 = 0; start2 = 90; break; case 4: outer = new RectangleF(r.Left, r.Bottom - b.ActualCornerSW, b.ActualCornerSW, b.ActualCornerSW); inner = RectangleF.FromLTRB(r.Left + b.ActualBorderLeftWidth, outer.Top, outer.Right, outer.Bottom - b.ActualBorderBottomWidth); start1 = 90; start2 = 180; outer.Y -= outer.Height; inner.Y -= inner.Height; break; } if (outer.Width <= 0f) outer.Width = 1f; if (outer.Height <= 0f) outer.Height = 1f; if (inner.Width <= 0f) inner.Width = 1f; if (inner.Height <= 0f) inner.Height = 1f; outer.Width *= 2; outer.Height *= 2; inner.Width *= 2; inner.Height *= 2; outer = RoundR(outer, b); inner = RoundR(inner, b); corner.AddArc(outer, start1, 90); corner.AddArc(inner, start2, -90); corner.CloseFigure(); return corner; }
/// <summary> /// Rounds the specified rectangle /// </summary> /// <param name="r"></param> /// <param name="b"></param> /// <returns></returns> private static RectangleF RoundR(RectangleF r, Box b) { //HACK: Don't round if in printing mode return System.Drawing.Rectangle.Round(r); }
/// <summary> /// Parses a length. Lengths are followed by an unit identifier (e.g. 10px, 3.1em) /// </summary> /// <param name="length">Specified length</param> /// <param name="hundredPercent">Equivalent to 100 percent when length is percentage</param> /// <param name="box"></param> /// <returns></returns> public static float ParseLength(string length, float hundredPercent, Box box) { return ParseLength(length, hundredPercent, box, box.GetEmHeight(), false); }
/// <summary> /// Creates a new BoxWord which represents an image /// </summary> /// <param name="owner"></param> /// <param name="image"></param> public BoxWord(Box owner, Image image) : this(owner) { Image = image; }
public Box(Box parentBox) : this() { ParentBox = parentBox; }
/// <summary> /// Static constructor and initialization /// </summary> static Box() { #region Initialize _properties, _inheritables and _defaults Dictionaries _properties = new Dictionary<string, PropertyInfo>(); _defaults = new Dictionary<string, string>(); _inheritables = new List<PropertyInfo>(); _cssproperties = new List<PropertyInfo>(); var props = typeof (Box).GetProperties(); for (var i = 0; i < props.Length; i++) { var att = Attribute.GetCustomAttribute(props[i], typeof (PropertyAttribute)) as PropertyAttribute; if (att != null) { _properties.Add(att.Name, props[i]); _defaults.Add(att.Name, GetDefaultValue(props[i])); _cssproperties.Add(props[i]); var inh = Attribute.GetCustomAttribute(props[i], typeof (PropertyInheritedAttribute)) as PropertyInheritedAttribute; if (inh != null) { _inheritables.Add(props[i]); } } } #endregion Empty = new Box(); }
/// <summary> /// Cascades to the TD's the border spacified in the TABLE tag. /// </summary> /// <param name="table"></param> /// <param name="border"></param> private void ApplyTablePadding(Box table, string padding) { foreach (var box in table.Boxes) { foreach (var cell in box.Boxes) { cell.Padding = TranslateLength(padding); } } }
/// <summary> /// Cascades to the TD's the border spacified in the TABLE tag. /// </summary> /// <param name="table"></param> /// <param name="border"></param> private void ApplyTableBorder(Box table, string border) { foreach (var box in table.Boxes) { foreach (var cell in box.Boxes) { cell.BorderWidth = TranslateLength(border); } } }
internal void TranslateAttributes(Box box) { var t = TagName.ToUpper(); foreach (var att in Attributes.Keys) { var value = Attributes[att]; switch (att) { case Constants.align: if (value == Constants.left || value == Constants.center || value == Constants.right || value == Constants.justify) box.TextAlign = value; else box.VerticalAlign = value; break; case Constants.background: box.BackgroundImage = value; break; case Constants.bgcolor: box.BackgroundColor = value; break; case Constants.border: box.BorderWidth = TranslateLength(value); if (t == Constants.TABLE) { ApplyTableBorder(box, value); } else { box.BorderStyle = Css.Constants.Solid; } break; case Constants.bordercolor: box.BorderColor = value; break; case Constants.cellspacing: box.BorderSpacing = TranslateLength(value); break; case Constants.cellpadding: ApplyTablePadding(box, value); break; case Constants.color: box.Color = value; break; case Constants.dir: box.Direction = value; break; case Constants.face: box.FontFamily = value; break; case Constants.height: box.Height = TranslateLength(value); break; case Constants.hspace: box.MarginRight = box.MarginLeft = TranslateLength(value); break; case Constants.nowrap: box.WhiteSpace = Css.Constants.Nowrap; break; case Constants.size: if (t == Constants.HR) box.Height = TranslateLength(value); break; case Constants.valign: box.VerticalAlign = value; break; case Constants.vspace: box.MarginTop = box.MarginBottom = TranslateLength(value); break; case Constants.width: box.Width = TranslateLength(value); break; } } }
/// <summary> /// Parses a length. Lengths are followed by an unit identifier (e.g. 10px, 3.1em) /// </summary> /// <param name="length">Specified length</param> /// <param name="hundredPercent">Equivalent to 100 percent when length is percentage</param> /// <param name="box"></param> /// <param name="emFactor"></param> /// <param name="returnPoints">Allows the return float to be in points. If false, result will be pixels</param> /// <returns></returns> public static float ParseLength(string length, float hundredPercent, Box box, float emFactor, bool returnPoints) { //Return zero if no length specified, zero specified if (string.IsNullOrEmpty(length) || length == "0") return 0f; //If percentage, use ParseNumber if (length.EndsWith("%")) return ParseNumber(length, hundredPercent); //If no units, return zero if (length.Length < 3) return 0f; //Get units of the length var unit = length.Substring(length.Length - 2, 2); //Factor will depend on the unit float factor; //Number of the length var number = length.Substring(0, length.Length - 2); //TODO: Units behave different in paper and in screen! switch (unit) { case Constants.Em: factor = emFactor; break; case Constants.Px: factor = 1f; break; case Constants.Mm: factor = 3f; //3 pixels per millimeter break; case Constants.Cm: factor = 37f; //37 pixels per centimeter break; case Constants.In: factor = 96f; //96 pixels per inch break; case Constants.Pt: factor = 96f/72f; // 1 point = 1/72 of inch if (returnPoints) { return ParseNumber(number, hundredPercent); } break; case Constants.Pc: factor = 96f/72f*12f; // 1 pica = 12 points break; default: factor = 0f; break; } return factor*ParseNumber(number, hundredPercent); }
/// <summary> /// Searches for the first word occourence inside the box, on the specified linebox /// </summary> /// <param name="b"></param> /// <param name="line"></param> /// <returns></returns> internal BoxWord FirstWordOccourence(Box b, LineBox line) { if (b.Words.Count == 0 && b.Boxes.Count == 0) { return null; } return b.Words.Count > 0 ? b.Words.FirstOrDefault(word => line.Words.Contains(word)) : b.Boxes.Select(bb => FirstWordOccourence(bb, line)).FirstOrDefault(w => w != null); }
internal BoxWord(Box owner) { OwnerBox = owner; Text = string.Empty; }
/// <summary> /// Gets the maximum bottom of the boxes inside the startBox /// </summary> /// <param name="startBox"></param> /// <param name="currentMaxBottom"></param> /// <returns></returns> internal float GetMaximumBottom(Box startBox, float 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, b.ActualBottom); currentMaxBottom = Math.Max(currentMaxBottom, GetMaximumBottom(b, currentMaxBottom)); } return currentMaxBottom; }
/// <summary> /// Rounds the specified point /// </summary> /// <param name="p"></param> /// <param name="b"></param> /// <returns></returns> private static PointF RoundP(PointF p, Box b) { //HACK: Don't round if in printing mode //return Point.Round(p); return p; }
/// <summary> /// Inherits inheritable values from specified box. /// </summary> /// <param name="everything">Set to true to inherit all CSS properties instead of only the ineritables</param> /// <param name="godfather">Box to inherit the properties</param> internal void InheritStyle(Box godfather, bool everything) { if (godfather != null) { IEnumerable<PropertyInfo> pps = everything ? _cssproperties : _inheritables; foreach (var prop in pps) { prop.SetValue(this, prop.GetValue(godfather, null), null); } } }
/// <summary> /// Makes a border path /// </summary> /// <param name="border">Desired border</param> /// <param name="b">Box wich the border corresponds</param> /// <param name="r"></param> /// <param name="isLineStart">Specifies if the border is for a starting line (no bevel on left)</param> /// <param name="isLineEnd">Specifies if the border is for an ending line (no bevel on right)</param> /// <returns>Beveled border path</returns> public static GraphicsPath GetBorderPath(Border border, Box b, RectangleF r, bool isLineStart, bool isLineEnd) { var pts = new PointF[4]; float bwidth = 0; GraphicsPath corner = null; switch (border) { case Border.Top: bwidth = b.ActualBorderTopWidth; pts[0] = RoundP(new PointF(r.Left + b.ActualCornerNW, r.Top), b); pts[1] = RoundP(new PointF(r.Right - b.ActualCornerNE, r.Top), b); pts[2] = RoundP(new PointF(r.Right - b.ActualCornerNE, r.Top + bwidth), b); pts[3] = RoundP(new PointF(r.Left + b.ActualCornerNW, r.Top + bwidth), b); if (isLineEnd && b.ActualCornerNE == 0f) pts[2].X -= b.ActualBorderRightWidth; if (isLineStart && b.ActualCornerNW == 0f) pts[3].X += b.ActualBorderLeftWidth; if (b.ActualCornerNW > 0f) corner = CreateCorner(b, r, 1); break; case Border.Right: bwidth = b.ActualBorderRightWidth; pts[0] = RoundP(new PointF(r.Right - bwidth, r.Top + b.ActualCornerNE), b); pts[1] = RoundP(new PointF(r.Right, r.Top + b.ActualCornerNE), b); pts[2] = RoundP(new PointF(r.Right, r.Bottom - b.ActualCornerSE), b); pts[3] = RoundP(new PointF(r.Right - bwidth, r.Bottom - b.ActualCornerSE), b); if (b.ActualCornerNE == 0f) pts[0].Y += b.ActualBorderTopWidth; if (b.ActualCornerSE == 0f) pts[3].Y -= b.ActualBorderBottomWidth; if (b.ActualCornerNE > 0f) corner = CreateCorner(b, r, 2); break; case Border.Bottom: bwidth = b.ActualBorderBottomWidth; pts[0] = RoundP(new PointF(r.Left + b.ActualCornerSW, r.Bottom - bwidth), b); pts[1] = RoundP(new PointF(r.Right - b.ActualCornerSE, r.Bottom - bwidth), b); pts[2] = RoundP(new PointF(r.Right - b.ActualCornerSE, r.Bottom), b); pts[3] = RoundP(new PointF(r.Left + b.ActualCornerSW, r.Bottom), b); if (isLineStart && b.ActualCornerSW == 0f) pts[0].X += b.ActualBorderLeftWidth; if (isLineEnd && b.ActualCornerSE == 0f) pts[1].X -= b.ActualBorderRightWidth; if (b.ActualCornerSE > 0f) corner = CreateCorner(b, r, 3); break; case Border.Left: bwidth = b.ActualBorderLeftWidth; pts[0] = RoundP(new PointF(r.Left, r.Top + b.ActualCornerNW), b); pts[1] = RoundP(new PointF(r.Left + bwidth, r.Top + b.ActualCornerNW), b); pts[2] = RoundP(new PointF(r.Left + bwidth, r.Bottom - b.ActualCornerSW), b); pts[3] = RoundP(new PointF(r.Left, r.Bottom - b.ActualCornerSW), b); if (b.ActualCornerNW == 0f) pts[1].Y += b.ActualBorderTopWidth; if (b.ActualCornerSW == 0f) pts[2].Y -= b.ActualBorderBottomWidth; if (b.ActualCornerSW > 0f) corner = CreateCorner(b, r, 4); break; } var path = new GraphicsPath(pts, new[] { (byte) PathPointType.Line, (byte) PathPointType.Line, (byte) PathPointType.Line, (byte) PathPointType.Line }); if (corner != null) { path.AddPath(corner, true); } return path; }
/// <summary> /// Creates the <see cref="ListItemBox" /> /// </summary> /// <param name="g"></param> private void CreateListItemBox(Graphics g) { if (Display == Constants.ListItem) { if (ListItemBox == null) { ListItemBox = new Box(); ListItemBox.InheritStyle(this, false); ListItemBox.Display = Constants.Inline; ListItemBox.SetInitialContainer(InitialContainer); if (ParentBox != null && ListStyleType == Constants.Decimal) { ListItemBox.Text = GetIndexForList() + "."; } else { ListItemBox.Text = "•"; } ListItemBox.MeasureBounds(g); ListItemBox.Size = new SizeF(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; } }