From 37e455d1ceec984db52e35f87c443dc795c9ccc9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:40:32 +0000 Subject: [PATCH 1/3] Initial plan From e3da2851d65521fe3596fd959da3d28e1278613a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:56:17 +0000 Subject: [PATCH 2/3] Add line spacing (LineHeight) support to designer UI and rendering pipeline Co-authored-by: majorsilence <656288+majorsilence@users.noreply.github.com> --- RdlDesign/FontCtl.cs | 97 +++++++++++++++++++- RdlDesign/Resources/Strings.Designer.cs | 9 ++ RdlDesign/Resources/Strings.resx | 3 + RdlDesign/StyleTextCtl.cs | 97 +++++++++++++++++++- RdlEngine/PageDrawing.cs | 112 ++++++++++++++++++++++- RdlEngine/Render/RenderPdf_iTextSharp.cs | 12 ++- 6 files changed, 316 insertions(+), 14 deletions(-) diff --git a/RdlDesign/FontCtl.cs b/RdlDesign/FontCtl.cs index e6f40239..7b8f5b1b 100644 --- a/RdlDesign/FontCtl.cs +++ b/RdlDesign/FontCtl.cs @@ -20,7 +20,7 @@ internal class FontCtl : System.Windows.Forms.UserControl, IProperty private List _ReportItems; private DesignXmlDraw _Draw; private bool fHorzAlign, fFormat, fDirection, fWritingMode, fTextDecoration; - private bool fColor, fVerticalAlign, fFontStyle, fFontWeight, fFontSize, fFontFamily; + private bool fColor, fVerticalAlign, fFontStyle, fFontWeight, fFontSize, fFontFamily, fLineHeight; private System.Windows.Forms.Label label4; private System.Windows.Forms.Label label5; private System.Windows.Forms.Label label6; @@ -57,6 +57,9 @@ internal class FontCtl : System.Windows.Forms.UserControl, IProperty private System.Windows.Forms.Button bVertical; private System.Windows.Forms.Button bWrtMode; private System.Windows.Forms.Button bFormat; + private System.Windows.Forms.Label lblLineHeight; + private System.Windows.Forms.ComboBox cbLineHeight; + private System.Windows.Forms.Button bLineHeight; /// /// Required designer variable. /// @@ -71,6 +74,9 @@ public FontCtl(DesignXmlDraw dxDraw, string[] names, List styles) // This call is required by the Windows.Forms Form Designer. InitializeComponent(); + // Add line height controls programmatically + InitLineHeightControls(); + // Initialize form using the style node values InitTextStyles(); } @@ -100,6 +106,7 @@ private void InitTextStyles() string sFormat=""; string sDirection="LTR"; string sWritingMode="lr-tb"; + string sLineHeight=""; foreach (XmlNode lNode in sNode) { if (lNode.NodeType != XmlNodeType.Element) @@ -139,6 +146,9 @@ private void InitTextStyles() case "WritingMode": sWritingMode = lNode.InnerText; break; + case "LineHeight": + sLineHeight = lNode.InnerText; + break; } } @@ -159,9 +169,10 @@ private void InitTextStyles() this.cbFormat.Text = sFormat; this.cbDirection.Text = sDirection; this.cbWritingMode.Text = sWritingMode; + this.cbLineHeight.Text = sLineHeight; fHorzAlign = fFormat = fDirection = fWritingMode = fTextDecoration = - fColor = fVerticalAlign = fFontStyle = fFontWeight = fFontSize = fFontFamily = false; + fColor = fVerticalAlign = fFontStyle = fFontWeight = fFontSize = fFontFamily = fLineHeight = false; return; } @@ -181,6 +192,47 @@ protected override void Dispose( bool disposing ) base.Dispose( disposing ); } + private void InitLineHeightControls() + { + // Line Height label + lblLineHeight = new System.Windows.Forms.Label(); + lblLineHeight.Text = "Line Height"; + lblLineHeight.Location = new System.Drawing.Point(16, 222); + lblLineHeight.Size = new System.Drawing.Size(80, 16); + lblLineHeight.TabIndex = 30; + lblLineHeight.Name = "lblLineHeight"; + + // Line Height combo box + cbLineHeight = new System.Windows.Forms.ComboBox(); + cbLineHeight.Location = new System.Drawing.Point(104, 219); + cbLineHeight.Size = new System.Drawing.Size(272, 21); + cbLineHeight.TabIndex = 31; + cbLineHeight.Name = "cbLineHeight"; + cbLineHeight.Items.AddRange(new object[] { + "", "8pt", "9pt", "10pt", "11pt", "12pt", "14pt", "16pt", "18pt", + "20pt", "22pt", "24pt", "26pt", "28pt", "36pt", "48pt", "72pt"}); + cbLineHeight.TextChanged += new System.EventHandler(this.cbLineHeight_TextChanged); + + // Line Height expression button + bLineHeight = new System.Windows.Forms.Button(); + bLineHeight.Location = new System.Drawing.Point(384, 222); + bLineHeight.Size = new System.Drawing.Size(22, 16); + bLineHeight.TabIndex = 32; + bLineHeight.Name = "bLineHeight"; + bLineHeight.Tag = "lineheight"; + bLineHeight.Text = "fx"; + bLineHeight.Font = new System.Drawing.Font("Arial", 8.25f, System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Italic); + bLineHeight.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + bLineHeight.Click += new System.EventHandler(this.bExpr_Click); + + // Expand the control to fit the new row + this.Size = new System.Drawing.Size(this.Width, 260); + + this.Controls.Add(lblLineHeight); + this.Controls.Add(cbLineHeight); + this.Controls.Add(bLineHeight); + } + #region Component Designer generated code /// /// Required method for Designer support - do not modify @@ -573,6 +625,19 @@ public bool IsValid() } } + if (fLineHeight && cbLineHeight.Text.Trim().Length > 0) + { + try + { + if (!this.cbLineHeight.Text.Trim().StartsWith("=")) + DesignerUtility.ValidateSize(this.cbLineHeight.Text, false, false); + } + catch (Exception e) + { + MessageBox.Show(e.Message, Strings.FontCtl_Show_InvalidLineHeight); + return false; + } + } return true; } @@ -587,7 +652,7 @@ public void Apply() ApplyChanges(riNode); fHorzAlign = fFormat = fDirection = fWritingMode = fTextDecoration = - fColor = fVerticalAlign = fFontStyle = fFontWeight = fFontSize = fFontFamily = false; + fColor = fVerticalAlign = fFontStyle = fFontWeight = fFontSize = fFontFamily = fLineHeight = false; } public void ApplyChanges(XmlNode node) @@ -639,6 +704,24 @@ public void ApplyChanges(XmlNode node) _Draw.SetElement(sNode, "Direction", cbDirection.Text); if (fWritingMode) _Draw.SetElement(sNode, "WritingMode", cbWritingMode.Text); + if (fLineHeight) + { + if (cbLineHeight.Text.Trim().Length == 0) + _Draw.RemoveElement(sNode, "LineHeight"); + else + { + float lh = DesignXmlDraw.GetSize(cbLineHeight.Text); + if (lh <= 0) + lh = DesignXmlDraw.GetSize(cbLineHeight.Text + "pt"); // Try assuming pt + if (lh > 0) + { + string rs = string.Format(NumberFormatInfo.InvariantInfo, "{0:0.#}pt", lh); + _Draw.SetElement(sNode, "LineHeight", rs); + } + else + _Draw.SetElement(sNode, "LineHeight", cbLineHeight.Text); // expression + } + } return; } @@ -797,6 +880,11 @@ private void cbFormat_TextChanged(object sender, System.EventArgs e) fFormat = true; } + private void cbLineHeight_TextChanged(object sender, System.EventArgs e) + { + fLineHeight = true; + } + private void bExpr_Click(object sender, System.EventArgs e) { Button b = sender as Button; @@ -840,6 +928,9 @@ private void bExpr_Click(object sender, System.EventArgs e) case "format": c = cbFormat; break; + case "lineheight": + c = cbLineHeight; + break; } if (c == null) diff --git a/RdlDesign/Resources/Strings.Designer.cs b/RdlDesign/Resources/Strings.Designer.cs index 706579c7..ab04920d 100644 --- a/RdlDesign/Resources/Strings.Designer.cs +++ b/RdlDesign/Resources/Strings.Designer.cs @@ -1259,6 +1259,15 @@ internal static string FontCtl_Show_InvalidFontSize { } } + /// + /// Looks up a localized string similar to Invalid Line Height. + /// + internal static string FontCtl_Show_InvalidLineHeight { + get { + return ResourceManager.GetString("FontCtl_Show_InvalidLineHeight", resourceCulture); + } + } + /// /// Looks up a localized string similar to Grid. /// diff --git a/RdlDesign/Resources/Strings.resx b/RdlDesign/Resources/Strings.resx index 3854b95f..baad1846 100644 --- a/RdlDesign/Resources/Strings.resx +++ b/RdlDesign/Resources/Strings.resx @@ -580,6 +580,9 @@ XML in error: Invalid Font Size + + Invalid Line Height + Grid diff --git a/RdlDesign/StyleTextCtl.cs b/RdlDesign/StyleTextCtl.cs index 67555f9d..6493a636 100644 --- a/RdlDesign/StyleTextCtl.cs +++ b/RdlDesign/StyleTextCtl.cs @@ -18,7 +18,7 @@ internal class StyleTextCtl : System.Windows.Forms.UserControl, IProperty private DesignXmlDraw _Draw; private string _DataSetName; private bool fHorzAlign, fFormat, fDirection, fWritingMode, fTextDecoration; - private bool fColor, fVerticalAlign, fFontStyle, fFontWeight, fFontSize, fFontFamily; + private bool fColor, fVerticalAlign, fFontStyle, fFontWeight, fFontSize, fFontFamily, fLineHeight; private bool fValue; private System.Windows.Forms.Label label4; private System.Windows.Forms.Label label5; @@ -59,6 +59,9 @@ internal class StyleTextCtl : System.Windows.Forms.UserControl, IProperty private System.Windows.Forms.Button bVertical; private System.Windows.Forms.Button bWrtMode; private System.Windows.Forms.Button bFormat; + private System.Windows.Forms.Label lblLineHeight; + private System.Windows.Forms.ComboBox cbLineHeight; + private System.Windows.Forms.Button bLineHeight; /// /// Required designer variable. /// @@ -71,6 +74,9 @@ internal StyleTextCtl(DesignXmlDraw dxDraw, List styles, PropertyDialog // This call is required by the Windows.Forms Form Designer. InitializeComponent(); + // Add line height controls programmatically + InitLineHeightControls(); + // Initialize form using the style node values InitTextStyles(); myDialog.Shown += MyDialog_Shown; @@ -149,6 +155,7 @@ private void InitTextStyles() string sFormat=""; string sDirection="LTR"; string sWritingMode="lr-tb"; + string sLineHeight=""; foreach (XmlNode lNode in sNode) { if (lNode.NodeType != XmlNodeType.Element) @@ -188,6 +195,9 @@ private void InitTextStyles() case "WritingMode": sWritingMode = lNode.InnerText; break; + case "LineHeight": + sLineHeight = lNode.InnerText; + break; } } @@ -208,10 +218,11 @@ private void InitTextStyles() this.cbFormat.Text = sFormat; this.cbDirection.Text = sDirection; this.cbWritingMode.Text = sWritingMode; + this.cbLineHeight.Text = sLineHeight; fHorzAlign = fFormat = fDirection = fWritingMode = fTextDecoration = fColor = fVerticalAlign = fFontStyle = fFontWeight = fFontSize = fFontFamily = - fValue = false; + fLineHeight = fValue = false; return; } @@ -231,6 +242,47 @@ protected override void Dispose( bool disposing ) base.Dispose( disposing ); } + private void InitLineHeightControls() + { + // Line Height label + lblLineHeight = new System.Windows.Forms.Label(); + lblLineHeight.Text = "Line Height"; + lblLineHeight.Location = new System.Drawing.Point(16, 263); + lblLineHeight.Size = new System.Drawing.Size(80, 16); + lblLineHeight.TabIndex = 30; + lblLineHeight.Name = "lblLineHeight"; + + // Line Height combo box + cbLineHeight = new System.Windows.Forms.ComboBox(); + cbLineHeight.Location = new System.Drawing.Point(104, 260); + cbLineHeight.Size = new System.Drawing.Size(272, 21); + cbLineHeight.TabIndex = 31; + cbLineHeight.Name = "cbLineHeight"; + cbLineHeight.Items.AddRange(new object[] { + "", "8pt", "9pt", "10pt", "11pt", "12pt", "14pt", "16pt", "18pt", + "20pt", "22pt", "24pt", "26pt", "28pt", "36pt", "48pt", "72pt"}); + cbLineHeight.TextChanged += new System.EventHandler(this.cbLineHeight_TextChanged); + + // Line Height expression button + bLineHeight = new System.Windows.Forms.Button(); + bLineHeight.Location = new System.Drawing.Point(384, 263); + bLineHeight.Size = new System.Drawing.Size(22, 16); + bLineHeight.TabIndex = 32; + bLineHeight.Name = "bLineHeight"; + bLineHeight.Tag = "lineheight"; + bLineHeight.Text = "fx"; + bLineHeight.Font = new System.Drawing.Font("Arial", 8.25f, System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Italic); + bLineHeight.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + bLineHeight.Click += new System.EventHandler(this.bExpr_Click); + + // Expand the control to fit the new row + this.Size = new System.Drawing.Size(this.Width, this.Height + 30); + + this.Controls.Add(lblLineHeight); + this.Controls.Add(cbLineHeight); + this.Controls.Add(bLineHeight); + } + #region Component Designer generated code /// /// Required method for Designer support - do not modify @@ -670,6 +722,19 @@ public bool IsValid() } } + if (fLineHeight && cbLineHeight.Text.Trim().Length > 0) + { + try + { + if (!cbLineHeight.Text.Trim().StartsWith("=")) + DesignerUtility.ValidateSize(cbLineHeight.Text, false, false); + } + catch (Exception e) + { + MessageBox.Show(e.Message, Strings.FontCtl_Show_InvalidLineHeight); + return false; + } + } return true; } @@ -685,7 +750,7 @@ public void Apply() fHorzAlign = fFormat = fDirection = fWritingMode = fTextDecoration = fColor = fVerticalAlign = fFontStyle = fFontWeight = fFontSize = fFontFamily = - fValue = false; + fLineHeight = fValue = false; } public void ApplyChanges(XmlNode node) @@ -741,6 +806,24 @@ public void ApplyChanges(XmlNode node) _Draw.SetElement(sNode, "Direction", cbDirection.Text); if (fWritingMode) _Draw.SetElement(sNode, "WritingMode", cbWritingMode.Text); + if (fLineHeight) + { + if (cbLineHeight.Text.Trim().Length == 0) + _Draw.RemoveElement(sNode, "LineHeight"); + else + { + float lh = DesignXmlDraw.GetSize(cbLineHeight.Text); + if (lh <= 0) + lh = DesignXmlDraw.GetSize(cbLineHeight.Text + "pt"); // Try assuming pt + if (lh > 0) + { + string rs = string.Format(NumberFormatInfo.InvariantInfo, "{0:0.#}pt", lh); + _Draw.SetElement(sNode, "LineHeight", rs); + } + else + _Draw.SetElement(sNode, "LineHeight", cbLineHeight.Text); // expression + } + } return; } @@ -900,6 +983,11 @@ private void cbFormat_TextChanged(object sender, System.EventArgs e) fFormat = true; } + private void cbLineHeight_TextChanged(object sender, System.EventArgs e) + { + fLineHeight = true; + } + private void bExpr_Click(object sender, System.EventArgs e) { Button b = sender as Button; @@ -946,6 +1034,9 @@ private void bExpr_Click(object sender, System.EventArgs e) case "format": c = cbFormat; break; + case "lineheight": + c = cbLineHeight; + break; } if (c == null) diff --git a/RdlEngine/PageDrawing.cs b/RdlEngine/PageDrawing.cs index 5eda09ca..610cca41 100644 --- a/RdlEngine/PageDrawing.cs +++ b/RdlEngine/PageDrawing.cs @@ -808,13 +808,14 @@ private void DrawString(PageText pt, Graphics g, RectangleF r) else if (pt.NoClip) // request not to clip text { g.DrawString(pt.Text, drawFont, drawBrush, new PointF(r.Left, r.Top), drawFormat); - //HighlightString(g, pt, new RectangleF(r.Left, r.Top, float.MaxValue, float.MaxValue), - // drawFont, drawFormat); + } + else if (si.LineHeight > 0) + { + DrawStringWithLineHeight(g, pt.Text, drawFont, drawBrush, r2, drawFormat, si); } else { g.DrawString(pt.Text, drawFont, drawBrush, r2, drawFormat); - //HighlightString(g, pt, r2, drawFont, drawFormat); } //if (SelectTool) //{ @@ -833,5 +834,110 @@ private void DrawString(PageText pt, Graphics g, RectangleF r) } } + private void DrawStringWithLineHeight(Graphics g, string text, Font font, Brush brush, RectangleF r, StringFormat sf, StyleInfo si) + { + // Convert line height from points to current graphics units + float lineHeightUnits; + if (g.PageUnit == GraphicsUnit.Pixel) + lineHeightUnits = (si.LineHeight * g.DpiY) / 72f; + else + lineHeightUnits = si.LineHeight; + + var wrappedLines = GetWrappedLines(g, text, font, r.Width); + + // Calculate starting Y for vertical alignment + float totalHeight = wrappedLines.Count * lineHeightUnits; + float startY = r.Top; + switch (si.VerticalAlign) + { + case VerticalAlignEnum.Middle: + startY = r.Top + (r.Height - totalHeight) / 2f; + break; + case VerticalAlignEnum.Bottom: + startY = r.Top + r.Height - totalHeight; + break; + default: + break; + } + + StringFormat lineFormat = null; + try + { + lineFormat = (StringFormat)sf.Clone(); + lineFormat.LineAlignment = StringAlignment.Near; + lineFormat.FormatFlags |= StringFormatFlags.NoWrap; + + foreach (var line in wrappedLines) + { + if (startY >= r.Bottom) + break; + var lineRect = new RectangleF(r.Left, startY, r.Width, lineHeightUnits); + g.DrawString(line, font, brush, lineRect, lineFormat); + startY += lineHeightUnits; + } + } + finally + { + if (lineFormat != null) + lineFormat.Dispose(); + } + } + + private System.Collections.Generic.List GetWrappedLines(Graphics g, string text, Font font, float width) + { + var result = new System.Collections.Generic.List(); + if (string.IsNullOrEmpty(text)) + return result; + + // Split by explicit newlines first + var paragraphs = text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + + StringFormat measureFormat = null; + try + { + measureFormat = new StringFormat(StringFormatFlags.NoWrap | StringFormatFlags.MeasureTrailingSpaces); + + foreach (var para in paragraphs) + { + if (para.Length == 0) + { + result.Add(""); + continue; + } + + string[] words = para.Split(' '); + var currentLine = new System.Text.StringBuilder(); + + foreach (string word in words) + { + string candidate = currentLine.Length == 0 ? word : currentLine + " " + word; + SizeF size = g.MeasureString(candidate, font, int.MaxValue, measureFormat); + + if (size.Width > width && currentLine.Length > 0) + { + result.Add(currentLine.ToString()); + currentLine.Clear(); + currentLine.Append(word); + } + else + { + if (currentLine.Length > 0) + currentLine.Append(" "); + currentLine.Append(word); + } + } + + if (currentLine.Length > 0) + result.Add(currentLine.ToString()); + } + } + finally + { + if (measureFormat != null) + measureFormat.Dispose(); + } + return result; + } + } } diff --git a/RdlEngine/Render/RenderPdf_iTextSharp.cs b/RdlEngine/Render/RenderPdf_iTextSharp.cs index 761011b1..514b46ab 100644 --- a/RdlEngine/Render/RenderPdf_iTextSharp.cs +++ b/RdlEngine/Render/RenderPdf_iTextSharp.cs @@ -659,9 +659,11 @@ protected internal override void AddText(float x, float y, float height, float w { string text = sa[i]; float textwidth = bf.GetWidthPoint(text, si.FontSize); + // Use LineHeight for line spacing if specified, otherwise use FontSize + float lineSpacing = si.LineHeight > 0 ? si.LineHeight : si.FontSize; // Calculate the x positino float startX = x + si.PaddingLeft; // TODO: handle tb_rl - float startY = y + si.PaddingTop + (i * si.FontSize); // TODO: handle tb_rl + float startY = y + si.PaddingTop + (i * lineSpacing); // TODO: handle tb_rl int align = 0; if (si.WritingMode == WritingModeEnum.lr_tb) { @@ -700,17 +702,17 @@ protected internal override void AddText(float x, float y, float height, float w // calculate the middle of the region startY = y + si.PaddingTop + ((height - si.PaddingTop - si.PaddingBottom) / 2) - - (si.FontSize / 2); + (lineSpacing / 2); // now go up or down depending on which line if (sa.Length == 1) break; if (sa.Length % 2 == 0) // even number { - startY = startY - (((sa.Length / 2) - i) * si.FontSize) + (si.FontSize / 2); + startY = startY - (((sa.Length / 2) - i) * lineSpacing) + (lineSpacing / 2); } else { - startY = startY - (((sa.Length / 2) - i) * si.FontSize); + startY = startY - (((sa.Length / 2) - i) * lineSpacing); } break; @@ -718,7 +720,7 @@ protected internal override void AddText(float x, float y, float height, float w if (height <= 0) break; - startY = y + height - si.PaddingBottom - (si.FontSize * (sa.Length - i)); + startY = y + height - si.PaddingBottom - (lineSpacing * (sa.Length - i)); break; case VerticalAlignEnum.Top: default: From d6ff7067b5038d0d3454aac45e143273d8715527 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 14:01:30 +0000 Subject: [PATCH 3/3] Fix typo and make height expansion consistent between FontCtl and StyleTextCtl Co-authored-by: majorsilence <656288+majorsilence@users.noreply.github.com> --- RdlDesign/FontCtl.cs | 2 +- RdlEngine/Render/RenderPdf_iTextSharp.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RdlDesign/FontCtl.cs b/RdlDesign/FontCtl.cs index 7b8f5b1b..8cd91061 100644 --- a/RdlDesign/FontCtl.cs +++ b/RdlDesign/FontCtl.cs @@ -226,7 +226,7 @@ private void InitLineHeightControls() bLineHeight.Click += new System.EventHandler(this.bExpr_Click); // Expand the control to fit the new row - this.Size = new System.Drawing.Size(this.Width, 260); + this.Size = new System.Drawing.Size(this.Width, this.Height + 26); this.Controls.Add(lblLineHeight); this.Controls.Add(cbLineHeight); diff --git a/RdlEngine/Render/RenderPdf_iTextSharp.cs b/RdlEngine/Render/RenderPdf_iTextSharp.cs index 514b46ab..0a0619fc 100644 --- a/RdlEngine/Render/RenderPdf_iTextSharp.cs +++ b/RdlEngine/Render/RenderPdf_iTextSharp.cs @@ -661,7 +661,7 @@ protected internal override void AddText(float x, float y, float height, float w float textwidth = bf.GetWidthPoint(text, si.FontSize); // Use LineHeight for line spacing if specified, otherwise use FontSize float lineSpacing = si.LineHeight > 0 ? si.LineHeight : si.FontSize; - // Calculate the x positino + // Calculate the x position float startX = x + si.PaddingLeft; // TODO: handle tb_rl float startY = y + si.PaddingTop + (i * lineSpacing); // TODO: handle tb_rl int align = 0;