Skip to content

Commit

Permalink
Avoid generating zero-width dashes for Cairo
Browse files Browse the repository at this point in the history
When the line cap is square this still draws a single square with the brush width, so this isn't a no-op like the butt line cap type.
Instead, we avoid this by cycling the space to the end of the pattern and then offsetting where the pattern starts drawing from.

Bug: 1959032
  • Loading branch information
cameronwhite committed Dec 24, 2022
1 parent 5111d34 commit 0a43b91
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 25 deletions.
45 changes: 32 additions & 13 deletions Pinta.Core/Extensions/CairoExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1988,12 +1988,17 @@ public enum ExtendedOperators
/// </summary>
/// <param name="dash_pattern">The dash pattern string.</param>
/// <param name="brush_width">The width of the brush.</param>
/// <returns>The cairo dash pattern.</returns>
public static double[] CreateDashPattern (string dash_pattern, double brush_width)
{
// An empty cairo pattern just draws a normal line.
if (string.IsNullOrEmpty (dash_pattern))
return Array.Empty<double> ();
/// <param name="dash_list">The Cairo dash pattern.</param>
/// <param name="offset">The offset into the dash pattern to begin drawing from.</param>
public static void CreateDashPattern (string dash_pattern, double brush_width, out double[] dash_list, out double offset)
{
// An empty cairo pattern or a pattern with no dashes just draws a normal line.
// Cairo draws a normal line when the dash list is empty.
if (!dash_pattern.Contains ('-')) {
dash_list = Array.Empty<double> ();
offset = 0.0;
return;
}

var dashes = new List<double> ();

Expand All @@ -2012,26 +2017,40 @@ public static double[] CreateDashPattern (string dash_pattern, double brush_widt
dashes.Add ((double) count);
}

// The cairo pattern starts with a dash, so if the string pattern
// started with a space we need to add a zero-width dash.
if (!dash_pattern.StartsWith ('-'))
dashes.Insert (0, 0.0);

// The cairo pattern must have an even number of dash and space sequences to loop,
// so add a zero length space if the string pattern ended with a dash.
if (dash_pattern.EndsWith ('-'))
dashes.Add (0.0);

// The cairo pattern starts with a dash, so if the string pattern
// started with a space we need to add a zero-width dash.
// However, we can't draw a zero-width dash if the line cap is square, so instead
// we need to shift the space to the end of the pattern and then use the offset parameter
// to start drawing from there.
offset = 0.0;
if (!dash_pattern.StartsWith ('-')) {
double space = dashes[0];
dashes.RemoveAt (0);
offset = dashes.Sum ();

// From above, the dash pattern must already have a space at the end so
// we can just increase its size.
// The list is non-empty since patterns containing only a space result in an early exit.
dashes[dashes.Count - 1] += space;
}

// Each dash / space follows the brush width.
return dashes.Select (x => x * brush_width).ToArray ();
dash_list = dashes.Select (x => x * brush_width).ToArray ();
offset *= brush_width;
}

/// <summary>
/// Sets the dash pattern from a string (see CreateDashPattern()).
/// </summary>
public static void SetDashFromString (this Context context, string dash_pattern, double brush_width)
{
context.SetDash (CreateDashPattern (dash_pattern, brush_width), 0.0);
CreateDashPattern (dash_pattern, brush_width, out var dashes, out var offset);
context.SetDash (dashes, offset);
}
}
}
27 changes: 15 additions & 12 deletions tests/Pinta.Core.Tests/DashPatternTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ namespace Pinta.Core.Tests
[TestFixture]
class DashPatternTest
{
[TestCase ("", ExpectedResult = new double[] { })]
[TestCase ("-", ExpectedResult = new[] { 3.0, 0.0 })]
[TestCase (" ", ExpectedResult = new[] { 0.0, 3.0 })]
[TestCase (" -", ExpectedResult = new[] { 0.0, 3.0, 3.0, 0.0 })]
[TestCase ("-- ", ExpectedResult = new[] { 6.0, 3.0 })]
[TestCase (" --", ExpectedResult = new[] { 0.0, 3.0, 6.0, 0.0 })]
[TestCase (" -", ExpectedResult = new[] { 0.0, 6.0, 3.0, 0.0 })]
[TestCase ("$ !-", ExpectedResult = new[] { 0.0, 9.0, 3.0, 0.0 })]
[TestCase (" - --", ExpectedResult = new[] { 0.0, 3.0, 3.0, 3.0, 6.0, 0.0 })]
[TestCase (" - - --------", ExpectedResult = new[] { 0.0, 3.0, 3.0, 3.0, 3.0, 3.0, 24.0, 0.0 })]
public double[] CreateDashPattern (string pattern)
[TestCase ("", new double[] { }, 0.0)]
[TestCase ("-", new[] { 3.0, 0.0 }, 0.0)]
[TestCase (" ", new double[] { }, 0.0)]
[TestCase (" -", new[] { 3.0, 3.0 }, 3.0)]
[TestCase ("- -", new[] { 3.0, 3.0, 3.0, 0.0 }, 0.0)]
[TestCase ("-- ", new[] { 6.0, 3.0 }, 0.0)]
[TestCase (" --", new[] { 6.0, 3.0 }, 6.0)]
[TestCase (" -", new[] { 3.0, 6.0 }, 3.0)]
[TestCase ("$ !-", new[] { 3.0, 9.0 }, 3.0)]
[TestCase (" - --", new[] { 3.0, 3.0, 6.0, 3.0 }, 12.0)]
[TestCase (" - - --------", new[] { 3.0, 3.0, 3.0, 3.0, 24.0, 3.0 }, 36.0)]
public void CreateDashPattern (string pattern, double[] expected_dashes, double expected_offset)
{
return CairoExtensions.CreateDashPattern (pattern, 3.0);
CairoExtensions.CreateDashPattern (pattern, 3.0, out var dashes, out var offset);
Assert.AreEqual (dashes, expected_dashes);
Assert.AreEqual (offset, expected_offset);
}
}
}

0 comments on commit 0a43b91

Please sign in to comment.