Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions JianpuEditor.Tests/Rendering/PdfRestMeasureRenderingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System.Drawing;
using System.Runtime.Versioning;
using JianpuEditor.Rendering;
using JianpuEditor.Tests.Helpers;
using Xunit;

namespace JianpuEditor.Tests.Rendering
{
[SupportedOSPlatform("windows")]
public sealed class PdfRestMeasureRenderingTests
{
[Fact]
public void PdfExportOptions_LeaveRestOnlyMeasuresBlank()
{
Assert.True(ScoreLayoutOptions.PdfExport.LeaveRestOnlyMeasuresBlank);
Assert.False(ScoreLayoutOptions.Editor.LeaveRestOnlyMeasuresBlank);
Assert.False(ScoreLayoutOptions.Default.LeaveRestOnlyMeasuresBlank);
}

[Fact]
public void RenderPdfPageToBitmap_RestOnlyMeasure_LeavesMelodyRowBlank()
{
var restMeasure = ScoreTestHelper.Measure(
ScoreTestHelper.Rest(),
ScoreTestHelper.Rest(),
ScoreTestHelper.Rest(),
ScoreTestHelper.Rest());
var noteMeasure = ScoreTestHelper.Measure(
ScoreTestHelper.Note(1),
ScoreTestHelper.Note(2),
ScoreTestHelper.Note(3),
ScoreTestHelper.Note(4));
var score = ScoreTestHelper.CreateScore(restMeasure, noteMeasure);

using (var renderer = new JianpuRenderer())
{
var options = ScoreLayoutOptions.PdfExport;
var bitmap = renderer.RenderPdfPageToBitmap(
score,
ScoreLayoutOptions.PdfRenderWidth,
options,
new PdfPageSlice { FirstLineIndex = 0, LineCount = 1, PageNumber = 1, TotalPages = 1 });
var layouts = renderer.GetMeasureLayouts(score, ScoreLayoutOptions.PdfRenderWidth, options);
Assert.Equal(2, layouts.Count);

var restInk = CountDarkPixels(bitmap, MelodySample(layouts[0]));
var noteInk = CountDarkPixels(bitmap, MelodySample(layouts[1]));

Assert.True(noteInk > 80);
Assert.True(restInk < 20);
Assert.True(restInk < noteInk / 10);
}
}

[Fact]
public void RenderToBitmap_Editor_StillDrawsRestZeros()
{
var restMeasure = ScoreTestHelper.Measure(
ScoreTestHelper.Rest(),
ScoreTestHelper.Rest(),
ScoreTestHelper.Rest(),
ScoreTestHelper.Rest());
var score = ScoreTestHelper.CreateScore(restMeasure);

using (var renderer = new JianpuRenderer())
{
var bitmap = renderer.RenderToBitmap(score, 1280, ScoreLayoutOptions.Editor);
var layouts = renderer.GetMeasureLayouts(score, 1280, ScoreLayoutOptions.Editor);
var restInk = CountDarkPixels(bitmap, MelodySample(layouts[0]));
Assert.True(restInk > 80);
}
}

private static Rectangle MelodySample(JianpuRenderer.MeasureLayout layout)
{
return new Rectangle(
layout.X + 8,
layout.BlockTop + 16,
Math.Max(8, layout.Width - 16),
JianpuRenderer.MelodyRowHeight - 28);
}

private static int CountDarkPixels(Bitmap bitmap, Rectangle bounds)
{
var count = 0;
var maxX = Math.Min(bitmap.Width, bounds.Right);
var maxY = Math.Min(bitmap.Height, bounds.Bottom);
var startX = Math.Max(0, bounds.Left);
var startY = Math.Max(0, bounds.Top);
for (var y = startY; y < maxY; y++)
{
for (var x = startX; x < maxX; x++)
{
var color = bitmap.GetPixel(x, y);
if (color.R < 200 || color.G < 200 || color.B < 200)
{
count++;
}
}
}

return count;
}
}
}
61 changes: 61 additions & 0 deletions JianpuEditor.Tests/Services/MeasureRestServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using JianpuEditor.Models;
using JianpuEditor.Services;
using JianpuEditor.Tests.Helpers;
using Xunit;

