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) { box.FirstHostingLineBox = line; foreach (CssBox cssBox in box.Boxes) { float actualMarginLeft = cssBox.ActualMarginLeft + cssBox.ActualBorderLeftWidth + cssBox.ActualPaddingLeft; float actualMarginRight = cssBox.ActualMarginRight + cssBox.ActualBorderRightWidth + cssBox.ActualPaddingRight; float actualBorderTopWidth = cssBox.ActualBorderTopWidth; float actualPaddingTop = cssBox.ActualPaddingTop; float actualBorderBottomWidth = cssBox.ActualBorderBottomWidth; float single = cssBox.ActualPaddingTop; cssBox.RectanglesReset(); cssBox.MeasureWordsSize(g); curx = curx + actualMarginLeft; if (cssBox.Words.Count <= 0) { CssLayoutEngine.FlowBox(g, blockbox, cssBox, maxright, linespacing, startx, ref line, ref curx, ref cury, ref maxbottom); } else { foreach (CssBoxWord word in cssBox.Words) { if (cssBox.WhiteSpace != "nowrap" && curx + word.Width + actualMarginRight > maxright || word.IsLineBreak) { curx = startx; cury = maxbottom + linespacing; line = new CssLineBox(blockbox); if (word.IsImage || word.Equals(cssBox.FirstWord)) { curx = curx + actualMarginLeft; } } line.ReportExistanceOf(word); word.Left = curx; word.Top = cury; curx = word.Right; maxbottom = Math.Max(maxbottom, word.Bottom); CssLayoutEngine._lastTreatedWord = word; } } curx = curx + actualMarginRight; } box.LastHostingLineBox = line; }
/// <summary> /// Simplest alignment, just arrange words. /// </summary> /// <param name="g"></param> /// <param name="lineBox"></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 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; } var indent = lineBox.Equals(lineBox.OwnerBox.LineBoxes[0]) ? lineBox.OwnerBox.ActualTextIndent : 0f; var textSum = 0f; var words = 0f; var availWidth = lineBox.OwnerBox.ClientRectangle.Width - indent; #region Gather text sum foreach (var w in lineBox.Words) { textSum += w.Width; words += 1f; } #endregion if (words <= 0f) { return; //Avoid Zero division } var spacing = (availWidth - textSum) / words; //Spacing that will be used var curx = lineBox.OwnerBox.ClientLeft + indent; foreach (var 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> /// 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(); 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 CssLineBox(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 == CssConstants.Rtl) { ApplyRightToLeft(linebox); } //linebox.DrawRectangles(g); } blockBox.ActualBottom = maxBottom + blockBox.ActualPaddingBottom + blockBox.ActualBorderBottomWidth; }
private static void ApplyVerticalAlignment(Graphics g, CssLineBox lineBox) { float maxWordBottom = lineBox.GetMaxWordBottom() - CssLayoutEngine.GetDescent(lineBox.OwnerBox.ActualFont) - 2f; foreach (CssBox cssBox in new List <CssBox>(lineBox.Rectangles.Keys)) { CssLayoutEngine.GetAscent(cssBox.ActualFont); CssLayoutEngine.GetDescent(cssBox.ActualFont); string verticalAlign = cssBox.VerticalAlign; string str = verticalAlign; if (verticalAlign != null) { switch (str) { case "sub": { RectangleF item = lineBox.Rectangles[cssBox]; lineBox.SetBaseLine(g, cssBox, maxWordBottom + item.Height * 0.2f); continue; } case "super": { RectangleF rectangleF = lineBox.Rectangles[cssBox]; lineBox.SetBaseLine(g, cssBox, maxWordBottom - rectangleF.Height * 0.2f); continue; } case "text-top": case "text-bottom": case "top": case "bottom": case "middle": { continue; } } } lineBox.SetBaseLine(g, cssBox, maxWordBottom); } }
/// <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; //TODO: Check for floats 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; //Extra amount of spacing that should be applied to lines when breaking them. float lineSpacing = 0f; //First line box CssLineBox line = new CssLineBox(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 (CssLineBox linebox in blockBox.LineBoxes) { BubbleRectangles(blockBox, linebox); linebox.AssignRectanglesToBoxes(); ApplyAlignment(g, linebox); if (blockBox.Direction == CssConstants.Rtl) ApplyRightToLeft(linebox); //linebox.DrawRectangles(g); } blockBox.ActualBottom = maxBottom + blockBox.ActualPaddingBottom + blockBox.ActualBorderBottomWidth; }
/// <summary> /// Applies right alignment to the text on the linebox /// </summary> /// <param name="g"></param> /// <param name="lineBox"></param> private static void ApplyRightAlignment(Graphics g, CssLineBox line) { if (line.Words.Count == 0) { return; } var lastWord = line.Words[line.Words.Count - 1]; var right = line.OwnerBox.ActualRight - line.OwnerBox.ActualPaddingRight - line.OwnerBox.ActualBorderRightWidth; var diff = right - lastWord.Right - lastWord.LastMeasureOffset.X - lastWord.OwnerBox.ActualBorderRightWidth - lastWord.OwnerBox.ActualPaddingRight; if (diff <= 0) { return; } //if (line.OwnerBox.Direction == CssConstants.Rtl) //{ //} foreach (var word in line.Words) { word.Left += diff; } foreach (var b in line.Rectangles.Keys) { var r = b.Rectangles[line]; b.Rectangles[line] = new RectangleF(r.X + diff, r.Y, r.Width, r.Height); } }
/// <summary> /// Applies vertical alignment to the linebox /// </summary> /// <param name="g"></param> /// <param name="lineBox"></param> private static void ApplyVerticalAlignment(Graphics g, CssLineBox lineBox) { bool isTableCell = lineBox.OwnerBox.Display == CssConstants.TableCell; float baseline = lineBox.GetMaxWordBottom() - GetDescent(lineBox.OwnerBox.ActualFont) - 2; List<CssBox> boxes = new List<CssBox>(lineBox.Rectangles.Keys); foreach (CssBox b in boxes) { float ascent = GetAscent(b.ActualFont); float descent = GetDescent(b.ActualFont); //Important notes on http://www.w3.org/TR/CSS21/tables.html#height-layout switch (b.VerticalAlign) { case CssConstants.Sub: lineBox.SetBaseLine(g, b, baseline + lineBox.Rectangles[b].Height * .2f); break; case CssConstants.Super: lineBox.SetBaseLine(g, b, baseline - lineBox.Rectangles[b].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, b, baseline); break; } ////Graphic cues //g.FillRectangle(Brushes.Aqua, r.Left, r.Top, r.Width, ascent); //g.FillRectangle(Brushes.Yellow, r.Left, r.Top + ascent, r.Width, descent); //g.DrawLine(Pens.Fuchsia, r.Left, baseline, r.Right, baseline); } }
private static void ApplyLeftAlignment(Graphics g, CssLineBox 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)) { 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> /// Searches for the first word occourence inside the box, on the specified linebox /// </summary> /// <param name="b"></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> /// Applies right alignment to the text on the linebox /// </summary> /// <param name="g"></param> /// <param name="lineBox"></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> /// 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 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> /// 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> /// 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> /// 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) { 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; float topspacing = b.ActualBorderTopWidth + b.ActualPaddingTop; float bottomspacing = b.ActualBorderBottomWidth + b.ActualPaddingTop; b.RectanglesReset(); b.MeasureWordsSize(g); curx += leftspacing; if (b.Words.Count > 0) { #region Flow words foreach (CssBoxWord word in b.Words) { //curx += word.SpacesBeforeWidth; if ((b.WhiteSpace != CssConstants.Nowrap && curx + word.Width + rightspacing > maxright) || word.IsLineBreak) { #region Break line curx = startx; cury = maxbottom + linespacing; line = new CssLineBox(blockbox); if (word.IsImage || word.Equals(b.FirstWord)) { curx += leftspacing; } #endregion } line.ReportExistanceOf(word); word.Left = curx;// -word.LastMeasureOffset.X + 1; word.Top = cury;// - word.LastMeasureOffset.Y; curx = word.Right;// +word.SpacesAfterWidth; maxbottom = Math.Max(maxbottom, word.Bottom );//+ (word.IsImage ? topspacing + bottomspacing : 0)); _lastTreatedWord = word; } #endregion } else { FlowBox(g, blockbox, b, maxright, linespacing, startx,ref line, ref curx, ref cury, ref maxbottom); } curx += rightspacing; } 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="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) { 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; float topspacing = b.ActualBorderTopWidth + b.ActualPaddingTop; float bottomspacing = b.ActualBorderBottomWidth + b.ActualPaddingTop; b.RectanglesReset(); b.MeasureWordsSize(g); curx += leftspacing; if (b.Words.Count > 0) { #region Flow words foreach (CssBoxWord word in b.Words) { //curx += word.SpacesBeforeWidth; if ((b.WhiteSpace != CssConstants.Nowrap && curx + word.Width + rightspacing > maxright) || word.IsLineBreak) { #region Break line curx = startx; cury = maxbottom + linespacing; line = new CssLineBox(blockbox); if (word.IsImage || word.Equals(b.FirstWord)) { curx += leftspacing; } #endregion } line.ReportExistanceOf(word); word.Left = curx; // -word.LastMeasureOffset.X + 1; word.Top = cury; // - word.LastMeasureOffset.Y; curx = word.Right; // +word.SpacesAfterWidth; maxbottom = Math.Max(maxbottom, word.Bottom); //+ (word.IsImage ? topspacing + bottomspacing : 0)); _lastTreatedWord = word; } #endregion } else { FlowBox(g, blockbox, b, maxright, linespacing, startx, ref line, ref curx, ref cury, ref maxbottom); } curx += rightspacing; } box.LastHostingLineBox = line; }