/// <summary> /// Applies right to left direction to words /// </summary> /// <param name="blockBox"></param> /// <param name="lineBox"></param> private static void ApplyRightToLeft(CssBox blockBox, CssLineBox lineBox) { if (blockBox.Direction == CssConstants.Rtl) { ApplyRightToLeftOnLine(lineBox); } else { foreach (var box in lineBox.RelatedBoxes) { if (box.Direction == CssConstants.Rtl) { ApplyRightToLeftOnSingleBox(lineBox, box); } } } }
/// <summary> /// Creates line boxes for the specified blockbox /// </summary> /// <param name="g"></param> /// <param name="blockBox"></param> public static void CreateLineBoxes(Graphics g, CssBox blockBox) { ArgChecker.AssertArgNotNull(g, "g"); ArgChecker.AssertArgNotNull(blockBox, "blockBox"); blockBox.LineBoxes.Clear(); float limitRight = blockBox.ActualRight - blockBox.ActualPaddingRight - blockBox.ActualBorderRightWidth; //Get the start x and y of the blockBox float startx = blockBox.Location.X + blockBox.ActualPaddingLeft - 0 + blockBox.ActualBorderLeftWidth; float starty = blockBox.Location.Y + blockBox.ActualPaddingTop - 0 + blockBox.ActualBorderTopWidth; float curx = startx + blockBox.ActualTextIndent; float cury = starty; //Reminds the maximum bottom reached float maxRight = startx; float 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 >= 99999) { blockBox.ActualRight = maxRight + blockBox.ActualPaddingRight + blockBox.ActualBorderRightWidth; } //Gets the rectangles foreach linebox foreach (var linebox in blockBox.LineBoxes) { BubbleRectangles(blockBox, linebox); linebox.AssignRectanglesToBoxes(); ApplyAlignment(g, linebox); if (blockBox.Direction == CssConstants.Rtl) { ApplyRightToLeft(linebox); } } blockBox.ActualBottom = maxBottom + blockBox.ActualPaddingBottom + blockBox.ActualBorderBottomWidth; }
/// <summary> /// Simplest alignment, just arrange words. /// </summary> /// <param name="g"></param> /// <param name="line"></param> private static void ApplyLeftAlignment(IGraphics g, CssLineBox line) { //No alignment needed. //foreach (LineBoxRectangle r in line.Rectangles) //{ // float curx = r.Left + (r.Index == 0 ? r.OwnerBox.ActualPaddingLeft + r.OwnerBox.ActualBorderLeftWidth / 2 : 0); // if (r.SpaceBefore) curx += r.OwnerBox.ActualWordSpacing; // foreach (BoxWord word in r.Words) // { // word.Left = curx; // word.Top = r.Top;// +r.OwnerBox.ActualPaddingTop + r.OwnerBox.ActualBorderTopWidth / 2; // curx = word.Right + r.OwnerBox.ActualWordSpacing; // } //} }
/// <summary> /// Applies centered alignment to the text on the linebox /// </summary> /// <param name="g"></param> /// <param name="lineBox"></param> private static void ApplyJustifyAlignment(Graphics g, CssLineBox lineBox) { if (lineBox.Equals(lineBox.OwnerBox.LineBoxes[lineBox.OwnerBox.LineBoxes.Count - 1])) { return; } float indent = lineBox.Equals(lineBox.OwnerBox.LineBoxes[0]) ? lineBox.OwnerBox.ActualTextIndent : 0f; float textSum = 0f; float words = 0f; float availWidth = lineBox.OwnerBox.ClientRectangle.Width - indent; #region Gather text sum foreach (CssBoxWord w in lineBox.Words) { textSum += w.Width; words += 1f; } #endregion if (words <= 0f) { return; //Avoid Zero division } float spacing = (availWidth - textSum) / words; //Spacing that will be used float curx = lineBox.OwnerBox.ClientLeft + indent; foreach (CssBoxWord word in lineBox.Words) { word.Left = curx; curx = word.Right + spacing; if (word == lineBox.Words[lineBox.Words.Count - 1]) { word.Left = lineBox.OwnerBox.ClientRight - word.Width; } //TODO: Background rectangles are being deactivated when justifying text. } }
/// <summary> /// Applies centered alignment to the text on the linebox /// </summary> /// <param name="g"></param> /// <param name="lineBox"></param> private static void ApplyJustifyAlignment(Graphics g, CssLineBox lineBox) { if (lineBox.Equals(lineBox.OwnerBox.LineBoxes[lineBox.OwnerBox.LineBoxes.Count - 1])) return; float indent = lineBox.Equals(lineBox.OwnerBox.LineBoxes[0]) ? lineBox.OwnerBox.ActualTextIndent : 0f; float textSum = 0f; float words = 0f; float availWidth = lineBox.OwnerBox.ClientRectangle.Width - indent; #region Gather text sum foreach (CssBoxWord w in lineBox.Words) { textSum += w.Width; words += 1f; } #endregion if (words <= 0f) return; //Avoid Zero division float spacing = (availWidth - textSum)/words; //Spacing that will be used float curx = lineBox.OwnerBox.ClientLeft + indent; foreach (CssBoxWord word in lineBox.Words) { word.Left = curx; curx = word.Right + spacing; if (word == lineBox.Words[lineBox.Words.Count - 1]) { word.Left = lineBox.OwnerBox.ClientRight - word.Width; } //TODO: Background rectangles are being deactivated when justifying text. } }
/// <summary> /// Applies vertical and horizontal alignment to words in lineboxes /// </summary> /// <param name="g"></param> /// <param name="lineBox"></param> private static void ApplyAlignment(Graphics g, CssLineBox lineBox) { #region Horizontal alignment switch (lineBox.OwnerBox.TextAlign) { case CssConstants.Right: ApplyRightAlignment(g, lineBox); break; case CssConstants.Center: ApplyCenterAlignment(g, lineBox); break; case CssConstants.Justify: ApplyJustifyAlignment(g, lineBox); break; default: ApplyLeftAlignment(g, lineBox); break; } #endregion ApplyVerticalAlignment(g, lineBox); }
/// <summary> /// Applies RTL direction to all the words on the line. /// </summary> /// <param name="line">the line to apply RTL to</param> private static void ApplyRightToLeftOnLine(CssLineBox line) { float left = line.Words[0].Left; float right = line.Words[line.Words.Count-1].Right; foreach (CssRect word in line.Words) { float diff = word.Left - left; float wright = right - diff; word.Left = wright - word.Width; } }
/// <summary> /// Recursively flows the content of the box using the inline model /// </summary> /// <param name="g">Device Info</param> /// <param name="blockbox">Blockbox that contains the text flow</param> /// <param name="box">Current box to flow its content</param> /// <param name="limitRight">Maximum reached right</param> /// <param name="linespacing">Space to use between rows of text</param> /// <param name="startx">x starting coordinate for when breaking lines of text</param> /// <param name="line">Current linebox being used</param> /// <param name="curx">Current x coordinate that will be the left of the next word</param> /// <param name="cury">Current y coordinate that will be the top of the next word</param> /// <param name="maxRight">Maximum right reached so far</param> /// <param name="maxbottom">Maximum bottom reached so far</param> private static void FlowBox(IGraphics g, CssBox blockbox, CssBox box, float limitRight, float linespacing, float startx, ref CssLineBox line, ref float curx, ref float cury, ref float maxRight, ref float maxbottom) { var startX = curx; var startY = cury; box.FirstHostingLineBox = line; var localCurx = curx; var localMaxRight = maxRight; var localmaxbottom = maxbottom; foreach (CssBox b in box.Boxes) { float leftspacing = b.Position != CssConstants.Absolute ? b.ActualMarginLeft + b.ActualBorderLeftWidth + b.ActualPaddingLeft : 0; float rightspacing = b.Position != CssConstants.Absolute ? b.ActualMarginRight + b.ActualBorderRightWidth + b.ActualPaddingRight : 0; b.RectanglesReset(); b.MeasureWordsSize(g); curx += leftspacing; if (b.Words.Count > 0) { bool wrapNoWrapBox = false; if (b.WhiteSpace == CssConstants.NoWrap && curx > startx) { var boxRight = curx; foreach (var word in b.Words) { boxRight += word.FullWidth; } if (boxRight > limitRight) { wrapNoWrapBox = true; } } if (DomUtils.IsBoxHasWhitespace(b)) { curx += box.ActualWordSpacing; } foreach (var word in b.Words) { if (maxbottom - cury < box.ActualLineHeight) { maxbottom += box.ActualLineHeight - (maxbottom - cury); } if ((b.WhiteSpace != CssConstants.NoWrap && b.WhiteSpace != CssConstants.Pre && curx + word.Width + rightspacing > limitRight && (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 RectangleF(startX, startY, box.ActualWidth, box.ActualHeight)); } // handle box that is only a whitespace if (box.Text != null && box.Text.IsWhitespace() && !box.IsImage && box.IsInline && box.Boxes.Count == 0 && box.Words.Count == 0) { curx += box.ActualWordSpacing; } // hack to support specific absolute position elements if (box.Position == CssConstants.Absolute) { curx = localCurx; maxRight = localMaxRight; maxbottom = localmaxbottom; AdjustAbsolutePosition(box, 0, 0); } box.LastHostingLineBox = line; }
/// <summary> /// Creates line boxes for the specified blockbox /// </summary> /// <param name="g"></param> /// <param name="blockBox"></param> public static void CreateLineBoxes(Graphics g, CssBox blockBox) { blockBox.LineBoxes.Clear(); float maxRight = blockBox.ActualRight - blockBox.ActualPaddingRight - blockBox.ActualBorderRightWidth; //Get the start x and y of the blockBox float startx = blockBox.Location.X + blockBox.ActualPaddingLeft - 0 + blockBox.ActualBorderLeftWidth; float starty = blockBox.Location.Y + blockBox.ActualPaddingTop - 0 + blockBox.ActualBorderTopWidth; float curx = startx + blockBox.ActualTextIndent; float cury = starty; //Reminds the maximum bottom reached float maxBottom = starty; //First line box CssLineBox line = new CssLineBox(blockBox); //Flow words and boxes FlowBox(g, blockBox, blockBox, maxRight, 0, 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 == CssConstants.Rtl) ApplyRightToLeft(linebox); } blockBox.ActualBottom = maxBottom + blockBox.ActualPaddingBottom + blockBox.ActualBorderBottomWidth; }
/// <summary> /// Recursively creates the rectangles of the blockBox, by bubbling from deep to outside of the boxes /// in the rectangle structure /// </summary> private static void BubbleRectangles(CssBox box, CssLineBox line) { if (box.Words.Count > 0) { float x = float.MaxValue, y = float.MaxValue, r = float.MinValue, b = float.MinValue; List<CssBoxWord> words = line.WordsOf(box); if (words.Count > 0) { foreach (CssBoxWord word in words) { x = Math.Min(x, word.Left); // - word.SpacesBeforeWidth); r = Math.Max(r, word.Right); // + word.SpacesAfterWidth); y = Math.Min(y, word.Top); b = Math.Max(b, word.Bottom); } line.UpdateRectangle(box, x, y, r, b); } } else { foreach (CssBox b in box.Boxes) { BubbleRectangles(b, line); } } }
/// <summary> /// Applies right to left direction to words /// </summary> /// <param name="line"></param> private static void ApplyRightToLeft(CssLineBox line) { float left = line.OwnerBox.ClientLeft; float right = line.OwnerBox.ClientRight; foreach (CssBoxWord word in line.Words) { float diff = word.Left - left; float wright = right - diff; word.Left = wright - word.Width; } }
/// <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 CssBoxWord FirstWordOccourence(CssBox b, CssLineBox line) { if (b.Words.Count == 0 && b.Boxes.Count == 0) { return null; } if (b.Words.Count > 0) { foreach (CssBoxWord word in b.Words) { if (line.Words.Contains(word)) { return word; } } return null; } else { foreach (CssBox bb in b.Boxes) { CssBoxWord w = FirstWordOccourence(bb, line); if (w != null) { return w; } } return null; } }
/// <summary> /// Get css word box under the given sub-tree at the given x,y location.<br/> /// the location must be in correct scroll offset. /// </summary> /// <param name="lineBox">the line box to search in</param> /// <param name="location">the location to find the box at</param> /// <returns>css word box if exists or null</returns> public static CssRect GetCssBoxWord(CssLineBox lineBox, Point location) { foreach (var rects in lineBox.Rectangles) { foreach (var word in rects.Key.Words) { // add word spacing to word width so sentance won't have hols in it when moving the mouse var rect = word.Rectangle; rect.Width += word.OwnerBox.ActualWordSpacing; if (rect.Contains(location)) { return word; } } } return null; }
/// <summary> /// Paints the fragment /// </summary> /// <param name="g"></param> private void PaintImp(Graphics g) { if (Display != CssConstants.None && (Display != CssConstants.TableCell || EmptyCells != CssConstants.Hide || !IsSpaceOrEmpty)) { var areas = Rectangles.Count == 0 ? new List <RectangleF>(new[] { Bounds }) : new List <RectangleF>(Rectangles.Values); RectangleF[] rects = areas.ToArray(); PointF offset = HtmlContainer != null ? HtmlContainer.ScrollOffset : PointF.Empty; for (int i = 0; i < rects.Length; i++) { var actualRect = rects[i]; actualRect.Offset(offset); PaintBackground(g, actualRect, i == rects.Length - 1); PaintBorder(g, actualRect, i == 0, i == rects.Length - 1); } if (IsImage) { var word = Words[0]; RectangleF r = word.Bounds; r.Offset(offset); r.Height -= ActualBorderTopWidth + ActualBorderBottomWidth + ActualPaddingTop + ActualPaddingBottom; r.Y += ActualBorderTopWidth + ActualPaddingTop; //HACK: round rectangle only when necessary g.DrawImage(word.Image, Rectangle.Round(r)); if (word.Selected) { g.FillRectangle(CssUtils.SelectionBackcolor, word.Left - word.LastMeasureOffset.X + offset.X, word.Top + offset.Y, word.Width, DomUtils.GetCssLineBoxByWord(word).LineHeight); } } else if (Words.Count > 0) { Font font = ActualFont; var brush = CssUtils.GetSolidBrush(CssValueParser.GetActualColor(Color)); foreach (var word in Words) { if (word.Selected) { // handle paint selected word background and with partial word selection var left = word.SelectedStartOffset > -1 ? word.SelectedStartOffset : 0; var width = word.SelectedEndOffset > -1 ? word.SelectedEndOffset + word.LastMeasureOffset.X : word.Width; //REFACTOR THIS STUFF Brush b = CssUtils.SelectionBackcolor; CssLineBox box = DomUtils.GetCssLineBoxByWord(word); float h = box != null ? box.LineHeight : 0; float w = width - left; float x = word.Left - word.LastMeasureOffset.X + offset.X + left; float y = word.Top + offset.Y; g.FillRectangle(b, x, y, w, h); } g.DrawString(word.Text, font, brush, word.Left - word.LastMeasureOffset.X + offset.X, word.Top + offset.Y); } } for (int i = 0; i < rects.Length; i++) { var actualRect = rects[i]; actualRect.Offset(offset); PaintDecoration(g, actualRect, i == 0, i == rects.Length - 1); } foreach (CssBox b in Boxes) { b.Paint(g); } CreateListItemBox(g); if (ListItemBox != null) { ListItemBox.Paint(g); } } }
/// <summary> /// Creates line boxes for the specified blockbox /// </summary> /// <param name="g"></param> /// <param name="blockBox"></param> public static void CreateLineBoxes(IGraphics g, CssBox blockBox) { ArgChecker.AssertArgNotNull(g, "g"); ArgChecker.AssertArgNotNull(blockBox, "blockBox"); blockBox.LineBoxes.Clear(); float limitRight = blockBox.ActualRight - blockBox.ActualPaddingRight - blockBox.ActualBorderRightWidth; //Get the start x and y of the blockBox float startx = blockBox.Location.X + blockBox.ActualPaddingLeft - 0 + blockBox.ActualBorderLeftWidth; float starty = blockBox.Location.Y + blockBox.ActualPaddingTop - 0 + blockBox.ActualBorderTopWidth; float curx = startx + blockBox.ActualTextIndent; float cury = starty; //Reminds the maximum bottom reached float maxRight = startx; float 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> /// Recursively flows the content of the box using the inline model /// </summary> /// <param name="g">Device Info</param> /// <param name="blockbox">Blockbox that contains the text flow</param> /// <param name="box">Current box to flow its content</param> /// <param name="limitRight">Maximum reached right</param> /// <param name="linespacing">Space to use between rows of text</param> /// <param name="startx">x starting coordinate for when breaking lines of text</param> /// <param name="line">Current linebox being used</param> /// <param name="curx">Current x coordinate that will be the left of the next word</param> /// <param name="cury">Current y coordinate that will be the top of the next word</param> /// <param name="maxRight">Maximum right reached so far</param> /// <param name="maxbottom">Maximum bottom reached so far</param> private static void FlowBox(IGraphics g, CssBox blockbox, CssBox box, float limitRight, float linespacing, float startx, ref CssLineBox line, ref float curx, ref float cury, ref float maxRight, ref float maxbottom) { var startX = curx; var startY = cury; box.FirstHostingLineBox = line; var localCurx = curx; var localMaxRight = maxRight; var localmaxbottom = maxbottom; foreach (CssBox b in box.Boxes) { float leftspacing = b.Position != CssConstants.Absolute ? b.ActualMarginLeft + b.ActualBorderLeftWidth + b.ActualPaddingLeft : 0; float rightspacing = b.Position != CssConstants.Absolute ? b.ActualMarginRight + b.ActualBorderRightWidth + b.ActualPaddingRight : 0; b.RectanglesReset(); b.MeasureWordsSize(g); curx += leftspacing; if (b.Words.Count > 0) { bool wrapNoWrapBox = false; if (b.WhiteSpace == CssConstants.NoWrap && curx > startx) { var boxRight = curx; foreach(var word in b.Words) boxRight += word.FullWidth; if( boxRight > limitRight ) wrapNoWrapBox = true; } if( DomUtils.IsBoxHasWhitespace(b) ) curx += box.ActualWordSpacing; foreach (var word in b.Words) { if (maxbottom - cury < box.ActualLineHeight) maxbottom += box.ActualLineHeight - (maxbottom - cury); if ((b.WhiteSpace != CssConstants.NoWrap && b.WhiteSpace != CssConstants.Pre && curx + word.Width + rightspacing > limitRight) || word.IsLineBreak || wrapNoWrapBox) { wrapNoWrapBox = false; curx = startx; // handle if line is wrapped for the first text element where parent has left margin\padding if (b == box.Boxes[0] && !word.IsLineBreak && (word == b.Words[0] || box.ParentBox.IsBlock)) curx += box.ActualMarginLeft + box.ActualBorderLeftWidth + box.ActualPaddingLeft; cury = maxbottom + linespacing; line = new CssLineBox(blockbox); if (word.IsImage || word.Equals(b.FirstWord)) { curx += leftspacing; } } line.ReportExistanceOf(word); word.Left = curx; word.Top = cury; curx = word.Left + word.FullWidth; maxRight = Math.Max(maxRight, word.Right); maxbottom = Math.Max(maxbottom, word.Bottom); if (b.Position == CssConstants.Absolute) { word.Left += box.ActualMarginLeft; word.Top += box.ActualMarginTop; } } } else { FlowBox(g, blockbox, b, limitRight, linespacing, startx, ref line, ref curx, ref cury, ref maxRight, ref maxbottom); } curx += rightspacing; } // handle height setting if (maxbottom - startY < box.ActualHeight) { maxbottom += box.ActualHeight - (maxbottom - startY); } // handle width setting if (box.IsInline && 0 <= curx - startX && curx - startX < box.ActualWidth) { // hack for actual width handling curx += box.ActualWidth - (curx - startX); line.Rectangles.Add(box, new RectangleF(startX, startY, box.ActualWidth, box.ActualHeight)); } // handle box that is only a whitespace if (box.Text != null && box.Text.IsWhitespace() && !box.IsImage && box.IsInline && box.Boxes.Count == 0 && box.Words.Count == 0) { curx += box.ActualWordSpacing; } // hack to support specific absolute position elements if (box.Position == CssConstants.Absolute) { curx = localCurx; maxRight = localMaxRight; maxbottom = localmaxbottom; AdjustAbsolutePosition(box, 0, 0); } box.LastHostingLineBox = line; }
/// <summary> /// Recursively creates the rectangles of the blockBox, by bubbling from deep to outside of the boxes /// in the rectangle structure /// </summary> private static void BubbleRectangles(CssBox box, CssLineBox line) { if (box.Words.Count > 0) { float x = Single.MaxValue, y = Single.MaxValue, r = Single.MinValue, b = Single.MinValue; List<CssRect> words = line.WordsOf(box); if (words.Count > 0) { foreach (CssRect word in words) { // handle if line is wrapped for the first text element where parent has left margin\padding var left = word.Left; if (box == box.ParentBox.Boxes[0] && word == box.Words[0] && word == line.Words[0] && line != line.OwnerBox.LineBoxes[0] && !word.IsLineBreak) left -= box.ParentBox.ActualMarginLeft + box.ParentBox.ActualBorderLeftWidth + box.ParentBox.ActualPaddingLeft; x = Math.Min(x, left); r = Math.Max(r, word.Right - word.ActualWordSpacing); y = Math.Min(y, word.Top); b = Math.Max(b, word.Bottom); } line.UpdateRectangle(box, x, y, r, b); } } else { foreach (CssBox b in box.Boxes) { BubbleRectangles(b, line); } } }
/// <summary> /// Applies RTL direction to specific box words on the line. /// </summary> /// <param name="lineBox"></param> /// <param name="box"></param> private static void ApplyRightToLeftOnSingleBox(CssLineBox lineBox, CssBox box) { int leftWordIdx = -1; int rightWordIdx = -1; for (int i = 0; i < lineBox.Words.Count; i++) { if (lineBox.Words[i].OwnerBox == box) { if (leftWordIdx < 0) leftWordIdx = i; rightWordIdx = i; } } if (leftWordIdx > -1 && rightWordIdx > leftWordIdx) { float left = lineBox.Words[leftWordIdx].Left; float right = lineBox.Words[rightWordIdx].Right; for (int i = leftWordIdx; i <= rightWordIdx; i++) { float diff = lineBox.Words[i].Left - left; float wright = right - diff; lineBox.Words[i].Left = wright - lineBox.Words[i].Width; } } }
/// <summary> /// Simplest alignment, just arrange words. /// </summary> /// <param name="g"></param> /// <param name="line"></param> private static void ApplyLeftAlignment(Graphics g, CssLineBox line) { //No alignment needed. //foreach (LineBoxRectangle r in line.Rectangles) //{ // float curx = r.Left + (r.Index == 0 ? r.OwnerBox.ActualPaddingLeft + r.OwnerBox.ActualBorderLeftWidth / 2 : 0); // if (r.SpaceBefore) curx += r.OwnerBox.ActualWordSpacing; // foreach (BoxWord word in r.Words) // { // word.Left = curx; // word.Top = r.Top;// +r.OwnerBox.ActualPaddingTop + r.OwnerBox.ActualBorderTopWidth / 2; // curx = word.Right + r.OwnerBox.ActualWordSpacing; // } //} }
/// <summary> /// Applies right alignment to the text on the linebox /// </summary> /// <param name="g"></param> /// <param name="line"></param> private static void ApplyRightAlignment(Graphics g, CssLineBox line) { if (line.Words.Count == 0) return; CssBoxWord lastWord = line.Words[line.Words.Count - 1]; float right = line.OwnerBox.ActualRight - line.OwnerBox.ActualPaddingRight - line.OwnerBox.ActualBorderRightWidth; float diff = right - lastWord.Right - lastWord.LastMeasureOffset.X - lastWord.OwnerBox.ActualBorderRightWidth - lastWord.OwnerBox.ActualPaddingRight; if (diff <= 0) return; //if (line.OwnerBox.Direction == CssConstants.Rtl) //{ //} foreach (CssBoxWord word in line.Words) { word.Left += diff; } foreach (CssBox b in line.Rectangles.Keys) { RectangleF r = b.Rectangles[line]; b.Rectangles[line] = new RectangleF(r.X + diff, r.Y, r.Width, r.Height); } }
/// <summary> /// Offsets the rectangle of the specified linebox by the specified gap, /// and goes deep for rectangles of children in that linebox. /// </summary> /// <param name="lineBox"></param> /// <param name="gap"></param> internal void OffsetRectangle(CssLineBox lineBox, float gap) { if (Rectangles.ContainsKey(lineBox)) { RectangleF r = Rectangles[lineBox]; Rectangles[lineBox] = new RectangleF(r.X, r.Y + gap, r.Width, r.Height); } //foreach (Box b in Boxes) //{ // b.OffsetRectangle(lineBox, gap); //} }
/// <summary> /// Applies vertical alignment to the linebox /// </summary> /// <param name="g"></param> /// <param name="lineBox"></param> private static void ApplyVerticalAlignment(Graphics g, CssLineBox lineBox) { float baseline = float.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(IGraphics g, CssLineBox line) { if (line.Words.Count == 0) return; CssRect lastWord = line.Words[line.Words.Count - 1]; float right = line.OwnerBox.ActualRight - line.OwnerBox.ActualPaddingRight - line.OwnerBox.ActualBorderRightWidth; float 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) { RectangleF r = b.Rectangles[line]; b.Rectangles[line] = new RectangleF(r.X + diff, r.Y, r.Width, r.Height); } } }
/// <summary> /// Recursively flows the content of the box using the inline model /// </summary> /// <param name="g">Device Info</param> /// <param name="blockbox">Blockbox that contains the text flow</param> /// <param name="box">Current box to flow its content</param> /// <param name="maxright">Maximum reached right</param> /// <param name="linespacing">Space to use between rows of text</param> /// <param name="startx">x starting coordinate for when breaking lines of text</param> /// <param name="line">Current linebox being used</param> /// <param name="curx">Current x coordinate that will be the left of the next word</param> /// <param name="cury">Current y coordinate that will be the top of the next word</param> /// <param name="maxbottom">Maximum bottom reached so far</param> private static void FlowBox(Graphics g, CssBox blockbox, CssBox box, float maxright, float linespacing, float startx, ref CssLineBox line, ref float curx, ref float cury, ref float maxbottom) { var startY = cury; box.FirstHostingLineBox = line; foreach (CssBox b in box.Boxes) { float leftspacing = b.ActualMarginLeft + b.ActualBorderLeftWidth + b.ActualPaddingLeft; float rightspacing = b.ActualMarginRight + b.ActualBorderRightWidth + b.ActualPaddingRight; b.RectanglesReset(); b.MeasureWordsSize(g); curx += leftspacing; if (b.Words.Count > 0) { // fix word space for first word in inline tag if (!b.Words[0].IsImage && b.Words[0].HasSpaceBefore && b.Display == CssConstants.Inline) { var sib = DomUtils.GetPreviousContainingBlockSibling(b); if (sib != null && sib.Display == CssConstants.Inline) curx += b.ActualWordSpacing; } foreach (var word in b.Words) { if (maxbottom - cury < box.ActualLineHeight) maxbottom += box.ActualLineHeight - (maxbottom - cury); if ((b.WhiteSpace != CssConstants.Nowrap && curx + word.Width + rightspacing > maxright) || word.IsLineBreak) { curx = startx; cury = maxbottom + linespacing; line = new CssLineBox(blockbox); if (word.IsImage || word.Equals(b.FirstWord)) { curx += leftspacing; } } // atodo: why? line.ReportExistanceOf(word); word.Left = curx; // -word.LastMeasureOffset.X + 1; word.Top = cury; // - word.LastMeasureOffset.Y; curx = word.Right + (word.IsImage ? b.ActualWordSpacing : 0); // +word.SpacesAfterWidth; maxbottom = Math.Max(maxbottom, word.Bottom); //+ (word.IsImage ? topspacing + bottomspacing : 0)); } // fix word space for last word that is right before inline tag var lastWord = b.Words[b.Words.Count-1]; if (!lastWord.IsImage && !lastWord.HasSpaceAfter) { var next = DomUtils.GetNextSibling(b); if (next == null || next.Display == CssConstants.Inline) curx -= b.ActualWordSpacing + CssUtils.GetWordEndWhitespace(b.ActualFont); } } else { FlowBox(g, blockbox, b, maxright, linespacing, startx, ref line, ref curx, ref cury, ref maxbottom); } curx += rightspacing; } // handle height setting if(maxbottom - startY < box.ActualHeight) { maxbottom += box.ActualHeight - (maxbottom - startY); } // handle box that is only a whitespace if (box.Text == " " && !box.IsImage && box.IsInline && box.Boxes.Count == 0 && box.Words.Count == 0) { curx += box.ActualWordSpacing; } box.LastHostingLineBox = line; }
/// <summary> /// Offsets the rectangle of the specified linebox by the specified gap, /// and goes deep for rectangles of children in that linebox. /// </summary> /// <param name="lineBox"></param> /// <param name="gap"></param> internal void OffsetRectangle(CssLineBox lineBox, float gap) { if (Rectangles.ContainsKey(lineBox)) { var r = Rectangles[lineBox]; Rectangles[lineBox] = new RectangleF(r.X, r.Y + gap, r.Width, r.Height); } }
/// <summary> /// Get css word box under the given sub-tree at the given x,y location.<br/> /// the location must be in correct scroll offset. /// </summary> /// <param name="lineBox">the line box to search in</param> /// <param name="location">the location to find the box at</param> /// <returns>css word box if exists or null</returns> public static CssBoxWord GetCssBoxWord(CssLineBox lineBox, Point location) { foreach (var rect in lineBox.Rectangles) { foreach (var word in rect.Key.Words) { if (word.Bounds.Contains(location)) { return word; } } } return null; }
/// <summary> /// Recursively flows the content of the box using the inline model /// </summary> /// <param name="g">Device Info</param> /// <param name="blockbox">Blockbox that contains the text flow</param> /// <param name="box">Current box to flow its content</param> /// <param name="maxright">Maximum reached right</param> /// <param name="linespacing">Space to use between rows of text</param> /// <param name="startx">x starting coordinate for when breaking lines of text</param> /// <param name="line">Current linebox being used</param> /// <param name="curx">Current x coordinate that will be the left of the next word</param> /// <param name="cury">Current y coordinate that will be the top of the next word</param> /// <param name="maxbottom">Maximum bottom reached so far</param> private static void FlowBox(Graphics g, CssBox blockbox, CssBox box, float maxright, float linespacing, float startx, ref CssLineBox line, ref float curx, ref float cury, ref float maxbottom) { var startY = cury; box.FirstHostingLineBox = line; foreach (CssBox b in box.Boxes) { float leftspacing = b.ActualMarginLeft + b.ActualBorderLeftWidth + b.ActualPaddingLeft; float rightspacing = b.ActualMarginRight + b.ActualBorderRightWidth + b.ActualPaddingRight; b.RectanglesReset(); b.MeasureWordsSize(g); curx += leftspacing; if (b.Words.Count > 0) { // fix word space for first word in inline tag if (!b.Words[0].IsImage && b.Words[0].HasSpaceBefore && b.Display == CssConstants.Inline) { var sib = DomUtils.GetPreviousContainingBlockSibling(b); if (sib != null && sib.Display == CssConstants.Inline) { curx += b.ActualWordSpacing; } } foreach (var word in b.Words) { if (maxbottom - cury < box.ActualLineHeight) { maxbottom += box.ActualLineHeight - (maxbottom - cury); } if ((b.WhiteSpace != CssConstants.Nowrap && curx + word.Width + rightspacing > maxright) || word.IsLineBreak) { curx = startx; cury = maxbottom + linespacing; line = new CssLineBox(blockbox); if (word.IsImage || word.Equals(b.FirstWord)) { curx += leftspacing; } } // atodo: why? line.ReportExistanceOf(word); word.Left = curx; // -word.LastMeasureOffset.X + 1; word.Top = cury; // - word.LastMeasureOffset.Y; curx = word.Right + (word.IsImage ? b.ActualWordSpacing : 0); // +word.SpacesAfterWidth; maxbottom = Math.Max(maxbottom, word.Bottom); //+ (word.IsImage ? topspacing + bottomspacing : 0)); } // fix word space for last word that is right before inline tag var lastWord = b.Words[b.Words.Count - 1]; if (!lastWord.IsImage && !lastWord.HasSpaceAfter) { var next = DomUtils.GetNextSibling(b); if (next == null || next.Display == CssConstants.Inline) { curx -= b.ActualWordSpacing + CssUtils.GetWordEndWhitespace(b.ActualFont); } } } else { FlowBox(g, blockbox, b, maxright, linespacing, startx, ref line, ref curx, ref cury, ref maxbottom); } curx += rightspacing; } // handle height setting if (maxbottom - startY < box.ActualHeight) { maxbottom += box.ActualHeight - (maxbottom - startY); } // handle box that is only a whitespace if (box.Text == " " && !box.IsImage && box.IsInline && box.Boxes.Count == 0 && box.Words.Count == 0) { curx += box.ActualWordSpacing; } box.LastHostingLineBox = line; }
/// <summary> /// Recursively flows the content of the box using the inline model /// </summary> /// <param name="g">Device Info</param> /// <param name="blockbox">Blockbox that contains the text flow</param> /// <param name="box">Current box to flow its content</param> /// <param name="limitRight">Maximum reached right</param> /// <param name="linespacing">Space to use between rows of text</param> /// <param name="startx">x starting coordinate for when breaking lines of text</param> /// <param name="line">Current linebox being used</param> /// <param name="curx">Current x coordinate that will be the left of the next word</param> /// <param name="cury">Current y coordinate that will be the top of the next word</param> /// <param name="maxRight">Maximum right reached so far</param> /// <param name="maxbottom">Maximum bottom reached so far</param> private static void FlowBox(Graphics g, CssBox blockbox, CssBox box, float limitRight, float linespacing, float startx, ref CssLineBox line, ref float curx, ref float cury, ref float maxRight, ref float maxbottom) { var startY = cury; box.FirstHostingLineBox = line; var localCurx = curx; var localMaxRight = maxRight; var localmaxbottom = maxbottom; foreach (CssBox b in box.Boxes) { float leftspacing = b.Position != CssConstants.Absolute ? b.ActualMarginLeft + b.ActualBorderLeftWidth + b.ActualPaddingLeft : 0; float rightspacing = b.Position != CssConstants.Absolute ? b.ActualMarginRight + b.ActualBorderRightWidth + b.ActualPaddingRight : 0; b.RectanglesReset(); b.MeasureWordsSize(g); curx += leftspacing; if (b.Words.Count > 0) { // fix word space for first word in inline tag if (!b.Words[0].IsImage && b.Words[0].HasSpaceBefore && b.IsInline) { var sib = DomUtils.GetPreviousContainingBlockSibling(b); if (sib != null && sib.IsInline) { curx += b.ActualWordSpacing; } } foreach (var word in b.Words) { if (maxbottom - cury < box.ActualLineHeight) { maxbottom += box.ActualLineHeight - (maxbottom - cury); } if ((b.WhiteSpace != CssConstants.Nowrap && b.WhiteSpace != CssConstants.Pre && curx + word.Width + rightspacing > limitRight) || word.IsLineBreak) { curx = startx; // handle if line is wrapped for the first text element where parent has left margin\padding if (b == box.Boxes[0] && word == b.Words[0] && !word.IsLineBreak) { curx += box.ActualMarginLeft + box.ActualBorderLeftWidth + box.ActualPaddingLeft; } cury = maxbottom + linespacing; line = new CssLineBox(blockbox); if (word.IsImage || word.Equals(b.FirstWord)) { curx += leftspacing; } } line.ReportExistanceOf(word); word.Left = curx; word.Top = cury; curx = word.Left + word.FullWidth - word.LastMeasureOffset.X / 2; maxRight = Math.Max(maxRight, word.Right); maxbottom = Math.Max(maxbottom, word.Bottom); if (b.Position != CssConstants.Absolute) { } else { word.Left += box.ActualMarginLeft; word.Top += box.ActualMarginTop; } } } else { FlowBox(g, blockbox, b, limitRight, linespacing, startx, ref line, ref curx, ref cury, ref maxRight, ref maxbottom); } curx += rightspacing; } // handle height setting if (maxbottom - startY < box.ActualHeight) { maxbottom += box.ActualHeight - (maxbottom - startY); } // handle box that is only a whitespace if (box.Text != null && box.Text.IsWhitespace() && !box.IsImage && box.IsInline && box.Boxes.Count == 0 && box.Words.Count == 0) { curx += box.ActualWordSpacing; } // hack to support specific absolute possition elements if (box.Position == CssConstants.Absolute) { curx = localCurx; maxRight = localMaxRight; maxbottom = localmaxbottom; AdjustAbsolutePosition(box, 0, 0); } box.LastHostingLineBox = line; }
/// <summary> /// Applies right to left direction to words /// </summary> /// <param name="blockBox"></param> /// <param name="lineBox"></param> private static void ApplyRightToLeft(CssBox blockBox, CssLineBox lineBox) { if( blockBox.Direction == CssConstants.Rtl ) { ApplyRightToLeftOnLine(lineBox); } else { foreach(var box in lineBox.RelatedBoxes) { if (box.Direction == CssConstants.Rtl) { ApplyRightToLeftOnSingleBox(lineBox, box); } } } }