namespace JianpuEditor.Tests.Services
{
public sealed class MeasureRestServiceTests
{
[Fact]
public void IsRestOnly_EmptyMeasure_IsTrue()
{
Assert.True(MeasureRestService.IsRestOnly(new JianpuMeasure()));
Assert.True(MeasureRestService.IsRestOnly(null));
}

[Fact]
public void IsRestOnly_AllRests_IsTrue()
{
var measure = ScoreTestHelper.Measure(
ScoreTestHelper.Rest(),
ScoreTestHelper.Rest(underlines: 1),
ScoreTestHelper.Rest(underlines: 2),
ScoreTestHelper.Rest());

Assert.True(MeasureRestService.IsRestOnly(measure));
}

[Fact]
public void IsRestOnly_ContainsMelodyNote_IsFalse()
{
var measure = ScoreTestHelper.Measure(
ScoreTestHelper.Rest(),
ScoreTestHelper.Note(1),
ScoreTestHelper.Rest(),
ScoreTestHelper.Rest());

Assert.False(MeasureRestService.IsRestOnly(measure));
}

[Fact]
public void IsRestOnly_SimultaneousSoundingNote_IsFalse()
{
var measure = ScoreTestHelper.Measure(
ScoreTestHelper.Rest(),
ScoreTestHelper.Rest(),
ScoreTestHelper.Rest(),
ScoreTestHelper.Rest());
measure.Chords = new List<JianpuChord>
{
new JianpuChord
{
BeatPosition = 0,
Notes = new List<JianpuNote> { ScoreTestHelper.Note(5) }
}
};

Assert.False(MeasureRestService.IsRestOnly(measure));
}
}
}
33 changes: 31 additions & 2 deletions JianpuEditor/Rendering/JianpuRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,8 @@ private void DrawTiesForLineRange(
if (startLayout == null
|| endLayout == null
|| !visibleMeasures.Contains(startLayout)
|| !visibleMeasures.Contains(endLayout))
|| !visibleMeasures.Contains(endLayout)
|| ShouldHideTieOnBlankRestMeasure(score, tie))
{
continue;
}
Expand Down Expand Up @@ -1047,7 +1048,8 @@ private void DrawTies(Graphics g, JianpuScore score, ScoreLayout layout, int sel

for (var i = 0; i < score.Ties.Count; i++)
{
if (!TryGetTieGeometry(score, layout, score.Ties[i], out var geometry))
if (ShouldHideTieOnBlankRestMeasure(score, score.Ties[i])
|| !TryGetTieGeometry(score, layout, score.Ties[i], out var geometry))
{
continue;
}
Expand Down Expand Up @@ -1095,6 +1097,26 @@ private void DrawTies(Graphics g, JianpuScore score, ScoreLayout layout, int sel
}
}

private bool ShouldHideTieOnBlankRestMeasure(JianpuScore score, JianpuTie tie)
{
if (_activeLayoutOptions == null
|| !_activeLayoutOptions.LeaveRestOnlyMeasuresBlank
|| score?.Measures == null
|| tie == null)
{
return false;
}

if (tie.StartMeasureIndex < 0 || tie.StartMeasureIndex >= score.Measures.Count
|| tie.EndMeasureIndex < 0 || tie.EndMeasureIndex >= score.Measures.Count)
{
return false;
}

return MeasureRestService.IsRestOnly(score.Measures[tie.StartMeasureIndex])
|| MeasureRestService.IsRestOnly(score.Measures[tie.EndMeasureIndex]);
}

private static bool TryGetTieGeometry(JianpuScore score, ScoreLayout layout, JianpuTie tie, out TieGeometry geometry)
{
geometry = default;
Expand Down Expand Up @@ -1219,6 +1241,13 @@ private void DrawMelodyRow(
DrawGapCaret(g, layout, selectedInsertIndex);
}

if (_activeLayoutOptions != null
&& _activeLayoutOptions.LeaveRestOnlyMeasuresBlank
&& MeasureRestService.IsRestOnly(measure))
{
return;
}

var melodyScale = layout.MelodyScale;
var minDrawWidth = melodyScale < 0.999
? Math.Max(6, (int)Math.Round(MinNoteWidth * melodyScale))
Expand Down
120 changes: 62 additions & 58 deletions JianpuEditor/Rendering/ScoreLayoutOptions.cs
Original file line number Diff line number Diff line change
@@ -1,58 +1,62 @@
namespace JianpuEditor.Rendering
{
public sealed class ScoreLayoutOptions
{
public static readonly ScoreLayoutOptions Default = new ScoreLayoutOptions();

public static readonly ScoreLayoutOptions Editor = new ScoreLayoutOptions
{
ChordMarkersTextOnly = true,
ShowChordEditorAffordances = true,
CompactAccidentalGlyphs = true
};

public static readonly ScoreLayoutOptions PdfExport = new ScoreLayoutOptions
{
MeasuresPerLine = 4,
EqualizeMeasureWidths = true,
NoteWidthScale = 1.0,
HeaderMarginTop = 96,
TitleFontSize = 36f,
MetaFontSize = 18f,
HeaderMetaLeftAligned = true,
ChordMarkersTextOnly = true,
CompactAccidentalGlyphs = true,
RespectAppTheme = false
};

public const int PdfRenderWidth = 1280;

/// <summary>0 = wrap by page width; otherwise fixed measures per line.</summary>
public int MeasuresPerLine { get; set; }

public bool EqualizeMeasureWidths { get; set; }

public double NoteWidthScale { get; set; } = 1.0;

/// <summary>0 = use renderer default top margin.</summary>
public int HeaderMarginTop { get; set; }

public float TitleFontSize { get; set; } = 22f;

public float MetaFontSize { get; set; } = 11f;

public bool HeaderMetaLeftAligned { get; set; }

/// <summary>PDF 等导出场景:仅绘制和弦文字,不绘制编辑边框与手柄。</summary>
public bool ChordMarkersTextOnly { get; set; }

/// <summary>编辑界面:显示拍位网格、提示,以及选中态的操作标记。</summary>
public bool ShowChordEditorAffordances { get; set; }

/// <summary>为 false 时(如 PDF 导出)始终使用浅色纸面。</summary>
public bool RespectAppTheme { get; set; } = true;

/// <summary>编辑器与 PDF:音符上方标注分层排布(升降号、高音点、装饰音纵向叠放)。</summary>
public bool CompactAccidentalGlyphs { get; set; }
}
}
namespace JianpuEditor.Rendering
{
public sealed class ScoreLayoutOptions
{
public static readonly ScoreLayoutOptions Default = new ScoreLayoutOptions();

public static readonly ScoreLayoutOptions Editor = new ScoreLayoutOptions
{
ChordMarkersTextOnly = true,
ShowChordEditorAffordances = true,
CompactAccidentalGlyphs = true
};

public static readonly ScoreLayoutOptions PdfExport = new ScoreLayoutOptions
{
MeasuresPerLine = 4,
EqualizeMeasureWidths = true,
NoteWidthScale = 1.0,
HeaderMarginTop = 96,
TitleFontSize = 36f,
MetaFontSize = 18f,
HeaderMetaLeftAligned = true,
ChordMarkersTextOnly = true,
CompactAccidentalGlyphs = true,
RespectAppTheme = false,
LeaveRestOnlyMeasuresBlank = true
};

public const int PdfRenderWidth = 1280;

/// <summary>0 = wrap by page width; otherwise fixed measures per line.</summary>
public int MeasuresPerLine { get; set; }

public bool EqualizeMeasureWidths { get; set; }

public double NoteWidthScale { get; set; } = 1.0;

/// <summary>0 = use renderer default top margin.</summary>
public int HeaderMarginTop { get; set; }

public float TitleFontSize { get; set; } = 22f;

public float MetaFontSize { get; set; } = 11f;

public bool HeaderMetaLeftAligned { get; set; }

/// <summary>PDF 等导出场景:仅绘制和弦文字,不绘制编辑边框与手柄。</summary>
public bool ChordMarkersTextOnly { get; set; }

/// <summary>编辑界面:显示拍位网格、提示,以及选中态的操作标记。</summary>
public bool ShowChordEditorAffordances { get; set; }

/// <summary>为 false 时(如 PDF 导出)始终使用浅色纸面。</summary>
public bool RespectAppTheme { get; set; } = true;

/// <summary>编辑器与 PDF:音符上方标注分层排布(升降号、高音点、装饰音纵向叠放)。</summary>
public bool CompactAccidentalGlyphs { get; set; }

/// <summary>PDF:主旋律全为休止符 0 的小节留空,便于手写音符。</summary>
public bool LeaveRestOnlyMeasuresBlank { get; set; }
}
}
Loading
Loading