arklumpus / VectSharp

A light library for C# vector graphics
GNU Lesser General Public License v3.0
240 stars 22 forks source link

drawing a dash using TextBaselines.Top #72

Closed bknoll22 closed 1 month ago

bknoll22 commented 2 months ago

Hello, It is very possible this is intended behavior, in which case I'm hoping for some direction on how to make it work for us.

My input into the library assumes the y coordinates are the top of the text being drawn. So we use TextBaselines.Top. I have a concept in my code where I draw some text, and then I draw an Em Dash (U+2014) using the same x,y coordinates to create a "strike through" through the text.

I was hoping this would draw the dash through the middle of the text, however it is currently drawing the dash at the top of the text. I'm assuming it is because TextBaselines.Top is using the height of the highest glyph being drawn. Since it is only a dash, there isn't very much height. If the dash is included with text, it is drawn in the middle of the line as expected.

Any suggestions on how to make it work to draw a dash through the middle of the text?

arklumpus commented 2 months ago

Hi! I assume you have a reason for not simply drawing a line through the text (using Graphics.StrokePath).

You're correct that this is the intended behaviour when using TextBaselines.Top; the "correct" way to ensure that the em dash is drawn at the normal position would be to use TextBaselines.Baseline (for both the text and the em dash).

You can "convert" between the different baseline coordinates using the values returned by the Font.MeasureTextAdvanced method. In particular, if the baseline is at $b$, the top $t = b -$ Top.

Here are four examples (click to expand the source code). ```CSharp using VectSharp; Page pag = new Page(140, 100); Graphics gpr = pag.Graphics; Font font = new Font(FontFamily.ResolveFontFamily(FontFamily.StandardFontFamilies.Courier), 12); string testText = "This is some text"; string emDashText = new string('\u2014', testText.Length); Font.DetailedFontMetrics textMetrics = font.MeasureTextAdvanced(testText); Font.DetailedFontMetrics emDashMetrics = font.MeasureTextAdvanced(emDashText); // Easiest, specify the baseline coordinates rather than the top: Point textPosition1 = new Point(10, 20); gpr.FillText(textPosition1, testText, font, Colours.Black, textBaseline: TextBaselines.Baseline); gpr.FillText(textPosition1, emDashText, font, Colours.Red, textBaseline: TextBaselines.Baseline); // If you really want to specify the top coordinates: Point textPosition2 = new Point(10, 40); // Compute the position of the baseline, given the position of the top. double textBaseline2 = textPosition2.Y + textMetrics.Top; // For the text to be correctly aligned, the baselines must be at the same position. double emDashBaseline2 = textBaseline2; // Compute the position of the top of the em dash, given the position of its baseline. double emDashTop2 = emDashBaseline2 - emDashMetrics.Top; Point emDashPosition2 = new Point(textPosition2.X, emDashTop2); gpr.FillText(textPosition2, testText, font, Colours.Black); // Default baseline is top. gpr.FillText(emDashPosition2, emDashText, font, Colours.Red); // Better solution: just draw a line instead of an em dash. Point textPosition3 = new Point(10, 60); gpr.FillText(textPosition3, testText, font, Colours.Black); // Default baseline is top. // Use the font's underline thickness to determine how thick the line should be. // You could also simply use a fixed value. double lineThickness = font.FontFamily.TrueTypeFile.Get1000EmUnderlineThickness() * font.FontSize / 1000; // Left side of the text. double strokeLeft = textPosition3.X; // Right side of the text. double strokeRight = strokeLeft + textMetrics.Width; // font.Ascent is the height of the tallest glyphs in the font. double strokeY = textPosition3.Y + textMetrics.Top - font.Ascent * 0.5; // Create the path. GraphicsPath strokePath = new GraphicsPath().MoveTo(strokeLeft, strokeY).LineTo(strokeRight, strokeY); // Stroke the line. gpr.StrokePath(strokePath, Colours.Red, lineThickness); // Emulate the em dash approach using a line. Point textPosition4 = new Point(10, 80); gpr.FillText(textPosition4, testText, font, Colours.Black); // Default baseline is top. // Use the em dash thickness to determine the line thickness. // The problem is that emDashMetrics.Height extends from the top to the baseline. Therefore, // to measure the actual height of the dash, we need to make it "forget" that it was text by // converting it to a GraphicsPath. double lineThickness4 = new GraphicsPath().AddText(0, 0, "\u2014", font).GetBounds().Size.Height; // Left side of the text. double strokeLeft4 = textPosition4.X; // Right side of the text. double strokeRight4 = strokeLeft4 + textMetrics.Width; // Use the em dash metrics to determine the position of the line. double strokeY4 = textPosition4.Y + textMetrics.Top - emDashMetrics.Top + lineThickness4 * 0.5; // Create the path. GraphicsPath strokePath4 = new GraphicsPath().MoveTo(strokeLeft4, strokeY4).LineTo(strokeRight4, strokeY4); // Stroke the line. gpr.StrokePath(strokePath4, Colours.Red, lineThickness4); ```

test

Note that examples 3 and 4 also work with proportional fonts, while in the first two examples the length of the strikethrough will not be correct in that case (try changing from Courier to Helvetica to see the difference).

You can read more about text positioning in the manual (e.g., depending on how accurate you need to be, you may want to take left- and right-side bearings into account).

Let me know if you have any more questions!

bknoll22 commented 1 month ago

Thank you for the response and the additional info!