From 35e8123a1174a2961126b5da9a5bb540683a2d00 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 01:03:13 +0200 Subject: [PATCH 01/38] Added native test tool --- .nuke/build.schema.json | 2 + .vscode/settings.json | 1 + build/Architecture.cs | 24 +- build/Build.LibAlphaSkia.cs | 113 ++- build/Build.LibSkia.cs | 4 +- build/Build.Native.cs | 33 +- build/TargetOperatingSystem.cs | 25 +- lib/dotnet/AlphaSkia.sln | 9 - lib/dotnet/AlphaSkia/AlphaSkiaCanvas.cs | 46 +- lib/dotnet/AlphaSkia/AlphaSkiaData.cs | 13 +- lib/dotnet/AlphaSkia/AlphaSkiaImage.cs | 36 +- lib/dotnet/AlphaSkia/AlphaSkiaNative.cs | 12 +- lib/dotnet/AlphaSkia/AlphaSkiaTypeface.cs | 12 +- lib/dotnet/AlphaSkia/NativeMethods.cs | 8 + .../net_alphatab_alphaskia_AlphaSkiaImage.h | 16 + lib/java/jni/src/AlphaSkiaImage.cpp | 16 + .../alphatab/alphaskia/AlphaSkiaImage.java | 29 + lib/node/addon/addon.cpp | 46 +- lib/node/alphaskia/src/AlphaSkiaCanvas.ts | 2 +- lib/node/alphaskia/src/AlphaSkiaImage.ts | 11 + lib/node/alphaskia/src/AlphaSkiaTypeface.ts | 4 +- lib/node/alphaskia/src/addon.ts | 4 +- .../reference/win11-x64-chrome-canvas.png | Bin 39163 -> 0 bytes .../test-generator/AlphaSkiaTestCanvas.cs | 235 ------ lib/test/test-generator/Program.cs | 22 - lib/test/test-generator/SourceBuilder.cs | 312 -------- .../AlphaSkia.Test/AlphaSkia.Test.csproj | 0 .../AlphaSkia.Test/AlphaTabGeneratedTest.cs | 0 .../AlphaSkia.Test/MusicSheetRenderTest.cs | 0 {lib => test}/dotnet/AlphaSkia.Test/Usings.cs | 0 test/java/.gitignore | 42 + test/java/build.gradle.kts | 165 ++++ test/java/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + test/java/gradlew | 234 ++++++ test/java/gradlew.bat | 89 +++ test/java/local.properties | 20 + test/java/main/build.gradle.kts | 30 + .../alphatab/alphaskia/AlphaSkiaCanvas.java | 247 ++++++ .../alphaskia/AlphaSkiaColorType.java | 38 + .../net/alphatab/alphaskia/AlphaSkiaData.java | 18 + .../alphatab/alphaskia/AlphaSkiaImage.java | 41 + .../alphatab/alphaskia/AlphaSkiaNative.java | 22 + .../alphatab/alphaskia/AlphaSkiaPlatform.java | 79 ++ .../alphaskia/AlphaSkiaTextAlign.java | 29 + .../alphaskia/AlphaSkiaTextBaseline.java | 34 + .../alphatab/alphaskia/AlphaSkiaTypeface.java | 60 ++ .../AlphaTabGeneratedRenderTest.java | 752 ++++++++++++++++++ .../alphaskia/MusicSheetRenderTest.java | 188 +++++ .../alphatab/alphaskia/RenderFunction.java | 8 + test/java/settings.gradle.kts | 7 + test/native/include/AlphaSkiaTestBridge.h | 27 + test/native/include/AlphaTabGeneratedTest.h | 5 + test/native/include/PixelMatch.h | 49 ++ test/native/src/AlphaSkiaTestBridge.cpp | 71 ++ test/native/src/AlphaTabGeneratedTest.cpp | 740 +++++++++++++++++ test/native/src/PixelMatch.cpp | 282 +++++++ test/native/src/main.cpp | 188 +++++ .../font/bravura/Bravura-FONTLOG.txt | 0 .../font/bravura/Bravura-OFL-FAQ.txt | 0 .../test-data}/font/bravura/Bravura-OFL.txt | 0 .../test-data}/font/bravura/Bravura.eot | Bin .../test-data}/font/bravura/Bravura.otf | Bin .../test-data}/font/bravura/Bravura.svg | 0 .../test-data}/font/bravura/Bravura.ttf | Bin .../test-data}/font/bravura/Bravura.woff | Bin .../test-data}/font/bravura/Bravura.woff2 | Bin .../test-data}/font/bravura/BravuraText.eot | Bin .../test-data}/font/bravura/BravuraText.otf | Bin .../test-data}/font/bravura/BravuraText.svg | 0 .../test-data}/font/bravura/BravuraText.woff | Bin .../test-data}/font/bravura/bravura-text.md | 0 .../font/bravura/bravura_metadata.json | 0 .../test-data}/font/ptserif/PTSerif-Bold.ttf | Bin .../font/ptserif/PTSerif-BoldItalic.ttf | Bin .../font/ptserif/PTSerif-Italic.ttf | Bin .../test-data}/font/ptserif/PTSerif-OFL.txt | 0 .../font/ptserif/PTSerif-Regular.ttf | Bin .../test-data}/font/roboto/LICENSE.txt | 0 .../test-data}/font/roboto/Roboto-Bold.ttf | Bin .../font/roboto/Roboto-BoldItalic.ttf | Bin .../test-data}/font/roboto/Roboto-Italic.ttf | Bin .../test-data}/font/roboto/Roboto-Regular.ttf | Bin .../test-data/reference/old-win-x64.png | Bin test/test-data/reference/win-x64.png | Bin 0 -> 34635 bytes .../.idea.TestGenerator/.idea/.gitignore | 0 .../.idea/.idea.TestGenerator/.idea/.name | 0 .../.idea.TestGenerator/.idea/encodings.xml | 0 .../.idea.TestGenerator/.idea/indexLayout.xml | 0 .../.idea/.idea.TestGenerator/.idea/vcs.xml | 3 +- test/test-generator/AlphaSkiaTestCanvas.cs | 233 ++++++ .../AlphaSkiaUnitTestGenerator.cs | 86 +- .../test => test}/test-generator/PathUtils.cs | 0 test/test-generator/Program.cs | 29 + .../test-generator/SkiaSharpTextMeasurer.cs | 26 +- {lib/test => test}/test-generator/TestFile.gp | Bin .../test-generator/TestGenerator.csproj | 0 .../test-generator/TestGenerator.sln | 0 test/test-generator/TestSourceBuilder.cs | 385 +++++++++ test/test-outputs/native/win-x64.diff.png | Bin 0 -> 5798 bytes test/test-outputs/native/win-x64.png | Bin 0 -> 34635 bytes test/test-outputs/native/win-x64diff.png | Bin 0 -> 3155 bytes wrapper/include/alphaskia.h | 2 + wrapper/src/AlphaSkiaCanvas.cpp | 10 + wrapper/src/alphaskia_data.cpp | 1 - wrapper/src/alphaskia_image.cpp | 21 +- 106 files changed, 4626 insertions(+), 676 deletions(-) delete mode 100644 lib/test/reference/win11-x64-chrome-canvas.png delete mode 100644 lib/test/test-generator/AlphaSkiaTestCanvas.cs delete mode 100644 lib/test/test-generator/Program.cs delete mode 100644 lib/test/test-generator/SourceBuilder.cs rename {lib => test}/dotnet/AlphaSkia.Test/AlphaSkia.Test.csproj (100%) rename {lib => test}/dotnet/AlphaSkia.Test/AlphaTabGeneratedTest.cs (100%) rename {lib => test}/dotnet/AlphaSkia.Test/MusicSheetRenderTest.cs (100%) rename {lib => test}/dotnet/AlphaSkia.Test/Usings.cs (100%) create mode 100644 test/java/.gitignore create mode 100644 test/java/build.gradle.kts create mode 100644 test/java/gradle/wrapper/gradle-wrapper.jar create mode 100644 test/java/gradle/wrapper/gradle-wrapper.properties create mode 100644 test/java/gradlew create mode 100644 test/java/gradlew.bat create mode 100644 test/java/local.properties create mode 100644 test/java/main/build.gradle.kts create mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaCanvas.java create mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaColorType.java create mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaData.java create mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java create mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaNative.java create mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaPlatform.java create mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextAlign.java create mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextBaseline.java create mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTypeface.java create mode 100644 test/java/main/src/test/java/net/alphatab/alphaskia/AlphaTabGeneratedRenderTest.java create mode 100644 test/java/main/src/test/java/net/alphatab/alphaskia/MusicSheetRenderTest.java create mode 100644 test/java/main/src/test/java/net/alphatab/alphaskia/RenderFunction.java create mode 100644 test/java/settings.gradle.kts create mode 100644 test/native/include/AlphaSkiaTestBridge.h create mode 100644 test/native/include/AlphaTabGeneratedTest.h create mode 100644 test/native/include/PixelMatch.h create mode 100644 test/native/src/AlphaSkiaTestBridge.cpp create mode 100644 test/native/src/AlphaTabGeneratedTest.cpp create mode 100644 test/native/src/PixelMatch.cpp create mode 100644 test/native/src/main.cpp rename {lib/test => test/test-data}/font/bravura/Bravura-FONTLOG.txt (100%) rename {lib/test => test/test-data}/font/bravura/Bravura-OFL-FAQ.txt (100%) rename {lib/test => test/test-data}/font/bravura/Bravura-OFL.txt (100%) rename {lib/test => test/test-data}/font/bravura/Bravura.eot (100%) rename {lib/test => test/test-data}/font/bravura/Bravura.otf (100%) rename {lib/test => test/test-data}/font/bravura/Bravura.svg (100%) rename {lib/test => test/test-data}/font/bravura/Bravura.ttf (100%) rename {lib/test => test/test-data}/font/bravura/Bravura.woff (100%) rename {lib/test => test/test-data}/font/bravura/Bravura.woff2 (100%) rename {lib/test => test/test-data}/font/bravura/BravuraText.eot (100%) rename {lib/test => test/test-data}/font/bravura/BravuraText.otf (100%) rename {lib/test => test/test-data}/font/bravura/BravuraText.svg (100%) rename {lib/test => test/test-data}/font/bravura/BravuraText.woff (100%) rename {lib/test => test/test-data}/font/bravura/bravura-text.md (100%) rename {lib/test => test/test-data}/font/bravura/bravura_metadata.json (100%) rename {lib/test => test/test-data}/font/ptserif/PTSerif-Bold.ttf (100%) rename {lib/test => test/test-data}/font/ptserif/PTSerif-BoldItalic.ttf (100%) rename {lib/test => test/test-data}/font/ptserif/PTSerif-Italic.ttf (100%) rename {lib/test => test/test-data}/font/ptserif/PTSerif-OFL.txt (100%) rename {lib/test => test/test-data}/font/ptserif/PTSerif-Regular.ttf (100%) rename {lib/test => test/test-data}/font/roboto/LICENSE.txt (100%) rename {lib/test => test/test-data}/font/roboto/Roboto-Bold.ttf (100%) rename {lib/test => test/test-data}/font/roboto/Roboto-BoldItalic.ttf (100%) rename {lib/test => test/test-data}/font/roboto/Roboto-Italic.ttf (100%) rename {lib/test => test/test-data}/font/roboto/Roboto-Regular.ttf (100%) rename lib/test/reference/win11-x64-skiasharp.png => test/test-data/reference/old-win-x64.png (100%) create mode 100644 test/test-data/reference/win-x64.png rename {lib/test => test}/test-generator/.idea/.idea.TestGenerator/.idea/.gitignore (100%) rename {lib/test => test}/test-generator/.idea/.idea.TestGenerator/.idea/.name (100%) rename {lib/test => test}/test-generator/.idea/.idea.TestGenerator/.idea/encodings.xml (100%) rename {lib/test => test}/test-generator/.idea/.idea.TestGenerator/.idea/indexLayout.xml (100%) rename {lib/test => test}/test-generator/.idea/.idea.TestGenerator/.idea/vcs.xml (54%) create mode 100644 test/test-generator/AlphaSkiaTestCanvas.cs rename {lib/test => test}/test-generator/AlphaSkiaUnitTestGenerator.cs (71%) rename {lib/test => test}/test-generator/PathUtils.cs (100%) create mode 100644 test/test-generator/Program.cs rename {lib/test => test}/test-generator/SkiaSharpTextMeasurer.cs (76%) rename {lib/test => test}/test-generator/TestFile.gp (100%) rename {lib/test => test}/test-generator/TestGenerator.csproj (100%) rename {lib/test => test}/test-generator/TestGenerator.sln (100%) create mode 100644 test/test-generator/TestSourceBuilder.cs create mode 100644 test/test-outputs/native/win-x64.diff.png create mode 100644 test/test-outputs/native/win-x64.png create mode 100644 test/test-outputs/native/win-x64diff.png diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index c6ea6dc..d5fb218 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -128,6 +128,7 @@ "LibAlphaSkia", "LibAlphaSkiaGitSyncDeps", "LibAlphaSkiaPatchSkiaBuildFiles", + "LibAlphaSkiaTest", "LibSkia", "LibSkiaGitSyncDeps", "LibSkiaPatchSkiaBuildFiles", @@ -157,6 +158,7 @@ "LibAlphaSkia", "LibAlphaSkiaGitSyncDeps", "LibAlphaSkiaPatchSkiaBuildFiles", + "LibAlphaSkiaTest", "LibSkia", "LibSkiaGitSyncDeps", "LibSkiaPatchSkiaBuildFiles", diff --git a/.vscode/settings.json b/.vscode/settings.json index 57d5e46..a34f94a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -118,5 +118,6 @@ "scoped_allocator": "cpp", "typeindex": "cpp", "__std_stream": "cpp", + "barrier": "cpp" } } \ No newline at end of file diff --git a/build/Architecture.cs b/build/Architecture.cs index 97acda8..b186e94 100644 --- a/build/Architecture.cs +++ b/build/Architecture.cs @@ -1,4 +1,6 @@ -using System.ComponentModel; +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; using Nuke.Common.Tooling; [TypeConverter(typeof(TypeConverter))] @@ -25,6 +27,26 @@ public class Architecture : Enumeration LinuxCrossTargetArch = "aarch64-linux-gnu" }; + public static Architecture Current + { + get + { + switch (RuntimeInformation.OSArchitecture) + { + case System.Runtime.InteropServices.Architecture.X86: + return X86; + case System.Runtime.InteropServices.Architecture.X64: + return X64; + case System.Runtime.InteropServices.Architecture.Arm: + return Arm; + case System.Runtime.InteropServices.Architecture.Arm64: + return Arm64; + default: + throw new PlatformNotSupportedException("Building on this platform is not fully supported"); + } + } + } + public string LinuxArch { get; private set; } public string LinuxCrossToolchain { get; private set; } public string LinuxCrossTargetArch { get; private set; } diff --git a/build/Build.LibAlphaSkia.cs b/build/Build.LibAlphaSkia.cs index f483fb4..d768b94 100644 --- a/build/Build.LibAlphaSkia.cs +++ b/build/Build.LibAlphaSkia.cs @@ -6,6 +6,7 @@ using Nuke.Common; using Nuke.Common.IO; using Nuke.Common.Tooling; +using Serilog; partial class Build { @@ -30,6 +31,12 @@ partial class Build .Requires(() => TargetOs) .Executes(BuildAlphaSkia); + public Target LibAlphaSkiaTest => _ => _ + .DependsOn(PrepareGitHubArtifacts, LibAlphaSkiaGitSyncDeps, LibAlphaSkiaPatchSkiaBuildFiles) + .Requires(() => Architecture) + .Requires(() => TargetOs) + .Executes(BuildAlphaSkiaTest); + public Target LibAlphaSkiaPatchSkiaBuildFiles => _ => _ .Unlisted() .Executes(() => @@ -51,6 +58,12 @@ partial class Build forward_variables_from(invoker, "*") } } + template("alphaskia_executable") { + _alphaskia_mode = "executable" + target(_alphaskia_mode, target_name) { + forward_variables_from(invoker, "*") + } + } set_defaults("alphaskia_build") { configs = default_configs if (!is_shared_alphaskia) { @@ -152,6 +165,21 @@ partial class Build sources += [ "../../lib/node/addon/win_delay_load_hook.cpp"] } } + + alphaskia_executable("libalphaskiatest") { + sources = [ + "../../test/native/src/AlphaSkiaTestBridge.cpp", + "../../test/native/src/AlphaTabGeneratedTest.cpp", + "../../test/native/src/main.cpp", + "../../test/native/src/PixelMatch.cpp" + ] + if (is_win) { + libs = [ "libalphaskia.dll.lib" ] + } + else { + libs = [ "libalphaskia" ] + } + } """; PatchSkiaFile(SkiaPath / "BUILD.gn", buildNew); PatchSkiaToolchain(); @@ -197,7 +225,7 @@ void BuildAlphaSkia() var gnArgs = PrepareNativeBuild(Variant); var staticLibPath = DistBasePath / GetLibDirectory(variant: Variant.Static); var gnFlags = new Dictionary(); - + string buildTarget; if (Variant == Variant.Static) { @@ -245,7 +273,7 @@ void BuildAlphaSkia() AppendToFlagList(gnArgs, "extra_ldflags", $"'/DELAYLOAD:node.exe', '/LIBPATH:{nodeLibPath}', 'node.lib', 'DelayImp.lib'"); } - else if(OperatingSystem.IsMacOS() && TargetOs == TargetOperatingSystem.MacOs) + else if (OperatingSystem.IsMacOS() && TargetOs == TargetOperatingSystem.MacOs) { // disable need of a libnode.dylib dependencies are resolve dynamically during runtime // and as the node binary has them built-in @@ -272,7 +300,7 @@ void BuildAlphaSkia() var artifactsLibPath = IsGitHubActions ? ArtifactBasePath / libDir : null; var distPath = DistBasePath / libDir; var outDir = SkiaPath / "out" / libDir; - var libExtension = GetLibExtension(Variant); + var libExtensions = new HashSet(GetLibExtensions(Variant), StringComparer.OrdinalIgnoreCase); AbsolutePath rcOutputDir = null; if (OperatingSystem.IsWindows() && TargetOs == TargetOperatingSystem.Windows) @@ -288,16 +316,15 @@ void BuildAlphaSkia() { // compile resource file. Is is added as "library" in the BUILD.gn as input // "llvm-rc.exe" /FO alphaskia.rc.obj alphaskia.rc /D RC_INVOKED /C 65001 - + var input = RootDirectory / "wrapper" / "src" / "alphaskia.rc"; rcOutputDir.CreateDirectory(); var output = rcOutputDir / "alphaskia.rc.obj"; - + const int utf8CodePage = 65001; ToolResolver.GetTool((AbsolutePath)LlvmHome / "bin" / "llvm-rc.exe")( $"/FO {output} {input} /D RC_INVOKED /C {utf8CodePage}", workingDirectory: SkiaPath); - } }); @@ -307,7 +334,7 @@ void CopyBuildOutputTo(AbsolutePath path) { // libs FileSystemTasks.CopyDirectoryRecursively(outDir, path, DirectoryExistsPolicy.Merge, - FileExistsPolicy.OverwriteIfNewer, null, file => file.Extension != libExtension); + FileExistsPolicy.OverwriteIfNewer, null, file => !libExtensions.Contains(file.Extension)); // copy header FileSystemTasks.CopyFile(RootDirectory / "wrapper" / "include" / "alphaskia.h", DistBasePath / "include" / "alphaskia" / "alphaskia.h", FileExistsPolicy.OverwriteIfNewer); @@ -327,9 +354,79 @@ void CopyBuildOutputTo(AbsolutePath path) } } + void BuildAlphaSkiaTest() + { + var gnArgs = PrepareNativeBuild(Variant.Shared); + var sharedLibPath = DistBasePath / GetLibDirectory("libalphaskia", variant: Variant.Shared); + var gnFlags = new Dictionary(); + + AppendToFlagList(gnArgs, "extra_cflags", $"'-DALPHASKIA_RID={TargetOs.RuntimeIdentifier}-{Architecture}'"); + + if (TargetOs == TargetOperatingSystem.Windows) + { + // TODO: check if clang-cl also works with the linux flags + AppendToFlagList(gnArgs, "extra_ldflags", $"'/LIBPATH:{sharedLibPath}'"); + } + else + { + AppendToFlagList(gnArgs, "extra_ldflags", $"'-L{sharedLibPath}'"); + } + + var buildTarget = "libalphaskiatest"; + var libDir = GetLibDirectory(buildTarget, TargetOs, Architecture, Variant.Shared); + var outDir = SkiaPath / "out" / libDir; + var exeExtension = GetExeExtension(); + + GnNinja($"out/{libDir}", buildTarget, gnArgs, gnFlags, SkiaPath); + + // copy shared lib beside executable + var libExtensions = new HashSet(GetLibExtensions(Variant.Shared), StringComparer.OrdinalIgnoreCase); + foreach (var file in sharedLibPath.GetFiles().Where(f => libExtensions.Contains(f.Extension))) + { + FileSystemTasks.CopyFile(file, outDir / file.Name, FileExistsPolicy.OverwriteIfNewer); + } + + // run executable + if (LibAlphaSkiaCanRunTests) + { + ToolResolver.GetTool(outDir / (buildTarget + exeExtension))( + "", + workingDirectory: outDir + ); + } + else + { + Log.Information( + $"Skipping test execution, cannot run {TargetOs.RuntimeIdentifier}-{Architecture} tests on {TargetOperatingSystem.Current.RuntimeIdentifier}-{Architecture.Current} host system"); + } + } + + public bool LibAlphaSkiaCanRunTests + { + get + { + if (TargetOs == TargetOperatingSystem.Current) + { + // If arch and OS match we can definitely run + if (Architecture == Architecture.Current) + { + return true; + } + + // x64 can run x86 processes + if (Architecture == Architecture.X86 && Architecture.Current == Architecture.X64) + { + return true; + } + } + + return false; + } + } + void NativeWriteVersionInfoHeader() { - var libExtension = GetLibExtension(Variant); + var libExtension = GetLibExtensions(Variant)[0]; var versionInfo = new StringBuilder(); versionInfo.AppendLine("#pragma once"); diff --git a/build/Build.LibSkia.cs b/build/Build.LibSkia.cs index 11d0657..e8165c2 100644 --- a/build/Build.LibSkia.cs +++ b/build/Build.LibSkia.cs @@ -112,7 +112,7 @@ void BuildSkia() var artifactsLibPath = IsGitHubActions ? ArtifactBasePath / libDir : null; var distPath = DistBasePath / libDir; var outDir = SkiaPath / "out" / libDir; - var libExtension = GetLibExtension(Variant.Static); + var libExtension = new HashSet(GetLibExtensions(Variant.Static), StringComparer.OrdinalIgnoreCase); // disable features we don't need gnArgs["skia_use_icu"] = "false"; @@ -152,7 +152,7 @@ void CopyBuildOutputTo(AbsolutePath path) { // libs FileSystemTasks.CopyDirectoryRecursively(outDir, path, DirectoryExistsPolicy.Merge, - FileExistsPolicy.OverwriteIfNewer, null, file => file.Extension != libExtension); + FileExistsPolicy.OverwriteIfNewer, null, file => !libExtension.Contains(file.Extension)); } CopyBuildOutputTo(distPath); diff --git a/build/Build.Native.cs b/build/Build.Native.cs index ae16e80..6cd08af 100644 --- a/build/Build.Native.cs +++ b/build/Build.Native.cs @@ -222,7 +222,7 @@ Dictionary ReadDepsData(TextReader reader) void GnNinja(string outDir, string target, Dictionary gnArgs, Dictionary gnFlags, - AbsolutePath workingDirectory, + AbsolutePath workingDirectory, Action beforeCompile = null) { gnArgs["skia_enable_tools"] = "false"; @@ -257,14 +257,15 @@ string QuoteValue(string value) { gnFlags["-v"] = ""; } - - var gnFlagsString = string.Join(" ", gnFlags.Select(kvp => kvp.Value.Length > 0 ? $"--{kvp.Key}={quote}{kvp.Value}{quote}" : $"--{kvp.Key}")); + + var gnFlagsString = string.Join(" ", + gnFlags.Select(kvp => kvp.Value.Length > 0 ? $"--{kvp.Key}={quote}{kvp.Value}{quote}" : $"--{kvp.Key}")); if (Rebuild) { (SkiaPath / outDir).DeleteDirectory(); } - + // not inlined to avoid it being treated as FormattedString var gnToolArgs = $"gen {outDir} {gnFlagsString}"; @@ -276,12 +277,13 @@ string QuoteValue(string value) ); beforeCompile?.Invoke(); - + var ninjaArgs = $"-d keeprsp -C {outDir} {target}"; if (NinjaVerbose) { ninjaArgs = "-v " + ninjaArgs; } + argument = new ArgumentStringHandler(ninjaArgs.Length, 0, out _); argument.AppendLiteral(ninjaArgs); NinjaTool( @@ -328,22 +330,22 @@ Dictionary PrepareNativeBuild(Variant variant) return gnArgs; } - string GetLibExtension(Variant variant) + string[] GetLibExtensions(Variant variant) { if (variant == Variant.Node) { - return ".node"; + return new[] { ".node" }; } if (TargetOs == TargetOperatingSystem.Windows) { - return variant.IsShared ? ".dll" : ".lib"; + return variant.IsShared ? new[] { ".dll", ".lib" } : new[] { ".lib" }; } if (TargetOs == TargetOperatingSystem.Linux || TargetOs == TargetOperatingSystem.Android) { - return variant.IsShared ? ".so" : ".a"; + return variant.IsShared ? new[] { ".so" } : new[] { ".a" }; } @@ -351,11 +353,22 @@ string GetLibExtension(Variant variant) TargetOs == TargetOperatingSystem.iOS || TargetOs == TargetOperatingSystem.iOSSimulator) { - return variant.IsShared ? ".dylib" : ".a"; + return variant.IsShared ? new[] { ".dylib" } : new[] { ".a" }; } throw new InvalidOperationException("Unhandled TargetOS: " + TargetOs); } + string GetExeExtension() + { + if (TargetOs == TargetOperatingSystem.Windows) + { + return ".exe"; + } + else + { + return ""; + } + } bool HasCachedFiles(string buildTarget, Variant variant) { diff --git a/build/TargetOperatingSystem.cs b/build/TargetOperatingSystem.cs index 347af54..f2621ca 100644 --- a/build/TargetOperatingSystem.cs +++ b/build/TargetOperatingSystem.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel; using Nuke.Common.Tooling; @@ -78,6 +79,28 @@ public class TargetOperatingSystem : Enumeration RuntimeIdentifier = "iossimulator" }; + public static TargetOperatingSystem Current + { + get + { + if (OperatingSystem.IsWindows()) + { + return Windows; + } + if (OperatingSystem.IsLinux()) + { + return Linux; + } + + if (OperatingSystem.IsMacOS()) + { + return MacOs; + } + + throw new PlatformNotSupportedException("Need Windows, Linux or MacOS"); + } + } + public string SkiaTargetOs { get; private init; } public Dictionary SkiaGnArgs { get; } = new(); diff --git a/lib/dotnet/AlphaSkia.sln b/lib/dotnet/AlphaSkia.sln index a1e5bd9..b7a6d11 100644 --- a/lib/dotnet/AlphaSkia.sln +++ b/lib/dotnet/AlphaSkia.sln @@ -6,10 +6,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlphaSkia.Native.Linux", "A EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlphaSkia", "AlphaSkia\AlphaSkia.csproj", "{AF194919-7A20-4749-895A-53A35CB666E9}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{20757D78-DE4E-450B-97CC-24F926593193}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlphaSkia.Test", "AlphaSkia.Test\AlphaSkia.Test.csproj", "{3693A53C-386E-4DBC-9B5E-9C3B7723FBE0}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlphaSkia.Native.Android", "AlphaSkia.Native.Android\AlphaSkia.Native.Android.csproj", "{0500984B-5733-499A-850F-3CD964B9BE5A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlphaSkia.Native.MacOs", "AlphaSkia.Native.MacOs\AlphaSkia.Native.MacOs.csproj", "{6727C66F-7AD0-4E81-BDDE-E9422B0601A5}" @@ -34,10 +30,6 @@ Global {AF194919-7A20-4749-895A-53A35CB666E9}.Debug|Any CPU.Build.0 = Debug|Any CPU {AF194919-7A20-4749-895A-53A35CB666E9}.Release|Any CPU.ActiveCfg = Release|Any CPU {AF194919-7A20-4749-895A-53A35CB666E9}.Release|Any CPU.Build.0 = Release|Any CPU - {3693A53C-386E-4DBC-9B5E-9C3B7723FBE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3693A53C-386E-4DBC-9B5E-9C3B7723FBE0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3693A53C-386E-4DBC-9B5E-9C3B7723FBE0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3693A53C-386E-4DBC-9B5E-9C3B7723FBE0}.Release|Any CPU.Build.0 = Release|Any CPU {0500984B-5733-499A-850F-3CD964B9BE5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0500984B-5733-499A-850F-3CD964B9BE5A}.Debug|Any CPU.Build.0 = Debug|Any CPU {0500984B-5733-499A-850F-3CD964B9BE5A}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -52,6 +44,5 @@ Global {E20172D9-D9E5-495B-91CC-B21B36BA3BF4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution - {3693A53C-386E-4DBC-9B5E-9C3B7723FBE0} = {20757D78-DE4E-450B-97CC-24F926593193} EndGlobalSection EndGlobal diff --git a/lib/dotnet/AlphaSkia/AlphaSkiaCanvas.cs b/lib/dotnet/AlphaSkia/AlphaSkiaCanvas.cs index 546e24b..7b5ecbf 100644 --- a/lib/dotnet/AlphaSkia/AlphaSkiaCanvas.cs +++ b/lib/dotnet/AlphaSkia/AlphaSkiaCanvas.cs @@ -38,12 +38,12 @@ public uint Color get { CheckDisposed(); - return NativeMethods.alphaskia_canvas_get_color(Native); + return NativeMethods.alphaskia_canvas_get_color(Handle); } set { CheckDisposed(); - NativeMethods.alphaskia_canvas_set_color(Native, value); + NativeMethods.alphaskia_canvas_set_color(Handle, value); } } @@ -70,12 +70,12 @@ public float LineWidth get { CheckDisposed(); - return NativeMethods.alphaskia_canvas_get_line_width(Native); + return NativeMethods.alphaskia_canvas_get_line_width(Handle); } set { CheckDisposed(); - NativeMethods.alphaskia_canvas_set_line_width(Native, value); + NativeMethods.alphaskia_canvas_set_line_width(Handle, value); } } @@ -88,7 +88,7 @@ public float LineWidth public void BeginRender(int width, int height, float renderScale = 1) { CheckDisposed(); - NativeMethods.alphaskia_canvas_begin_render(Native, width, height, renderScale); + NativeMethods.alphaskia_canvas_begin_render(Handle, width, height, renderScale); } /// @@ -102,7 +102,7 @@ public void BeginRender(int width, int height, float renderScale = 1) public void DrawImage(AlphaSkiaImage image, float x, float y, float w, float h) { CheckDisposed(); - NativeMethods.alphaskia_canvas_draw_image(Native, image.Native, x, y, w, h); + NativeMethods.alphaskia_canvas_draw_image(Handle, image.Handle, x, y, w, h); } /// @@ -112,7 +112,7 @@ public void DrawImage(AlphaSkiaImage image, float x, float y, float w, float h) public AlphaSkiaImage? EndRender() { CheckDisposed(); - var image = NativeMethods.alphaskia_canvas_end_render(Native); + var image = NativeMethods.alphaskia_canvas_end_render(Handle); if (image == IntPtr.Zero) { return null; @@ -131,7 +131,7 @@ public void DrawImage(AlphaSkiaImage image, float x, float y, float w, float h) public void FillRect(float x, float y, float width, float height) { CheckDisposed(); - NativeMethods.alphaskia_canvas_fill_rect(Native, x, y, width, height); + NativeMethods.alphaskia_canvas_fill_rect(Handle, x, y, width, height); } /// @@ -144,7 +144,7 @@ public void FillRect(float x, float y, float width, float height) public void StrokeRect(float x, float y, float width, float height) { CheckDisposed(); - NativeMethods.alphaskia_canvas_stroke_rect(Native, x, y, width, height); + NativeMethods.alphaskia_canvas_stroke_rect(Handle, x, y, width, height); } /// @@ -153,7 +153,7 @@ public void StrokeRect(float x, float y, float width, float height) public void BeginPath() { CheckDisposed(); - NativeMethods.alphaskia_canvas_begin_path(Native); + NativeMethods.alphaskia_canvas_begin_path(Handle); } /// @@ -162,7 +162,7 @@ public void BeginPath() public void ClosePath() { CheckDisposed(); - NativeMethods.alphaskia_canvas_close_path(Native); + NativeMethods.alphaskia_canvas_close_path(Handle); } /// @@ -173,7 +173,7 @@ public void ClosePath() public void MoveTo(float x, float y) { CheckDisposed(); - NativeMethods.alphaskia_canvas_move_to(Native, x, y); + NativeMethods.alphaskia_canvas_move_to(Handle, x, y); } /// @@ -184,7 +184,7 @@ public void MoveTo(float x, float y) public void LineTo(float x, float y) { CheckDisposed(); - NativeMethods.alphaskia_canvas_line_to(Native, x, y); + NativeMethods.alphaskia_canvas_line_to(Handle, x, y); } /// @@ -197,7 +197,7 @@ public void LineTo(float x, float y) public void QuadraticCurveTo(float cpx, float cpy, float x, float y) { CheckDisposed(); - NativeMethods.alphaskia_canvas_quadratic_curve_to(Native, cpx, cpy, x, y); + NativeMethods.alphaskia_canvas_quadratic_curve_to(Handle, cpx, cpy, x, y); } /// @@ -212,7 +212,7 @@ public void QuadraticCurveTo(float cpx, float cpy, float x, float y) public void BezierCurveTo(float cp1X, float cp1Y, float cp2X, float cp2Y, float x, float y) { CheckDisposed(); - NativeMethods.alphaskia_canvas_bezier_curve_to(Native, cp1X, cp1Y, cp2X, cp2Y, x, y); + NativeMethods.alphaskia_canvas_bezier_curve_to(Handle, cp1X, cp1Y, cp2X, cp2Y, x, y); } /// @@ -224,7 +224,7 @@ public void BezierCurveTo(float cp1X, float cp1Y, float cp2X, float cp2Y, float public void FillCircle(float x, float y, float radius) { CheckDisposed(); - NativeMethods.alphaskia_canvas_fill_circle(Native, x, y, radius); + NativeMethods.alphaskia_canvas_fill_circle(Handle, x, y, radius); } /// @@ -236,7 +236,7 @@ public void FillCircle(float x, float y, float radius) public void StrokeCircle(float x, float y, float radius) { CheckDisposed(); - NativeMethods.alphaskia_canvas_fill_circle(Native, x, y, radius); + NativeMethods.alphaskia_canvas_fill_circle(Handle, x, y, radius); } /// @@ -245,7 +245,7 @@ public void StrokeCircle(float x, float y, float radius) public void Fill() { CheckDisposed(); - NativeMethods.alphaskia_canvas_fill(Native); + NativeMethods.alphaskia_canvas_fill(Handle); } /// @@ -254,7 +254,7 @@ public void Fill() public void Stroke() { CheckDisposed(); - NativeMethods.alphaskia_canvas_stroke(Native); + NativeMethods.alphaskia_canvas_stroke(Handle); } /// @@ -272,7 +272,7 @@ public void FillText(string text, AlphaSkiaTypeface typeface, float fontSize, fl AlphaSkiaTextBaseline baseline) { CheckDisposed(); - NativeMethods.alphaskia_canvas_fill_text(Native, text, typeface.Native, fontSize, x, y, textAlign, baseline); + NativeMethods.alphaskia_canvas_fill_text(Handle, text, typeface.Handle, fontSize, x, y, textAlign, baseline); } /// @@ -285,7 +285,7 @@ public void FillText(string text, AlphaSkiaTypeface typeface, float fontSize, fl public float MeasureText(string text, AlphaSkiaTypeface typeface, float fontSize) { CheckDisposed(); - return NativeMethods.alphaskia_canvas_measure_text(Native, text, typeface.Native, fontSize); + return NativeMethods.alphaskia_canvas_measure_text(Handle, text, typeface.Handle, fontSize); } /// @@ -297,7 +297,7 @@ public float MeasureText(string text, AlphaSkiaTypeface typeface, float fontSize public void BeginRotate(float centerX, float centerY, float angle) { CheckDisposed(); - NativeMethods.alphaskia_canvas_begin_rotate(Native, centerX, centerY, angle); + NativeMethods.alphaskia_canvas_begin_rotate(Handle, centerX, centerY, angle); } /// @@ -306,6 +306,6 @@ public void BeginRotate(float centerX, float centerY, float angle) public void EndRotate() { CheckDisposed(); - NativeMethods.alphaskia_canvas_end_rotate(Native); + NativeMethods.alphaskia_canvas_end_rotate(Handle); } } \ No newline at end of file diff --git a/lib/dotnet/AlphaSkia/AlphaSkiaData.cs b/lib/dotnet/AlphaSkia/AlphaSkiaData.cs index 68087b2..8475cd8 100644 --- a/lib/dotnet/AlphaSkia/AlphaSkiaData.cs +++ b/lib/dotnet/AlphaSkia/AlphaSkiaData.cs @@ -4,17 +4,22 @@ namespace AlphaSkia; internal sealed class AlphaSkiaData : AlphaSkiaNative { - public AlphaSkiaData(IntPtr native) - : base(native, NativeMethods.alphaskia_data_free) + public AlphaSkiaData(IntPtr handle) + : base(handle, NativeMethods.alphaskia_data_free) + { + } + + public AlphaSkiaData(byte[] data) + : base(NativeMethods.alphaskia_data_new_copy(data, (ulong)data.LongLength), NativeMethods.alphaskia_data_free) { } public byte[] ToArray() { CheckDisposed(); - var dataLength = NativeMethods.alphaskia_data_get_length(Native); + var dataLength = NativeMethods.alphaskia_data_get_length(Handle); var data = new byte[dataLength]; - var dataPtr = NativeMethods.alphaskia_data_get_data(Native); + var dataPtr = NativeMethods.alphaskia_data_get_data(Handle); Marshal.Copy(dataPtr, data, 0, data.Length); return data; } diff --git a/lib/dotnet/AlphaSkia/AlphaSkiaImage.cs b/lib/dotnet/AlphaSkia/AlphaSkiaImage.cs index c73e85d..8117ce4 100644 --- a/lib/dotnet/AlphaSkia/AlphaSkiaImage.cs +++ b/lib/dotnet/AlphaSkia/AlphaSkiaImage.cs @@ -13,7 +13,7 @@ public int Width get { CheckDisposed(); - return NativeMethods.alphaskia_image_get_width(Native); + return NativeMethods.alphaskia_image_get_width(Handle); } } @@ -25,12 +25,12 @@ public int Height get { CheckDisposed(); - return NativeMethods.alphaskia_image_get_height(Native); + return NativeMethods.alphaskia_image_get_height(Handle); } } - internal AlphaSkiaImage(IntPtr native) - : base(native, NativeMethods.alphaskia_image_free) + internal AlphaSkiaImage(IntPtr handle) + : base(handle, NativeMethods.alphaskia_image_free) { } @@ -43,7 +43,7 @@ internal AlphaSkiaImage(IntPtr native) public bool ReadPixels(IntPtr pixels, ulong rowBytes) { CheckDisposed(); - return NativeMethods.alphaskia_image_read_pixels(Native, pixels, rowBytes) != 0; + return NativeMethods.alphaskia_image_read_pixels(Handle, pixels, rowBytes) != 0; } /// @@ -53,8 +53,32 @@ public bool ReadPixels(IntPtr pixels, ulong rowBytes) public byte[]? ToPng() { CheckDisposed(); - var data = NativeMethods.alphaskia_image_encode_png(Native); + var data = NativeMethods.alphaskia_image_encode_png(Handle); using var wrapper = new AlphaSkiaData(data); return wrapper.ToArray(); } + + /// + /// Decodes the given bytes into an image using supported image formats like PNG. + /// + /// The raw iamge bytes. + /// The decoded image or null if the image could not be decoded. + public static AlphaSkiaImage? Decode(byte[] bytes) + { + var image = NativeMethods.alphaskia_image_decode(bytes, (ulong)bytes.LongLength); + return (image == IntPtr.Zero) ? null : new AlphaSkiaImage(image); + } + + /// + /// Creates an image from the raw pixels assuming the default internal pixel format of alphaSkia. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The raw pixel bytes. + /// The decoded image or null if the creation of the image from the pixels failed. + public static AlphaSkiaImage? FromPixels(int width, int height, byte[] pixels) + { + var image = NativeMethods.alphaskia_image_from_pixels(width, height, pixels); + return image == IntPtr.Zero ? null : new AlphaSkiaImage(image); + } } \ No newline at end of file diff --git a/lib/dotnet/AlphaSkia/AlphaSkiaNative.cs b/lib/dotnet/AlphaSkia/AlphaSkiaNative.cs index 7356f77..2cfd540 100644 --- a/lib/dotnet/AlphaSkia/AlphaSkiaNative.cs +++ b/lib/dotnet/AlphaSkia/AlphaSkiaNative.cs @@ -9,18 +9,18 @@ public class AlphaSkiaNative : IDisposable { private readonly Action _release; - internal IntPtr Native { get; private set; } + internal IntPtr Handle { get; private set; } - internal AlphaSkiaNative(IntPtr native, Action release) + internal AlphaSkiaNative(IntPtr handle, Action release) { _release = release; - Native = native; + Handle = handle; } private void ReleaseUnmanagedResources() { - _release(Native); - Native = IntPtr.Zero; + _release(Handle); + Handle = IntPtr.Zero; } /// @@ -55,7 +55,7 @@ public void Dispose() /// Thrown if this instance was already disposed. protected void CheckDisposed() { - if (Native == IntPtr.Zero) + if (Handle == IntPtr.Zero) { throw new ObjectDisposedException("this"); } diff --git a/lib/dotnet/AlphaSkia/AlphaSkiaTypeface.cs b/lib/dotnet/AlphaSkia/AlphaSkiaTypeface.cs index 0d7d5e5..4fce1b2 100644 --- a/lib/dotnet/AlphaSkia/AlphaSkiaTypeface.cs +++ b/lib/dotnet/AlphaSkia/AlphaSkiaTypeface.cs @@ -7,8 +7,8 @@ public sealed class AlphaSkiaTypeface : AlphaSkiaNative { private readonly AlphaSkiaData? _nativeData; - private AlphaSkiaTypeface(IntPtr native, AlphaSkiaData? nativeData = null) - : base(native, NativeMethods.alphaskia_typeface_free) + private AlphaSkiaTypeface(IntPtr handle, AlphaSkiaData? nativeData = null) + : base(handle, NativeMethods.alphaskia_typeface_free) { _nativeData = nativeData; } @@ -30,15 +30,15 @@ protected override void Dispose(bool disposing) /// The loaded typeface to use for text rendering or null if the loading failed. public static AlphaSkiaTypeface? Register(byte[] data) { - var nativeData = NativeMethods.alphaskia_data_new_copy(data, (ulong)data.LongLength); - var typeface = NativeMethods.alphaskia_typeface_register(nativeData); + var nativeData = new AlphaSkiaData(data); + var typeface = NativeMethods.alphaskia_typeface_register(nativeData.Handle); if (typeface == IntPtr.Zero) { - NativeMethods.alphaskia_data_free(nativeData); + nativeData.Dispose(); return null; } - return new AlphaSkiaTypeface(typeface, new AlphaSkiaData(nativeData)); + return new AlphaSkiaTypeface(typeface, nativeData); } /// diff --git a/lib/dotnet/AlphaSkia/NativeMethods.cs b/lib/dotnet/AlphaSkia/NativeMethods.cs index 312f563..d1e1d73 100644 --- a/lib/dotnet/AlphaSkia/NativeMethods.cs +++ b/lib/dotnet/AlphaSkia/NativeMethods.cs @@ -47,7 +47,15 @@ public static partial alphaskia_typeface_t alphaskia_typeface_make_from_name(str [LibraryImport(AlphaSkiaNativeLibName)] [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] public static partial void alphaskia_image_free(alphaskia_image_t result); + + [LibraryImport(AlphaSkiaNativeLibName)] + [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] + public static partial alphaskia_image_t alphaskia_image_decode(byte[] data, ulong length); + [LibraryImport(AlphaSkiaNativeLibName)] + [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] + public static partial alphaskia_image_t alphaskia_image_from_pixels(int width, int height, byte[] pixels); + [LibraryImport(AlphaSkiaNativeLibName)] [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] public static partial int alphaskia_image_get_width(alphaskia_image_t image); diff --git a/lib/java/jni/include/net_alphatab_alphaskia_AlphaSkiaImage.h b/lib/java/jni/include/net_alphatab_alphaskia_AlphaSkiaImage.h index 4fb39a5..f875ce8 100644 --- a/lib/java/jni/include/net_alphatab_alphaskia_AlphaSkiaImage.h +++ b/lib/java/jni/include/net_alphatab_alphaskia_AlphaSkiaImage.h @@ -47,6 +47,22 @@ JNIEXPORT jbyteArray JNICALL Java_net_alphatab_alphaskia_AlphaSkiaImage_readPixe JNIEXPORT jbyteArray JNICALL Java_net_alphatab_alphaskia_AlphaSkiaImage_toPng (JNIEnv *, jobject); +/* + * Class: net_alphatab_alphaskia_AlphaSkiaImage + * Method: allocateDecoded + * Signature: ([B)J + */ +JNIEXPORT jlong JNICALL Java_net_alphatab_alphaskia_AlphaSkiaImage_allocateDecoded + (JNIEnv *, jclass, jbyteArray); + +/* + * Class: net_alphatab_alphaskia_AlphaSkiaImage + * Method: createFromPixels + * Signature: (II[B)J + */ +JNIEXPORT jlong JNICALL Java_net_alphatab_alphaskia_AlphaSkiaImage_createFromPixels + (JNIEnv *, jclass, jint, jint, jbyteArray); + #ifdef __cplusplus } #endif diff --git a/lib/java/jni/src/AlphaSkiaImage.cpp b/lib/java/jni/src/AlphaSkiaImage.cpp index 3f16622..34713bb 100644 --- a/lib/java/jni/src/AlphaSkiaImage.cpp +++ b/lib/java/jni/src/AlphaSkiaImage.cpp @@ -59,4 +59,20 @@ extern "C" return png; } + + JNIEXPORT jlong JNICALL Java_net_alphatab_alphaskia_AlphaSkiaImage_createFromPixels(JNIEnv *env, jclass clz, jint width, jint height, jbyteArray pixels) + { + jbyte *bytes = env->GetByteArrayElements(pixels, nullptr); + alphaskia_image_t nativeImage = alphaskia_image_from_pixels(static_cast(width), static_cast(height), reinterpret_cast(bytes)); + env->ReleaseByteArrayElements(pixels, bytes, 0); + return reinterpret_cast(nativeImage); + } + + JNIEXPORT jlong JNICALL Java_net_alphatab_alphaskia_AlphaSkiaImage_allocateDecoded(JNIEnv *env, jclass clz, jbyteArray bytes) + { + jbyte *raw = env->GetByteArrayElements(bytes, nullptr); + alphaskia_image_t nativeImage = alphaskia_image_decode(reinterpret_cast(raw), env->GetArrayLength(bytes)); + env->ReleaseByteArrayElements(bytes, raw, 0); + return reinterpret_cast(nativeImage); + } } \ No newline at end of file diff --git a/lib/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java b/lib/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java index f072960..ad1aae9 100644 --- a/lib/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java +++ b/lib/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java @@ -38,4 +38,33 @@ public class AlphaSkiaImage extends AlphaSkiaNative { * @return The raw PNG bytes for further usage. */ public native byte[] toPng(); + + /** + * Decodes the given bytes into an image using supported image formats like PNG. + * @param bytes The raw iamge bytes. + * @return The decoded image or null if the image could not be decoded. + */ + public static AlphaSkiaImage decode(byte[] bytes) + { + long handle = allocateDecoded(bytes); + return handle == 0 ? null : new AlphaSkiaImage(handle); + } + + private static native long allocateDecoded(byte[] bytes); + + + /** + * Creates an image from the raw pixels assuming the default internal pixel format of alphaSkia. + * @param width The width of the image in pixels. + * @param height The height of the image in pixels. + * @param pixels The raw pixel bytes. + * @return The decoded image or null if the creation of the image from the pixels failed. + */ + public static AlphaSkiaImage fromPixels(int width, int height, byte[] pixels) + { + long handle = createFromPixels(width, height, pixels); + return handle == 0 ? null : new AlphaSkiaImage(handle); + } + + private static native long createFromPixels(int width, int height, byte[] pixels); } diff --git a/lib/node/addon/addon.cpp b/lib/node/addon/addon.cpp index 8917980..a56fb60 100644 --- a/lib/node/addon/addon.cpp +++ b/lib/node/addon/addon.cpp @@ -10,7 +10,12 @@ #define CONCAT(a, b) a##b #define STRINGIFY(s) _STRINGIFY(s) #define _STRINGIFY(s) #s -#define ASSERT_STATUS() if(status != napi_ok) { napi_throw_error(env, "status", "Internal error"); return nullptr; } +#define ASSERT_STATUS() \ + if (status != napi_ok) \ + { \ + napi_throw_error(env, "status", "Internal error"); \ + return nullptr; \ + } static const napi_type_tag alphaskia_data_t_tag = {0x6a960ece6a0c4caf, 0xad61688dc0a28531}; static const napi_type_tag alphaskia_typeface_t_tag = {0x0068df0314224b96, 0x8048b968915995f1}; @@ -655,6 +660,43 @@ static napi_value node_alphaskia_canvas_draw_image(napi_env env, napi_callback_i RETURN_UNDEFINED(); } +static napi_value node_alphaskia_image_from_pixels(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(3); + GET_ARGUMENT_INT32(0, width); + GET_ARGUMENT_INT32(1, height); + + CHECK_ARGUMENT_IS_ARRAYBUFFER(2); + + void *node_pixels(nullptr); + size_t node_pixels_length(0); + status = napi_get_arraybuffer_info(env, node_argv[2], &node_pixels, &node_pixels_length); + ASSERT_STATUS(); + + alphaskia_image_t image = alphaskia_image_from_pixels(width, height, reinterpret_cast(node_pixels)); + WRAP_HANDLE(alphaskia_image_t, wrapped, image); + return wrapped; +} + +static napi_value node_alphaskia_image_decode(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + CHECK_ARGUMENT_IS_ARRAYBUFFER(0); + + void *node_bytes(nullptr); + size_t node_bytes_length(0); + status = napi_get_arraybuffer_info(env, node_argv[0], &node_bytes, &node_bytes_length); + ASSERT_STATUS(); + + alphaskia_image_t image = alphaskia_image_decode(reinterpret_cast(node_bytes), node_bytes_length); + WRAP_HANDLE(alphaskia_image_t, wrapped, image); + return wrapped; +} + static napi_value node_alphaskia_canvas_fill_text(napi_env env, napi_callback_info info) { napi_status status(napi_ok); @@ -745,6 +787,8 @@ napi_value init(napi_env env, napi_value exports) EXPORT_NODE_FUNCTION(alphaskia_image_get_height); EXPORT_NODE_FUNCTION(alphaskia_image_read_pixels); EXPORT_NODE_FUNCTION(alphaskia_image_encode_png); + EXPORT_NODE_FUNCTION(alphaskia_image_from_pixels); + EXPORT_NODE_FUNCTION(alphaskia_image_decode); EXPORT_NODE_FUNCTION(alphaskia_image_free); EXPORT_NODE_FUNCTION(alphaskia_canvas_new); diff --git a/lib/node/alphaskia/src/AlphaSkiaCanvas.ts b/lib/node/alphaskia/src/AlphaSkiaCanvas.ts index 6315ff4..fde50ee 100644 --- a/lib/node/alphaskia/src/AlphaSkiaCanvas.ts +++ b/lib/node/alphaskia/src/AlphaSkiaCanvas.ts @@ -109,7 +109,7 @@ export class AlphaSkiaCanvas extends AlphaSkiaNative { /** * Ends the rendering session and provides the rendered result. * - * @return The rendered result or {@code null} if something went wrong. + * @return The rendered result or {@code undefined} if something went wrong. */ public endRender(): AlphaSkiaImage | undefined { this.checkDisposed(); diff --git a/lib/node/alphaskia/src/AlphaSkiaImage.ts b/lib/node/alphaskia/src/AlphaSkiaImage.ts index 83ca2e6..3e4026a 100644 --- a/lib/node/alphaskia/src/AlphaSkiaImage.ts +++ b/lib/node/alphaskia/src/AlphaSkiaImage.ts @@ -1,3 +1,4 @@ +import { loadavg } from 'os'; import { AlphaSkiaNative } from './AlphaSkiaNative'; import { AlphaSkiaImageHandle, loadAddon } from './addon'; @@ -51,4 +52,14 @@ export class AlphaSkiaImage extends AlphaSkiaNative { this.checkDisposed(); return loadAddon().alphaskia_image_encode_png(this.handle!); } + + /** + * Decodes the given bytes into an image using supported image formats like PNG. + * @param bytes The raw iamge bytes. + * @return The decoded image or {@code undefined} if the image could not be decoded. + */ + public static decode(bytes: ArrayBuffer): AlphaSkiaImage | undefined { + const handle = loadAddon().alphaskia_image_decode(bytes); + return !handle ? undefined : new AlphaSkiaImage(handle); + } } \ No newline at end of file diff --git a/lib/node/alphaskia/src/AlphaSkiaTypeface.ts b/lib/node/alphaskia/src/AlphaSkiaTypeface.ts index 454a5ab..a5b7737 100644 --- a/lib/node/alphaskia/src/AlphaSkiaTypeface.ts +++ b/lib/node/alphaskia/src/AlphaSkiaTypeface.ts @@ -27,7 +27,7 @@ export class AlphaSkiaTypeface extends AlphaSkiaNative /** * Register a new custom font from the given binary data containing the data of a font compatible with Skia (e.g. TTF). * @param data The raw binary data of the font. - * @return The loaded typeface to use for text rendering or {@code null} if the loading failed. + * @return The loaded typeface to use for text rendering or {@code undefined} if the loading failed. */ public static register(data: ArrayBuffer): AlphaSkiaTypeface | undefined { const nativeData = loadAddon().alphaskia_data_new_copy(data); @@ -48,7 +48,7 @@ export class AlphaSkiaTypeface extends AlphaSkiaNative * @param name The name of the typeface. * @param bold Whether the bold version of the typeface should be loaded. * @param italic Whether the italic version of the typeface should be loaded. - * @return The typeface if it can be found in the already loaded fonts or the system fonts, otherwise {@code null}. + * @return The typeface if it can be found in the already loaded fonts or the system fonts, otherwise {@code undefined}. */ public static create(name: string, bold: boolean, italic: boolean): AlphaSkiaTypeface | undefined { const typeface = loadAddon().alphaskia_typeface_make_from_name(name, bold, italic); diff --git a/lib/node/alphaskia/src/addon.ts b/lib/node/alphaskia/src/addon.ts index ca630e8..1a12825 100644 --- a/lib/node/alphaskia/src/addon.ts +++ b/lib/node/alphaskia/src/addon.ts @@ -83,6 +83,8 @@ export interface AlphaSkiaNodeAddon { alphaskia_image_get_height(image: AlphaSkiaImageHandle): number; alphaskia_image_read_pixels(image: AlphaSkiaImageHandle): ArrayBuffer | undefined; alphaskia_image_encode_png(image: AlphaSkiaImageHandle): ArrayBuffer; + alphaskia_image_from_pixels(width: number, height: number, pixels: ArrayBuffer): AlphaSkiaImageHandle; + alphaskia_image_decode(pixels: ArrayBuffer): AlphaSkiaImageHandle; alphaskia_image_free(image: AlphaSkiaImageHandle): void; alphaskia_canvas_new(): AlphaSkiaCanvasHandle | undefined; @@ -109,7 +111,7 @@ export interface AlphaSkiaNodeAddon { alphaskia_canvas_fill(canvas: AlphaSkiaCanvasHandle): void; alphaskia_canvas_stroke(canvas: AlphaSkiaCanvasHandle): void; alphaskia_canvas_draw_image(canvas: AlphaSkiaCanvasHandle, image: AlphaSkiaImageHandle, x: number, y: number, w: number, h: number): void; - + alphaskia_canvas_fill_text(canvas: AlphaSkiaCanvasHandle, text: string, typeface: AlphaSkiaTypefaceHandle, font_size: number, x: number, y: number, text_align: AlphaSkiaTextAlign, baseline: AlphaSkiaTextBaseline): void; alphaskia_canvas_measure_text(canvas: AlphaSkiaCanvasHandle, text: string, typeface: AlphaSkiaTypefaceHandle, font_size: number): number; alphaskia_canvas_begin_rotate(canvas: AlphaSkiaCanvasHandle, center_x: number, center_y: number, angle: number): void; diff --git a/lib/test/reference/win11-x64-chrome-canvas.png b/lib/test/reference/win11-x64-chrome-canvas.png deleted file mode 100644 index bb360a862a95e2a13979328bb27633fec8eb0262..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39163 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 diff --git a/lib/test/test-generator/AlphaSkiaTestCanvas.cs b/lib/test/test-generator/AlphaSkiaTestCanvas.cs deleted file mode 100644 index 9c3fc14..0000000 --- a/lib/test/test-generator/AlphaSkiaTestCanvas.cs +++ /dev/null @@ -1,235 +0,0 @@ -using AlphaTab; -using AlphaTab.Model; -using AlphaTab.Platform; - -namespace TestGenerator; - -class AlphaSkiaTestCanvas : ICanvas -{ - private readonly SourceBuilder _source; - private Color _color = new Color(255, 255, 255, 0xFF); - private double _lineWidth = 1; - private Font _font = new Font("Arial", 10); - private TextAlign _textAlign = TextAlign.Left; - private TextBaseline _textBaseline = TextBaseline.Top; - - private int _partialCounter = 1; - - public AlphaSkiaTestCanvas(SourceBuilder source) - { - _source = source; - } - - public Settings Settings { get; set; } = new(); - - public Color Color - { - get => _color; - set - { - if ((int)_color.Raw == (int)value.Raw) - { - return; - } - _source.WriteSetCanvasProperty("Color", - _source.MakeCallStaticMethod("AlphaSkiaCanvas", "RgbaToColor", - $"{_source.MakeCastToByte(value.R)}, {_source.MakeCastToByte(value.G)}, {_source.MakeCastToByte(value.B)}, {_source.MakeCastToByte(value.A)}", - false - ) - ); - _color = value; - } - } - - public double LineWidth - { - get => _lineWidth; - set - { - _source.WriteSetCanvasProperty("LineWidth", $"{_source.MakeCastToFloat(value)}"); - _lineWidth = value; - } - } - - public Font Font - { - get => _font; - set - { - _source.WriteSetProperty("Typeface", _source.MakeCallMethod(_source.MakeMethodAccess("GetTypeface"), - $"\"{value.Families[0]}\", {(value.IsBold ? "true" : "false")}, {(value.IsItalic ? "true" : "false")}", - false - )); - _source.WriteSetProperty("FontSize", $"{_source.MakeCastToFloat(value.Size)}"); - _font = value; - } - } - - public TextAlign TextAlign - { - get => _textAlign; - set - { - _source.WriteSetProperty("TextAlign", _source.MakeEnumAccess("AlphaSkiaTextAlign", value.ToString())); - _textAlign = value; - } - } - - public TextBaseline TextBaseline - { - get => _textBaseline; - set - { - _source.WriteSetProperty("TextBaseline", _source.MakeEnumAccess("AlphaSkiaTextBaseline", value.ToString())); - _textBaseline = value; - } - } - - public void FillRect(double x, double y, double w, double h) - { - _source.WriteCallCanvasMethod("FillRect", $"{_source.MakeCastToFloat(x)}, {_source.MakeCastToFloat(y)}, {_source.MakeCastToFloat(w)}, {_source.MakeCastToFloat(h)}"); - } - - public void StrokeRect(double x, double y, double w, double h) - { - _source.WriteCallCanvasMethod("StrokeRect", $"{_source.MakeCastToFloat(x)}, {_source.MakeCastToFloat(y)}, {_source.MakeCastToFloat(w)}, {_source.MakeCastToFloat(h)}"); - } - - public void FillCircle(double x, double y, double radius) - { - _source.WriteCallCanvasMethod("FillCircle", $"{_source.MakeCastToFloat(x)}, {_source.MakeCastToFloat(y)}, {_source.MakeCastToFloat(radius)}"); - } - - public void StrokeCircle(double x, double y, double radius) - { - _source.WriteCallCanvasMethod("StrokeCircle", $"{_source.MakeCastToFloat(x)}, {_source.MakeCastToFloat(y)}, {_source.MakeCastToFloat(radius)}"); - } - - public void BeginGroup(string identifier) - { - } - - public void EndGroup() - { - } - - public void FillText(string text, double x, double y) - { - _source.WriteCallCanvasMethod("FillText", - $"{_source.EncodeString(text)}, {_source.MakeGetProperty("Typeface")}, {_source.MakeGetProperty("FontSize")}, {_source.MakeCastToFloat(x)}, {_source.MakeCastToFloat(y)}, {_source.MakeGetProperty("TextAlign")}, {_source.MakeGetProperty("TextBaseline")}"); - } - - public double MeasureText(string text) - { - return SkiaSharpTextMeasurer.MeasureText(_font, text); - } - - public void FillMusicFontSymbol(double x, double y, double scale, MusicFontSymbol symbol, - bool centerAtPosition = false) - { - FillMusicFontSymbols(x, y, scale, new List - { - symbol - }, centerAtPosition); - } - - public void FillMusicFontSymbols(double x, double y, double scale, IList symbols, - bool centerAtPosition = false) - { - var text = _source.EncodeString( - new string(symbols.Select(s => (char)((int)s)).ToArray())); - - var textAlign = centerAtPosition - ? _source.MakeEnumAccess("AlphaSkiaTextAlign", "Center") - : _source.MakeEnumAccess("AlphaSkiaTextAlign", "Left"); - - _source.WriteCallCanvasMethod("FillText", - $"{text}, {_source.MakeGetProperty("MusicTypeface")}, {MakeGetMusicFontSize(scale)}, {_source.MakeCastToFloat(x)}, {_source.MakeCastToFloat(y)}, {textAlign}, {_source.MakeEnumAccess("AlphaSkiaTextBaseline", "Alphabetic")}"); - } - - private string MakeGetMusicFontSize(double? scale = null) - { - if(scale == null) - { - return _source.MakeCastToFloat(_source.MakeGetProperty("MusicFontSize")); - } - else - { - return _source.MakeCastToFloat($"{_source.MakeGetProperty("MusicFontSize")} * {scale}"); - } - } - - public void BeginRender(double width, double height) - { - _source.Resume(); - var methodName = "DrawMusicSheetPart" + _partialCounter++; - _source.WriteMethodDeclaration("private", "AlphaSkiaImage", methodName, new[] { ("AlphaSkiaCanvas", "canvas" ) }); - _source.BeginBlock(); - _source.WriteCallCanvasMethod("BeginRender", $"{(int)width}, {(int)height}, {_source.MakeCastToFloat(_source.MakeGetProperty("RenderScale"))}"); - } - - public object? EndRender() - { - _source.WriteLine( - $"return {_source.MakeCallMethod(_source.MakeMethodAccess("canvas", "EndRender"), notNull: true)}"); - _source.EndBlock(); - _source.Suspend(); - return _source; - } - - public object? OnRenderFinished() - { - return null; - } - - public void BeginRotate(double centerX, double centerY, double angle) - { - _source.WriteCallCanvasMethod("BeginRotate", $"{_source.MakeCastToFloat(centerX)}, {_source.MakeCastToFloat(centerY)}, {_source.MakeCastToFloat(angle)}"); - } - - public void EndRotate() - { - _source.WriteCallCanvasMethod("EndRotate"); - } - - public void BeginPath() - { - _source.WriteCallCanvasMethod("BeginPath"); - } - - public void ClosePath() - { - _source.WriteCallCanvasMethod("ClosePath"); - } - - public void Fill() - { - _source.WriteCallCanvasMethod("Fill"); - } - - public void Stroke() - { - _source.WriteCallCanvasMethod("Stroke"); - } - - public void MoveTo(double x, double y) - { - _source.WriteCallCanvasMethod("MoveTo", $"{_source.MakeCastToFloat(x)}, {_source.MakeCastToFloat(y)}"); - } - - public void LineTo(double x, double y) - { - _source.WriteCallCanvasMethod("LineTo", $"{_source.MakeCastToFloat(x)}, {_source.MakeCastToFloat(y)}"); - } - - public void BezierCurveTo(double cp1X, double cp1Y, double cp2X, double cp2Y, double x, double y) - { - _source.WriteCallCanvasMethod("BezierCurveTo", - $"{_source.MakeCastToFloat(cp1X)}, {_source.MakeCastToFloat(cp1Y)}, {_source.MakeCastToFloat(cp2X)}, {_source.MakeCastToFloat(cp2Y)}, {_source.MakeCastToFloat(x)}, {_source.MakeCastToFloat(y)}"); - } - - public void QuadraticCurveTo(double cpx, double cpy, double x, double y) - { - _source.WriteCallCanvasMethod("QuadraticCurveTo", $"{_source.MakeCastToFloat(cpx)}, {_source.MakeCastToFloat(cpy)}, {_source.MakeCastToFloat(x)}, {_source.MakeCastToFloat(y)}"); - } -} \ No newline at end of file diff --git a/lib/test/test-generator/Program.cs b/lib/test/test-generator/Program.cs deleted file mode 100644 index 723878b..0000000 --- a/lib/test/test-generator/Program.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Globalization; -using TestGenerator; - -CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; -CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; -CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; -CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture; - -var rootDir = PathUtils.RepositoryRoot; - -var csharpTestFile = Path.Combine(rootDir, "lib", "dotnet", "AlphaSkia.Test", "AlphaTabGeneratedTest.cs"); -File.WriteAllText(csharpTestFile, AlphaSkiaUnitTestGenerator.GenerateCSharp()); -Console.WriteLine("Updated C# source in {0}", csharpTestFile); - -var javaTestFile = Path.Combine(rootDir, "lib", "java", "main", "src", "test", "java", "net", "alphatab", "alphaskia", - "AlphaTabGeneratedRenderTest.java"); -File.WriteAllText(javaTestFile, AlphaSkiaUnitTestGenerator.GenerateJava()); -Console.WriteLine("Updated Java source in {0}", javaTestFile); - -var typeScriptTestFile = Path.Combine(rootDir, "lib", "node", "test", "AlphaTabGeneratedRenderTest.test.ts"); -File.WriteAllText(typeScriptTestFile, AlphaSkiaUnitTestGenerator.GenerateTypeScript()); -Console.WriteLine("Updated TypeScript source in {0}", typeScriptTestFile); \ No newline at end of file diff --git a/lib/test/test-generator/SourceBuilder.cs b/lib/test/test-generator/SourceBuilder.cs deleted file mode 100644 index dd8af69..0000000 --- a/lib/test/test-generator/SourceBuilder.cs +++ /dev/null @@ -1,312 +0,0 @@ -using System.Text; - -namespace TestGenerator; - -abstract class SourceBuilder -{ - private readonly StringBuilder _source = new(); - private bool _isStartOfLine; - private int _indent; - private bool _suspended; - - public override string ToString() - { - return _source.ToString(); - } - - public void WriteLine(string? text = null) - { - if (_suspended) - { - return; - } - if (text != null) - { - Write(text); - } - - _source.AppendLine(); - _isStartOfLine = true; - } - - public void Write(string text) - { - if (_suspended) - { - return; - } - WriteIndent(); - _source.Append(text); - _isStartOfLine = false; - } - - private void WriteIndent() - { - if (_isStartOfLine && _indent > 0) - { - _source.Append("".PadLeft(4 * _indent, ' ')); - _isStartOfLine = false; - } - } - - public void BeginBlock() - { - if (_suspended) - { - return; - } - WriteLine("{"); - _indent++; - } - - public void EndBlock(bool statement = false) - { - if (_suspended) - { - return; - } - _indent--; - WriteLine("}" + (statement ? ";" : "")); - } - - public abstract string MakeGetProperty(string property); - public abstract string MakeMethodAccess(string expression, string member); - public abstract string MakeMethodAccess(string member); - - public abstract void WriteSetProperty(string property, string value); - public abstract void WriteSetCanvasProperty(string property, string value); - public abstract void WriteMethodDeclaration(string visibility, string returnType, string methodName, (string type, string name)[] parameters); - - public abstract string UnicodeEscape(int x); - public abstract string MakeEnumAccess(string type, string field); - public string MakeCastToFloat(double value) - { - return MakeCastToFloat(value.ToString(System.Globalization.CultureInfo.InvariantCulture)); - } - public abstract string MakeCastToFloat(string value); - public abstract string MakeCastToByte(double value); - - public void WriteCallCanvasMethod(string methodName, string args = "", bool statement = true) - { - WriteLine(this.MakeCallMethod(MakeMethodAccess("canvas", methodName), args, statement)); - } - - public abstract string MakeCallMethod(string methodName, string args = "", bool statement = true, bool notNull = false); - - public string MakeCallStaticMethod(string typeName, string methodName, string args = "", bool statement = true) - { - return $"{MakeMethodAccess(typeName, methodName)}({args}){(statement ? ";" : "")}"; - } - - protected static string ToPascalCase(string s) - { - return s.Substring(0, 1).ToUpperInvariant() + s.Substring(1); - } - - protected string ToCamelCase(string s) - { - return s.Substring(0, 1).ToLowerInvariant() + s.Substring(1); - } - - public string EncodeString(string text) - { - return System.Text.Json.JsonSerializer.Serialize(text); - } - - public void Resume() - { - _suspended = false; - } - - public void Suspend() - { - _suspended = true; - } -} - -class CSharpSourceBuilder : SourceBuilder -{ - public override void WriteMethodDeclaration(string visibility, string returnType, string methodName, (string type, string name)[] parameters) - { - var p = string.Join(", ", parameters.Select(p => $"{p.type} {p.name}")); - WriteLine($"{visibility} {returnType} {ToPascalCase(methodName)}({p})"); - } - - public override string MakeCastToFloat(string value) - { - return $"(float)({value})"; - } - - public override string MakeCastToByte(double value) - { - return $"(byte)({value})"; - } - - public override string MakeCallMethod(string methodName, string args = "", bool statement = true, bool notNull = false) - { - return $"{methodName}({args}){(notNull ? "!" : "")}{(statement ? ";" : "")}"; - } - - private string MakePropertyAccess(string expression, string member) - { - return $"{expression}.{ToPascalCase(member)}"; - } - - public override string MakeMethodAccess(string member) - { - return ToPascalCase(member); - } - - public override string MakeMethodAccess(string expression, string member) - { - return $"{expression}.{ToPascalCase(member)}"; - } - - public override void WriteSetProperty(string property, string value) - { - WriteLine($"{ToPascalCase(property)} = {value};"); - } - - public override string MakeGetProperty(string property) - { - return ToPascalCase(property); - } - - public override string MakeEnumAccess(string type, string field) - { - return $"{type}.{ToPascalCase(field)}"; - } - - public override string UnicodeEscape(int x) - { - return $"\\x{x:X4}"; - } - - public override void WriteSetCanvasProperty(string property, string value) - { - WriteLine($"{MakePropertyAccess("canvas", property)} = {value};"); - } -} - -class JavaSourceBuilder : SourceBuilder -{ - public override void WriteMethodDeclaration(string visibility, string returnType, string methodName, (string type, string name)[] parameters) - { - var p = string.Join(", ", parameters.Select(p => $"{p.type} {p.name}")); - Write($"{visibility} {returnType} {ToCamelCase(methodName)}({p}) "); - } - - public override string MakeCallMethod(string methodName, string args = "", bool statement = true, bool notNull = false) - { - return $"{methodName}({args}){(statement ? ";" : "")}"; - } - - public override string MakeCastToFloat(string value) - { - return $"(float)({value})"; - } - - public override string MakeCastToByte(double value) - { - return $"(byte)({value})"; - } - - public override string MakeMethodAccess(string member) - { - return ToCamelCase(member); - } - - public override string MakeMethodAccess(string expression, string member) - { - return $"{expression}.{ToCamelCase(member)}"; - } - - public override void WriteSetProperty(string property, string value) - { - WriteLine($"set{ToPascalCase(property)}({value});"); - } - - public override string MakeGetProperty(string property) - { - return $"get{ToPascalCase(property)}()"; - } - - public override string MakeEnumAccess(string type, string field) - { - return $"{type}.{field.ToUpperInvariant()}"; - } - - public override string UnicodeEscape(int x) - { - return $"\\u{x:XXXX}"; - } - - public override void WriteSetCanvasProperty(string property, string value) - { - WriteLine($"canvas.set{ToPascalCase(property)}({value});"); - } -} - -class TypeScriptSourceBuilder : SourceBuilder -{ - public override void WriteMethodDeclaration(string visibility, string returnType, string methodName, (string type, string name)[] parameters) - { - var p = string.Join(", ", parameters.Select(p => $"{p.name}: {p.type}")); - Write($"function {ToCamelCase(methodName)}({p}) : {returnType} "); - } - - public override string MakeCallMethod(string methodName, string args = "", bool statement = true, bool notNull = false) - { - return $"{methodName}({args}){(notNull ? "!" : "")}{(statement ? ";" : "")}"; - } - - public override string MakeCastToFloat(string value) - { - return value; - } - - public override string MakeCastToByte(double value) - { - return $"{(byte)(value)}"; - } - - private string MakePropertyAccess(string expression, string member) - { - return $"{expression}.{ToCamelCase(member)}"; - } - - public override string MakeMethodAccess(string member) - { - return $"test.{ToCamelCase(member)}"; - } - - public override string MakeMethodAccess(string expression, string member) - { - return $"{expression}.{ToCamelCase(member)}"; - } - - public override void WriteSetProperty(string property, string value) - { - WriteLine($"test.{ToCamelCase(property)} = {value};"); - } - - public override string MakeGetProperty(string property) - { - return $"test.{ToCamelCase(property)}"; - } - - public override string MakeEnumAccess(string type, string field) - { - return $"{type}.{ToPascalCase(field)}"; - } - - public override string UnicodeEscape(int x) - { - return $"\\x{x:X4}"; - } - - public override void WriteSetCanvasProperty(string property, string value) - { - WriteLine($"{MakePropertyAccess("canvas", property)} = {value};"); - } -} diff --git a/lib/dotnet/AlphaSkia.Test/AlphaSkia.Test.csproj b/test/dotnet/AlphaSkia.Test/AlphaSkia.Test.csproj similarity index 100% rename from lib/dotnet/AlphaSkia.Test/AlphaSkia.Test.csproj rename to test/dotnet/AlphaSkia.Test/AlphaSkia.Test.csproj diff --git a/lib/dotnet/AlphaSkia.Test/AlphaTabGeneratedTest.cs b/test/dotnet/AlphaSkia.Test/AlphaTabGeneratedTest.cs similarity index 100% rename from lib/dotnet/AlphaSkia.Test/AlphaTabGeneratedTest.cs rename to test/dotnet/AlphaSkia.Test/AlphaTabGeneratedTest.cs diff --git a/lib/dotnet/AlphaSkia.Test/MusicSheetRenderTest.cs b/test/dotnet/AlphaSkia.Test/MusicSheetRenderTest.cs similarity index 100% rename from lib/dotnet/AlphaSkia.Test/MusicSheetRenderTest.cs rename to test/dotnet/AlphaSkia.Test/MusicSheetRenderTest.cs diff --git a/lib/dotnet/AlphaSkia.Test/Usings.cs b/test/dotnet/AlphaSkia.Test/Usings.cs similarity index 100% rename from lib/dotnet/AlphaSkia.Test/Usings.cs rename to test/dotnet/AlphaSkia.Test/Usings.cs diff --git a/test/java/.gitignore b/test/java/.gitignore new file mode 100644 index 0000000..b63da45 --- /dev/null +++ b/test/java/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/test/java/build.gradle.kts b/test/java/build.gradle.kts new file mode 100644 index 0000000..da9e4a0 --- /dev/null +++ b/test/java/build.gradle.kts @@ -0,0 +1,165 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import java.io.FileInputStream +import java.util.* + +buildscript { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} + + +var sonatypeSigningKeyId = "" +var sonatypeSigningPassword = "" +var sonatypeSigningKey = "" +var ossrhUsername = "" +var ossrhPassword = "" +var sonatypeStagingProfileId = "" + +var libDescription = "" +var libAuthorId = "" +var libAuthorName = "" +var libOrgUrl = "" +var libCompany = "" +var libVersion = "" +var libProjectUrl = "" +var libGitUrlHttp = "" +var libGitUrlGit = "" +var libLicenseSpdx = "" +var libLicenseUrl = "" +var libIssuesUrl = "" + +val props = Properties() +val propsFile = project.rootProject.file("local.properties") +if (propsFile.exists()) { + FileInputStream(propsFile).use { + props.load(it) + } +} + +fun loadSetting(envKey: String, propKey: String, setter: (value: String) -> Unit) { + if (props.containsKey(propKey)) { + setter(props.getProperty(propKey)) + } else { + val env = providers + .environmentVariable(envKey) + if (env.isPresent) { + setter(env.get()) + } + } +} + +loadSetting("OSSRH_USERNAME", "ossrhUsername") { ossrhUsername = it } +loadSetting("OSSRH_PASSWORD", "ossrhPassword") { ossrhPassword = it } +loadSetting("SONATYPE_STAGING_PROFILE_ID", "sonatypeStagingProfileId") { sonatypeStagingProfileId = it } +loadSetting("SONATYPE_SIGNING_KEY_ID", "sonatypeSigningKeyId") { sonatypeSigningKeyId = it } +loadSetting("SONATYPE_SIGNING_PASSWORD", "sonatypeSigningPassword") { sonatypeSigningPassword = it } +loadSetting("SONATYPE_SIGNING_KEY", "sonatypeSigningKey") { sonatypeSigningKey = it } +loadSetting("ALPHASKIA_DESCRIPTION", "alphaskiaDescription") { libDescription = it } +loadSetting("ALPHASKIA_AUTHOR_ID", "alphaskiaAuthorId") { libAuthorId = it } +loadSetting("ALPHASKIA_AUTHOR_NAME", "alphaskiaAuthorName") { libAuthorName = it } +loadSetting("ALPHASKIA_ORG_URL", "alphaskiaOrgUrl") { libOrgUrl = it } +loadSetting("ALPHASKIA_COMPANY", "alphaskiaCompany") { libCompany = it } +loadSetting("ALPHASKIA_VERSION", "alphaskiaVersion") { libVersion = it } +loadSetting("ALPHASKIA_PROJECT_URL", "alphaskiaProjectUrl") { libProjectUrl = it } +loadSetting("ALPHASKIA_GIT_URL_HTTP", "alphaskiaGitUrlHttp") { libGitUrlHttp = it } +loadSetting("ALPHASKIA_GIT_URL_GIT", "alphaskiaGitUrlGit") { libGitUrlGit = it } +loadSetting("ALPHASKIA_LICENSE_SPDX", "alphaskiaLicenseSpdx") { libLicenseSpdx = it } +loadSetting("ALPHASKIA_LICENSE_URL", "alphaskiaLicenseUrl") { libLicenseUrl = it } +loadSetting("ALPHASKIA_ISSUES_URL", "alphaskiaIssuesUrl") { libIssuesUrl = it } + + + +subprojects { + apply() + apply() + apply(plugin = "maven-publish") + + group = "net.alphatab" + version = libVersion + + repositories { + google() + mavenCentral() + } + + configure { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } + withSourcesJar() + withJavadocJar() + } + + tasks.withType().configureEach { + systemProperty("alphaskia.library.path", rootProject.projectDir.resolve("../../dist/")) + systemProperty("testdata.path", rootProject.projectDir.resolve("../test/")) + systemProperty("testoutput.path", projectDir.resolve("../test-outputs/")) + testLogging { + exceptionFormat = TestExceptionFormat.FULL + showStackTraces = true + } + } + + configure { + repositories { +// maven { +// name = "sonatype" +// url = uri( +// if (libVersion.endsWith("SNAPSHOT")) +// "https://s01.oss.sonatype.org/content/repositories/snapshots/" +// else +// "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" +// ) +// credentials { +// username = ossrhUsername +// password = ossrhPassword +// } +// } + + maven { + name = "DistPath" + url = rootProject.projectDir.resolve("dist").toURI() + } + } + + publications { + create("mavenJava") { + from(components["java"]) + afterEvaluate { + artifactId = tasks.withType().first().archiveBaseName.get() + + pom { + description = libDescription + url = libProjectUrl + licenses { + license { + name = libLicenseSpdx + url = libLicenseUrl + } + } + developers { + developer { + id = libAuthorId + name = libAuthorName + organization = libCompany + organizationUrl = libOrgUrl + } + } + scm { + url = libGitUrlHttp + connection = "scm:git:$libGitUrlGit" + developerConnection = "scm:git:$libGitUrlGit" + } + issueManagement { + system = "GitHub" + url = libIssuesUrl + } + } + } + } + } + } +} diff --git a/test/java/gradle/wrapper/gradle-wrapper.jar b/test/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/test/java/gradlew.bat b/test/java/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/test/java/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/test/java/local.properties b/test/java/local.properties new file mode 100644 index 0000000..592b3f2 --- /dev/null +++ b/test/java/local.properties @@ -0,0 +1,20 @@ +## This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Sat Oct 14 15:29:14 CEST 2023 +alphaskiaAuthorId=danielku15 +alphaskiaAuthorName=Daniel Kuschny +alphaskiaCompany=CoderLine +alphaskiaDescription=A Skia based rendering backend for alphaTab. +alphaskiaGitUrlGit"=git\://github.com/CoderLine/alphaSkia.git +alphaskiaGitUrlHttp=https\://github.com/CoderLine/alphaSkia.git +alphaskiaIssuesUrl=https\://github.com/CoderLine/alphaSkia/issues +alphaskiaLicenseSpdx=BSD-3-Clause +alphaskiaLicenseUrl=https\://opensource.org/license/bsd-3-clause +alphaskiaOrgUrl=https\://github.com/CoderLine +alphaskiaProjectUrl=https\://github.com/CoderLine/alphaSkia +alphaskiaVersion=1.0.0-LOCAL +sdk.dir=C\:\\Users\\Daniel\\AppData\\Local\\Android\\Sdk diff --git a/test/java/main/build.gradle.kts b/test/java/main/build.gradle.kts new file mode 100644 index 0000000..e8903d4 --- /dev/null +++ b/test/java/main/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + id("java-library") + `maven-publish` + signing +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } + withSourcesJar() + withJavadocJar() +} + +tasks.withType().configureEach { + options.headerOutputDirectory = rootProject.projectDir.resolve("jni/include") +} + +dependencies { + testImplementation(platform("org.junit:junit-bom:5.9.1")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +tasks.test { + useJUnitPlatform() +} + +tasks.jar { + archiveBaseName = "net.alphatab.alphaskia" +} \ No newline at end of file diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaCanvas.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaCanvas.java new file mode 100644 index 0000000..8991876 --- /dev/null +++ b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaCanvas.java @@ -0,0 +1,247 @@ +package net.alphatab.alphaskia; + +/** + * A Skia based canvas for rendering images. + */ +public class AlphaSkiaCanvas extends AlphaSkiaNative { + private static AlphaSkiaColorType colorType; + + /** + * Gets the {@link AlphaSkiaColorType} used when generating images. + * AlphaSkia tries to use the native color tpe of the system but this can vary based on OS. + * @return The {@link AlphaSkiaColorType} used when generating images + */ + public static AlphaSkiaColorType getColorType() { + if (colorType == null) { + colorType = AlphaSkiaColorType.fromValue(alphaskiaColorType()); + } + return colorType; + } + + /** + * Gets the color to use for drawing operations in the native canvas. + * See also {@link #rgbaToColor} + * @return The color to use for drawing operations in the native canvas + */ + public native int getColor(); + + /** + * Sets the color to use for drawing operations in the native canvas. + * See also {@link #rgbaToColor} + * @param color The color to use for drawing operations in the native canvas. + */ + public native void setColor(int color); + + /** + * Encodes the given color components into the right format. + * + * @param r The red component of the color. + * @param g The green component of the color. + * @param b The blue component of the color. + * @param a The alpha channel of the color. + * @return The encoded color. + */ + public static int rgbaToColor(byte r, byte g, byte b, byte a) { + return (((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | + (b & 0xFF)); + } + + /** + * Gets the line width to use when drawing strokes and lines. + * @return The line width to use when drawing strokes and lines. + */ + public native float getLineWidth(); + + /** + * Sets the line width to use when drawing strokes and lines. + * @param lineWidth The line width to use when drawing strokes and lines + */ + public native void setLineWidth(float lineWidth); + + /** + * Initializes a new empty canvas to use for drawing. + */ + public AlphaSkiaCanvas() { + super(alphaskiaCanvasAllocate()); + } + + @Override + public native void close(); + + /** + * Starts a new rendering session in the canvas. + * + * @param width The width of the image to produce. + * @param height The height of the image to produce. + */ + public void beginRender(int width, int height) + { + beginRender(width, height, 1); + } + + /** + * Starts a new rendering session in the canvas. + * + * @param width The width of the image to produce. + * @param height The height of the image to produce. + * @param scaleFactor The scale factor for the image (e.g. for high DPI rendering with keeping coordinates). + */ + public native void beginRender(int width, int height, float scaleFactor); + + /** + * Draws the given image into the canvas. + * + * @param image The image to draw. + * @param x The X-coordinate at which to draw the image. + * @param y The Y-coordinate at which to draw the image. + * @param w The target width to which the image should be scaled. + * @param h The target height to which the image should be scaled. + */ + public native void drawImage(AlphaSkiaImage image, float x, float y, float w, float h); + + /** + * Ends the rendering session and provides the rendered result. + * + * @return The rendered result or {@code null} if something went wrong. + */ + public native AlphaSkiaImage endRender(); + + /** + * Fills the given rectangle with the current color. + * + * @param x The X-coordinate of the rectangle + * @param y The Y-coordinate of the rectangle + * @param width The width of the rectangle + * @param height The height of the rectangle + */ + public native void fillRect(float x, float y, float width, float height); + + /** + * Strokes the given rectangle with the current color. + * + * @param x The X-coordinate of the rectangle + * @param y The Y-coordinate of the rectangle + * @param width The width of the rectangle + * @param height The height of the rectangle + */ + public native void strokeRect(float x, float y, float width, float height); + + /** + * Begins a new dynamic path for rendering. + */ + public native void beginPath(); + + /** + * Closes the started path by connecting the last point and the first point. + */ + public native void closePath(); + + /** + * Moves the current position within the current path to the given position. + * + * @param x The X-position where to continue the path. + * @param y The Y-position where to continue the path. + */ + public native void moveTo(float x, float y); + + /** + * Draws a line from the current path position to the given position. + * + * @param x The X-position to which to draw a line. + * @param y The Y-position to which to draw a line. + */ + public native void lineTo(float x, float y); + + /** + * Draws a quadratic curve from the current path position to the given position. + * + * @param cpx The X-position of the control point. + * @param cpy The Y-position of the control point. + * @param x The X-position of the curve end. + * @param y The Y-position of the curve end. + */ + public native void quadraticCurveTo(float cpx, float cpy, float x, float y); + + /** + * Draws a Bézier curve from the current path position to the given position. + * + * @param cp1X The X-position of the first control point. + * @param cp1Y The Y-position of the first control point. + * @param cp2X The X-position of the second control point. + * @param cp2Y The Y-position of the second control point. + * @param x The X-position of the curve end. + * @param y The Y-position of the curve end. + */ + public native void bezierCurveTo(float cp1X, float cp1Y, float cp2X, float cp2Y, float x, float y); + + /** + * Fills a circle with the current color. + * + * @param x The X-position of the circle center. + * @param y The Y-position of the circle center. + * @param radius The circle radius. + */ + public native void fillCircle(float x, float y, float radius); + + /** + * Strokes a circle with the current color and line width. + * + * @param x The X-position of the circle center. + * @param y The Y-position of the circle center. + * @param radius The circle radius. + */ + public native void strokeCircle(float x, float y, float radius); + + /** + * Fills the current path with the current color. + */ + public native void fill(); + + /** + * Strokes the current path with the current color and line width. + */ + public native void stroke(); + + /** + * Fills a text with the current color and provided details. + * + * @param text The text to draw. + * @param typeface The typeface to use for drawing the text. + * @param fontSize The font size to use when drawing the text. + * @param x The X-position where to draw the text to. + * @param y The Y-position where to draw the text to. + * @param textAlign How to align the text at the given position horizontally. + * @param baseline How to align the text at the given position vertically. + */ + public native void fillText(String text, AlphaSkiaTypeface typeface, float fontSize, float x, float y, + AlphaSkiaTextAlign textAlign, + AlphaSkiaTextBaseline baseline); + + /** + * Measures the given text. + * + * @param text The text to measure. + * @param typeface The typeface to use for drawing the text. + * @param fontSize The font size to use when drawing the text. + * @return The horizontal width of the text when it would be drawn. + */ + public native float measureText(String text, AlphaSkiaTypeface typeface, float fontSize); + + /** + * Rotates the canvas allowing angled drawing. . + * + * @param centerX The X-position of the center around which to rotate. + * @param centerY The Y-position of the center around which to rotate. + * @param angle The angle in degrees to rotate. + */ + public native void beginRotate(float centerX, float centerY, float angle); + + /** + * Restores the previous rotation state after {@link #beginRotate} was called. + */ + public native void endRotate(); + + private native static long alphaskiaCanvasAllocate(); + + private static native int alphaskiaColorType(); +} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaColorType.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaColorType.java new file mode 100644 index 0000000..35b4e30 --- /dev/null +++ b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaColorType.java @@ -0,0 +1,38 @@ +package net.alphatab.alphaskia; + +/** + * Lists all known color type with which AlphaSkia creates images. + */ +public enum AlphaSkiaColorType { + // NOTE: we internally use kN32_SkColorType which is either kBGRA_8888_SkColorType or kRGBA_8888_SkColorType + // hence we don't need to expose the other SkColorType values + + /** + * pixel with 8 bits for red, green, blue, alpha; in 32-bit word + */ + RGBA_8888(4), + + /** + * pixel with 8 bits for blue, green, red, alpha; in 32-bit word + */ + BGRA_8888(6); + + private final int value; + + int getValue() { + return value; + } + + AlphaSkiaColorType(int value) { + this.value = value; + } + + static AlphaSkiaColorType fromValue(int value) { + if (value == RGBA_8888.getValue()) { + return RGBA_8888; + } else if (value == BGRA_8888.getValue()) { + return BGRA_8888; + } + return null; + } +} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaData.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaData.java new file mode 100644 index 0000000..0fd16e7 --- /dev/null +++ b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaData.java @@ -0,0 +1,18 @@ +package net.alphatab.alphaskia; + +class AlphaSkiaData extends AlphaSkiaNative { + AlphaSkiaData(long handle) { + super(handle); + } + + AlphaSkiaData(byte[] raw) { + super(allocateCopy(raw)); + } + + private static native long allocateCopy(byte[] raw); + + @Override + public native void close(); + + public native byte[] toArray(); +} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java new file mode 100644 index 0000000..f072960 --- /dev/null +++ b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java @@ -0,0 +1,41 @@ +package net.alphatab.alphaskia; + +/** + * Represents a final rendered image. + */ +public class AlphaSkiaImage extends AlphaSkiaNative { + /** + * Gets the width of the image. + * + * @return the width of the image + */ + public native int getWidth(); + + /** + * Gets the height of the image. + * + * @return the height of the image + */ + public native int getHeight(); + + AlphaSkiaImage(long handle) { + super(handle); + } + + @Override + public native void close(); + + /** + * Reads the raw pixel data of this image as byte array. + * + * @return A copy of the raw pixel data. + */ + public native byte[] readPixels(); + + /** + * Encodes the image to a PNG. + * + * @return The raw PNG bytes for further usage. + */ + public native byte[] toPng(); +} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaNative.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaNative.java new file mode 100644 index 0000000..49a357d --- /dev/null +++ b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaNative.java @@ -0,0 +1,22 @@ +package net.alphatab.alphaskia; + +/** + * The base class for AlphaSkia objects wrapping native Skia objects. + */ +public abstract class AlphaSkiaNative implements AutoCloseable { + static { + if(!AlphaSkiaPlatform.isNativeLibLoaded()) { + throw new IllegalStateException("Initialize the alphaSkia platform first"); + } + } + + long handle; + + AlphaSkiaNative(long handle) { + this.handle = handle; + } + + + @Override + public abstract void close() throws Exception; +} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaPlatform.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaPlatform.java new file mode 100644 index 0000000..0142ed7 --- /dev/null +++ b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaPlatform.java @@ -0,0 +1,79 @@ +package net.alphatab.alphaskia; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * This class provides a way of resolving and loading the native libraries required + * by alphaSkia. + */ +public final class AlphaSkiaPlatform { + private static boolean nativeLibLoaded = false; + + /** + * Gets a value indicating whether the native libraries required by alphaSkia were loaded. + * + * @return {@code true} if the libraries were loaded, otherwise {@code false}. + */ + public static boolean isNativeLibLoaded() { + return nativeLibLoaded; + } + + /** + * Initiates the loading of native libraries from the given file paths assuming they are the correct ones for alphaSkia. + * + * @param nativeLibraryPaths The paths to the files to load. + * @throws IOException Thrown if any of the paths provided point to a non-existent file. + */ + public static void loadLibrary(String[] nativeLibraryPaths) throws IOException { + for (String library : nativeLibraryPaths) { + var file = new File(library); + if (!file.exists()) { + throw new FileNotFoundException("Could not find file: " + library); + } + System.load(library); + } + nativeLibLoaded = true; + } + + /** + * Initiates loading of native libraries from the given platform information class shipped as part of + * individual alphaSkia platform packages. + * + * @param platformInfo The platform info class like {@code net.alphatab.alphaskia.ALphaSkiaWindows} provided + * through individual platform packages. + * @throws IOException Thrown if the resources expected to be provided by the platform package could not be found or extracted. + * @throws IllegalArgumentException Thrown if the provided platform info class is not compliant with the needs of alphaSkia. Indicates typically an incompatibility or a wrong class being provided. + */ + public static void loadLibrary(Class platformInfo) throws IOException { + try { + String[] libraries = (String[]) platformInfo.getDeclaredField("libraries").get(null); + + Path tmpDir = Files.createTempDirectory("alphaskia"); + for (String library : libraries) { + URL resource = platformInfo.getResource(library); + if (resource == null) { + throw new IOException("Resource " + library + " not found"); + } + File tmpLib = tmpDir.resolve(Paths.get(library).getFileName().toString()).toFile(); + tmpLib.deleteOnExit(); + try (InputStream in = resource.openStream()) { + Files.copy(in, tmpLib.toPath()); + } + System.load(tmpLib.getAbsolutePath()); + } + nativeLibLoaded = true; + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new IllegalArgumentException("Type " + platformInfo.getName() + " seems not to be a valid AlphaSkia Target", e); + } + } + + private AlphaSkiaPlatform() { + } +} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextAlign.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextAlign.java new file mode 100644 index 0000000..7a4473d --- /dev/null +++ b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextAlign.java @@ -0,0 +1,29 @@ +package net.alphatab.alphaskia; + +/** + * Lists all text alignments which can be used to draw text. + */ +public enum AlphaSkiaTextAlign { + /** + * The text is drawn left aligned to the provided position. + */ + LEFT(0), + /** + * The text is drawn centered to the provided position. + */ + CENTER(1), + /** + * The text is drawn right aligned to the provided position. + */ + RIGHT(2); + + private final int value; + + int getValue() { + return this.value; + } + + AlphaSkiaTextAlign(int value) { + this.value = value; + } +} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextBaseline.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextBaseline.java new file mode 100644 index 0000000..10c6181 --- /dev/null +++ b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextBaseline.java @@ -0,0 +1,34 @@ +package net.alphatab.alphaskia; + +/** + * Lists all vertical text baseline alignments which can be used to draw text. + */ +public enum AlphaSkiaTextBaseline { + /** + * The text is drawn using the alphabetic baseline. + */ + ALPHABETIC(0), + /** + * The text is top-aligned to the provided position. + */ + TOP(1), + /** + * The test is middle-aligned (vertically centered) to the provided position. + */ + MIDDLE(2), + + /** + * The text is bottom-aligned to the provided position. + */ + BOTTOM(3); + + private final int value; + + int getValue() { + return this.value; + } + + AlphaSkiaTextBaseline(int value) { + this.value = value; + } +} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTypeface.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTypeface.java new file mode 100644 index 0000000..55bffd7 --- /dev/null +++ b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTypeface.java @@ -0,0 +1,60 @@ +package net.alphatab.alphaskia; + +/** + * Represents a typeface to draw text. + */ +public class AlphaSkiaTypeface extends AlphaSkiaNative { + private final AlphaSkiaData data; + + private AlphaSkiaTypeface(long handle, AlphaSkiaData data) { + super(handle); + this.data = data; + } + + @Override + public void close() { + AlphaSkiaData data = this.data; + if (data != null) { + data.close(); + } + release(handle); + handle = 0; + } + + private native void release(long handle); + + /** + * Register a new custom font from the given binary data containing the data of a font compatible with Skia (e.g. TTF). + * @param data The raw binary data of the font. + * @return The loaded typeface to use for text rendering or {@code null} if the loading failed. + */ + public static AlphaSkiaTypeface register(byte[] data) { + var nativeData = new AlphaSkiaData(data); + var typeface = register(nativeData.handle); + if (typeface == 0) { + nativeData.close(); + return null; + } + return new AlphaSkiaTypeface(typeface, nativeData); + } + + private static native long register(long handle); + + /** + * Creates a typeface using the provided information. + * @param name The name of the typeface. + * @param bold Whether the bold version of the typeface should be loaded. + * @param italic Whether the italic version of the typeface should be loaded. + * @return The typeface if it can be found in the already loaded fonts or the system fonts, otherwise {@code null}. + */ + public static AlphaSkiaTypeface create(String name, boolean bold, boolean italic) { + var typeface = makeFromName(name, bold, italic); + if (typeface == 0) { + return null; + } + + return new AlphaSkiaTypeface(typeface, null); + } + + private static native long makeFromName(String name, boolean bold, boolean italic); +} diff --git a/test/java/main/src/test/java/net/alphatab/alphaskia/AlphaTabGeneratedRenderTest.java b/test/java/main/src/test/java/net/alphatab/alphaskia/AlphaTabGeneratedRenderTest.java new file mode 100644 index 0000000..862f1a7 --- /dev/null +++ b/test/java/main/src/test/java/net/alphatab/alphaskia/AlphaTabGeneratedRenderTest.java @@ -0,0 +1,752 @@ +package net.alphatab.alphaskia; + +public class AlphaTabGeneratedRenderTest extends MusicSheetRenderTest +{ + private AlphaSkiaImage drawMusicSheetPart1(AlphaSkiaCanvas canvas) { + canvas.beginRender(1300, 164, (float)(getRenderScale())); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + setTextAlign(AlphaSkiaTextAlign.CENTER); + setTypeface(getTypeface("PT Serif", false, false)); + setFontSize((float)(32)); + setTextAlign(AlphaSkiaTextAlign.CENTER); + canvas.fillText("Title", getTypeface(), getFontSize(), (float)(650), (float)(40), getTextAlign(), getTextBaseline()); + setTextAlign(AlphaSkiaTextAlign.CENTER); + setTypeface(getTypeface("PT Serif", false, false)); + setFontSize((float)(20)); + setTextAlign(AlphaSkiaTextAlign.CENTER); + canvas.fillText("Subtitle", getTypeface(), getFontSize(), (float)(650), (float)(72), getTextAlign(), getTextBaseline()); + setTextAlign(AlphaSkiaTextAlign.CENTER); + setTypeface(getTypeface("PT Serif", false, false)); + setFontSize((float)(20)); + setTextAlign(AlphaSkiaTextAlign.CENTER); + canvas.fillText("Artist", getTypeface(), getFontSize(), (float)(650), (float)(92), getTextAlign(), getTextBaseline()); + setTextAlign(AlphaSkiaTextAlign.CENTER); + setTypeface(getTypeface("PT Serif", false, false)); + setFontSize((float)(20)); + setTextAlign(AlphaSkiaTextAlign.CENTER); + canvas.fillText("Album", getTypeface(), getFontSize(), (float)(650), (float)(112), getTextAlign(), getTextBaseline()); + setTextAlign(AlphaSkiaTextAlign.CENTER); + setTypeface(getTypeface("PT Serif", false, false)); + setFontSize((float)(15)); + setTextAlign(AlphaSkiaTextAlign.RIGHT); + canvas.fillText("Music by Music", getTypeface(), getFontSize(), (float)(1260), (float)(132), getTextAlign(), getTextBaseline()); + setTextAlign(AlphaSkiaTextAlign.CENTER); + setTypeface(getTypeface("PT Serif", false, false)); + setFontSize((float)(15)); + setTextAlign(AlphaSkiaTextAlign.LEFT); + canvas.fillText("Words by Words", getTypeface(), getFontSize(), (float)(40), (float)(132), getTextAlign(), getTextBaseline()); + setTextAlign(AlphaSkiaTextAlign.CENTER); + return canvas.endRender(); + } + private AlphaSkiaImage drawMusicSheetPart2(AlphaSkiaCanvas canvas) { + canvas.beginRender(1300, 38, (float)(getRenderScale())); + setTextAlign(AlphaSkiaTextAlign.CENTER); + setTypeface(getTypeface("PT Serif", false, true)); + setFontSize((float)(12)); + setTextAlign(AlphaSkiaTextAlign.LEFT); + canvas.fillText("Guitar Standard Tuning", getTypeface(), getFontSize(), (float)(40), (float)(3), getTextAlign(), getTextBaseline()); + setTextAlign(AlphaSkiaTextAlign.CENTER); + return canvas.endRender(); + } + private AlphaSkiaImage drawMusicSheetPart3(AlphaSkiaCanvas canvas) { + canvas.beginRender(1300, 6, (float)(getRenderScale())); + setTextAlign(AlphaSkiaTextAlign.CENTER); + return canvas.endRender(); + } + private AlphaSkiaImage drawMusicSheetPart4(AlphaSkiaCanvas canvas) { + canvas.beginRender(1300, 373, (float)(getRenderScale())); + setTextAlign(AlphaSkiaTextAlign.LEFT); + setTypeface(getTypeface("PT Serif", true, false)); + setFontSize((float)(14)); + canvas.fillText("\uE1D5", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(156.6767578125), (float)(40), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("= 120", getTypeface(), getFontSize(), (float)(169.1767578125), (float)(27), getTextAlign(), getTextBaseline()); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); + canvas.fillRect((float)(92), (float)(130), (float)(254), (float)(1.04)); + canvas.fillRect((float)(92), (float)(139), (float)(254), (float)(1.04)); + canvas.fillRect((float)(92), (float)(148), (float)(254), (float)(1.04)); + canvas.fillRect((float)(92), (float)(157), (float)(254), (float)(1.04)); + canvas.fillRect((float)(92), (float)(166), (float)(254), (float)(1.04)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + canvas.fillText("\uE050", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(94), (float)(157.52), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE084", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(127), (float)(139), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE084", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(127), (float)(157), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(200), (byte)(0), (byte)(0), (byte)(255))); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("1", getTypeface(), getFontSize(), (float)(141), (float)(118.75), getTextAlign(), getTextBaseline()); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(152.1767578125), (float)(148), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE272", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(172.86069962449085), (float)(148), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(180.86069962449085), (float)(149), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.beginPath(); + canvas.moveTo((float)(156.6767578125), (float)(144)); + canvas.lineTo((float)(170.855110706305), (float)(133.50723450810483)); + canvas.lineTo((float)(184.23569962449085), (float)(145)); + canvas.stroke(); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(192.23569962449085), (float)(148), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(220.91964143648167), (float)(144.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.beginPath(); + canvas.moveTo((float)(196.73569962449085), (float)(144)); + canvas.lineTo((float)(209.12929655256082), (float)(131.33765133817155)); + canvas.lineTo((float)(224.29464143648167), (float)(140.5)); + canvas.stroke(); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(232.29464143648167), (float)(148), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE262", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(252.9785832484725), (float)(143.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(260.9785832484725), (float)(144.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.beginPath(); + canvas.moveTo((float)(236.79464143648167), (float)(144)); + canvas.lineTo((float)(249.1882383645516), (float)(131.33765133817155)); + canvas.lineTo((float)(264.3535832484725), (float)(140.5)); + canvas.stroke(); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(272.3535832484725), (float)(148), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(294.5517916242362), (float)(140), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.beginPath(); + canvas.moveTo((float)(276.8535832484725), (float)(152)); + canvas.lineTo((float)(292.1060513036615), (float)(156.93783819474976)); + canvas.lineTo((float)(297.9267916242362), (float)(142)); + canvas.stroke(); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(305.9267916242362), (float)(148), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE262", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(324.625), (float)(139), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(332.625), (float)(140), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.beginPath(); + canvas.moveTo((float)(310.4267916242362), (float)(152)); + canvas.lineTo((float)(327.2193887931866), (float)(157.24460932567123)); + canvas.lineTo((float)(336), (float)(142)); + canvas.stroke(); + canvas.fillRect((float)(345), (float)(130.1), (float)(1), (float)(36)); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(152.1767578125), (float)(148)); + canvas.lineTo((float)(152.1767578125), (float)(175)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(192.23569962449085), (float)(148)); + canvas.lineTo((float)(192.23569962449085), (float)(175)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(232.29464143648167), (float)(148)); + canvas.lineTo((float)(232.29464143648167), (float)(175)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(281.3535832484725), (float)(148)); + canvas.lineTo((float)(281.3535832484725), (float)(121)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.beginPath(); + canvas.moveTo((float)(281.3535832484725), (float)(121)); + canvas.lineTo((float)(314.9267916242362), (float)(121)); + canvas.lineTo((float)(314.9267916242362), (float)(125)); + canvas.lineTo((float)(281.3535832484725), (float)(125)); + canvas.closePath(); + canvas.fill(); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(314.9267916242362), (float)(148)); + canvas.lineTo((float)(314.9267916242362), (float)(121)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(100))); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); + canvas.fillRect((float)(346), (float)(130), (float)(126), (float)(1.04)); + canvas.fillRect((float)(346), (float)(139), (float)(126), (float)(1.04)); + canvas.fillRect((float)(346), (float)(148), (float)(126), (float)(1.04)); + canvas.fillRect((float)(346), (float)(157), (float)(126), (float)(1.04)); + canvas.fillRect((float)(346), (float)(166), (float)(126), (float)(1.04)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(200), (byte)(0), (byte)(0), (byte)(255))); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("2", getTypeface(), getFontSize(), (float)(346), (float)(118.75), getTextAlign(), getTextBaseline()); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(357.1767578125), (float)(139), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(378.359547484596), (float)(139), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(399.542337156692), (float)(161.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A3", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(426.623147703567), (float)(157), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillRect((float)(471), (float)(130.1), (float)(1), (float)(36)); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(366.1767578125), (float)(139)); + canvas.lineTo((float)(366.1767578125), (float)(112)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.beginPath(); + canvas.moveTo((float)(366.1767578125), (float)(112)); + canvas.lineTo((float)(387.359547484596), (float)(117)); + canvas.lineTo((float)(387.359547484596), (float)(121)); + canvas.lineTo((float)(366.1767578125), (float)(116)); + canvas.closePath(); + canvas.fill(); + canvas.beginPath(); + canvas.moveTo((float)(366.1767578125), (float)(118)); + canvas.lineTo((float)(387.359547484596), (float)(123)); + canvas.lineTo((float)(387.359547484596), (float)(127)); + canvas.lineTo((float)(366.1767578125), (float)(122)); + canvas.closePath(); + canvas.fill(); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(387.359547484596), (float)(139)); + canvas.lineTo((float)(387.359547484596), (float)(117)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.beginPath(); + canvas.moveTo((float)(387.359547484596), (float)(117)); + canvas.lineTo((float)(408.542337156692), (float)(122)); + canvas.lineTo((float)(408.542337156692), (float)(126)); + canvas.lineTo((float)(387.359547484596), (float)(121)); + canvas.closePath(); + canvas.fill(); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(408.542337156692), (float)(161.5)); + canvas.lineTo((float)(408.542337156692), (float)(122)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(435.623147703567), (float)(130)); + canvas.lineTo((float)(435.623147703567), (float)(157)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(100))); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + setTextAlign(AlphaSkiaTextAlign.CENTER); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("PT Serif", false, true)); + setFontSize((float)(12)); + canvas.beginPath(); + canvas.moveTo((float)(366.1767578125), (float)(102)); + canvas.lineTo((float)(366.1767578125), (float)(97)); + canvas.lineTo((float)(394.7559555656507), (float)(104)); + canvas.stroke(); + canvas.beginPath(); + canvas.moveTo((float)(407.04394995041633), (float)(107)); + canvas.lineTo((float)(435.623147703567), (float)(115)); + canvas.lineTo((float)(435.623147703567), (float)(120)); + canvas.stroke(); + canvas.fillText("3", getTypeface(), getFontSize(), (float)(400.8999527580335), (float)(106), getTextAlign(), getTextBaseline()); + setTextAlign(AlphaSkiaTextAlign.LEFT); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); + canvas.fillRect((float)(472), (float)(130), (float)(206), (float)(1.04)); + canvas.fillRect((float)(472), (float)(139), (float)(206), (float)(1.04)); + canvas.fillRect((float)(472), (float)(148), (float)(206), (float)(1.04)); + canvas.fillRect((float)(472), (float)(157), (float)(206), (float)(1.04)); + canvas.fillRect((float)(472), (float)(166), (float)(206), (float)(1.04)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(200), (byte)(0), (byte)(0), (byte)(255))); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("3", getTypeface(), getFontSize(), (float)(472), (float)(118.75), getTextAlign(), getTextBaseline()); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(496.32666015625), (float)(157), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(496.32666015625), (float)(143.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(496.32666015625), (float)(134.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.beginRotate((float)(527.8699951171875), (float)(172), (float)(-90)); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.endRotate(); + canvas.beginPath(); + canvas.moveTo((float)(527.8699951171875), (float)(180)); + canvas.lineTo((float)(531.8699951171875), (float)(172)); + canvas.lineTo((float)(523.8699951171875), (float)(172)); + canvas.closePath(); + canvas.fill(); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); + canvas.fillRect((float)(533.8699951171875), (float)(175), (float)(15), (float)(1)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(536.8699951171875), (float)(175), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(536.8699951171875), (float)(166), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(536.8699951171875), (float)(157), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(536.8699951171875), (float)(143.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(536.8699951171875), (float)(134.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); + canvas.fillRect((float)(574.413330078125), (float)(175), (float)(15), (float)(1)); + canvas.fillRect((float)(574.413330078125), (float)(184), (float)(15), (float)(1)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(577.413330078125), (float)(184), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(577.413330078125), (float)(166), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(577.413330078125), (float)(152.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.beginRotate((float)(596.9566650390625), (float)(137.5), (float)(90)); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(43.199999999999996), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.endRotate(); + canvas.beginPath(); + canvas.moveTo((float)(596.9566650390625), (float)(129.5)); + canvas.lineTo((float)(600.9566650390625), (float)(137.5)); + canvas.lineTo((float)(592.9566650390625), (float)(137.5)); + canvas.closePath(); + canvas.fill(); + canvas.fillText("\uE262", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(607.9566650390625), (float)(143.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); + canvas.fillRect((float)(614.9566650390625), (float)(175), (float)(15), (float)(1)); + canvas.fillRect((float)(614.9566650390625), (float)(184), (float)(15), (float)(1)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(617.9566650390625), (float)(184), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(617.9566650390625), (float)(166), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(617.9566650390625), (float)(152.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(617.9566650390625), (float)(143.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(617.9566650390625), (float)(134.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillRect((float)(670), (float)(130.1), (float)(1), (float)(36)); + canvas.fillRect((float)(674), (float)(130.1), (float)(4), (float)(36)); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(496.32666015625), (float)(134.5)); + canvas.lineTo((float)(496.32666015625), (float)(184)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(545.8699951171875), (float)(107.5)); + canvas.lineTo((float)(545.8699951171875), (float)(175)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(586.413330078125), (float)(125.5)); + canvas.lineTo((float)(586.413330078125), (float)(184)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.setLineWidth((float)(0.96)); + canvas.beginPath(); + canvas.moveTo((float)(626.9566650390625), (float)(107.5)); + canvas.lineTo((float)(626.9566650390625), (float)(184)); + canvas.stroke(); + canvas.setLineWidth((float)(1)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(100))); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); + canvas.fillRect((float)(92), (float)(284), (float)(254), (float)(1.04)); + canvas.fillRect((float)(92), (float)(295), (float)(254), (float)(1.04)); + canvas.fillRect((float)(92), (float)(306), (float)(254), (float)(1.04)); + canvas.fillRect((float)(92), (float)(317), (float)(61.02685546875), (float)(1.04)); + canvas.fillRect((float)(161.32666015625), (float)(317), (float)(31.759140014648438), (float)(1.04)); + canvas.fillRect((float)(201.38560485839844), (float)(317), (float)(31.759140014648438), (float)(1.04)); + canvas.fillRect((float)(241.44454956054688), (float)(317), (float)(31.759124755859375), (float)(1.04)); + canvas.fillRect((float)(281.50347900390625), (float)(317), (float)(25.273406982421875), (float)(1.04)); + canvas.fillRect((float)(315.0766906738281), (float)(317), (float)(30.923309326171875), (float)(1.04)); + canvas.fillRect((float)(92), (float)(328), (float)(254), (float)(1.04)); + canvas.fillRect((float)(92), (float)(339), (float)(254), (float)(1.04)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + canvas.fillText("\uE06D", getMusicTypeface(), (float)(getMusicFontSize() * 0.9230769230769231), (float)(97), (float)(311.8), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(163.32666015625), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("9", getTypeface(), getFontSize(), (float)(153.02685546875), (float)(317.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + canvas.beginPath(); + canvas.beginPath(); + canvas.moveTo((float)(188.23569962449085), (float)(274.7)); + canvas.lineTo((float)(185.23569962449085), (float)(280.7)); + canvas.lineTo((float)(191.23569962449085), (float)(280.7)); + canvas.closePath(); + canvas.fill(); + canvas.stroke(); + canvas.moveTo((float)(162.32666015625), (float)(317.3)); + canvas.bezierCurveTo((float)(175.28117989037042), (float)(317.3), (float)(188.23569962449085), (float)(317.3), (float)(188.23569962449085), (float)(280.7)); + canvas.stroke(); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("\u00BC", getTypeface(), getFontSize(), (float)(183.47495743699085), (float)(260.7), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(203.38560196824085), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("9", getTypeface(), getFontSize(), (float)(193.08579728074085), (float)(317.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + canvas.beginPath(); + canvas.beginPath(); + canvas.moveTo((float)(228.29464143648167), (float)(268.7)); + canvas.lineTo((float)(225.29464143648167), (float)(274.7)); + canvas.lineTo((float)(231.29464143648167), (float)(274.7)); + canvas.closePath(); + canvas.fill(); + canvas.stroke(); + canvas.moveTo((float)(202.38560196824085), (float)(317.3)); + canvas.bezierCurveTo((float)(215.34012170236127), (float)(317.3), (float)(228.29464143648167), (float)(317.3), (float)(228.29464143648167), (float)(274.7)); + canvas.stroke(); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("\u00BD", getTypeface(), getFontSize(), (float)(223.25142854585667), (float)(254.7), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(243.44454378023167), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("9", getTypeface(), getFontSize(), (float)(233.14473909273167), (float)(317.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + canvas.beginPath(); + canvas.beginPath(); + canvas.moveTo((float)(268.3535832484725), (float)(256.7)); + canvas.lineTo((float)(265.3535832484725), (float)(262.7)); + canvas.lineTo((float)(271.3535832484725), (float)(262.7)); + canvas.closePath(); + canvas.fill(); + canvas.stroke(); + canvas.moveTo((float)(242.44454378023167), (float)(317.3)); + canvas.bezierCurveTo((float)(255.39906351435206), (float)(317.3), (float)(268.3535832484725), (float)(317.3), (float)(268.3535832484725), (float)(262.7)); + canvas.stroke(); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("full", getTypeface(), getFontSize(), (float)(259.3589543422225), (float)(242.7), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(283.5034855922225), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("9", getTypeface(), getFontSize(), (float)(273.2036809047225), (float)(317.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + canvas.beginPath(); + canvas.beginPath(); + canvas.moveTo((float)(301.9267916242362), (float)(244.7)); + canvas.lineTo((float)(298.9267916242362), (float)(250.7)); + canvas.lineTo((float)(304.9267916242362), (float)(250.7)); + canvas.closePath(); + canvas.fill(); + canvas.stroke(); + canvas.moveTo((float)(282.5034855922225), (float)(317.3)); + canvas.bezierCurveTo((float)(292.2151386082294), (float)(317.3), (float)(301.9267916242362), (float)(317.3), (float)(301.9267916242362), (float)(250.7)); + canvas.stroke(); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("1\u00BD", getTypeface(), getFontSize(), (float)(293.2336763898612), (float)(230.7), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(317.0766939679862), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("9", getTypeface(), getFontSize(), (float)(306.7768892804862), (float)(317.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + canvas.beginPath(); + canvas.beginPath(); + canvas.moveTo((float)(340), (float)(232.7)); + canvas.lineTo((float)(337), (float)(238.7)); + canvas.lineTo((float)(343), (float)(238.7)); + canvas.closePath(); + canvas.fill(); + canvas.stroke(); + canvas.moveTo((float)(316.0766939679862), (float)(317.3)); + canvas.bezierCurveTo((float)(328.0383469839931), (float)(317.3), (float)(340), (float)(317.3), (float)(340), (float)(238.7)); + canvas.stroke(); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("2", getTypeface(), getFontSize(), (float)(336.35009765625), (float)(218.7), getTextAlign(), getTextBaseline()); + canvas.fillRect((float)(345), (float)(284.3), (float)(1), (float)(54.99999999999994)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); + canvas.fillRect((float)(346), (float)(284), (float)(126), (float)(1.04)); + canvas.fillRect((float)(346), (float)(295), (float)(12.02685546875), (float)(1.04)); + canvas.fillRect((float)(366.32666015625), (float)(295), (float)(12.882984161376953), (float)(1.04)); + canvas.fillRect((float)(387.50944900512695), (float)(295), (float)(84.49055099487305), (float)(1.04)); + canvas.fillRect((float)(346), (float)(306), (float)(126), (float)(1.04)); + canvas.fillRect((float)(346), (float)(317), (float)(54.392433166503906), (float)(1.04)); + canvas.fillRect((float)(408.6922378540039), (float)(317), (float)(18.781005859375), (float)(1.04)); + canvas.fillRect((float)(435.7730484008789), (float)(317), (float)(36.226951599121094), (float)(1.04)); + canvas.fillRect((float)(346), (float)(328), (float)(126), (float)(1.04)); + canvas.fillRect((float)(346), (float)(339), (float)(126), (float)(1.04)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(368.32666015625), (float)(295.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("3", getTypeface(), getFontSize(), (float)(358.02685546875), (float)(295.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(389.509449828346), (float)(295.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("3", getTypeface(), getFontSize(), (float)(379.209645140846), (float)(295.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(410.692239500442), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("3", getTypeface(), getFontSize(), (float)(400.392434812942), (float)(317.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(437.773050047317), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("5", getTypeface(), getFontSize(), (float)(427.473245359817), (float)(317.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + canvas.fillRect((float)(471), (float)(284.3), (float)(1), (float)(54.99999999999994)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); + canvas.fillRect((float)(472), (float)(284), (float)(25.1767578125), (float)(1.04)); + canvas.fillRect((float)(505.4765625), (float)(284), (float)(32.2435302734375), (float)(1.04)); + canvas.fillRect((float)(546.0198974609375), (float)(284), (float)(72.786865234375), (float)(1.04)); + canvas.fillRect((float)(627.1065673828125), (float)(284), (float)(50.8934326171875), (float)(1.04)); + canvas.fillRect((float)(472), (float)(295), (float)(25.1767578125), (float)(1.04)); + canvas.fillRect((float)(505.4765625), (float)(295), (float)(32.2435302734375), (float)(1.04)); + canvas.fillRect((float)(546.0198974609375), (float)(295), (float)(72.786865234375), (float)(1.04)); + canvas.fillRect((float)(627.1065673828125), (float)(295), (float)(50.8934326171875), (float)(1.04)); + canvas.fillRect((float)(472), (float)(306), (float)(25.1767578125), (float)(1.04)); + canvas.fillRect((float)(505.4765625), (float)(306), (float)(32.2435302734375), (float)(1.04)); + canvas.fillRect((float)(546.0198974609375), (float)(306), (float)(32.2435302734375), (float)(1.04)); + canvas.fillRect((float)(586.563232421875), (float)(306), (float)(32.2435302734375), (float)(1.04)); + canvas.fillRect((float)(627.1065673828125), (float)(306), (float)(50.8934326171875), (float)(1.04)); + canvas.fillRect((float)(472), (float)(317), (float)(65.7200927734375), (float)(1.04)); + canvas.fillRect((float)(546.0198974609375), (float)(317), (float)(32.2435302734375), (float)(1.04)); + canvas.fillRect((float)(586.563232421875), (float)(317), (float)(32.2435302734375), (float)(1.04)); + canvas.fillRect((float)(627.1065673828125), (float)(317), (float)(50.8934326171875), (float)(1.04)); + canvas.fillRect((float)(472), (float)(328), (float)(65.7200927734375), (float)(1.04)); + canvas.fillRect((float)(546.0198974609375), (float)(328), (float)(32.2435302734375), (float)(1.04)); + canvas.fillRect((float)(586.563232421875), (float)(328), (float)(32.2435302734375), (float)(1.04)); + canvas.fillRect((float)(627.1065673828125), (float)(328), (float)(50.8934326171875), (float)(1.04)); + canvas.fillRect((float)(472), (float)(339), (float)(206), (float)(1.04)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + canvas.beginPath(); + canvas.moveTo((float)(488), (float)(275.8)); + canvas.lineTo((float)(488), (float)(312.8)); + canvas.stroke(); + canvas.beginPath(); + canvas.moveTo((float)(488), (float)(275.8)); + canvas.lineTo((float)(492), (float)(283.8)); + canvas.lineTo((float)(484), (float)(283.8)); + canvas.closePath(); + canvas.fill(); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(507.4765625), (float)(284.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("0", getTypeface(), getFontSize(), (float)(497.1767578125), (float)(284.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(507.4765625), (float)(295.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("1", getTypeface(), getFontSize(), (float)(497.1767578125), (float)(295.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(507.4765625), (float)(306.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("0", getTypeface(), getFontSize(), (float)(497.1767578125), (float)(306.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + canvas.beginRotate((float)(527.7200927734375), (float)(326.8), (float)(-90)); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(43.199999999999996), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.endRotate(); + canvas.beginPath(); + canvas.moveTo((float)(528), (float)(334.8)); + canvas.lineTo((float)(532), (float)(326.8)); + canvas.lineTo((float)(524), (float)(326.8)); + canvas.closePath(); + canvas.fill(); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(548.0198974609375), (float)(284.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("0", getTypeface(), getFontSize(), (float)(537.7200927734375), (float)(284.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(548.0198974609375), (float)(295.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("1", getTypeface(), getFontSize(), (float)(537.7200927734375), (float)(295.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(548.0198974609375), (float)(306.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("0", getTypeface(), getFontSize(), (float)(537.7200927734375), (float)(306.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(548.0198974609375), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("2", getTypeface(), getFontSize(), (float)(537.7200927734375), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(548.0198974609375), (float)(328.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("3", getTypeface(), getFontSize(), (float)(537.7200927734375), (float)(328.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + canvas.beginPath(); + canvas.moveTo((float)(569), (float)(297.8)); + canvas.lineTo((float)(569), (float)(334.8)); + canvas.stroke(); + canvas.beginPath(); + canvas.moveTo((float)(569), (float)(297.8)); + canvas.lineTo((float)(573), (float)(305.8)); + canvas.lineTo((float)(565), (float)(305.8)); + canvas.closePath(); + canvas.fill(); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(588.563232421875), (float)(306.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("2", getTypeface(), getFontSize(), (float)(578.263427734375), (float)(306.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(588.563232421875), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("2", getTypeface(), getFontSize(), (float)(578.263427734375), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(588.563232421875), (float)(328.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("0", getTypeface(), getFontSize(), (float)(578.263427734375), (float)(328.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + canvas.beginRotate((float)(596.8067626953125), (float)(283.8), (float)(90)); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(43.199999999999996), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); + canvas.endRotate(); + canvas.beginPath(); + canvas.moveTo((float)(597), (float)(275.8)); + canvas.lineTo((float)(601), (float)(283.8)); + canvas.lineTo((float)(593), (float)(283.8)); + canvas.closePath(); + canvas.fill(); + setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(629.1065673828125), (float)(284.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("0", getTypeface(), getFontSize(), (float)(618.8067626953125), (float)(284.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(629.1065673828125), (float)(295.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("2", getTypeface(), getFontSize(), (float)(618.8067626953125), (float)(295.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(629.1065673828125), (float)(306.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("2", getTypeface(), getFontSize(), (float)(618.8067626953125), (float)(306.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(629.1065673828125), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("2", getTypeface(), getFontSize(), (float)(618.8067626953125), (float)(317.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(11)); + canvas.fillText("", getTypeface(), getFontSize(), (float)(629.1065673828125), (float)(328.3), getTextAlign(), getTextBaseline()); + setTypeface(getTypeface("Roboto", false, false)); + setFontSize((float)(13)); + canvas.fillText("0", getTypeface(), getFontSize(), (float)(618.8067626953125), (float)(328.3), getTextAlign(), getTextBaseline()); + setTextBaseline(AlphaSkiaTextBaseline.TOP); + canvas.fillRect((float)(670), (float)(284.3), (float)(1), (float)(54.99999999999994)); + canvas.fillRect((float)(674), (float)(284.3), (float)(4), (float)(54.99999999999994)); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(34), (byte)(34), (byte)(17), (byte)(255))); + canvas.beginPath(); + canvas.moveTo((float)(92), (float)(130.1)); + canvas.lineTo((float)(92), (float)(339.3)); + canvas.stroke(); + setTypeface(getTypeface("PT Serif", false, true)); + setFontSize((float)(12)); + canvas.fillText("s.guit.", getTypeface(), getFontSize(), (float)(50), (float)(130.1), getTextAlign(), getTextBaseline()); + canvas.fillRect((float)(86), (float)(118.1), (float)(3), (float)(233.20000000000002)); + canvas.beginPath(); + canvas.moveTo((float)(86), (float)(118.1)); + canvas.bezierCurveTo((float)(86), (float)(118.1), (float)(86), (float)(118.1), (float)(98), (float)(115.1)); + canvas.bezierCurveTo((float)(92), (float)(121.1), (float)(86), (float)(121.1), (float)(86), (float)(121.1)); + canvas.closePath(); + canvas.fill(); + canvas.beginPath(); + canvas.moveTo((float)(86), (float)(351.3)); + canvas.bezierCurveTo((float)(86), (float)(351.3), (float)(92), (float)(351.3), (float)(98), (float)(354.3)); + canvas.bezierCurveTo((float)(92), (float)(348.3), (float)(86), (float)(348.3), (float)(86), (float)(348.3)); + canvas.closePath(); + canvas.fill(); + return canvas.endRender(); + } + private AlphaSkiaImage drawMusicSheetPart5(AlphaSkiaCanvas canvas) { + canvas.beginRender(116, 24, (float)(getRenderScale())); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + setTypeface(getTypeface("Roboto", true, false)); + setFontSize((float)(12)); + setTextAlign(AlphaSkiaTextAlign.LEFT); + canvas.fillText("rendered by alphaTab", getTypeface(), getFontSize(), (float)(0), (float)(12), getTextAlign(), getTextBaseline()); + return canvas.endRender(); + } + @Override + protected float[][] getPartPositions() + { + return new float[][] + { + {(float)0, (float)0}, {(float)0, (float)164}, {(float)0, (float)202}, {(float)0, (float)208}, {(float)591.962890625, (float)581} + }; + } + @Override + protected int getTotalWidth() { return (int)1300; } + @Override + protected int getTotalHeight() { return (int)605; } + @Override + protected RenderFunction[] getAllParts() + { + return new RenderFunction[] + { + this::drawMusicSheetPart1, this::drawMusicSheetPart2, this::drawMusicSheetPart3, this::drawMusicSheetPart4, this::drawMusicSheetPart5 + }; + } +} diff --git a/test/java/main/src/test/java/net/alphatab/alphaskia/MusicSheetRenderTest.java b/test/java/main/src/test/java/net/alphatab/alphaskia/MusicSheetRenderTest.java new file mode 100644 index 0000000..847acb3 --- /dev/null +++ b/test/java/main/src/test/java/net/alphatab/alphaskia/MusicSheetRenderTest.java @@ -0,0 +1,188 @@ +package net.alphatab.alphaskia; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public abstract class MusicSheetRenderTest { + private static AlphaSkiaTypeface _musicTypeface; + private static final int _musicFontSize = 34; + private static final float _renderScale = 1.0f; + + protected static float getRenderScale() { + return _renderScale; + } + + protected static AlphaSkiaTypeface getMusicTypeface() { + return _musicTypeface; + } + + protected static int getMusicFontSize() { + return _musicFontSize; + } + + private static final Map _customTypefaces = + new HashMap<>(); + + private static String customTypefaceKey(String fontFamily, boolean isBold, boolean isItalic) { + return fontFamily.toLowerCase() + "_" + isBold + "_" + isItalic; + } + + @BeforeAll + public static void setup() throws IOException { + var os = System.getProperty("os.name"); + String libName; + String target; + if (os.startsWith("Mac")) { + target = "macos"; + libName = "libalphaskiajni.dylib"; + } else if (os.startsWith("Win")) { + target = "win"; + libName = "libalphaskiajni.dll"; + } else if (os.startsWith("Linux")) { + target = "linux"; + libName = "libalphaskiajni.so"; + } else { + target = os.toLowerCase(); + libName = "libalphaskiajni"; + } + + var jarch = System.getProperty("os.arch"); + var arch = switch (jarch) { + case "x86", "i368", "i486", "i586", "i686" -> "x86"; + case "x86_64", "amd64" -> "x64"; + case "arm" -> "arm"; + case "aarch64" -> "arm64"; + default -> jarch; + }; + + AlphaSkiaPlatform.loadLibrary(new String[]{ + Path.of(System.getProperty("alphaskia.library.path"), "libalphaskiajni-" + target + "-" + arch + "-jni", libName).toAbsolutePath().toString() + }); + + var bravura = readFont("font/bravura/Bravura.ttf"); + + _musicTypeface = AlphaSkiaTypeface.register(bravura); + + _customTypefaces.put(customTypefaceKey("Roboto", false, false), + AlphaSkiaTypeface.register(readFont("font/roboto/Roboto-Regular.ttf"))); + _customTypefaces.put(customTypefaceKey("Roboto", true, false), + AlphaSkiaTypeface.register(readFont("font/roboto/Roboto-Bold.ttf"))); + _customTypefaces.put(customTypefaceKey("Roboto", false, true), + AlphaSkiaTypeface.register(readFont("font/roboto/Roboto-Italic.ttf"))); + _customTypefaces.put(customTypefaceKey("Roboto", true, true), + AlphaSkiaTypeface.register(readFont("font/roboto/Roboto-BoldItalic.ttf"))); + + _customTypefaces.put(customTypefaceKey("PT Serif", false, false), + AlphaSkiaTypeface.register(readFont("font/ptserif/PTSerif-Regular.ttf"))); + _customTypefaces.put(customTypefaceKey("PT Serif", true, false), + AlphaSkiaTypeface.register(readFont("font/ptserif/PTSerif-Bold.ttf"))); + _customTypefaces.put(customTypefaceKey("PT Serif", false, true), + AlphaSkiaTypeface.register(readFont("font/ptserif/PTSerif-Italic.ttf"))); + _customTypefaces.put(customTypefaceKey("PT Serif", true, true), + AlphaSkiaTypeface.register(readFont("font/ptserif/PTSerif-BoldItalic.ttf"))); + } + + protected static AlphaSkiaTypeface getTypeface(String name, boolean isBold, boolean isItalic) { + var face = _customTypefaces.getOrDefault(customTypefaceKey(name, isBold, isItalic), null); + if (face == null) { + throw new IllegalStateException("Unknown font requested: " + name); + } + return face; + } + + + private AlphaSkiaTextAlign textAlign = AlphaSkiaTextAlign.CENTER; + + protected AlphaSkiaTextAlign getTextAlign() { + return textAlign; + } + + protected void setTextAlign(AlphaSkiaTextAlign textAlign) { + this.textAlign = textAlign; + } + + private AlphaSkiaTextBaseline textBaseline = AlphaSkiaTextBaseline.TOP; + + protected AlphaSkiaTextBaseline getTextBaseline() { + return textBaseline; + } + + protected void setTextBaseline(AlphaSkiaTextBaseline textBaseline) { + this.textBaseline = textBaseline; + } + + private AlphaSkiaTypeface typeface; + + protected AlphaSkiaTypeface getTypeface() { + return typeface; + } + + protected void setTypeface(AlphaSkiaTypeface typeface) { + this.typeface = typeface; + } + + private float fontSize; + + public float getFontSize() { + return fontSize; + } + + public void setFontSize(float fontSize) { + this.fontSize = fontSize; + } + + private static byte[] readFont(String relativePath) throws IOException { + var fullPath = Path.of(System.getProperty("testdata.path")).resolve(relativePath); + try (var stream = new FileInputStream(fullPath.toFile())) { + return stream.readAllBytes(); + } + } + + private AlphaSkiaImage renderFullImage() { + try (var canvas = new AlphaSkiaCanvas()) { + var images = Arrays.stream(getAllParts()).map(p -> p.render(canvas)).toArray(AlphaSkiaImage[]::new); + + canvas.beginRender(getTotalWidth(), getTotalHeight()); + + var partPositions = getPartPositions(); + for (var i = 0; i < images.length; i++) { + canvas.drawImage(images[i], partPositions[i][0], partPositions[i][1], images[i].getWidth() / getRenderScale(), images[i].getHeight() / getRenderScale()); + images[i].close(); + } + + return canvas.endRender(); + } + } + + @Test + public void renderTest() throws IOException { + try (var finalImage = renderFullImage()) { + var testOutputPath = Path.of(System.getProperty("testoutput.path")); + Files.createDirectories(testOutputPath); + + var file = testOutputPath.resolve(this.getClass().getSimpleName() +".png").toFile(); + byte[] png = finalImage.toPng(); + + try (var stream = new FileOutputStream(file)) { + stream.write(png); + } + } + + } + + protected abstract int getTotalWidth(); + + protected abstract int getTotalHeight(); + + protected abstract float[][] getPartPositions(); + + protected abstract RenderFunction[] getAllParts(); +} + diff --git a/test/java/main/src/test/java/net/alphatab/alphaskia/RenderFunction.java b/test/java/main/src/test/java/net/alphatab/alphaskia/RenderFunction.java new file mode 100644 index 0000000..44ae5fd --- /dev/null +++ b/test/java/main/src/test/java/net/alphatab/alphaskia/RenderFunction.java @@ -0,0 +1,8 @@ +package net.alphatab.alphaskia; + +import net.alphatab.alphaskia.AlphaSkiaCanvas; +import net.alphatab.alphaskia.AlphaSkiaImage; + +public interface RenderFunction { + AlphaSkiaImage render(AlphaSkiaCanvas canvas); +} \ No newline at end of file diff --git a/test/java/settings.gradle.kts b/test/java/settings.gradle.kts new file mode 100644 index 0000000..86cb29e --- /dev/null +++ b/test/java/settings.gradle.kts @@ -0,0 +1,7 @@ +rootProject.name = "AlphaSkia" + +include("main") +include("linux") +include("windows") +include("android") +include("macos") diff --git a/test/native/include/AlphaSkiaTestBridge.h b/test/native/include/AlphaSkiaTestBridge.h new file mode 100644 index 0000000..6def37a --- /dev/null +++ b/test/native/include/AlphaSkiaTestBridge.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../../../wrapper/include/alphaskia.h" + +#include +#include +#include +#include +#include + +#define alphaskia_rgba_to_color(r, g, b, a) static_cast(((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) << 0)) + +typedef std::function render_function_t; + +extern alphaskia_text_align_t text_align; +extern alphaskia_text_baseline_t text_baseline; +extern alphaskia_typeface_t typeface; +extern alphaskia_typeface_t music_typeface; +extern float music_font_size; +extern float font_size; +extern float render_scale; +extern int32_t total_width; +extern int32_t total_height; + +alphaskia_typeface_t alphaskia_get_typeface(const char *name, bool is_bold, bool is_italic); +alphaskia_typeface_t alphaskia_load_typeface(const char *name, bool is_bold, bool is_italic, std::string file_path); +void read_file(std::string file_path, std::vector &data); \ No newline at end of file diff --git a/test/native/include/AlphaTabGeneratedTest.h b/test/native/include/AlphaTabGeneratedTest.h new file mode 100644 index 0000000..d70db7d --- /dev/null +++ b/test/native/include/AlphaTabGeneratedTest.h @@ -0,0 +1,5 @@ +#pragma once +#include "AlphaSkiaTestBridge.h" + +extern std::array, 5> part_positions; +extern std::array all_parts; diff --git a/test/native/include/PixelMatch.h b/test/native/include/PixelMatch.h new file mode 100644 index 0000000..822ae25 --- /dev/null +++ b/test/native/include/PixelMatch.h @@ -0,0 +1,49 @@ +/** + * Based on https://github.com/mapbox/pixelmatch + * ISC License + * Copyright (c) 2019, Mapbox + * + * Permission to use, copy, modify, and/or distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright notice + * and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ +#pragma once + +#include + +typedef struct +{ + uint32_t total_pixels; + uint32_t different_pixels; + uint32_t transparent_pixels; +} pixel_match_result_t; + +typedef struct +{ + uint8_t r; + uint8_t g; + uint8_t b; +} pixel_match_color_t; + +typedef struct +{ + double threshold; + double alpha; + pixel_match_color_t diff_color; + bool diff_mask; +} pixel_match_options_t; + +pixel_match_result_t pixel_match(const std::vector& actual_pixels, + const std::vector& expected_pixels, + std::vector& diff_pixels, + int32_t width, + int32_t height, + pixel_match_options_t options); \ No newline at end of file diff --git a/test/native/src/AlphaSkiaTestBridge.cpp b/test/native/src/AlphaSkiaTestBridge.cpp new file mode 100644 index 0000000..bbbcb65 --- /dev/null +++ b/test/native/src/AlphaSkiaTestBridge.cpp @@ -0,0 +1,71 @@ +#include "../include/AlphaSkiaTestBridge.h" + +#include +#include +#include +#include + +alphaskia_text_align_t text_align = alphaskia_text_align_left; +alphaskia_text_baseline_t text_baseline = alphaskia_text_baseline_top; +alphaskia_typeface_t typeface = nullptr; +alphaskia_typeface_t music_typeface = nullptr; +float music_font_size = 34; +float render_scale = 1; +float font_size = 0.0f; + +static std::map custom_type_faces; + +std::string custom_typeface_key(const char *name, bool is_bold, bool is_italic) +{ + return std::string(name) + "_" + (is_bold ? "true" : "false") + "_" + (is_italic ? "true" : "false"); +} + +alphaskia_typeface_t alphaskia_get_typeface(const char *name, bool is_bold, bool is_italic) +{ + auto key = custom_typeface_key(name, is_bold, is_italic); + auto it = custom_type_faces.find(key); + if (it == custom_type_faces.end()) + { + throw std::exception(("Unknown font requested: " + key).c_str()); + } + return it->second; +} + +void read_file(std::string file_path, std::vector &data) +{ + std::ifstream in(file_path, std::ios::binary | std::ios::ate); + if (!in) + { + throw std::exception(("Could not open file " + file_path).c_str()); + } + + std::streampos size = in.tellg(); + data.resize(size); + in.seekg(0); + in.read(reinterpret_cast(data.data()), size); +} + +alphaskia_typeface_t alphaskia_load_typeface(const char *name, bool is_bold, bool is_italic, std::string file_path) +{ + auto key = custom_typeface_key(name, is_bold, is_italic); + std::cout << "Loading font " << key << " from " << file_path << std::endl; + + std::vector data; + read_file(file_path, data); + alphaskia_data_t font_data = alphaskia_data_new_copy(data.data(), data.size()); + if (!font_data) + { + throw std::exception(("Could allocate graphic font data " + file_path).c_str()); + } + + std::cout << "Read " << data.size() << " bytes from file, decoding font" << std::endl; + alphaskia_typeface_t typeface = alphaskia_typeface_register(font_data); + if (!typeface) + { + alphaskia_data_free(font_data); + throw std::exception("Could not create font from data"); + } + std::cout << "Font " << key << " loaded and registered" << std::endl; + custom_type_faces[key] = typeface; + return typeface; +} \ No newline at end of file diff --git a/test/native/src/AlphaTabGeneratedTest.cpp b/test/native/src/AlphaTabGeneratedTest.cpp new file mode 100644 index 0000000..90d3fa5 --- /dev/null +++ b/test/native/src/AlphaTabGeneratedTest.cpp @@ -0,0 +1,740 @@ +#include "../include/AlphaTabGeneratedTest.h" + +alphaskia_image_t draw_music_sheet_part_1(alphaskia_canvas_t canvas) { + alphaskia_canvas_begin_render(canvas, 1300, 164, static_cast(render_scale)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(153), static_cast(204), static_cast(30), static_cast(255))); + text_align = alphaskia_text_align_center; + typeface = alphaskia_get_typeface("PT Serif", false, false); + font_size = static_cast(32); + text_align = alphaskia_text_align_center; + alphaskia_canvas_fill_text(canvas, u"Title", typeface, font_size, static_cast(650), static_cast(40), text_align, text_baseline); + text_align = alphaskia_text_align_center; + typeface = alphaskia_get_typeface("PT Serif", false, false); + font_size = static_cast(20); + text_align = alphaskia_text_align_center; + alphaskia_canvas_fill_text(canvas, u"Subtitle", typeface, font_size, static_cast(650), static_cast(72), text_align, text_baseline); + text_align = alphaskia_text_align_center; + typeface = alphaskia_get_typeface("PT Serif", false, false); + font_size = static_cast(20); + text_align = alphaskia_text_align_center; + alphaskia_canvas_fill_text(canvas, u"Artist", typeface, font_size, static_cast(650), static_cast(92), text_align, text_baseline); + text_align = alphaskia_text_align_center; + typeface = alphaskia_get_typeface("PT Serif", false, false); + font_size = static_cast(20); + text_align = alphaskia_text_align_center; + alphaskia_canvas_fill_text(canvas, u"Album", typeface, font_size, static_cast(650), static_cast(112), text_align, text_baseline); + text_align = alphaskia_text_align_center; + typeface = alphaskia_get_typeface("PT Serif", false, false); + font_size = static_cast(15); + text_align = alphaskia_text_align_right; + alphaskia_canvas_fill_text(canvas, u"Music by Music", typeface, font_size, static_cast(1260), static_cast(132), text_align, text_baseline); + text_align = alphaskia_text_align_center; + typeface = alphaskia_get_typeface("PT Serif", false, false); + font_size = static_cast(15); + text_align = alphaskia_text_align_left; + alphaskia_canvas_fill_text(canvas, u"Words by Words", typeface, font_size, static_cast(40), static_cast(132), text_align, text_baseline); + text_align = alphaskia_text_align_center; + return alphaskia_canvas_end_render(canvas); +} +alphaskia_image_t draw_music_sheet_part_2(alphaskia_canvas_t canvas) { + alphaskia_canvas_begin_render(canvas, 1300, 38, static_cast(render_scale)); + text_align = alphaskia_text_align_center; + typeface = alphaskia_get_typeface("PT Serif", false, true); + font_size = static_cast(12); + text_align = alphaskia_text_align_left; + alphaskia_canvas_fill_text(canvas, u"Guitar Standard Tuning", typeface, font_size, static_cast(40), static_cast(3), text_align, text_baseline); + text_align = alphaskia_text_align_center; + return alphaskia_canvas_end_render(canvas); +} +alphaskia_image_t draw_music_sheet_part_3(alphaskia_canvas_t canvas) { + alphaskia_canvas_begin_render(canvas, 1300, 6, static_cast(render_scale)); + text_align = alphaskia_text_align_center; + return alphaskia_canvas_end_render(canvas); +} +alphaskia_image_t draw_music_sheet_part_4(alphaskia_canvas_t canvas) { + alphaskia_canvas_begin_render(canvas, 1300, 373, static_cast(render_scale)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + text_align = alphaskia_text_align_left; + typeface = alphaskia_get_typeface("PT Serif", true, false); + font_size = static_cast(14); + alphaskia_canvas_fill_text(canvas, u"\uE1D5", music_typeface, static_cast(music_font_size * 0.75), static_cast(156.6767578125), static_cast(40), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"= 120", typeface, font_size, static_cast(169.1767578125), static_cast(27), text_align, text_baseline); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(165), static_cast(165), static_cast(165), static_cast(255))); + alphaskia_canvas_fill_rect(canvas, static_cast(92), static_cast(130), static_cast(254), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(92), static_cast(139), static_cast(254), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(92), static_cast(148), static_cast(254), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(92), static_cast(157), static_cast(254), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(92), static_cast(166), static_cast(254), static_cast(1.04)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + alphaskia_canvas_fill_text(canvas, u"\uE050", music_typeface, static_cast(music_font_size * 1), static_cast(94), static_cast(157.52), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE084", music_typeface, static_cast(music_font_size * 1), static_cast(127), static_cast(139), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE084", music_typeface, static_cast(music_font_size * 1), static_cast(127), static_cast(157), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(200), static_cast(0), static_cast(0), static_cast(255))); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"1", typeface, font_size, static_cast(141), static_cast(118.75), text_align, text_baseline); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(152.1767578125), static_cast(148), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE272", music_typeface, static_cast(music_font_size * 0.75), static_cast(172.86069962449085), static_cast(148), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 0.75), static_cast(180.86069962449085), static_cast(149), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(156.6767578125), static_cast(144)); + alphaskia_canvas_line_to(canvas, static_cast(170.855110706305), static_cast(133.50723450810483)); + alphaskia_canvas_line_to(canvas, static_cast(184.23569962449085), static_cast(145)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(192.23569962449085), static_cast(148), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 0.75), static_cast(220.91964143648167), static_cast(144.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(196.73569962449085), static_cast(144)); + alphaskia_canvas_line_to(canvas, static_cast(209.12929655256082), static_cast(131.33765133817155)); + alphaskia_canvas_line_to(canvas, static_cast(224.29464143648167), static_cast(140.5)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(232.29464143648167), static_cast(148), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE262", music_typeface, static_cast(music_font_size * 0.75), static_cast(252.9785832484725), static_cast(143.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 0.75), static_cast(260.9785832484725), static_cast(144.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(236.79464143648167), static_cast(144)); + alphaskia_canvas_line_to(canvas, static_cast(249.1882383645516), static_cast(131.33765133817155)); + alphaskia_canvas_line_to(canvas, static_cast(264.3535832484725), static_cast(140.5)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(272.3535832484725), static_cast(148), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 0.75), static_cast(294.5517916242362), static_cast(140), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(276.8535832484725), static_cast(152)); + alphaskia_canvas_line_to(canvas, static_cast(292.1060513036615), static_cast(156.93783819474976)); + alphaskia_canvas_line_to(canvas, static_cast(297.9267916242362), static_cast(142)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(305.9267916242362), static_cast(148), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE262", music_typeface, static_cast(music_font_size * 0.75), static_cast(324.625), static_cast(139), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 0.75), static_cast(332.625), static_cast(140), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(310.4267916242362), static_cast(152)); + alphaskia_canvas_line_to(canvas, static_cast(327.2193887931866), static_cast(157.24460932567123)); + alphaskia_canvas_line_to(canvas, static_cast(336), static_cast(142)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_fill_rect(canvas, static_cast(345), static_cast(130.1), static_cast(1), static_cast(36)); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(152.1767578125), static_cast(148)); + alphaskia_canvas_line_to(canvas, static_cast(152.1767578125), static_cast(175)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(192.23569962449085), static_cast(148)); + alphaskia_canvas_line_to(canvas, static_cast(192.23569962449085), static_cast(175)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(232.29464143648167), static_cast(148)); + alphaskia_canvas_line_to(canvas, static_cast(232.29464143648167), static_cast(175)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(281.3535832484725), static_cast(148)); + alphaskia_canvas_line_to(canvas, static_cast(281.3535832484725), static_cast(121)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(281.3535832484725), static_cast(121)); + alphaskia_canvas_line_to(canvas, static_cast(314.9267916242362), static_cast(121)); + alphaskia_canvas_line_to(canvas, static_cast(314.9267916242362), static_cast(125)); + alphaskia_canvas_line_to(canvas, static_cast(281.3535832484725), static_cast(125)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(314.9267916242362), static_cast(148)); + alphaskia_canvas_line_to(canvas, static_cast(314.9267916242362), static_cast(121)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(100))); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(165), static_cast(165), static_cast(165), static_cast(255))); + alphaskia_canvas_fill_rect(canvas, static_cast(346), static_cast(130), static_cast(126), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(346), static_cast(139), static_cast(126), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(346), static_cast(148), static_cast(126), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(346), static_cast(157), static_cast(126), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(346), static_cast(166), static_cast(126), static_cast(1.04)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(200), static_cast(0), static_cast(0), static_cast(255))); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"2", typeface, font_size, static_cast(346), static_cast(118.75), text_align, text_baseline); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(357.1767578125), static_cast(139), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(378.359547484596), static_cast(139), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(399.542337156692), static_cast(161.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A3", music_typeface, static_cast(music_font_size * 1), static_cast(426.623147703567), static_cast(157), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_rect(canvas, static_cast(471), static_cast(130.1), static_cast(1), static_cast(36)); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(366.1767578125), static_cast(139)); + alphaskia_canvas_line_to(canvas, static_cast(366.1767578125), static_cast(112)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(366.1767578125), static_cast(112)); + alphaskia_canvas_line_to(canvas, static_cast(387.359547484596), static_cast(117)); + alphaskia_canvas_line_to(canvas, static_cast(387.359547484596), static_cast(121)); + alphaskia_canvas_line_to(canvas, static_cast(366.1767578125), static_cast(116)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(366.1767578125), static_cast(118)); + alphaskia_canvas_line_to(canvas, static_cast(387.359547484596), static_cast(123)); + alphaskia_canvas_line_to(canvas, static_cast(387.359547484596), static_cast(127)); + alphaskia_canvas_line_to(canvas, static_cast(366.1767578125), static_cast(122)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(387.359547484596), static_cast(139)); + alphaskia_canvas_line_to(canvas, static_cast(387.359547484596), static_cast(117)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(387.359547484596), static_cast(117)); + alphaskia_canvas_line_to(canvas, static_cast(408.542337156692), static_cast(122)); + alphaskia_canvas_line_to(canvas, static_cast(408.542337156692), static_cast(126)); + alphaskia_canvas_line_to(canvas, static_cast(387.359547484596), static_cast(121)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(408.542337156692), static_cast(161.5)); + alphaskia_canvas_line_to(canvas, static_cast(408.542337156692), static_cast(122)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(435.623147703567), static_cast(130)); + alphaskia_canvas_line_to(canvas, static_cast(435.623147703567), static_cast(157)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(100))); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + text_align = alphaskia_text_align_center; + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("PT Serif", false, true); + font_size = static_cast(12); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(366.1767578125), static_cast(102)); + alphaskia_canvas_line_to(canvas, static_cast(366.1767578125), static_cast(97)); + alphaskia_canvas_line_to(canvas, static_cast(394.7559555656507), static_cast(104)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(407.04394995041633), static_cast(107)); + alphaskia_canvas_line_to(canvas, static_cast(435.623147703567), static_cast(115)); + alphaskia_canvas_line_to(canvas, static_cast(435.623147703567), static_cast(120)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_fill_text(canvas, u"3", typeface, font_size, static_cast(400.8999527580335), static_cast(106), text_align, text_baseline); + text_align = alphaskia_text_align_left; + text_baseline = alphaskia_text_baseline_top; + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(165), static_cast(165), static_cast(165), static_cast(255))); + alphaskia_canvas_fill_rect(canvas, static_cast(472), static_cast(130), static_cast(206), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(472), static_cast(139), static_cast(206), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(472), static_cast(148), static_cast(206), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(472), static_cast(157), static_cast(206), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(472), static_cast(166), static_cast(206), static_cast(1.04)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(200), static_cast(0), static_cast(0), static_cast(255))); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"3", typeface, font_size, static_cast(472), static_cast(118.75), text_align, text_baseline); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(496.32666015625), static_cast(157), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(496.32666015625), static_cast(143.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(496.32666015625), static_cast(134.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_begin_rotate(canvas, static_cast(527.8699951171875), static_cast(172), static_cast(-90)); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(0), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(10.799999999999999), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(21.599999999999998), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(32.4), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_end_rotate(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(527.8699951171875), static_cast(180)); + alphaskia_canvas_line_to(canvas, static_cast(531.8699951171875), static_cast(172)); + alphaskia_canvas_line_to(canvas, static_cast(523.8699951171875), static_cast(172)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(165), static_cast(165), static_cast(165), static_cast(255))); + alphaskia_canvas_fill_rect(canvas, static_cast(533.8699951171875), static_cast(175), static_cast(15), static_cast(1)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(536.8699951171875), static_cast(175), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(536.8699951171875), static_cast(166), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(536.8699951171875), static_cast(157), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(536.8699951171875), static_cast(143.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(536.8699951171875), static_cast(134.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(165), static_cast(165), static_cast(165), static_cast(255))); + alphaskia_canvas_fill_rect(canvas, static_cast(574.413330078125), static_cast(175), static_cast(15), static_cast(1)); + alphaskia_canvas_fill_rect(canvas, static_cast(574.413330078125), static_cast(184), static_cast(15), static_cast(1)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(577.413330078125), static_cast(184), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(577.413330078125), static_cast(166), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(577.413330078125), static_cast(152.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_begin_rotate(canvas, static_cast(596.9566650390625), static_cast(137.5), static_cast(90)); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(0), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(10.799999999999999), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(21.599999999999998), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(32.4), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(43.199999999999996), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_end_rotate(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(596.9566650390625), static_cast(129.5)); + alphaskia_canvas_line_to(canvas, static_cast(600.9566650390625), static_cast(137.5)); + alphaskia_canvas_line_to(canvas, static_cast(592.9566650390625), static_cast(137.5)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + alphaskia_canvas_fill_text(canvas, u"\uE262", music_typeface, static_cast(music_font_size * 1), static_cast(607.9566650390625), static_cast(143.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(165), static_cast(165), static_cast(165), static_cast(255))); + alphaskia_canvas_fill_rect(canvas, static_cast(614.9566650390625), static_cast(175), static_cast(15), static_cast(1)); + alphaskia_canvas_fill_rect(canvas, static_cast(614.9566650390625), static_cast(184), static_cast(15), static_cast(1)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(617.9566650390625), static_cast(184), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(617.9566650390625), static_cast(166), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(617.9566650390625), static_cast(152.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(617.9566650390625), static_cast(143.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uE0A4", music_typeface, static_cast(music_font_size * 1), static_cast(617.9566650390625), static_cast(134.5), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_rect(canvas, static_cast(670), static_cast(130.1), static_cast(1), static_cast(36)); + alphaskia_canvas_fill_rect(canvas, static_cast(674), static_cast(130.1), static_cast(4), static_cast(36)); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(496.32666015625), static_cast(134.5)); + alphaskia_canvas_line_to(canvas, static_cast(496.32666015625), static_cast(184)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(545.8699951171875), static_cast(107.5)); + alphaskia_canvas_line_to(canvas, static_cast(545.8699951171875), static_cast(175)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(586.413330078125), static_cast(125.5)); + alphaskia_canvas_line_to(canvas, static_cast(586.413330078125), static_cast(184)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_set_line_width(canvas, static_cast(0.96)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(626.9566650390625), static_cast(107.5)); + alphaskia_canvas_line_to(canvas, static_cast(626.9566650390625), static_cast(184)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_set_line_width(canvas, static_cast(1)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(100))); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(165), static_cast(165), static_cast(165), static_cast(255))); + alphaskia_canvas_fill_rect(canvas, static_cast(92), static_cast(284), static_cast(254), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(92), static_cast(295), static_cast(254), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(92), static_cast(306), static_cast(254), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(92), static_cast(317), static_cast(61.02685546875), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(161.32666015625), static_cast(317), static_cast(31.759140014648438), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(201.38560485839844), static_cast(317), static_cast(31.759140014648438), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(241.44454956054688), static_cast(317), static_cast(31.759124755859375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(281.50347900390625), static_cast(317), static_cast(25.273406982421875), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(315.0766906738281), static_cast(317), static_cast(30.923309326171875), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(92), static_cast(328), static_cast(254), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(92), static_cast(339), static_cast(254), static_cast(1.04)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + alphaskia_canvas_fill_text(canvas, u"\uE06D", music_typeface, static_cast(music_font_size * 0.9230769230769231), static_cast(97), static_cast(311.8), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(163.32666015625), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"9", typeface, font_size, static_cast(153.02685546875), static_cast(317.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(188.23569962449085), static_cast(274.7)); + alphaskia_canvas_line_to(canvas, static_cast(185.23569962449085), static_cast(280.7)); + alphaskia_canvas_line_to(canvas, static_cast(191.23569962449085), static_cast(280.7)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_move_to(canvas, static_cast(162.32666015625), static_cast(317.3)); + alphaskia_canvas_bezier_curve_to(canvas, static_cast(175.28117989037042), static_cast(317.3), static_cast(188.23569962449085), static_cast(317.3), static_cast(188.23569962449085), static_cast(280.7)); + alphaskia_canvas_stroke(canvas); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"\u00BC", typeface, font_size, static_cast(183.47495743699085), static_cast(260.7), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(203.38560196824085), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"9", typeface, font_size, static_cast(193.08579728074085), static_cast(317.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(228.29464143648167), static_cast(268.7)); + alphaskia_canvas_line_to(canvas, static_cast(225.29464143648167), static_cast(274.7)); + alphaskia_canvas_line_to(canvas, static_cast(231.29464143648167), static_cast(274.7)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_move_to(canvas, static_cast(202.38560196824085), static_cast(317.3)); + alphaskia_canvas_bezier_curve_to(canvas, static_cast(215.34012170236127), static_cast(317.3), static_cast(228.29464143648167), static_cast(317.3), static_cast(228.29464143648167), static_cast(274.7)); + alphaskia_canvas_stroke(canvas); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"\u00BD", typeface, font_size, static_cast(223.25142854585667), static_cast(254.7), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(243.44454378023167), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"9", typeface, font_size, static_cast(233.14473909273167), static_cast(317.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(268.3535832484725), static_cast(256.7)); + alphaskia_canvas_line_to(canvas, static_cast(265.3535832484725), static_cast(262.7)); + alphaskia_canvas_line_to(canvas, static_cast(271.3535832484725), static_cast(262.7)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_move_to(canvas, static_cast(242.44454378023167), static_cast(317.3)); + alphaskia_canvas_bezier_curve_to(canvas, static_cast(255.39906351435206), static_cast(317.3), static_cast(268.3535832484725), static_cast(317.3), static_cast(268.3535832484725), static_cast(262.7)); + alphaskia_canvas_stroke(canvas); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"full", typeface, font_size, static_cast(259.3589543422225), static_cast(242.7), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(283.5034855922225), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"9", typeface, font_size, static_cast(273.2036809047225), static_cast(317.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(301.9267916242362), static_cast(244.7)); + alphaskia_canvas_line_to(canvas, static_cast(298.9267916242362), static_cast(250.7)); + alphaskia_canvas_line_to(canvas, static_cast(304.9267916242362), static_cast(250.7)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_move_to(canvas, static_cast(282.5034855922225), static_cast(317.3)); + alphaskia_canvas_bezier_curve_to(canvas, static_cast(292.2151386082294), static_cast(317.3), static_cast(301.9267916242362), static_cast(317.3), static_cast(301.9267916242362), static_cast(250.7)); + alphaskia_canvas_stroke(canvas); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"1\u00BD", typeface, font_size, static_cast(293.2336763898612), static_cast(230.7), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(317.0766939679862), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"9", typeface, font_size, static_cast(306.7768892804862), static_cast(317.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(340), static_cast(232.7)); + alphaskia_canvas_line_to(canvas, static_cast(337), static_cast(238.7)); + alphaskia_canvas_line_to(canvas, static_cast(343), static_cast(238.7)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_move_to(canvas, static_cast(316.0766939679862), static_cast(317.3)); + alphaskia_canvas_bezier_curve_to(canvas, static_cast(328.0383469839931), static_cast(317.3), static_cast(340), static_cast(317.3), static_cast(340), static_cast(238.7)); + alphaskia_canvas_stroke(canvas); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"2", typeface, font_size, static_cast(336.35009765625), static_cast(218.7), text_align, text_baseline); + alphaskia_canvas_fill_rect(canvas, static_cast(345), static_cast(284.3), static_cast(1), static_cast(54.99999999999994)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(165), static_cast(165), static_cast(165), static_cast(255))); + alphaskia_canvas_fill_rect(canvas, static_cast(346), static_cast(284), static_cast(126), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(346), static_cast(295), static_cast(12.02685546875), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(366.32666015625), static_cast(295), static_cast(12.882984161376953), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(387.50944900512695), static_cast(295), static_cast(84.49055099487305), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(346), static_cast(306), static_cast(126), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(346), static_cast(317), static_cast(54.392433166503906), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(408.6922378540039), static_cast(317), static_cast(18.781005859375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(435.7730484008789), static_cast(317), static_cast(36.226951599121094), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(346), static_cast(328), static_cast(126), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(346), static_cast(339), static_cast(126), static_cast(1.04)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(368.32666015625), static_cast(295.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"3", typeface, font_size, static_cast(358.02685546875), static_cast(295.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(389.509449828346), static_cast(295.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"3", typeface, font_size, static_cast(379.209645140846), static_cast(295.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(410.692239500442), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"3", typeface, font_size, static_cast(400.392434812942), static_cast(317.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(437.773050047317), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"5", typeface, font_size, static_cast(427.473245359817), static_cast(317.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + alphaskia_canvas_fill_rect(canvas, static_cast(471), static_cast(284.3), static_cast(1), static_cast(54.99999999999994)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(165), static_cast(165), static_cast(165), static_cast(255))); + alphaskia_canvas_fill_rect(canvas, static_cast(472), static_cast(284), static_cast(25.1767578125), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(505.4765625), static_cast(284), static_cast(32.2435302734375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(546.0198974609375), static_cast(284), static_cast(72.786865234375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(627.1065673828125), static_cast(284), static_cast(50.8934326171875), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(472), static_cast(295), static_cast(25.1767578125), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(505.4765625), static_cast(295), static_cast(32.2435302734375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(546.0198974609375), static_cast(295), static_cast(72.786865234375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(627.1065673828125), static_cast(295), static_cast(50.8934326171875), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(472), static_cast(306), static_cast(25.1767578125), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(505.4765625), static_cast(306), static_cast(32.2435302734375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(546.0198974609375), static_cast(306), static_cast(32.2435302734375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(586.563232421875), static_cast(306), static_cast(32.2435302734375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(627.1065673828125), static_cast(306), static_cast(50.8934326171875), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(472), static_cast(317), static_cast(65.7200927734375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(546.0198974609375), static_cast(317), static_cast(32.2435302734375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(586.563232421875), static_cast(317), static_cast(32.2435302734375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(627.1065673828125), static_cast(317), static_cast(50.8934326171875), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(472), static_cast(328), static_cast(65.7200927734375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(546.0198974609375), static_cast(328), static_cast(32.2435302734375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(586.563232421875), static_cast(328), static_cast(32.2435302734375), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(627.1065673828125), static_cast(328), static_cast(50.8934326171875), static_cast(1.04)); + alphaskia_canvas_fill_rect(canvas, static_cast(472), static_cast(339), static_cast(206), static_cast(1.04)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(488), static_cast(275.8)); + alphaskia_canvas_line_to(canvas, static_cast(488), static_cast(312.8)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(488), static_cast(275.8)); + alphaskia_canvas_line_to(canvas, static_cast(492), static_cast(283.8)); + alphaskia_canvas_line_to(canvas, static_cast(484), static_cast(283.8)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(507.4765625), static_cast(284.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"0", typeface, font_size, static_cast(497.1767578125), static_cast(284.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(507.4765625), static_cast(295.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"1", typeface, font_size, static_cast(497.1767578125), static_cast(295.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(507.4765625), static_cast(306.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"0", typeface, font_size, static_cast(497.1767578125), static_cast(306.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + alphaskia_canvas_begin_rotate(canvas, static_cast(527.7200927734375), static_cast(326.8), static_cast(-90)); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(0), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(10.799999999999999), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(21.599999999999998), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(32.4), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(43.199999999999996), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_end_rotate(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(528), static_cast(334.8)); + alphaskia_canvas_line_to(canvas, static_cast(532), static_cast(326.8)); + alphaskia_canvas_line_to(canvas, static_cast(524), static_cast(326.8)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(548.0198974609375), static_cast(284.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"0", typeface, font_size, static_cast(537.7200927734375), static_cast(284.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(548.0198974609375), static_cast(295.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"1", typeface, font_size, static_cast(537.7200927734375), static_cast(295.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(548.0198974609375), static_cast(306.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"0", typeface, font_size, static_cast(537.7200927734375), static_cast(306.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(548.0198974609375), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"2", typeface, font_size, static_cast(537.7200927734375), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(548.0198974609375), static_cast(328.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"3", typeface, font_size, static_cast(537.7200927734375), static_cast(328.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(569), static_cast(297.8)); + alphaskia_canvas_line_to(canvas, static_cast(569), static_cast(334.8)); + alphaskia_canvas_stroke(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(569), static_cast(297.8)); + alphaskia_canvas_line_to(canvas, static_cast(573), static_cast(305.8)); + alphaskia_canvas_line_to(canvas, static_cast(565), static_cast(305.8)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(588.563232421875), static_cast(306.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"2", typeface, font_size, static_cast(578.263427734375), static_cast(306.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(588.563232421875), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"2", typeface, font_size, static_cast(578.263427734375), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(588.563232421875), static_cast(328.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"0", typeface, font_size, static_cast(578.263427734375), static_cast(328.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + alphaskia_canvas_begin_rotate(canvas, static_cast(596.8067626953125), static_cast(283.8), static_cast(90)); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(0), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(10.799999999999999), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(21.599999999999998), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(32.4), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_fill_text(canvas, u"\uEAA4", music_typeface, static_cast(music_font_size * 1.2), static_cast(43.199999999999996), static_cast(10.799999999999999), alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + alphaskia_canvas_end_rotate(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(597), static_cast(275.8)); + alphaskia_canvas_line_to(canvas, static_cast(601), static_cast(283.8)); + alphaskia_canvas_line_to(canvas, static_cast(593), static_cast(283.8)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + text_baseline = alphaskia_text_baseline_middle; + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(629.1065673828125), static_cast(284.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"0", typeface, font_size, static_cast(618.8067626953125), static_cast(284.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(629.1065673828125), static_cast(295.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"2", typeface, font_size, static_cast(618.8067626953125), static_cast(295.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(629.1065673828125), static_cast(306.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"2", typeface, font_size, static_cast(618.8067626953125), static_cast(306.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(629.1065673828125), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"2", typeface, font_size, static_cast(618.8067626953125), static_cast(317.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(11); + alphaskia_canvas_fill_text(canvas, u"", typeface, font_size, static_cast(629.1065673828125), static_cast(328.3), text_align, text_baseline); + typeface = alphaskia_get_typeface("Roboto", false, false); + font_size = static_cast(13); + alphaskia_canvas_fill_text(canvas, u"0", typeface, font_size, static_cast(618.8067626953125), static_cast(328.3), text_align, text_baseline); + text_baseline = alphaskia_text_baseline_top; + alphaskia_canvas_fill_rect(canvas, static_cast(670), static_cast(284.3), static_cast(1), static_cast(54.99999999999994)); + alphaskia_canvas_fill_rect(canvas, static_cast(674), static_cast(284.3), static_cast(4), static_cast(54.99999999999994)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(153), static_cast(51), static_cast(204), static_cast(100))); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(92), static_cast(130.1)); + alphaskia_canvas_line_to(canvas, static_cast(92), static_cast(339.3)); + alphaskia_canvas_stroke(canvas); + typeface = alphaskia_get_typeface("PT Serif", false, true); + font_size = static_cast(12); + alphaskia_canvas_fill_text(canvas, u"s.guit.", typeface, font_size, static_cast(50), static_cast(130.1), text_align, text_baseline); + alphaskia_canvas_fill_rect(canvas, static_cast(86), static_cast(118.1), static_cast(3), static_cast(233.20000000000002)); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(86), static_cast(118.1)); + alphaskia_canvas_bezier_curve_to(canvas, static_cast(86), static_cast(118.1), static_cast(86), static_cast(118.1), static_cast(98), static_cast(115.1)); + alphaskia_canvas_bezier_curve_to(canvas, static_cast(92), static_cast(121.1), static_cast(86), static_cast(121.1), static_cast(86), static_cast(121.1)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + alphaskia_canvas_begin_path(canvas); + alphaskia_canvas_move_to(canvas, static_cast(86), static_cast(351.3)); + alphaskia_canvas_bezier_curve_to(canvas, static_cast(86), static_cast(351.3), static_cast(92), static_cast(351.3), static_cast(98), static_cast(354.3)); + alphaskia_canvas_bezier_curve_to(canvas, static_cast(92), static_cast(348.3), static_cast(86), static_cast(348.3), static_cast(86), static_cast(348.3)); + alphaskia_canvas_close_path(canvas); + alphaskia_canvas_fill(canvas); + return alphaskia_canvas_end_render(canvas); +} +alphaskia_image_t draw_music_sheet_part_5(alphaskia_canvas_t canvas) { + alphaskia_canvas_begin_render(canvas, 116, 24, static_cast(render_scale)); + alphaskia_canvas_set_color(canvas, alphaskia_rgba_to_color(static_cast(0), static_cast(0), static_cast(0), static_cast(255))); + typeface = alphaskia_get_typeface("Roboto", true, false); + font_size = static_cast(12); + text_align = alphaskia_text_align_left; + alphaskia_canvas_fill_text(canvas, u"rendered by alphaTab", typeface, font_size, static_cast(0), static_cast(12), text_align, text_baseline); + return alphaskia_canvas_end_render(canvas); +} +std::array, 5> part_positions = +{{ + {static_cast(0), static_cast(0), static_cast(1300), static_cast(164)}, {static_cast(0), static_cast(164), static_cast(1300), static_cast(38)}, {static_cast(0), static_cast(202), static_cast(1300), static_cast(6)}, {static_cast(0), static_cast(208), static_cast(1300), static_cast(373)}, {static_cast(591.962890625), static_cast(581), static_cast(116.07421875), static_cast(24)} + }}; +int32_t total_width = 1300; +int32_t total_height = 605; +std::array all_parts = +{ + &draw_music_sheet_part_1, &draw_music_sheet_part_2, &draw_music_sheet_part_3, &draw_music_sheet_part_4, &draw_music_sheet_part_5 +}; diff --git a/test/native/src/PixelMatch.cpp b/test/native/src/PixelMatch.cpp new file mode 100644 index 0000000..c14171f --- /dev/null +++ b/test/native/src/PixelMatch.cpp @@ -0,0 +1,282 @@ +#include "../include/PixelMatch.h" +#include +#include + +// blend semi-transparent color with white +uint8_t blend(uint8_t c, uint8_t a) +{ + return 255 + (c - 255) * a; +} + +double rgb2y(uint8_t r, uint8_t g, uint8_t b) +{ + return r * 0.29889531 + g * 0.58662247 + b * 0.11448223; +} +double rgb2i(uint8_t r, uint8_t g, uint8_t b) +{ + return r * 0.59597799 - g * 0.2741761 - b * 0.32180189; +} +double rgb2q(uint8_t r, uint8_t g, uint8_t b) +{ + return r * 0.21147017 - g * 0.52261711 + b * 0.31114694; +} + +void draw_pixel(std::vector &output, int32_t pos, pixel_match_color_t color) +{ + output[pos + 0] = color.b; + output[pos + 1] = color.g; + output[pos + 2] = color.r; + output[pos + 3] = 255; +} + +double color_delta(const std::vector &img1, + const std::vector &img2, + int32_t k, + int32_t m, + bool yOnly) +{ + uint8_t r1 = img1[k + 0]; + uint8_t g1 = img1[k + 1]; + uint8_t b1 = img1[k + 2]; + uint8_t a1 = img1[k + 3]; + + uint8_t r2 = img2[m + 0]; + uint8_t g2 = img2[m + 1]; + uint8_t b2 = img2[m + 2]; + uint8_t a2 = img2[m + 3]; + + if (a1 == a2 && r1 == r2 && g1 == g2 && b1 == b2) + return 0; + + if (a1 < 255) + { + a1 /= 255; + r1 = blend(r1, a1); + g1 = blend(g1, a1); + b1 = blend(b1, a1); + } + + if (a2 < 255) + { + a2 /= 255; + r2 = blend(r2, a2); + g2 = blend(g2, a2); + b2 = blend(b2, a2); + } + + const double y = rgb2y(r1, g1, b1) - rgb2y(r2, g2, b2); + + if (yOnly) + return y; // brightness difference only + + const double i = rgb2i(r1, g1, b1) - rgb2i(r2, g2, b2); + const double q = rgb2q(r1, g1, b1) - rgb2q(r2, g2, b2); + + return 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q; +} + +// check if a pixel has 3+ adjacent pixels of the same color. +bool has_many_siblings(const std::vector &img, int32_t x1, int32_t y1, int32_t width, int32_t height) +{ + const int32_t distance = 1; + const int32_t x0 = std::max(x1 - distance, 0); + const int32_t y0 = std::max(y1 - distance, 0); + const int32_t x2 = std::min(x1 + distance, width - 1); + const int32_t y2 = std::min(y1 + distance, height - 1); + const int32_t pos = (y1 * width + x1) * 4; + int32_t zeroes = x1 == x0 || x1 == x2 || y1 == y0 || y1 == y2 ? 1 : 0; + + // go through 8 adjacent pixels + for (int32_t x = x0; x <= x2; x++) + { + for (int32_t y = y0; y <= y2; y++) + { + if (x == x1 && y == y1) + continue; + + const int32_t pos2 = (y * width + x) * 4; + if ( + img[pos] == img[pos2] && + img[pos + 1] == img[pos2 + 1] && + img[pos + 2] == img[pos2 + 2] && + img[pos + 3] == img[pos2 + 3]) + { + zeroes++; + } + + if (zeroes > 2) + return true; + } + } + + return false; +} + +// check if a pixel is likely a part of anti-aliasing; +// based on "Anti-aliased Pixel and Intensity Slope Detector" paper by V. Vysniauskas, 2009 + +bool antialiased(const std::vector &img, int32_t x1, int32_t y1, int32_t width, int32_t height, const std::vector &img2) +{ + const int32_t distance = 1; + const int32_t x0 = std::max(x1 - distance, 0); + const int32_t y0 = std::max(y1 - distance, 0); + const int32_t x2 = std::min(x1 + distance, width - 1); + const int32_t y2 = std::min(y1 + distance, height - 1); + const int32_t pos = (y1 * width + x1) * 4; + int32_t zeroes = x1 == x0 || x1 == x2 || y1 == y0 || y1 == y2 ? 1 : 0; + int32_t min = 0; + int32_t max = 0; + int32_t minX = 0; + int32_t minY = 0; + int32_t maxX = 0; + int32_t maxY = 0; + + // go through 8 adjacent pixels + for (int32_t x = x0; x <= x2; x++) + { + for (int32_t y = y0; y <= y2; y++) + { + if (x == x1 && y == y1) + { + continue; + } + + // brightness delta between the center pixel and adjacent one + const double delta = color_delta(img, img, pos, (y * width + x) * 4, true); + + // count the number of equal, darker and brighter adjacent pixels + if (delta == 0) + { + zeroes++; + // if found more than 2 equal siblings, it's definitely not anti-aliasing + if (zeroes > 2) + return false; + + // remember the darkest pixel + } + else if (delta < min) + { + min = delta; + minX = x; + minY = y; + + // remember the brightest pixel + } + else if (delta > max) + { + max = delta; + maxX = x; + maxY = y; + } + } + } + + // if there are no both darker and brighter pixels among siblings, it's not anti-aliasing + if (min == 0 || max == 0) + { + return false; + } + + // if either the darkest or the brightest pixel has 3+ equal siblings in both images + // (definitely not anti-aliased), this pixel is anti-aliased + return ( + (has_many_siblings(img, minX, minY, width, height) && + has_many_siblings(img2, minX, minY, width, height)) || + (has_many_siblings(img, maxX, maxY, width, height) && + has_many_siblings(img2, maxX, maxY, width, height))); +} + +pixel_match_result_t pixel_match(const std::vector &img1, + const std::vector &img2, + std::vector &diff_pixels, + int32_t width, + int32_t height, + pixel_match_options_t options) +{ + if (img1.size() != img2.size() || diff_pixels.size() != img1.size()) + { + throw std::exception("Image sizes do not match"); + } + + if (img1.size() != width * height * sizeof(uint32_t)) + { + throw std::exception("Image data size does not match width/height."); + } + + // check if images are identical + const uint32_t len = width * height; + bool identical = true; + uint32_t transparentPixels = 0; + + for (uint32_t i = 0; i < len; i++) + { + const uint8_t img1r = img1[(i * 4) + 0]; + const uint8_t img1g = img1[(i * 4) + 1]; + const uint8_t img1b = img1[(i * 4) + 2]; + const uint8_t img1a = img1[(i * 4) + 3]; + + const uint8_t img2r = img2[(i * 4) + 0]; + const uint8_t img2g = img2[(i * 4) + 1]; + const uint8_t img2b = img2[(i * 4) + 2]; + const uint8_t img2a = img2[(i * 4) + 3]; + + if (img1r != img2r || img1g != img2g || img1b != img2b || img1a != img2a) + { + identical = false; + break; + } + if (img1a == 0) + { + transparentPixels++; + } + } + + if (identical) + { + return {len, 0, transparentPixels}; + } + + transparentPixels = 0; + + // maximum acceptable square distance between two colors; + // 35215 is the maximum possible value for the YIQ difference metric + const double maxDelta = 35215 * options.threshold * options.threshold; + + uint32_t diff = 0; + + // compare each pixel of one image against the other one + for (int32_t y = 0; y < height; y++) + { + for (int32_t x = 0; x < width; x++) + { + const int32_t pos = (y * width + x) * sizeof(uint32_t); + + if (img1[pos + 3] == 0) + { + transparentPixels++; + } + // squared YUV distance between colors at this pixel position + const double delta = color_delta(img1, img2, pos, pos, false); + + // the color difference is above the threshold + if (delta > maxDelta) + { + // check it's a real rendering difference or just anti-aliasing + if ((antialiased(img1, x, y, width, height, img2) || + antialiased(img2, x, y, width, height, img1))) + { + // match + } + else + { + // found substantial difference not caused by anti-aliasing; draw it as red + draw_pixel(diff_pixels, pos, options.diff_color); + diff++; + } + } + } + } + + // return the number of different pixels + return {len, diff, transparentPixels}; +} \ No newline at end of file diff --git a/test/native/src/main.cpp b/test/native/src/main.cpp new file mode 100644 index 0000000..ec78d2b --- /dev/null +++ b/test/native/src/main.cpp @@ -0,0 +1,188 @@ +#include "../include/AlphaTabGeneratedTest.h" +#include "../include/PixelMatch.h" + +#include +#include +#include + +#ifndef ALPHASKIA_RID +#error "Missing alphaSkia runtime identifier, please specify ALPHASKIA_RID"; +#endif + +#define STRINGIFY(s) _STRINGIFY(s) +#define _STRINGIFY(s) #s + +std::filesystem::path find_repository_root(std::filesystem::path current) +{ + if (std::filesystem::exists(current / ".nuke")) + { + return current; + } + + if (!current.has_parent_path()) + { + throw std::exception("Could not find repository root"); + } + + return find_repository_root(current.parent_path()); +} + +alphaskia_image_t render_full_image() +{ + alphaskia_canvas_t full_image_canvas = alphaskia_canvas_new(); + std::cout << "Begin render with size " << total_width << "x" << total_height << " at scale" << render_scale << std::endl; + alphaskia_canvas_begin_render(full_image_canvas, total_width, total_height, render_scale); + + alphaskia_canvas_t partial_canvas = alphaskia_canvas_new(); + for (uint32_t i = 0; i < all_parts.size(); i++) + { + std::cout << "Render part " << i << std::endl; + alphaskia_image_t part = all_parts[i](partial_canvas); + + float x = part_positions[i][0]; + float y = part_positions[i][1]; + float w = part_positions[i][2]; + float h = part_positions[i][3]; + + std::cout << "Drawing part " << i << "into full image at " << x << "/" << y << "/" << w << "/" << h << std::endl; + alphaskia_canvas_draw_image(full_image_canvas, part, x, y, w, h); + alphaskia_image_free(part); + } + alphaskia_canvas_free(partial_canvas); + + alphaskia_image_t full_image = alphaskia_canvas_end_render(full_image_canvas); + alphaskia_canvas_free(full_image_canvas); + std::cout << "Render of full image completed" << std::endl; + + return full_image; +} + +void write_data_to_file_and_free(alphaskia_data_t data, std::string path) +{ + std::ofstream stream(path, std::ios::binary); + if (!stream) + { + alphaskia_data_free(data); + throw std::exception(("Could not open file " + path).c_str()); + } + + uint8_t *data_raw = alphaskia_data_get_data(data); + uint64_t data_length = alphaskia_data_get_length(data); + stream.write(reinterpret_cast(data_raw), static_cast(data_length)); + stream.flush(); + stream.close(); + + alphaskia_data_free(data); +} + +int main() +{ + const double tolerance_percent = 1; + try + { + std::filesystem::path repository_root = find_repository_root(std::filesystem::current_path()); + + // Load all fonts for rendering + std::cout << "Loading fonts" << std::endl; + std::filesystem::path test_data_path = repository_root / "test" / "test-data"; + music_typeface = alphaskia_load_typeface("Bravura", false, false, (test_data_path / "font" / "bravura" / "Bravura.ttf").generic_string()); + alphaskia_load_typeface("Roboto", false, false, (test_data_path / "font" / "roboto" / "Roboto-Regular.ttf").generic_string()); + alphaskia_load_typeface("Roboto", true, false, (test_data_path / "font" / "roboto" / "Roboto-Bold.ttf").generic_string()); + alphaskia_load_typeface("Roboto", false, true, (test_data_path / "font" / "roboto" / "Roboto-Italic.ttf").generic_string()); + alphaskia_load_typeface("Roboto", true, true, (test_data_path / "font" / "roboto" / "Roboto-BoldItalic.ttf").generic_string()); + alphaskia_load_typeface("PT Serif", false, false, (test_data_path / "font" / "ptserif" / "PTSerif-Regular.ttf").generic_string()); + alphaskia_load_typeface("PT Serif", true, false, (test_data_path / "font" / "ptserif" / "PTSerif-Bold.ttf").generic_string()); + alphaskia_load_typeface("PT Serif", false, true, (test_data_path / "font" / "ptserif" / "PTSerif-Italic.ttf").generic_string()); + alphaskia_load_typeface("PT Serif", true, true, (test_data_path / "font" / "ptserif" / "PTSerif-BoldItalic.ttf").generic_string()); + std::cout << "Fonts loaded" << std::endl; + + // render full image + std::cout << "Rendering image" << std::endl; + alphaskia_image_t actual_image = render_full_image(); + std::cout << "Image rendered" << std::endl; + + // save png for future reference + std::cout << "Saving image as PNG" << std::endl; + + std::filesystem::path test_output_path = repository_root / "test" / "test-outputs" / "native"; + std::filesystem::create_directories(test_output_path); + + std::filesystem::path test_output_file = test_output_path / (std::string(STRINGIFY(ALPHASKIA_RID)) + ".png"); + alphaskia_data_t png_data = alphaskia_image_encode_png(actual_image); + if (!png_data) + { + throw std::exception("Failed to encode final image to png"); + } + write_data_to_file_and_free(png_data, test_output_file.generic_string()); + std::cout << "Image saved to " << test_output_file.generic_string() << std::endl; + + // load reference image + std::filesystem::path test_reference_path = test_data_path / "reference" / (std::string(STRINGIFY(ALPHASKIA_RID)) + ".png"); + std::cout << "Loading reference image " << test_reference_path.generic_string() << std::endl; + std::vector test_reference_data; + read_file(test_reference_path.generic_string(), test_reference_data); + alphaskia_image_t expected_image = alphaskia_image_decode(test_reference_data.data(), test_reference_data.size()); + std::cout << "Reference image loaded" << std::endl; + + // compare images + std::cout << "Comparing images" << std::endl; + auto actual_width = alphaskia_image_get_width(actual_image); + auto actual_height = alphaskia_image_get_height(actual_image); + + auto expected_width = alphaskia_image_get_width(expected_image); + auto expected_height = alphaskia_image_get_height(expected_image); + if (actual_width != expected_width || actual_height != expected_height) + { + std::cerr << "Image sizes do not match: Actual[" << actual_width << "x" << actual_height << "] Expected[" << expected_width << "x" << actual_height << "]" << std::endl; + return 1; + } + + std::vector actual_pixels; + std::vector expected_pixels; + std::vector diff_pixels; + + size_t row_bytes = actual_width * sizeof(uint32_t); + size_t pixel_bytes = row_bytes * actual_height; + actual_pixels.resize(pixel_bytes); + expected_pixels.resize(pixel_bytes); + diff_pixels.resize(pixel_bytes); + + alphaskia_image_read_pixels(actual_image, actual_pixels.data(), row_bytes); + alphaskia_image_read_pixels(expected_image, expected_pixels.data(), row_bytes); + + auto compare_result = pixel_match(actual_pixels, + expected_pixels, + diff_pixels, + actual_width, + actual_height, + {/* threshold: */ 0.3, + /* alpha: */ 1, + /* diff_color: */ {255, 0, 0}, true}); + + int32_t totalPixels = compare_result.total_pixels - compare_result.transparent_pixels; + double percentDifference = ((double)compare_result.different_pixels / totalPixels) * 100; + bool pass = percentDifference < tolerance_percent; + if (!pass) + { + std::cerr << "Difference between original and new image is too big: " << compare_result.different_pixels << "/" << totalPixels << "(" << percentDifference << "%)"; + + // create diff image as PNG + alphaskia_image_t diff_image = alphaskia_image_from_pixels(actual_width, actual_height, diff_pixels.data()); + alphaskia_data_t diff_image_png_data = alphaskia_image_encode_png(diff_image); + alphaskia_image_free(diff_image); + + auto diff_output_path = test_output_path / (std::string(STRINGIFY(ALPHASKIA_RID)) + ".diff.png"); + write_data_to_file_and_free(diff_image_png_data, diff_output_path.generic_string()); + std::cout << "Error diff image saved to " << diff_output_path.generic_string() << std::endl; + return 1; + } + + std::cout << "Images match. Total Pixels: " << compare_result.total_pixels << ", Transparent Pixels: " << compare_result.transparent_pixels << ", Percent difference:" << percentDifference << "%" << std::endl; + return 0; + } + catch (std::exception &e) + { + std::cerr << "Failed to run test: " << e.what() << std::endl; + return 1; + } +} \ No newline at end of file diff --git a/lib/test/font/bravura/Bravura-FONTLOG.txt b/test/test-data/font/bravura/Bravura-FONTLOG.txt similarity index 100% rename from lib/test/font/bravura/Bravura-FONTLOG.txt rename to test/test-data/font/bravura/Bravura-FONTLOG.txt diff --git a/lib/test/font/bravura/Bravura-OFL-FAQ.txt b/test/test-data/font/bravura/Bravura-OFL-FAQ.txt similarity index 100% rename from lib/test/font/bravura/Bravura-OFL-FAQ.txt rename to test/test-data/font/bravura/Bravura-OFL-FAQ.txt diff --git a/lib/test/font/bravura/Bravura-OFL.txt b/test/test-data/font/bravura/Bravura-OFL.txt similarity index 100% rename from lib/test/font/bravura/Bravura-OFL.txt rename to test/test-data/font/bravura/Bravura-OFL.txt diff --git a/lib/test/font/bravura/Bravura.eot b/test/test-data/font/bravura/Bravura.eot similarity index 100% rename from lib/test/font/bravura/Bravura.eot rename to test/test-data/font/bravura/Bravura.eot diff --git a/lib/test/font/bravura/Bravura.otf b/test/test-data/font/bravura/Bravura.otf similarity index 100% rename from lib/test/font/bravura/Bravura.otf rename to test/test-data/font/bravura/Bravura.otf diff --git a/lib/test/font/bravura/Bravura.svg b/test/test-data/font/bravura/Bravura.svg similarity index 100% rename from lib/test/font/bravura/Bravura.svg rename to test/test-data/font/bravura/Bravura.svg diff --git a/lib/test/font/bravura/Bravura.ttf b/test/test-data/font/bravura/Bravura.ttf similarity index 100% rename from lib/test/font/bravura/Bravura.ttf rename to test/test-data/font/bravura/Bravura.ttf diff --git a/lib/test/font/bravura/Bravura.woff b/test/test-data/font/bravura/Bravura.woff similarity index 100% rename from lib/test/font/bravura/Bravura.woff rename to test/test-data/font/bravura/Bravura.woff diff --git a/lib/test/font/bravura/Bravura.woff2 b/test/test-data/font/bravura/Bravura.woff2 similarity index 100% rename from lib/test/font/bravura/Bravura.woff2 rename to test/test-data/font/bravura/Bravura.woff2 diff --git a/lib/test/font/bravura/BravuraText.eot b/test/test-data/font/bravura/BravuraText.eot similarity index 100% rename from lib/test/font/bravura/BravuraText.eot rename to test/test-data/font/bravura/BravuraText.eot diff --git a/lib/test/font/bravura/BravuraText.otf b/test/test-data/font/bravura/BravuraText.otf similarity index 100% rename from lib/test/font/bravura/BravuraText.otf rename to test/test-data/font/bravura/BravuraText.otf diff --git a/lib/test/font/bravura/BravuraText.svg b/test/test-data/font/bravura/BravuraText.svg similarity index 100% rename from lib/test/font/bravura/BravuraText.svg rename to test/test-data/font/bravura/BravuraText.svg diff --git a/lib/test/font/bravura/BravuraText.woff b/test/test-data/font/bravura/BravuraText.woff similarity index 100% rename from lib/test/font/bravura/BravuraText.woff rename to test/test-data/font/bravura/BravuraText.woff diff --git a/lib/test/font/bravura/bravura-text.md b/test/test-data/font/bravura/bravura-text.md similarity index 100% rename from lib/test/font/bravura/bravura-text.md rename to test/test-data/font/bravura/bravura-text.md diff --git a/lib/test/font/bravura/bravura_metadata.json b/test/test-data/font/bravura/bravura_metadata.json similarity index 100% rename from lib/test/font/bravura/bravura_metadata.json rename to test/test-data/font/bravura/bravura_metadata.json diff --git a/lib/test/font/ptserif/PTSerif-Bold.ttf b/test/test-data/font/ptserif/PTSerif-Bold.ttf similarity index 100% rename from lib/test/font/ptserif/PTSerif-Bold.ttf rename to test/test-data/font/ptserif/PTSerif-Bold.ttf diff --git a/lib/test/font/ptserif/PTSerif-BoldItalic.ttf b/test/test-data/font/ptserif/PTSerif-BoldItalic.ttf similarity index 100% rename from lib/test/font/ptserif/PTSerif-BoldItalic.ttf rename to test/test-data/font/ptserif/PTSerif-BoldItalic.ttf diff --git a/lib/test/font/ptserif/PTSerif-Italic.ttf b/test/test-data/font/ptserif/PTSerif-Italic.ttf similarity index 100% rename from lib/test/font/ptserif/PTSerif-Italic.ttf rename to test/test-data/font/ptserif/PTSerif-Italic.ttf diff --git a/lib/test/font/ptserif/PTSerif-OFL.txt b/test/test-data/font/ptserif/PTSerif-OFL.txt similarity index 100% rename from lib/test/font/ptserif/PTSerif-OFL.txt rename to test/test-data/font/ptserif/PTSerif-OFL.txt diff --git a/lib/test/font/ptserif/PTSerif-Regular.ttf b/test/test-data/font/ptserif/PTSerif-Regular.ttf similarity index 100% rename from lib/test/font/ptserif/PTSerif-Regular.ttf rename to test/test-data/font/ptserif/PTSerif-Regular.ttf diff --git a/lib/test/font/roboto/LICENSE.txt b/test/test-data/font/roboto/LICENSE.txt similarity index 100% rename from lib/test/font/roboto/LICENSE.txt rename to test/test-data/font/roboto/LICENSE.txt diff --git a/lib/test/font/roboto/Roboto-Bold.ttf b/test/test-data/font/roboto/Roboto-Bold.ttf similarity index 100% rename from lib/test/font/roboto/Roboto-Bold.ttf rename to test/test-data/font/roboto/Roboto-Bold.ttf diff --git a/lib/test/font/roboto/Roboto-BoldItalic.ttf b/test/test-data/font/roboto/Roboto-BoldItalic.ttf similarity index 100% rename from lib/test/font/roboto/Roboto-BoldItalic.ttf rename to test/test-data/font/roboto/Roboto-BoldItalic.ttf diff --git a/lib/test/font/roboto/Roboto-Italic.ttf b/test/test-data/font/roboto/Roboto-Italic.ttf similarity index 100% rename from lib/test/font/roboto/Roboto-Italic.ttf rename to test/test-data/font/roboto/Roboto-Italic.ttf diff --git a/lib/test/font/roboto/Roboto-Regular.ttf b/test/test-data/font/roboto/Roboto-Regular.ttf similarity index 100% rename from lib/test/font/roboto/Roboto-Regular.ttf rename to test/test-data/font/roboto/Roboto-Regular.ttf diff --git a/lib/test/reference/win11-x64-skiasharp.png b/test/test-data/reference/old-win-x64.png similarity index 100% rename from lib/test/reference/win11-x64-skiasharp.png rename to test/test-data/reference/old-win-x64.png diff --git a/test/test-data/reference/win-x64.png b/test/test-data/reference/win-x64.png new file mode 100644 index 0000000000000000000000000000000000000000..81479f137ae9f61bffb346684ed8ba0f2ccc9a06 GIT binary patch literal 34635 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 E52p*L$p8QV literal 0 HcmV?d00001 diff --git a/lib/test/test-generator/.idea/.idea.TestGenerator/.idea/.gitignore b/test/test-generator/.idea/.idea.TestGenerator/.idea/.gitignore similarity index 100% rename from lib/test/test-generator/.idea/.idea.TestGenerator/.idea/.gitignore rename to test/test-generator/.idea/.idea.TestGenerator/.idea/.gitignore diff --git a/lib/test/test-generator/.idea/.idea.TestGenerator/.idea/.name b/test/test-generator/.idea/.idea.TestGenerator/.idea/.name similarity index 100% rename from lib/test/test-generator/.idea/.idea.TestGenerator/.idea/.name rename to test/test-generator/.idea/.idea.TestGenerator/.idea/.name diff --git a/lib/test/test-generator/.idea/.idea.TestGenerator/.idea/encodings.xml b/test/test-generator/.idea/.idea.TestGenerator/.idea/encodings.xml similarity index 100% rename from lib/test/test-generator/.idea/.idea.TestGenerator/.idea/encodings.xml rename to test/test-generator/.idea/.idea.TestGenerator/.idea/encodings.xml diff --git a/lib/test/test-generator/.idea/.idea.TestGenerator/.idea/indexLayout.xml b/test/test-generator/.idea/.idea.TestGenerator/.idea/indexLayout.xml similarity index 100% rename from lib/test/test-generator/.idea/.idea.TestGenerator/.idea/indexLayout.xml rename to test/test-generator/.idea/.idea.TestGenerator/.idea/indexLayout.xml diff --git a/lib/test/test-generator/.idea/.idea.TestGenerator/.idea/vcs.xml b/test/test-generator/.idea/.idea.TestGenerator/.idea/vcs.xml similarity index 54% rename from lib/test/test-generator/.idea/.idea.TestGenerator/.idea/vcs.xml rename to test/test-generator/.idea/.idea.TestGenerator/.idea/vcs.xml index 1c8a6da..def3818 100644 --- a/lib/test/test-generator/.idea/.idea.TestGenerator/.idea/vcs.xml +++ b/test/test-generator/.idea/.idea.TestGenerator/.idea/vcs.xml @@ -2,7 +2,6 @@ - - + \ No newline at end of file diff --git a/test/test-generator/AlphaSkiaTestCanvas.cs b/test/test-generator/AlphaSkiaTestCanvas.cs new file mode 100644 index 0000000..d0d4bf2 --- /dev/null +++ b/test/test-generator/AlphaSkiaTestCanvas.cs @@ -0,0 +1,233 @@ +using AlphaTab; +using AlphaTab.Model; +using AlphaTab.Platform; + +namespace TestGenerator; + +class AlphaSkiaTestCanvas : ICanvas +{ + private readonly TestSourceBuilder _testSource; + private Color _color = new(255, 255, 255); + private double _lineWidth = 1; + private Font _font = new("Arial", 10); + private TextAlign _textAlign = TextAlign.Left; + private TextBaseline _textBaseline = TextBaseline.Top; + + private int _partialCounter = 1; + + public AlphaSkiaTestCanvas(TestSourceBuilder testSource) + { + _testSource = testSource; + } + + public Settings Settings { get; set; } = new(); + + public Color Color + { + get => _color; + set + { + if ((int)_color.Raw == (int)value.Raw) + { + return; + } + _testSource.WriteSetCanvasProperty("Color", + _testSource.MakeCallHelperMethod("RgbaToColor", + $"{_testSource.MakeCastToByte(value.R)}, {_testSource.MakeCastToByte(value.G)}, {_testSource.MakeCastToByte(value.B)}, {_testSource.MakeCastToByte(value.A)}", + false + ) + ); + _color = value; + } + } + + public double LineWidth + { + get => _lineWidth; + set + { + _testSource.WriteSetCanvasProperty("LineWidth", $"{_testSource.MakeCastToFloat(value)}"); + _lineWidth = value; + } + } + + public Font Font + { + get => _font; + set + { + _testSource.WriteSetTestProperty("Typeface", _testSource.MakeCallTestMethod("GetTypeface", + $"\"{value.Families[0]}\", {(value.IsBold ? "true" : "false")}, {(value.IsItalic ? "true" : "false")}", + false + )); + _testSource.WriteSetTestProperty("FontSize", $"{_testSource.MakeCastToFloat(value.Size)}"); + _font = value; + } + } + + public TextAlign TextAlign + { + get => _textAlign; + set + { + _testSource.WriteSetTestProperty("TextAlign", _testSource.MakeEnumAccess("AlphaSkiaTextAlign", value.ToString())); + _textAlign = value; + } + } + + public TextBaseline TextBaseline + { + get => _textBaseline; + set + { + _testSource.WriteSetTestProperty("TextBaseline", _testSource.MakeEnumAccess("AlphaSkiaTextBaseline", value.ToString())); + _textBaseline = value; + } + } + + public void FillRect(double x, double y, double w, double h) + { + _testSource.WriteCallCanvasMethod("FillRect", $"{_testSource.MakeCastToFloat(x)}, {_testSource.MakeCastToFloat(y)}, {_testSource.MakeCastToFloat(w)}, {_testSource.MakeCastToFloat(h)}"); + } + + public void StrokeRect(double x, double y, double w, double h) + { + _testSource.WriteCallCanvasMethod("StrokeRect", $"{_testSource.MakeCastToFloat(x)}, {_testSource.MakeCastToFloat(y)}, {_testSource.MakeCastToFloat(w)}, {_testSource.MakeCastToFloat(h)}"); + } + + public void FillCircle(double x, double y, double radius) + { + _testSource.WriteCallCanvasMethod("FillCircle", $"{_testSource.MakeCastToFloat(x)}, {_testSource.MakeCastToFloat(y)}, {_testSource.MakeCastToFloat(radius)}"); + } + + public void StrokeCircle(double x, double y, double radius) + { + _testSource.WriteCallCanvasMethod("StrokeCircle", $"{_testSource.MakeCastToFloat(x)}, {_testSource.MakeCastToFloat(y)}, {_testSource.MakeCastToFloat(radius)}"); + } + + public void BeginGroup(string identifier) + { + } + + public void EndGroup() + { + } + + public void FillText(string text, double x, double y) + { + _testSource.WriteCallCanvasMethod("FillText", + $"{_testSource.EncodeString(text)}, {_testSource.MakeTestGetProperty("Typeface")}, {_testSource.MakeTestGetProperty("FontSize")}, {_testSource.MakeCastToFloat(x)}, {_testSource.MakeCastToFloat(y)}, {_testSource.MakeTestGetProperty("TextAlign")}, {_testSource.MakeTestGetProperty("TextBaseline")}"); + } + + public double MeasureText(string text) + { + return SkiaSharpTextMeasurer.MeasureText(_font, text); + } + + public void FillMusicFontSymbol(double x, double y, double scale, MusicFontSymbol symbol, + bool centerAtPosition = false) + { + FillMusicFontSymbols(x, y, scale, new List + { + symbol + }, centerAtPosition); + } + + public void FillMusicFontSymbols(double x, double y, double scale, IList symbols, + bool centerAtPosition = false) + { + var text = _testSource.EncodeString( + new string(symbols.Select(s => (char)((int)s)).ToArray())); + + var textAlign = centerAtPosition + ? _testSource.MakeEnumAccess("AlphaSkiaTextAlign", "Center") + : _testSource.MakeEnumAccess("AlphaSkiaTextAlign", "Left"); + + _testSource.WriteCallCanvasMethod("FillText", + $"{text}, {_testSource.MakeTestGetProperty("MusicTypeface")}, {MakeGetMusicFontSize(scale)}, {_testSource.MakeCastToFloat(x)}, {_testSource.MakeCastToFloat(y)}, {textAlign}, {_testSource.MakeEnumAccess("AlphaSkiaTextBaseline", "Alphabetic")}"); + } + + private string MakeGetMusicFontSize(double? scale = null) + { + if(scale == null) + { + return _testSource.MakeCastToFloat(_testSource.MakeTestGetProperty("MusicFontSize")); + } + else + { + return _testSource.MakeCastToFloat($"{_testSource.MakeTestGetProperty("MusicFontSize")} * {scale}"); + } + } + + public void BeginRender(double width, double height) + { + _testSource.Resume(); + _testSource.WritePartialRenderMethod("DrawMusicSheetPart" + _partialCounter++); + _testSource.BeginBlock(); + _testSource.WriteCallCanvasMethod("BeginRender", $"{(int)width}, {(int)height}, {_testSource.MakeCastToFloat(_testSource.MakeTestGetProperty("RenderScale"))}"); + } + + public object EndRender() + { + _testSource.WriteLine($"return {_testSource.MakeCallCanvasMethod("EndRender", "", true)}"); + _testSource.EndBlock(); + _testSource.Suspend(); + return _testSource; + } + + public object? OnRenderFinished() + { + return null; + } + + public void BeginRotate(double centerX, double centerY, double angle) + { + _testSource.WriteCallCanvasMethod("BeginRotate", $"{_testSource.MakeCastToFloat(centerX)}, {_testSource.MakeCastToFloat(centerY)}, {_testSource.MakeCastToFloat(angle)}"); + } + + public void EndRotate() + { + _testSource.WriteCallCanvasMethod("EndRotate"); + } + + public void BeginPath() + { + _testSource.WriteCallCanvasMethod("BeginPath"); + } + + public void ClosePath() + { + _testSource.WriteCallCanvasMethod("ClosePath"); + } + + public void Fill() + { + _testSource.WriteCallCanvasMethod("Fill"); + } + + public void Stroke() + { + _testSource.WriteCallCanvasMethod("Stroke"); + } + + public void MoveTo(double x, double y) + { + _testSource.WriteCallCanvasMethod("MoveTo", $"{_testSource.MakeCastToFloat(x)}, {_testSource.MakeCastToFloat(y)}"); + } + + public void LineTo(double x, double y) + { + _testSource.WriteCallCanvasMethod("LineTo", $"{_testSource.MakeCastToFloat(x)}, {_testSource.MakeCastToFloat(y)}"); + } + + public void BezierCurveTo(double cp1X, double cp1Y, double cp2X, double cp2Y, double x, double y) + { + _testSource.WriteCallCanvasMethod("BezierCurveTo", + $"{_testSource.MakeCastToFloat(cp1X)}, {_testSource.MakeCastToFloat(cp1Y)}, {_testSource.MakeCastToFloat(cp2X)}, {_testSource.MakeCastToFloat(cp2Y)}, {_testSource.MakeCastToFloat(x)}, {_testSource.MakeCastToFloat(y)}"); + } + + public void QuadraticCurveTo(double cpx, double cpy, double x, double y) + { + _testSource.WriteCallCanvasMethod("QuadraticCurveTo", $"{_testSource.MakeCastToFloat(cpx)}, {_testSource.MakeCastToFloat(cpy)}, {_testSource.MakeCastToFloat(x)}, {_testSource.MakeCastToFloat(y)}"); + } +} \ No newline at end of file diff --git a/lib/test/test-generator/AlphaSkiaUnitTestGenerator.cs b/test/test-generator/AlphaSkiaUnitTestGenerator.cs similarity index 71% rename from lib/test/test-generator/AlphaSkiaUnitTestGenerator.cs rename to test/test-generator/AlphaSkiaUnitTestGenerator.cs index 09a6407..a6e304d 100644 --- a/lib/test/test-generator/AlphaSkiaUnitTestGenerator.cs +++ b/test/test-generator/AlphaSkiaUnitTestGenerator.cs @@ -1,5 +1,7 @@ -using AlphaTab; +using System.Diagnostics.Tracing; +using AlphaTab; using AlphaTab.Importer; +using AlphaTab.Model; using AlphaTab.Rendering; namespace TestGenerator; @@ -12,6 +14,7 @@ private static (List partialPositions, double totalWidth, double total settings.Core.UseWorkers = false; settings.Core.EnableLazyLoading = false; + // custom fonts settings.Display.Resources.CopyrightFont.Families = new[] { "Roboto" }; settings.Display.Resources.TitleFont.Families = new[] { "PT Serif" }; settings.Display.Resources.SubTitleFont.Families = new[] { "PT Serif" }; @@ -24,6 +27,10 @@ private static (List partialPositions, double totalWidth, double total settings.Display.Resources.FingeringFont.Families = new[] { "PT Serif" }; settings.Display.Resources.MarkerFont.Families = new[] { "PT Serif" }; + // add some colors to ensure we test this as well + settings.Display.Resources.ScoreInfoColor = new Color(153, 204, 30); + settings.Display.Resources.BarSeparatorColor = new Color(153, 51, 204, 100); + var scoreRenderer = new ScoreRenderer(settings) { Width = 1300 @@ -47,7 +54,7 @@ void HandleResult(RenderFinishedEventArgs e) { totalWidth = e.TotalWidth; totalHeight = e.TotalHeight; - partialPositions.Add(new[] { e.X, e.Y }); + partialPositions.Add(new[] { e.X, e.Y, e.Width, e.Height }); } } @@ -72,7 +79,7 @@ public static string GenerateCSharp() Engine = "CSharpTestCode" } }; - var sourceCode = new CSharpSourceBuilder(); + var sourceCode = new CSharpTestSourceBuilder(); AlphaTab.Environment.RenderEngines.Set( settings.Core.Engine, new RenderEngineFactory(false, () => new AlphaSkiaTestCanvas(sourceCode)) @@ -118,7 +125,7 @@ public static string GenerateJava() Engine = "JavaTestCode" } }; - var sourceCode = new JavaSourceBuilder(); + var sourceCode = new JavaTestSourceBuilder(); AlphaTab.Environment.RenderEngines.Set( settings.Core.Engine, new RenderEngineFactory(false, () => new AlphaSkiaTestCanvas(sourceCode)) @@ -181,16 +188,17 @@ public static string GenerateTypeScript() Engine = "TypeScriptTestCode" } }; - var sourceCode = new TypeScriptSourceBuilder(); + var sourceCode = new TypeScriptTestSourceBuilder(); AlphaTab.Environment.RenderEngines.Set( settings.Core.Engine, new RenderEngineFactory(false, () => new AlphaSkiaTestCanvas(sourceCode)) ); sourceCode.Resume(); - + sourceCode.WriteLine("import { MusicSheetRenderTest } from './MusicSheetRenderTest'; "); - sourceCode.WriteLine("import { AlphaSkiaTextBaseline, AlphaSkiaTextAlign, AlphaSkiaCanvas, AlphaSkiaImage } from 'src/alphaskia'; "); + sourceCode.WriteLine( + "import { AlphaSkiaTextBaseline, AlphaSkiaTextAlign, AlphaSkiaCanvas, AlphaSkiaImage } from 'src/alphaskia'; "); sourceCode.WriteLine(); sourceCode.Write("describe('AlphaTabGeneratedRenderTest', function() "); @@ -205,7 +213,7 @@ public static string GenerateTypeScript() sourceCode.Resume(); sourceCode.WriteLine("test.partPositions = ["); sourceCode.WriteLine(" " + string.Join(", ", - result.partialPositions.Select(p => "[" + string.Join(", ", p) + "]"))); + result.partialPositions.Select(p => "[" + string.Join(", ", p) + "]"))); sourceCode.WriteLine("];"); sourceCode.WriteLine($"test.totalWidth = {result.totalWidth};"); sourceCode.WriteLine($"test.totalHeight = {result.totalHeight};"); @@ -221,5 +229,65 @@ public static string GenerateTypeScript() return sourceCode.ToString(); } - + + public static string GenerateCppSource(out string headerSourceCode) + { + var settings = new Settings + { + Core = + { + Engine = "CppTestCode" + } + }; + var sourceCode = new CppTestSourceBuilder(); + AlphaTab.Environment.RenderEngines.Set( + settings.Core.Engine, + new RenderEngineFactory(false, () => new AlphaSkiaTestCanvas(sourceCode)) + ); + + sourceCode.Resume(); + + sourceCode.WriteLine("#include \"../include/AlphaTabGeneratedTest.h\""); + sourceCode.WriteLine(); + sourceCode.Suspend(); + + var result = GenerateTestCode(settings); + + sourceCode.Resume(); + sourceCode.WriteLine($"std::array, {result.partialPositions.Count}> part_positions = "); + sourceCode.Write("{"); + sourceCode.BeginBlock(); + + sourceCode.WriteLine(" " + string.Join(", ", + result.partialPositions.Select( + p => "{" + string.Join(", ", p.Select(d => $"static_cast({d})")) + "}"))); + + sourceCode.Write("}"); + sourceCode.EndBlock(true); + + sourceCode.WriteLine($"int32_t total_width = {(int)result.totalWidth};"); + sourceCode.WriteLine($"int32_t total_height = {(int)result.totalHeight};"); + + sourceCode.WriteLine($"std::array all_parts = "); + sourceCode.BeginBlock(); + var parts = result.partialPositions.Select((_, i) => $"&draw_music_sheet_part_{i + 1}"); + sourceCode.WriteLine(string.Join(", ", parts)); + sourceCode.EndBlock(true); + + sourceCode.Suspend(); + + var headerSourceCodeBuilder = new CppTestSourceBuilder(); + headerSourceCodeBuilder.Resume(); + + headerSourceCodeBuilder.WriteLine("#pragma once"); + headerSourceCodeBuilder.WriteLine("#include \"AlphaSkiaTestBridge.h\""); + headerSourceCodeBuilder.WriteLine(); + headerSourceCodeBuilder.WriteLine( + $"extern std::array, {result.partialPositions.Count}> part_positions;"); + headerSourceCodeBuilder.WriteLine( + $"extern std::array all_parts;"); + headerSourceCode = headerSourceCodeBuilder.ToString(); + + return sourceCode.ToString(); + } } \ No newline at end of file diff --git a/lib/test/test-generator/PathUtils.cs b/test/test-generator/PathUtils.cs similarity index 100% rename from lib/test/test-generator/PathUtils.cs rename to test/test-generator/PathUtils.cs diff --git a/test/test-generator/Program.cs b/test/test-generator/Program.cs new file mode 100644 index 0000000..9956ccf --- /dev/null +++ b/test/test-generator/Program.cs @@ -0,0 +1,29 @@ +using System.Globalization; +using TestGenerator; + +CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; +CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; +CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; +CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture; + +var rootDir = PathUtils.RepositoryRoot; + +var nativeTestFileCpp = Path.Combine(rootDir, "test", "native", "src", "AlphaTabGeneratedTest.cpp"); +var nativeTestFileH = Path.Combine(rootDir, "test", "native", "include", "AlphaTabGeneratedTest.h"); +File.WriteAllText(nativeTestFileCpp, AlphaSkiaUnitTestGenerator.GenerateCppSource(out var headerSource)); +Console.WriteLine("Updated C++ source in {0}", nativeTestFileCpp); +File.WriteAllText(nativeTestFileH, headerSource); +Console.WriteLine("Updated C++ Header in {0}", nativeTestFileH); + +// var csharpTestFile = Path.Combine(rootDir, "test", "dotnet", "AlphaSkia.Test", "AlphaTabGeneratedTest.cs"); +// File.WriteAllText(csharpTestFile, AlphaSkiaUnitTestGenerator.GenerateCSharp()); +// Console.WriteLine("Updated C# source in {0}", csharpTestFile); +// +// var javaTestFile = Path.Combine(rootDir, "test", "src", "main", "java", "net", "alphatab", "alphaskia", +// "AlphaTabGeneratedRenderTest.java"); +// File.WriteAllText(javaTestFile, AlphaSkiaUnitTestGenerator.GenerateJava()); +// Console.WriteLine("Updated Java source in {0}", javaTestFile); +// +// var typeScriptTestFile = Path.Combine(rootDir, "test", "node", "src", "AlphaTabGeneratedRenderTest.ts"); +// File.WriteAllText(typeScriptTestFile, AlphaSkiaUnitTestGenerator.GenerateTypeScript()); +// Console.WriteLine("Updated TypeScript source in {0}", typeScriptTestFile); \ No newline at end of file diff --git a/lib/test/test-generator/SkiaSharpTextMeasurer.cs b/test/test-generator/SkiaSharpTextMeasurer.cs similarity index 76% rename from lib/test/test-generator/SkiaSharpTextMeasurer.cs rename to test/test-generator/SkiaSharpTextMeasurer.cs index 54a9ddf..11c643a 100644 --- a/lib/test/test-generator/SkiaSharpTextMeasurer.cs +++ b/test/test-generator/SkiaSharpTextMeasurer.cs @@ -40,20 +40,22 @@ private static string CustomTypeFaceKey(string fontFamily, bool isBold, bool isI static SkiaSharpTextMeasurer() { var repositoryRoot = PathUtils.RepositoryRoot; - RegisterCustomFont(Path.Combine(repositoryRoot, "lib", "test", "font", "bravura", "Bravura.ttf")); - - RegisterCustomFont(Path.Combine(repositoryRoot, "lib", "test", "font", "roboto", "Roboto-Regular.ttf")); - RegisterCustomFont(Path.Combine(repositoryRoot, "lib", "test", "font", "roboto", "Roboto-Bold.ttf")); - RegisterCustomFont(Path.Combine(repositoryRoot, "lib", "test", "font", "roboto", "Roboto-Italic.ttf")); - RegisterCustomFont(Path.Combine(repositoryRoot, "lib", "test", "font", "roboto", "Roboto-BoldItalic.ttf")); - - RegisterCustomFont(Path.Combine(repositoryRoot, "lib", "test", "font", "ptserif", "PTSerif-Regular.ttf")); - RegisterCustomFont(Path.Combine(repositoryRoot, "lib", "test", "font", "ptserif", "PTSerif-Bold.ttf")); - RegisterCustomFont(Path.Combine(repositoryRoot, "lib", "test", "font", "ptserif", "PTSerif-Italic.ttf")); - RegisterCustomFont(Path.Combine(repositoryRoot, "lib", "test", "font", "ptserif", "PTSerif-BoldItalic.ttf")); + RegisterCustomFont(Path.Combine(repositoryRoot, "test", "test-data", "font", "bravura", "Bravura.ttf")); + + RegisterCustomFont(Path.Combine(repositoryRoot, "test", "test-data", "font", "roboto", "Roboto-Regular.ttf")); + RegisterCustomFont(Path.Combine(repositoryRoot, "test", "test-data", "font", "roboto", "Roboto-Bold.ttf")); + RegisterCustomFont(Path.Combine(repositoryRoot, "test", "test-data", "font", "roboto", "Roboto-Italic.ttf")); + RegisterCustomFont(Path.Combine(repositoryRoot, "test", "test-data", "font", "roboto", + "Roboto-BoldItalic.ttf")); + + RegisterCustomFont(Path.Combine(repositoryRoot, "test", "test-data", "font", "ptserif", "PTSerif-Regular.ttf")); + RegisterCustomFont(Path.Combine(repositoryRoot, "test", "test-data", "font", "ptserif", "PTSerif-Bold.ttf")); + RegisterCustomFont(Path.Combine(repositoryRoot, "test", "test-data", "font", "ptserif", "PTSerif-Italic.ttf")); + RegisterCustomFont(Path.Combine(repositoryRoot, "test", "test-data", "font", "ptserif", + "PTSerif-BoldItalic.ttf")); } - + public static double MeasureText(Font font, string text) { if (string.IsNullOrEmpty(text)) diff --git a/lib/test/test-generator/TestFile.gp b/test/test-generator/TestFile.gp similarity index 100% rename from lib/test/test-generator/TestFile.gp rename to test/test-generator/TestFile.gp diff --git a/lib/test/test-generator/TestGenerator.csproj b/test/test-generator/TestGenerator.csproj similarity index 100% rename from lib/test/test-generator/TestGenerator.csproj rename to test/test-generator/TestGenerator.csproj diff --git a/lib/test/test-generator/TestGenerator.sln b/test/test-generator/TestGenerator.sln similarity index 100% rename from lib/test/test-generator/TestGenerator.sln rename to test/test-generator/TestGenerator.sln diff --git a/test/test-generator/TestSourceBuilder.cs b/test/test-generator/TestSourceBuilder.cs new file mode 100644 index 0000000..297d7c7 --- /dev/null +++ b/test/test-generator/TestSourceBuilder.cs @@ -0,0 +1,385 @@ +using System.Text; +using System.Text.RegularExpressions; + +namespace TestGenerator; + +abstract partial class TestSourceBuilder +{ + private readonly StringBuilder _source = new(); + private bool _isStartOfLine; + private int _indent; + private bool _suspended; + + public override string ToString() + { + return _source.ToString(); + } + + public void WriteLine(string? text = null) + { + if (_suspended) + { + return; + } + + if (text != null) + { + Write(text); + } + + _source.AppendLine(); + _isStartOfLine = true; + } + + public void Write(string text) + { + if (_suspended) + { + return; + } + + WriteIndent(); + _source.Append(text); + _isStartOfLine = false; + } + + private void WriteIndent() + { + if (_isStartOfLine && _indent > 0) + { + _source.Append("".PadLeft(4 * _indent, ' ')); + _isStartOfLine = false; + } + } + + public void BeginBlock() + { + if (_suspended) + { + return; + } + + WriteLine("{"); + _indent++; + } + + public void EndBlock(bool statement = false) + { + if (_suspended) + { + return; + } + + _indent--; + WriteLine("}" + (statement ? ";" : "")); + } + + public abstract void WritePartialRenderMethod(string methodName); + public abstract string MakeEnumAccess(string type, string field); + + public abstract string MakeTestGetProperty(string property); + + public abstract string MakeCallHelperMethod(string methodName, string args, bool statement); + public abstract string MakeCallTestMethod(string methodName, string args, bool statement); + public abstract string MakeCallCanvasMethod(string methodName, string args, bool statement); + + public abstract void WriteSetTestProperty(string property, string value); + public abstract void WriteSetCanvasProperty(string property, string value); + + public abstract string MakeCastToFloat(string value); + public abstract string MakeCastToByte(double value); + + public string MakeCastToFloat(double value) + { + return MakeCastToFloat(value.ToString(System.Globalization.CultureInfo.InvariantCulture)); + } + + public void WriteCallCanvasMethod(string methodName, string args = "", bool statement = true) + { + WriteLine(MakeCallCanvasMethod(methodName, args, statement)); + } + + protected static string ToPascalCase(string s) + { + return s[..1].ToUpperInvariant() + s[1..]; + } + + protected static string ToCamelCase(string s) + { + return s[..1].ToLowerInvariant() + s[1..]; + } + + [GeneratedRegex("(? 0 ? "canvas, " + args : "canvas"; + return MakeCallMethod($"alphaskia_canvas_{ToSnakeCase(methodName)}", args, statement); + } + + private string MakeCallMethod(string methodName, string args = "", bool statement = true) + { + return $"{methodName}({args}){(statement ? ";" : "")}"; + } + + public override string MakeCastToFloat(string value) + { + return $"static_cast({value})"; + } + + public override string MakeCastToByte(double value) + { + return $"static_cast({(byte)value})"; + } + + public override void WriteSetTestProperty(string property, string value) + { + WriteLine($"{ToSnakeCase(property)} = {value};"); + } + + public override string MakeTestGetProperty(string property) + { + return $"{ToSnakeCase(property)}"; + } + + public override string MakeEnumAccess(string type, string field) + { + return $"{ToSnakeCase(type)}_{field.ToLower()}"; + } + + public override void WriteSetCanvasProperty(string property, string value) + { + WriteLine($"alphaskia_canvas_set_{ToSnakeCase(property)}(canvas, {value});"); + } + + public override string EncodeString(string text) + { + return "u" + base.EncodeString(text); + } +} \ No newline at end of file diff --git a/test/test-outputs/native/win-x64.diff.png b/test/test-outputs/native/win-x64.diff.png new file mode 100644 index 0000000000000000000000000000000000000000..3bb574a1f9973fbad0d8236467b973040a7a2b45 GIT binary patch literal 5798 zcmeHLc~q0f79X~Y??b>0~^Hy8;R~JkYZH6Nd#c7H_>i` z`=KmxOJ8WwW=-r#hJj?d{;7?9o??G3xx2>GHSeyR^}+DVBYm}36rudzj~Z^CcJ1wD z0dP}K-~+c8?>(n`u)dW#1sk%3Zeg(}}l7V>3yL;Lx#b zp*jFH7whZ*wqNCUDs-brnwDa? zAIf1I0IBH?j@vXqjxS?y2H-IEOjxa9r@PfZ9VoqgdDu%41Z3Lq0k9khyee@^95-uo z9uEnL(w`A$X@(Z{?~<*+(J}9eyXf0#0LX7W)K}W>wSHh7nMluSntpkmQ5g@wixHWF zri5w~g40M+0swyFbmTJOYrIb~WF>=0b6S2G5GAr2jL$m(J5 z13>5IT!BJzzVXl_C!sF6~M8 zs;yv#m6@$&4No0+kP;3&V~WwTKR|67jH}AP1|N;?=VbBISV?K1mVYs_%AgN`aLZI+wO&wqsBZ<2DZX4LbE#~B|MFOe$DgGz0!GAf z#yVa5(Wls_1}YgfL7|uuk3tih3XV+iq`fdICcL6d z@_jh-PEl$mly_H6Mo>xk^J(>iJ5KEvJh$u~j(lC(wf~&&anZ1_bg08Pl^rudV&1#<^yN+l%mT+B3g?` z(d)e`QeVXj=)!y)^zlw7uaePRLF9-u?y+fFh1I<$%!^;UtbCN0=|)T4_iOB|^Z9}I zDU!bAr?irkEVOBJ&x{nS8!5T|7enb!wxp2ulwK~%VM$LCh7=C&UFb3KkCw?zpIb}` z&9bGLHHF%$P$t;or%xHdOPIOO=HDWEY`8whBCj&bY=S?hV3lCx*o~N0H@YYMIsEc*p zH*>x3e2>Wp;+xT)wYx>9xaCsjsNedr5oi=lYCNCA7o2a7X?Cd!TeC$~V{9W;9v0sxl#$+n2dqZRE{!>?S@ke9G{v2$7Mj}kYA z3TB*8Llb-oJ2~nqZt=0y`7ieBiE+b`I6t|d?KSomvtyu)Sz^1W?OQ%PBymTqC zaZ5VwSEk=6V|2xNWjpXI%4%JjO*Y z)2qX#@qjeZ(%89_i*9P@}#%J`Lg1IP7 z%F;6!)LEL>-nbjZ?*T=7AHU?Jd|5PCEi)KY8%hjtL^0h)J&Y?^j6=9b-t@uKqU}D* zgDKf{vUmtycl4WTU1cQ|siA31aI;~(^zJqUwjYGzKY*!*3|Jh0JD$tEGK&!7OWonu zXASRptc?XeoWkz-XZ}1{A%6tby#K{4?`2I2PrK6D2`-+#etmsk(o@&U1Cw(y1eo5R zO@Mbb0L-X4iY)u~?f|yC(B;g3&boRN={I9q`BAT`f*2IW*~rSe?Yw9g8~+7@v0=uW zH+hX*9azAB$zWsK9!;}&e=huB``&|GRod7NJ$QXn$J8>pI;uO|G$c!t7o%)<*U>kn%Act7$hY|u7&HG-4_o(CVP1tvn3dws91r%I5or!izv?tjyHtsz9 E8_1)#L;wH) literal 0 HcmV?d00001 diff --git a/test/test-outputs/native/win-x64.png b/test/test-outputs/native/win-x64.png new file mode 100644 index 0000000000000000000000000000000000000000..81479f137ae9f61bffb346684ed8ba0f2ccc9a06 GIT binary patch literal 34635 zcmd?QS6EY9_&yl**bXWl3y2C>0;n{liZqX?7^;HOo6@8U0YXS1*g!-S>gQt^UTFOGjlUHGy4KG*?aA^zVd$W_pU|cZ6n?Ne;@xF z0)gy@-MVf9f$Tg2fo!+kvkQEpBgFXve*Fc%2D`flTzhR=`|Xvj_#r9r z{#ggDL$AhP>pZm4?VKmU>8W?gS3|48o`3zq4wje3Ki3=Ud}w?4@6$FPDDyG%YkIn6 z(AM6OP%ZHo<_|S$E1jdIOSkCN&(Plv=Jx-!YX)Y?0PC_)OCRHnoL`&FOiM^f4X=7- zSNn-|vtu;Hiigyzg08*K^eo7-tMXv>zt+F20D*Y4k@IbVEh1)K?d#5R%NSIsB-_@`kHaoigo@(FO0ZFs?>m_)~N>u##N%fD2*3qY2b$waYikOI9 zx;Zz3)8Cfw!p3gasu4E0=t9KJj^oFUZ!G;Oz1z!p9^|NMvGE$42A5$#ASJ}*!oY11 zNZW^Aoe>nqw<1Sn+kFC@6$lS85uY9XaIaxL}Rw?EoTJ%Py*J6Us4zv7z`1kx=2_za|B?}Pczj|Ez#S*UZfxSg4|M&I+ziL?lT}zBl+2yZ&SGM~rK(h~AP8Y@pxEzrNQGvFzvNvK^^XS_NQS$OZ-&`T*saI*rZ_<|MOA($0*x`mB38nT1l2uu~>;k)^Qrx;s zPs8aTGGcS8sNGRjvK<|xWBo0(w0G7R8|Sye_oR8T5d=azd;2fQ<$w3>A4Rw_T5a=v z8Iq@DtYe|d;@(29li$ntRU+ziK5TrgP7gQap*X#1}QnV z{RRYLW>gpf0@urm0pjy=Am0@w1$rS)o83?S<}M^`hcp~I2z*;o5Wb6~F{u6w$@hYJ zxflc@wDs=#D`#DOY&}m@CXmw57vGx8RcV4jV}P^6EV~zTIg=t$UWIXy`8jj0DAyVc z8{Mobtpna6v==xbseRLZzB|r*nPoDJdi3kdN6{(`r66j0F5b;g=e#-M=DOglW$8TisMK%>;vZ zxIk2=t7{)dI-egH)%4LITZaQL`>vL5Wl`IxiD+?Fy=NcoXN*6{om-kyOK~>1xYr}x zYUoOPc~7T^Sbri`pBD0=QcMpy(VT)81~2vC`(fs~tJAv<)YboNm$EXUgv{7v~p%?M~rtrB7v1hLzxcFOi`NY~OB(!&F-RAjq$a zbsgWYrh9RI{Q7f=d#5HL*fMuAyy;#~zq-aF>+=J|ACJmdlLA>rp8D$tWSUmMA~i#{ zaH@;Jx4xpnZPS_e??3nugVWtZ}8d?tXvGdXv{OS`5LWV^|K9%o`g6|B_o$fK$ zt6~=&8Cf0@jK#j9 z*);#BO=`ij@mAW^KdM}3dMrHfb$C-bjc~sh%ev#s6DxJuv`Co~=4Bv}quWmMt!+`= zH<}g8lt{4Y6qys(w4({h97dvBwud8tHC*_Z+56bm`esH~`LUK8u!QrsWp=c7&299} z*dEyh39D>>;|kuMt*B8_tT;?U(O84#J^<YLvJ7v%&`!!}D~>05nSuFH0GN7)|vLm>*I zbRyoNe!mCBFBR+L=bJbs8zZ5jiY_%_SVOmb;DTX)>)ek=nqLvp3@ zGKZB5zd?1Le(d7OUzzzJQ#q$*C-e>^?f>0nHo~tBAXwNkbmc2&xopJEUG9A3^t0Oy z%$)DDg?W(%aiZrZhdaIxSPORgnycI5rnYN+!miNwraO;Ey$)Mjhm@;F2HTfyS8`0iHIQk7WQi1VAf|GF>uA-^gE zSkh$htfrm$L47%Z>#g2p)XLim9@MBOLvEc6Lh8d-g3-6!OcvpfECSNj4Z*xoE|wU4 z-=sO#X?~%>EB3TQuCUVB=ygTa2LNt&>9+p(_0Ua+G?<70s25SGK1{0HFEIqiW+1f;4{@L|B^6ud%a3E}J70e!{L~-9A5IyP(AwEEuwm*=W93C)( zX{R3t-2SCq5 zTy>kPsBz1;Nh1Gt#{` znDqEvGY~D1Xd~Ak2Ya8eME{86Y%x$66dSK|=UUfW$n*0fX28%kZvjEzrH>p<&54#? zDN7zQfp_s2qz(dEy!S+DozH283gP_!MIJaDwU`uv{JPvDqK5JSsTVR_^cMjU^sCCG zOcQ1@gX5FX0+UD(nL7U!spkJMN2A22h+7#arE>a#==_MCi68*l$>M{cn7rjXhj3x# zNnG$}n>vJ-Se7&ed;_qrXFD+>|+hbs?1%-$+QVKWUTdh%@# zKp;}lvF2MAuq*$hN4TOaoD~OG2M8s~FHeqc9{e$~5>7~}F3WTS{G_Iv6nR(Cv){{p zOq5gBrzH6Hs18LS-0X6aszDm__{j0# zp<~BS%0^UL{+Aenm;)mW_(@VjBEOaYKe=3-?<61%GSKJ;LwpDmV;}+U&a5kMrL>uU z!8jm5u3L}|X^3=F_?T3BRaAN&p#QI1SI+JTnErY9(^e|EBQcy^(*O&M&YM^3%2~cK zpTUg=CUWaVT#7#R*DXB&H3ng_YcT#G`+(rEnO+q_&;~1L&d9HJ{g8G~4_Dt`1!fmk z>6~}=4z2A_uwvq<&2J0CNnwHc`uJt1Bjn^Ld}zKZJnw%R-U#t748}x7B__IPv~lPP zKbIZO+coy|RuAqxNd_1WD~vcWbK1Q1IjCdgaX=_d^$9YSVpe$Efm2U?PKQPw7R!>pzDu%=-MA z);`SYoDoxg+pO9w76H7=+;6TIveYJsGVI6$v@Tx$Ab7I{sTQfnsQZAJ@!U54fKRxU zlrCHK8cAh?P<%nLJM7$SLNoBnDILFG!%7ifN`rzRG+dV(K$T!zIWE#!TKL{F?GcAd zQ{k^bfnoXbEBL70E3dxpswlTQ*(voOLxt=GW_If6NI>4mKn#Om*465_{$pYogxhkb z+en1#ybDTy{@^rWHwV!h>4!MKLe8E10!366xBj4&l1V~sYEI~-;DGuB8$eRt7fvoP zvC#2Yua0AOp&I@@pe+bHY+E>BuSalL|3;MVH1{!*jeTsbZV)_J;W<)}soIq!tL;Wq zQG?Cz>6%(2cQ3+evgP|4=LmMzx<0!U=`VV{=ehyXX;wJTrjXlnT_q=5yNcA46a5vl ztD>jyodtNZldR@iYeBJTAu4#rBC7X>U=yv~w7K;%6cQ_0DR2MKE|w4X?H(5wM_Kh&<(PGkzAC`E=XN_AGuJ1wu~)`-2me+|*joGwe^lmc+72#T?7Fds|5W>f0@CBX6M=L#n+R&9HWPWkz`DGWydRl^WBA)?!XdD#oPClJb-}uEYe7 zpS63BRq~5lyq?4@upH^-gcBJ%rQ!c@AeIJqVM4;L{9wd&@ z6}hkP>Y}O&KUdZdW3BPgsTnAc$>Et16jNDinC|wC395c`b@}ephrd zqrOpqYxjT@~4e{Ajb^rt^bJEtyG)_dKD*vV%yKBUYCVRVUs15`NNU0v8kGOT4!?RjF=) zFCxjOZoCL&L##r{8`bY71({uemWE^r#=+koFNy47^`s8nxCbIhO*_*z_Px8J>+ieO zLso)4ab6NDZO;uKTJ4IiqS@(K-;_^3eogbXE351;Fz4PJN$cd}too!!9;IvKJ5&2F zqptmY?kYicGH$k#o3;Ho{xE$Ho(xPnbeaT{n@%q;4*6J35K8rGw_r9Vh|8-v679m^ zI8|#+FtV5A;dHg?hT%cy-kgA_!j;>FAt=4+WZjK7fjT`<;gw;^-A{w%5^GCs@ufqj z;KW}6RZLo^BI)-HU5#UW;ABby;je!3mWSkW^!9XVeal;+oi6`0*M9IvjE#~zT)pPM zyNES^w93MZvd}-@Ui&_3MW3z@%p7*|EBcx}-d|e=43?=rQ5^?IHCB*)9vLJ0R6a>7 z`?ysrk9f3xTMORC+P60}fI@SlE_W<65LGPgZY}f=@_`p2kuj5_nM17qs9yYA7M^y+ zP2sMr+DBD%+P-@zEhwXY2Lt-4{qZChCYmv?^E3Rl7h;0nVY!CK6EOEIUW#Ra6*L?- zX>G3ckZZ%cTq~TMb3=ZWKeE#bS}B-E~jF$eI}^u-sMCnzZ|G>k%pCf!BPt332Rpi2S^^Rf8bki zTj^trBB@&c(R2x>T`VmOu>ph;AY%EXRF`5zuEv9^eU2_2m&!*+Jt%E=Q&=s!v{spH zif@1WtMJ|4($2r9b2%eT0fOq;19l#)#EI7Pcxa_qUI?<;22ZX?%cEth45`VGQqF2W zapM!ag24S?7LevBehrgzx?~&O4)pPc$Ra-bMIayXzZ7!&FnerJVLh!!Zahfhwv7bi z%^v{GeY86x7W^(k$j$GTe<8}OPKbZ^|JzrX8PNTsZ}OWU&>j*4{^MjtXjyKZX-m)l zVuM`@Hgp{5haceR6EkN2wJggOnq0MIt86+SM;g}oEJ)_=;es9k(9!Cn^a~@RkCgG{ z2hitnxYZR)0|e%5LWT)$;23tALhLKh1du4j*VH7vy zTd{#i!TWYYDuMJhq+Cd`(%mP*Ong!80O;!N*|~iNNqfK= zO2i7*u9o)f8}gb3RHzVi=c!?ZHWsnvDHo*;FPp(Jox2<+hgHt;+2?+GE%OxaXZUkI zYuG=+kA{JC_@j=~JzJ6uAhRZZH>+LkK)G@IY)h7a5lXzudtH+JO^pXEu=0XlB6Zo4 zu35#SY0yd*!|ht*T*;U>m#=A7x@>kong|Z%*zg%wxlb9JI%m~7LTK(d`aMuU^r45x zOzMnsdNGUE|NNvm#m7FU(M}P}9@1N(EPk%n1lP||b_+ge#8Mds91gJBSj|KFa>gyT zFx6uPE4O`6v17l%z(YHV)r#$YgnJuaRXtj=G7W^}NFYp}`aW~S=*Ddy}?zpE*y=vGU^L}Q#6L)pado2LL42^V@B_Mc9TSZPy>Ne2=r3x~<<^Pd}>Srbqz0(i`LGnd>*-b%iq z1NQ5P*D3fHy7Sl0#|p%E*X9hRdOIqV7WNV~vz#~;&u^!UALs!k4`d=U$nR3@+!-l& zaW}gE!5PLAsG^XG2V4}s9!y_^- zCTys)1JFRp!EjU18B%QUwS1#b<2wi?Z589#Wi2}1uB&^Ahj;f8eyr9BPxHE;ypy;E z^I?a{6&EkHZYufBPdjU2saE?dWUPk(U(2{1m`BUlN|Q( z@BIJ4Rq}ACPL;pT-gkVRDQT>YFW&-O!mn)Y-12M2X`pI?OUM978{m>j0^$X@EG~lW zQE-(w0O15&4xjnj4tU@RkjXnqgFrF)?<@fLENmxVcL$e%ai%#o?NT!_HFm=nyEr#)97w|Tao30E`eFh|qg|RjL}JZN{uBaq-+hmqZgw4D z<<9jb`jdEu6r#L8$=j-GYudfr9-o=Hkv?7(LW~vqBjFZ&YR>)%UwVKAzMesYC=p$X$Xy>tjMa4QI++z<=@kA=du)B@O}}@uI8`4 zpmrICcZRFIp`R^fO;M*g%VGoc+hBin7ktIQ(G~6Q?hDO%!o+E2r2&jAyOrQ zT3-gd&$?~b<^MMfCjNcw{_ovq4EUa~V79mmH|(8SR*FjVG+x3(GmQM`d1hNq;@|!M z`>Wxlni~-Re|_S5vS+yYR$B%yq1mQQ;J^m!5(c|*2Wu{&=A#@165!X@pMZct9(D6T zJV+qXSV8F8XXw1~5*wzKGGQL;XsVdv1`O*FtDzn>lSMoWOZOcjzF>j-EToL=z?l&X z6{_BneQkB--h-QNSXW7;Ep`aI;f&R0;6rB44Qihy2c zr#ptamT#v`A+Q^USYrvKX-ZGl{cPcE%l-&Q_NK%@)!-K1v^krmKuXyv899>p3uI9@ zv$sSEauGDfns~@kcQkRr)x7RSfVVN$yeJUhRhq><&xF z+kp!%mu)HYMi!eNQ7CivqPG50JyhJND|K-krqxRb2~qwV80ps|u+dc@FDxOVzohF% zGgQY3spFN&9-VDns})d8@=?me<1MCj)#d@>m`h!#i=n+c#b>CiR&oGeW2`*PS~m%as6kK>PxFs}n<4mEYhF6v~kE9XY0NA}1q3!?AMaapm0ArIwV92W~<~kUdoswKtTi-`vA#j}Qpub(?7hg2ynA zw)MHic+u~B4tW+YHhnDG_zQjt>6}g69mOSr3@F&Uy~$bW(j9SF`_ts_k4bBDZkvvg z!B8A-!WTAY`J^g(01;cA=;VXVDRvPp5gSg{D3+vLkwxlZHR8}49TOxULoMR7M5I|Z zTh8+-NnPP6k2^Fc>lUt2P1jIuCrRse$}KO!N1E;1mcx9N>Gx;tn>;Af@~If{j1@ub zydl;{l7u>i53%-RU*IeMCFiy`O)}{rArdf!<*CnEe{UKYFAc%u443j@qr~eBu}Uv; z6xQX7a#FKk4l;G)dhr{gnmwlS`OaBZRX{Ss0Vkr4PnfLR0JJ;ikqQpF4$(;U$&2t)=R1| zzf-wX0~4(Ieo1?aNj|&?6@u+*htEZAca!>qN8FEWKbcB_=3rEj95UGhS5|{_XI!8Z z>m44Ahf<7|!e#2s9qL+`>al30c5zvk+K+XcALbV65(CK-J7BzZizw}%o*qPP*WQU7 zL8viE5dJIl1)Emxo4N=J+P`r^VY$-Q2Od>u#isGvClImqp~=CyBX66Em(Wng2bs+( z&T5)SAF}0;&3KLPsWoEeRBnyzTIvq+e%LNYaEuG$klv-^8?w5FWDy=U<%Kyio0K#w z%$Tg9RxXs@6&@)uQ1?W@uR>a*njj#am^E*^+GMa2hjvfFE5uZRV`=$H#pDy(;O)uT zyZ<~_UfXc*Yr3PR*{(MNfm{`Q{ClwaQv0>Ng&DUFzd`$8G0KkOCA&ftK>><#Un<=J zV~mcHzBr>Vfi^EgzpH#fJj>E%susHG6jeLtZH zn+vj963(|3*1f?ehUFJ0kPMU+g`06}NxY6tdOI=V7Jkh~uk5ZgOgw&W*r7qAmT;(V z&~@_hNn(qlzNhu!e!J?y0DekKXddY!exB8 z2JzWY?k|nzH2k6GkgG9LnaLSjeps^%B#L?Nko7y+R%EUY( z6A&2{i$en5uFcV+$l-CZ#F09Lc$JT*r;NF6z&$(OQX+lI7_)8?mtI<0`YJkFtcn|f zS&b<79seeQ-z61YjGmN6Qc$C@nkZI*yQnG^4e!ukws}&C+)oq&EycryyCK_s)ZFGR zo5&_Kqj?5r&z@bIkkU?0ByCLU_tLm*Y6gZ|ycsaMK~t5tOob^ZDa9d3oDsFjIMalR zk%4-eaxvM%o-?SyDvhyadB}b>!BB;#!6#N?=!MTv7zVRhW;ZhyA>Pqy$11B|&XR8z zuv>VtGecEHB>{?D{wSU+FTMjP7L9MuCGxr zJdD-L?e^^2J_19Xy4GNrqYT<*wqj9}!y1 z5&5D_-)?QCBe8)U|Z#+M-V= zGgZEJIxpX@>aG`&w^FQ4CvxUE3*q}uRLR{-g)Q;zw%lv&!)@kIhgccM=%D4>bEk9E z(>s{dJc6tN?{D|tCZFv9x5U(j`>~89W2(Y>8`ZFa947;uM_!}0Cr`f*lCD9K^9BJ<`by?n1RvY zs&wjrh9B>a%DvdxBxlaIIbw>-orEkuTjM$gvkpXH&b5EJ zq|d-esRf7(rnl2$HyZx7^*Eid&e2NjQsj_^2-TC*cDye#v-Gbl=viiW^>57V<-c8_ zYxV+{=LQWlGdzTS32^KN?c|Vl3f{i)o@El_So?-&8yYmN1v_ZBHJ<3M7{033NuUOd zW_S&wc%L(D7%8=(rF0%azkjX`=frq$k?yo|jewOrh;pV>^&B{6pE02h8%>&+f!U=^ z7{XYH>cT2O*rmzE5=bbyLuuPM$#}^+-=Z(LQnOXKrQt#SC9=HeSkk0L%}g4U^pm+N z-r+l(t%UI$&@i^%>^sy`VI>wjrJZ4y{G5^%6pS6keWs>P{477oqMHtXw7NkhFq{YZN zUPcY?e8xtr4?BHh1*~$;xhlHN=(q;2$P`eM<>DWj1DR?gJa%8Z-YFWwroy1>&7 zhE0@OYX+>oluOE9%VOShyY{11ar1$;R9Q3VA~J?T(AFvJ&DAtlnc~-$>$g8uxA&}9 zzj8#}Owm4e^1OjT3WZ}*C)0(o+vqvjEbH{3V}-{<-OJwnB4*0FE^r%NORk+#rA}9) z2`gYur9vGAYJ@;=CXeSuJkZ3?yYW$+ogPWF)~9~GPrAy`oG)SkmReUiG`>*I|A#W! zl_Q3nV1~qKEUiJOJjK*xVWCr|l#|9O4rRDu#Jsdt0MEXfzEAWdntCBTawv8q0@sWw zdFG|<^KD$^%{o`2{@c&~Sjn^*{Ux#z>0JmBwdrL!_v8u!x=-{HD zn6&JiUwUh6P~)wS5!rjOc5fzFN~am_^$8w5ENG%6Ub`#eft>ES@;6pmgN2jjBSS;w zHSr^^9?S$K&5st^M0rEXpR-7Fth`*~ihUn^(>7W2pt=;3o#(-wG&xeC=fX1b6a^~4 z*|mY4C*h#w2MkqcV)gGMQtNXg{7^q3?*5K8Km4UT{hGx+AlZ+H(OH6+M8(w8ml;WQG+gN@AGLm$ye@N zrV`;FD()=%m4$nbgxZ^I`XtKhu0+uCa0i|tR6T(+6J-^^HOOYZnS`@U^z1m0o!`^q44(Ytl* zaoafDQ>TRP!GyU5>xuLdh$r3RK7W?kJhFSYB3s--=#U9UfgGZ5-tjVZK)tkwWujr0 z<2ua@%FOu85C=8hq3NXg3vALgg8rVrz-i!bu08+x@It8u;$^ zki%52duLm+h17a>yK&9mnukHGbKpTqNl;R3)`>avo$OGzHFKD^(cvq$wbvQ)-7z^L zX6r3&TEb)52G3vl=r5g>zS~!%L^$9&6RD7~8Rzo;1(udbqf;=emr!n4OoB_%*XIxE ze`?RRm#po^Ze&1(<&n-&<;q>Cu91xF#6N!ONs>;|Zo&5L8#-Uq+`iz85i_-_LyoL#(P?_yK_TkT@R~m zz|>7)u^KK_u;Y|6x{6)hTb?7@;Tv^Wv<$gAX3Po#dDIr2s&<5pL-eucB{9nW3mfWe zfEv3;`8cQ%@Vi@4(&)-mMg{z=QJR0J_CUZRCMTFO19GOYm&~#DFdQ6xnPZd|d{g15 zT!IOy8;9VUY9YOShQJmbj0HqGhXiluwl2jU9WQ$<5E16=ROuqELz8Abd7?=;wPvApj`mYbG#6hpaQQ(s$t)USd(&Czz- zo;5n}H6*>5ATuJK=E9us+<3J*tpSd@w~J?wVy7|^5m{^13pl8CLVm;1r4FD2=B%-& zSJc+=;o`A0#q}?3JT-2il4dLlCA8^_;P8~Xo`P2JXaX9`&lxgVS}JbAXY6>% z;%eOk8fD7l*zf}fi1W5|?!dSCDRqX_;P{XuHut#|QSm2YhMeU+SH=9Sq#1`>E{u%e z>DUw56`*{&JPbfYf0L?|zDnsH(l67@E=NvmU`ocLb{nq~ePsWry?*^Vt7~dv6eVc? zb#1>-kPdF)nUu_@^s{SR>Lz#Nr#SbH7`vuRv?a*adWahzeg*jT|wHXXyG zuL1IrYQOs2-l9w;A|iqbn5(>e&}V_AFT%t}cf4M|dNpx4WA%FIT4Id%*Cjyf{C?gL zcJ}vI5?}uj;WtlU(hc{U5V;{P#i&1h-^S!C!+5e`R!0=jI&e7JK zL6UegB%EPtwy`eWu{nAeJStFB$<^qf^U0D^NPZ9P+P?k!Cr9Q(5rFf=Nq8YtijnOe zOM`txm6(uO$3(7`fNuqr{ZaYKl@vBpD2)uX83)85$t#$qPR7NQ4)Lf<%N6)Ts}A&aL)ovgg~D zBWbST5zNAt%lQ%7!M$Ci&BV<>mePmjtVFx@0urZ=#2Fu8rTev2(ndQfO( zt#T($ii>Y{nY84Ht$flYEHB1jHXdSa5F-J`28wKI=1ftYck4=@{U$!E#(TyU!*aze z@ncb)0-7Xo)MAu<{VzLF%#svlsmi?m_fb*`v<@-faYNX4IhxOjV>ZUbd1J-0_q~Mq zhLEqHOt9uPnN%i2hpK%jfJ4I{7%nzS0;Mp?nA9pnLe^3{W`)YU)Zz%|UZJ zG-_WAJ{M;Q;YLhmee2wCkcyj)w=qF_>7X)`_evXJUN^ZdM3Zl$cy1!`c= z=Mzsp2r)V1v6qt#ycJ_(W0R4Z;LjXy`T6;Eq{hOo0d-4jez&;B!|IVh&T`<8SM?O| z&-gr3GrOqByJxy5KjsRsDm>>#f7&f*i`d@;&w(nilx29vVM z(_{}b?VG9yAip9sfO*O6QB?8BU7>)001{?nvAI}t{m;jbt;E2_VLbeVm9;hI?Jekm za~19Fs#J}jfih?9od?!`#)N(qaZS6^%fuN-GR@xfg5|8QHfx6tCS7tIB#41_So}aJ zNTwJJhfz(!>17>gshxSgZO5+RHQ?>soVyojM3oN(nek8~`jalGI4uJ7?@TY^^iUd?MTA+L>3KaQW`+8I6Ud~2C~^l~7Lr-!~n z+*?x~71N%0J6(?>sWP%ye7EXp;OVD(8e7$eoY$BQ#&tn$XpH%DJ@&4^OT<^k2g6Mf zX7Bs1n$yp39C#F%Japh}y_`2iEiijck62@7QgcZ^FSb-t;N;mGNycSAeDeqtKhIMe z`|mZ1?_tLGzU1fE%_l!mH zTI0D%$Y*Mf#-}#v)@R2mt^S-03``10^P}9EBi+D4VVYNqb8pK?YHqBxWxth_TCJ63CzmEa|Rt$N(C4o@=J*?jTZq@`nTNCu?p5Eh&UKeBLf zB)>&E3m4tvGimlt%;=aqpqab*fZ#Q$?V)s*UJm)FU=)g62857@Vy8s zr*zu0G_SVJ{OlWe)x1t$16_bV{k9fbW&n?7$WFI=(x_iMrJI4Ep-r`90^ zWc@aFBR*30S<8n}2buR@JP}`hOt`w~5Bv8bpUI0+4uu=}qy|vMAM>7Fs7A*pH2qeO zxxL2rR2`S5ogBO{f5lPey~5(bT48Ff#tiGdY;J%0YQnZ+rzV7aiLEdz{?hSPJE^K- zsrm%7LswL^Q;8aHm-61(?b6cu)L{GXSPLQf+1&3tW~|CX;g^c?7eg*+P5Miw7%Nc_ zXCJxsGSJ1$n}Y~Rxrx4`FQnvroNY;IE$K7CgW-7@R^M2pPd|JmfIrHTZWrv2z+w*M zK7V#PyZRt8?uN=|j2LIalAS9zC#~4rr_T+8b5J@=Bevl(lJPUyxxlz+{~)nlXE-~8`R&*9!8dkJ_j8UMo_nwP(2b_AQR%N+%SdDmP z=GqrEviawMTeti)Z&=4$y$=_=)6{3Fi^V+P>NWR4X7w*?7vBz5eo*%WIIIjOD>99^YeHJ-}O8}%}M%SkxQNXtodvdu8&UEi>}W>Ws9_$Tx5 z{lbQV<&`4dpXwcBA!;z*rr?niSr;=FbSNlWjVciD-4c>V##Nt2_v}t~df;+-vUb^- znD4BR-H2YUahkmoL z$Oex^&EX@!SHdGjKS=nb=K{i^tNfn|E^WtH!-`7paEh)6jJ3J`qs@ShQ6Kb|T)g)S zPSI=z#GwUMuqrbBOZb&uYy50ks}`DY;N<0!uK6z8cqAi{Q`>?*proX9@sWdd2F}_W|NMSq zxNrach#Nv4u3iIW&wG1+l&pUg=bkgS4^i&3McDpi0M27JJw2VI;H)>$Os*HEUjagD zvIf#Gf?ifx84qfD370j*1T8?BbF0v<11G-&$^}0Z`B(EvxWPbkwCSA3SqgGtGkdGd zFecU$k;)XR+4)M`Ve_w$pddZ2wVgPoja!oR)D5I@%H1#;^0M=73fO@TpZ` zEwe*?e%G;!4h^kQqV`&(21%c6SNuXVLORn`9A zKg+4+wzeaAl^%V#gj>)nJ@1#^M~KxT8^X5HwE@3~s9*ec!kovdCTY&lQhh?_K)XfV z9NQvK0-_!((>=PPfIj%_kg+KFWp9UKR7zf>PrdwRu6O3GLyRWtTx~lbz1j}|Yt+uX zMIS8sT0I`E9z9y)JMIP=1$BX6K(5Cz`SR{=uw}r6r$-7p0Pe!zg-ib<-Y}Ju!NY_J z9*X>`Jn2aaWDI_2zq1aavObi%exSIpqXwwcxPIqE+m2h%L!_Ar9aX7Ak?tKBD@7Hr z!R6nSP*!=2ani-Hkxwxzo%-^cBi-Ffb15E0YZ;$n?q%P~V1v#=bYZDgp8Dn%^jjP! zxG=L>9D~OE%591g7FYEiuCb3BP|l*3 z+;5yhYVEK$gdW(7<3?+ZvGFi3WJ~f zCWUy_f1_eEo+wuHc0<&o+;+Rh)yYHlc;9v@|02&!4_NZbT|{(oz%v)wfg;GaqnCU2 zziRKcyi|ByoSNz%68)Vsb&Q(v#}3_m5PJkO^VM;w{Ss@B7yMN~_4aT0!&%Xx`=DW6 zzYk=lVjB!4ss<{*hrE8)F@HNWR(g*PDrbjJTz^qow&|MwRet4tad|>bE5gav{#J9i zu&N19IQH!``4i)N_I@gI&kR{A+gYPP2$g+*CcFIJLF>JH)1~*OhpOJeLr0E2%gfxW zHbXzu(OmVcCQ2gq!>i&$TBp`mdO5cdsH99$x)Z*571@O+rW9*#7y|T0|$wo z{zcZ;b#>{(yv@$U_@tq{_=U+p+nubPr``O|zgJuoK+f4GYT4oA-QuH*J1>Y*yZ>fL zojpt~ZzyeO4%_{>_#pNJ;&8Y!OTIKibdQd}JmbO1nNDA&9h}1#g01%U=oBA3l-`tw z_EX-z`8T>cYVp(3w?pACyw7Y9G*gMhkhFh$!b+c=E-n5MXlG^;{K*^Pl-Eo7=pT>X z;nny=agnecy=(R1^~Ly)Y2Fv--;!E2pRJZtO+)(`FRom0y|pTT=5i5;|GRJYHmy3D zpU}X;?Y*0T)#YUc_BJA`CH76WiT*~4*=7h7i>oi%d^y*0wRS4beqF4Y$ z>DTEmf^y04HyiiP$Eg4CZ3!p7!(F70>i8QZ#26IEao}KvXk_RNE+;i9&pb+$7`2DTc<7~9oZTBA zWDLPw(orPInb3gOwudxt!M;3rtWF4R#53GJ*0`i4Gwd7Cvu$L0MZwqeEXHL3odJTtQO^Jn{kVC-wgAPE8N`v z%>Bjevu2*{i!8C4Qz0Rz?;ohY|14Zy^E2F^kozLf=lsRw+ak-NzfS#PH~8$a&)mHI z{20&ZmS3ovXtQ#I_Muu@)j=N1r{ShdgTO(=1@1_JUhei{?;j)|G0S45_9^#-EaGZppEg05FEm}qa`42?o>wcRJY$?iV=bO ze4KCm5ffnqgm4e^tgbFa86$oRnC!mS$Kq`II(-rmmNkz~3u>*N`j0bJ9^D2C!CQET z+U~G{a@TS?4iy?iO;PqpQiDqh2+5p!dkb9?3QD2P|5n{wMrF0NZNsEANC_gLq=HCy zDP7Vn-HmjYqBICdNlAA}_qCCbmhSFGx_Ref?|nbdJ;wLr`}w|(;outAwbq<-opa9f zIO90ya_r-PBRf@(_}7Tp0l8J#6yDg+pC62s>J1!k4o};}KaqdIpkA`lMVDsW{dHzP zrE0NmJ=N&qa8=vQz&6Uv#%=Kvkw&8%3ovP1EqhZJFV@q{KqA!e_xCsD9px^&TIAlx zil1>P`u$PJCw6aWVx+z$B{4kI+@1bmf>B(Y&ZHtDf~cG;w|coe{O>~ z;{&5km*j$SV8|Q|2(_#<)5<2m&i^bkkm#A2-0hnB_BUOuCG{rr#DExA$%0Y*OU>34 z*SX2Xin<{Xl>lIl0SrLzhUFROaUE(rv|t?RCS z`GnW921D2Ta!`Uo0Qvm(CytQUGLX)dfjC2yq=LHonK^wd?*_1==B&nwG@r1Zad2_n zAs$@yfH=!AWIqIoWT}vWxDGJ?RKya@sn z0PR>{DzuZyaF9CudX~*q@Il^tOWA8nIjvyj6=Z%~4kevvo2599-9F*8=Dq=z*TBt* z{t;x02++wU9N4;^3i;k%E5o_Wfgotbcp5M%a;@hYFRtKDPEO2L<|r!uHB0~zfHC{m z`?dP|dR1Wi3NS`VHfn4xn+fIuL*fiZ)N$m5(SNoAWLpoC zvkgTJpJej7d;+HsB;#v>z0z$?Qg_(f8(n34ALAL~dV=cMlYPj4y>omiax@c9*-H~= z=NqmW z9ZwQ-sRfTt*zymygLT&GhGgb&VNxM-Z874@8ReB`PKUc!kZ*^`9cbvKWszhqaE38S z*_te?ym7v2{LG-F#(HwPXiF3mE5N;U4RIPV(3tV(k+l&2_HADMXX!1T=&!cDtW$q%phk zYF(GguS4{VUWG{oPz-@#Wa$n(8~9Tn&76LV91uLq_S1(GpI2t?^~Qob&E~Vd0J#Cr z)tz;=&7RdtyCtP=UUYR1%uJ)ebsi_1A$O@JdBj!Kr!9)<%N*H}@fO-uj{>EAUc2G6 zL^B_|;}6o}R#YKap$>Gh5!_m9z>~_J%laQQnxy{Jx-lq7ttRfVF?ZCubU;YAP#7Wc zw<&*?2u5UR_{p#l05wM^ZUOfX2CQ*hdf7O*NgbZs*H@x?E`e(d8R#m}4y0-woQ$vm zdbi%bfVNRfSz1#on&cQ~Rl&X3%^s@miBpr-gR{4=ip^ERRvbf$aO(R0Cl7ivy~C`~ zQWioV%e<@EU8W3u1CsUkZ=ovRN1@Lxq4m5YJ=zgw1kSkFUpxd> zo%k>}VZt`*DS93Ux|#WB-tI5oV?66p??V3`kER!tajrz2f2M)u6&?|sfPsd^r^UxF z-1PR;iS#O}McT*Z@s)c6D=_cs9Q~VB^}SuP_q;L^h5nF+&>6#zNxatv-Q<6-uSMJ_ z{w7|+&*pfu6Zi3st;x3S`$~PBPebFo!#_{BY*%8{br(U%-?qkQ0p`ztGNO!0&YvI^ zvp$1#{1VN30yi2xrq!&NJEeQ%syX6!p_ ze+Ko&F}^dm!K(WS;?rzv=9Us816QF;&A%YOA2?!Y&soQ^ zL6q_@a?c&WID2ASYp^v|{O)cQcw_=~OYmyH8Ea-(ZvjwUECF1;bDb$)sQ^9tx*zp% z<f8&q*-x^=KPdF)V-QriUQAgP1m!`f-i{Vq0RQl>P@I$Oj+{?P#%3-+%x_WaAD@#jWy7mX=D$jZfNd#-$R69DdJg+gV;);7R8V)Vy^q)@i67c^)t-FL<%C6#A-uqyBk> zo&`YCxm)rVP#{S9-k^yqa;5?cgH+Lfmd;-V`4qnj|ApOiO>J@IX-8onl5_W&H12AY?& zPGrrQZ7vLcA1$untr^Q)FFi!&Bvkb9)4vJgk8c%Sbw3_xL8!%_2tBCN?h?0v+eUpm z4b&Xk9ZK0|6r|})J{j8N=idCz0TJJab*y^Sr-gDF(bQFrLSP|a25B4QZ^Iv7TVtuZ0Q<&Ay|5I zbOZH@Fh|^WN-3q1X}^{gO<%rr50*4w^q%d$>QKfY@w@v~pEAu4B`LOIp=r71S$7dg zpISa7Hrx!m8kQ-(6!XYem4`Q4!WNGH(Tm{o#f%Itl!(dp8qgod5Gt#y9d0ky<7XCk zCM)io!G4m>R?4r!r<3bFL(~#u)T-R@1X(BL?#WXSb247*O@#8A`Nst>x{Mv(jL`-T zHeWi}yDq=0ciLPCf5H_vJF!~Qutyms`M#H>Q#)Qdn^QiS+kSel<0*Ks7qs`uBh4RU z;lQqN5cLnl%(SsB*fG&fwdu^+FO0=bjLzC3!M^6rF)Gd6W!tKzoF=@~?$ON=BQ~Si z0Zzcl51JRZ?mAfN@XAKKP$>8Ec_9CI#ArZ7;$sYdrr&>Lgar)TMn6q#Dn_e#SD;*^oR{c=-T*_JM5*ccQXW`z(bX1OY;Yu83*w`vQc5SY z;t4Y46EneZN&cZ52`b6*d1clifP(M&q1qk)CQ2R%+xxTQ^RYN*827?9fI?>se?(MA z_A0n>yfvDao<0EX=&c{E_PuF9;7(w^ek?p{SnTUFis+eJYDaH$g@QVp5{N7QL`8(E zjB&M{4oF5d8LfS47+qVMc&yR2-+S~j157zfjkCj5>cIsE<~jX)-Tq4~8uByAONR&V zEwev%E8!%gYQMe`XSVX=qvVHN;}L(6RI72*R-&>UupU(BCIh12>$*h!o&~&+K-a{Q zMLMObH?;_drNAhs=aj@Hs`JZf-rsS1Q{{+_5m z3ov+|ez

kQ1RSi?=Fb`c~=J8tEC|H;i$!@idZFO+k3cq~WqROn-;`0bb4Y#N??1P`2*0778yrlwt5Tp1QC z-MIE0K=6WJk(6lS48eI3Yu28gNGx$7`!20z=FqmXBt3iyR!o6e$jmqbDO%C#rgy(b(C!O}>v%Ub&G{q?H> z#_KAww7{2q@t{8)Xo<@ow8BPlO~W-H_f;NgqQjJ1C!=13*n$69-43G>vr^m%^h=%p zQti2PbXK!1+q0;NL}SVTV?9$zhmqJ1+o*pMWr1Xsc4WZ?)XuY20T++tgZLgoH);Lz z9q)p%eoeyi{o3sa$N*QL6;buJgWjo6(4Y~6RT)hk+Xwvwea!lCKU>g86;6CGC->iW zn?iF9eN>vfZy5SWpM)lM|0}hW2^;i0&;|L{|L-@NwP%5}2A_Y-ij^111}Pw^=nP{Z zfco_Z3q0SkCms`Hg@ymu4<+z!@aEFW*=CgkSc2DrikyMyZ+#d&)_s^&g)F{!Ez%LUX0Mt$Rc?+X7xo^3fe z>~KM7RZ2x0f6B~B1_E3R)nX0*i06+Sr;|Zo@XJK)YK(1TJ9cL+BTCnDaKP-0hX$H&30B!w@HhWXKM_Bg z%^n;7P`_ovATzbGlSCF$z5DS2$KZK>Za4WBKcTr~>6K6Ha)NB0T(5Z>{r##`Vp;<# z#UleBH8!|-bNZ)WDZ)C!b^&RR`2w!~Lj{z^6kXSMvG*H;I-mZ&ySSAZA5L#~-OC>( zA?31idb$s+kzbWAmEUiY0$w1+0+YfQOgtL#6>W=DFccQDcmElQRIH-J20LWUODUCt z-J(=dffiiuZepE8*{dh%ab>LX<^&Gf$k2o!tt))}T8vwZHj%W&#kINl&z8DGVj!R~ zndbDad)5C`dg9q09QylT75DOx2cmlc!g4h$y$Wm8yMcl@RJU1^7nROxB{X)qME4Wo z!2zVr`ptiFbK5xpyQj(&MW3FT4b)XlkZ(}9K7@Lv76ThSD|N+7SgAZ}s!(YM#X6i1 z_5SV*XwdRe`EtE^RrKvPel4A30ni;%ieMe*C)6xqKb6VhWyy@1Do|qoXCCpv!0Fd& zV9&ol^S(+THsX2chNOYUAJr_#+u`QGvjT+zqDScNm$&5<@4}-6!tcnnoE;i+vU)x^ z=Vb4V!pc{SKHaOH{QF0owiFIrw|=`xlgYE~aauij^h7)#TT*pXBjpcX6^E!c)L@n* z_ll;HymBF0yEp33)ce`l8ii)iLJ-00q3c6;F9J8Ny{Gr15BY{B_CnXl-yK#~i@5p5 zvPyXHZ&i2SU%MGGbMS95o@w@}diwEiWJ}m^Zdh(~druGlU8>L{{?WwpLUwy;HH!xJ zM{&8R$_zFPX9*i&;xQyw_XG&u{#K{iQ-iOG5oWb5ejD;h3n?nYL-(-|&x$_HO00M|mn{?E-AEv3l_ZW8tNeM_!+pM|hTz z7WDPe%Xs2~rk-H&i%726pftY8`)*B5{km|sbfm+*OI6+mf=%!^Vw&lBbGH1uF~M*| za5bF|Wu7@&$n;_%`e14!SX1`q64zXzNwrt_!B=9r>k=vRrv+`+W@94E1^;K^eag7} zMr}ZBZ#CmjQ1(sXMm=;3wa;+rRME2ZqU-g-`oDtX&`8f~&cfwJ(h!z>IVS8-M0bID z@Mh-C!vgC0P`)5iKDe~7e;O_L3tj94xi8>ix8y+yH70`~^;nQcj+WH(F~d(wF0gn1 z)ubWidz5sHeRcoPWvpC@OE@>dy{CRBBf4CxNM$VltfPVl_#s;uB&dYO@^Ewr>$oJT zuZb_UWn6;z{3sv5dv=l`F(*$wIkf&~Cd|N0=>3-7$ig%@cOwfCRJl^-5)XYHjQL}A!0+a2_l_FU~2zo3y$gwD;8jd!_=8Bv<{^bG~08KeKVjH`ZW+doC_FH(@*?kVp zjdAa37X1^9k-xWg))v^=?WIxGTWw)lMM^Wl=SdybD43yO{kb6Nf{4wPC5jPi%@ErWBcGtmMe+qvRwMX=?V_J2jXp+QV zroz^aNSURv=zDO_2h@r5(yuLmV&N#qB zrylbxr}k+c0HJ83KRSnfe11N!f+LEb$bol5O^{3AG+=oz-)65mY|ML??5t)d6bQ-Q z0FwPLC&gwwx8Ukm-S#;1eX84pAt-ovJk#jcG;72TyK>ikmLs0M-4!J&?bG=%oq&>_ z;sE2}iSEy%e-Uo>SiP9NxnUM2T;4j~HEn&rX)qX|=3H@DROl3I_`f@O4A)5H`cP~H z7caG{!2e58&@i39t5L+WadOyQC?DzxdW?nMX566UpOBg;o|;0Iv_+lG9*4P#Lk??A zjlVRO*b_?x%u+^^2GXI4|8BE{{kkASOaA%Z0|*GjMudgSA0?%M*~ku~mFU|5EyhByxA=$E;9?I|)d+8@hGqJc(=dHukRVZC{plHbLS=UyECvd`5f zp*Y!^9eXF4c-rxVQRJUqwgF}k2~z=4AV*NLH{9auE64w*&9041rUcM8-p@LZvw+k< z&2rdsw9G4m!on&6(})XE556mcZ;OeeSLlV!$0qY4xs_V5OKoJ*SK1wD5HKA^ucGF|HY@ z??sQ^-uYiRKJN#wFOJ4LA4^oDP#r}*1Pn=%X2v@TR0|6f6(Ohh1oMA~B3Q=)WGT)#^Z^DD z3}Plfa(#&GMSgAp5NS=G6s$jeDSB(SIz7>&PIq>oL^!QKwOho6u z(on_fl7v_ff0?5leoZYERL-R-e$G`AW`5VSNeXDRN<5*^y7nXgV^yv4>?X(;8)kT~ zn}ZN64N_r|xNCHURqo7Z55_vk0xm0iacGiI9m8HBtEnv3(i!Ur75#E4hDE`wgeC>^HaTupH>KYtlrZ2V%x{FTpahxN9%^ny-iqDv;nCAA_CKh-?kCBLazq z6*g{sI&9t$?8!M8zCB91pdH(L3y=J`j^yRlZQ*1amp8)(;PExG5Mcz0ix4Chd8i5)S+N9K4e;W9R+ z=HU3TwcniM8t@)GY=5Hnb@{iBBqQyU8VzV+JMLQ{JuAvZmig1eu)(^YF6A{lV zTpbR5_3&r)qT58~g)UlHFX=F^_9EIVX#}+V^&aMED-3YPGN&yXWpfsNXBdhfSE_e# z<6tH?N0<8bO#10zE2vECg<3z=D2_K9OpUryN^`b07)r2Zd|p znK#B4biyC@XHg@gsC(m2AExu>;}I(S9J@Io4pL#Ib=}I_W{j#6}xX{-HHna(W{leP$-N~cP0lwZZcIhsL0h5ge9Q@ zle(2?872G9=z(M&NAAO4R6HBsKd!~k)z{nBZ_=L5`}zu=)eY#7sAGbR054#-Va$UX za;|nlkrKVT%W;T3pX@xQDFz63UDQC0rd&2m+jo+ofZRa&K3+leH#8~Ov|o(BGx+Tc zj?S5NMFgnbm;g})vS(=Q@d~O{#)BDR@qj$9GuGu@;8()GKMB;opi({oU5e9Cui-R^ zmqI#ysp8XK3m~|a$Ym?dZ&{MIS5{WO2gG_dKp;@>2!Epfw?b!WKOLT#xfT?-zN0Es zF9pT%C65I?Pj}cT&wG7%6J^Q2HqXJ$h@AT`{-i(z+5J1|^TnbykdKY-nw!d$03NmL z_TZiWe(=8;b4JbJQ&6cs0H4#F#I+A)!W|hgxpoJek5cw!JpiBy#Yj?vV1$eypllw0 zD}Gnwa~ebykyp3)F;ehyLm6b#6WM*P&*wV{e*)3-orZJGaWh3psuUvRW##1L)JKoi>L;{p*Lpm6+C7;l%ijVRAq7KDlw z{~bXz*)t?J&{!&yMA-4nfnV<+6NTqPWa8r%2 zT5bd388&aP{*o3}!WGUQ-qoy8)toKFMK%`qB|mZ<&bD`*OdF@nlC>3frmC+{9<-On29PPZZ$}Us)uJacnce<{Rr1AnIKZ8 z7DjW4bb=2%N8fiOW9DeeJL~F=)Wixz$o_n_uH4tsG^onmkq&d$tSJrJ;7+_y0nSg> zH+|swefWslmBwg~q6Ety5hv6a)T6zfzzawZ^$p^Qunt5J0&e09@w%hIu_+wR7=N;% zo)y%{3cazDN5bn?KqusnvjL}29|_<>Eg5t2d5p^iCh{CSy33d1=%0RiZNS?0MI?^^ zjTDpDCRZDjnW--(#QZ4n4Oh6)>ebMH{H)pJtI)ckdLJZ0FpNqXaklk?1=fgBbnS#@T{$r$^<8Q6-jQ$3DLjGUxa7(ZGy+K9 zmChjwWOp8Dfp>*gHzFD#T@~>>pk5IR;X0D?!FS?*z8DRuu)h@@jhn&#X;tvPcN$GK zq#>?+*TsE|vB;X6;+yOh@HfTL)n8N(m237Y!l+Ehk2r*hE`zfpi|bfBN#tYYFpIxhv_wO}ocLm1X0DlDcr(*al0MkX3}gBmN_ z63q87Kd4WYTb$vUeI2UX=YI9!7R5h79|kLF5Y8x7E=%u?|5KLmhw~vyPjVQM`)PbB z+0erS7W-ogo%o>zg@_-b$_cUIFP`*?)o3<*@wm>|qX5o07euJptKTk>;b3Q1dC@wz z42(@buAIg(~#zfCFsuh*1lPWnhp4kfkE{4<)gA zH@=51g;6boN<=eIUbdW<86h2em0-*&<>=@*IbGK*jkGdI5!7?NIh36-_98rzW=bqF1pp54|fzL$W-s~!nm2j+2xnEg{`em8tSc3 zv@545#twn&$njx*b8)v%WSYXu4G85rcfjeIacn144+~|Buok+S93a>DVJu~~JP9b$ z8OiV7R^`7NxEjiqVS2D72Vko(#D)uClq;=V@9yrjsJSM=VlA%Abp4jiSw5qd8BapG_sPTo0 z4rc&1*-Zd!>T5q`fyh~Y`+6Rm+2b={p%4np10vV?Q|~JB1Q|)%>cUhnP}qA)#%e4n zpV&crR;3soq6IBAv;U?^N%J2oJQd4}&Xd} z_NURJl0-V_+GC$OHB%^179n;GXB@vobadT8aM=b(Lg_@=e=RQ)0C zObw6B`V1C9v2^fYVn7}K9f|8x_g>^?64!?oci1z)!?C#Wi+M5kLM^1{!|^KZ0%2>i z&uahOI2qfIl702}h0OCy|9Bq;{eNj4K1{cCz)z94)p^_5!5!;D`K&q6oFZ-c695AO z43LnG^H{)}a<$b?)jeZso42pYJ*Pi_J5ZVNUtuBT#yMZFMeH(S|DCo`U+U5k@3)T# z0%Vd?u>r{m3rb5)1xm#V1bR2kV zz0%oCPpG&%sD(#m)uV&MMJe=hFwE3xzn7w{4RYq1X5T#`&#|fXR##zdo(y1G)!~sh zvPkN&Cf7;AVVnj*X1_nPGxBz7@(Dkg*pS%R;`LNmea)J@`>R^ARsSc-y%e3B`u&%fR%7@k%Q`XGeOnmf7&87B-Znh3)#A`OVgsM>a2z3XkqqZ_-q} zqQAnU7vKI7H@ER-f5wk3qX$;_n;xB~z1hSjtMV$R4}2u1Jj8(H0fQHa*{S#fq^9lC;EAZ2IL%T+kOGa9ZtyPp zh00~^HtJ|N#?aMPx78Z;0$0Fd*t-T|dA;(GFJbe^x*6nSzNBTGtv7}Ip~La6U(0t( zDoYKmX|EFoH7?5Pv*6wQf4{^KP7d||*yCo+V3U%SdF@qla_|1<9oPg<<%dmOA_!aswasi z(9ZpSKV8WTps&|K^Ub9~Ma%sYz+`)d_j$9VP{}JifIbcfRm1@WmzQ!sHDaH+^*SgJ zPUxA}1z!_Fxk)b_CRIvOgbBhTzwr*d*Gn>)wd+y*{>_qrmAQ3)4wwz>f-tN&`P_vH zlZy1U8@VgWZIl9XeK51xk_aezecalz`=)iiebT-9g5Wn3Stp#nByY7ZHGkkh?j2Uk zj_FHpMOv1oFJG)fTJ;yKVBP`GF6r=c=(sG`!gIRHlj;a<%0v4+};fe4H zhHQ~i&24R)*CY7$xHrK83IZaZF&81mOp;Bt0VqCOqN2D!ZP=@4_=tjI3QPr~H1AUb zKFOg0d>YnS8OZ3D5fc0YJTVpd~RK*QMqHj6c= z5dtJTa&}HmHTLRCdFq(1Gyfjc;PnewQUKG@Tj0me-*n0QIQ*4%cWnmPYGly=t?liL ziB7=|=*$FA1K$ZaI^8~3yPbd%&OkmRA$S-i4~nG>M54}31wl1@MXK+uI~)Mf&V|1| zP6BJ_9GuqHmFnr@>tszI>*2N3795Bh9i;cRwE2;SXdcOIBj9;pfz#qfJ0DL)ef9UV93- z#>n(vwE+o<2=Io$>4~oJKZ%%hlg|NLiM+6_oIagPX#X)pa0Fby#jnb8iy%b!ymInm zmoY0FnHb^$oWaIkU{^gT20RO1K&C|<%Q{WC_s>ZB|4Z(L@fTgwt zU!+j9Q4*&lY^J@Rg(Gja_3n5HYcE?dn67I;QP~~! zmIlKrh~Rq1AGFgQt>&gG#BySNhdgI+(1u>e-I7#_v~yFjC2P!J1>zy?53?YeEDSMg+;6LTsu2w1OT@#afx)(PDYZn^WR!_ZH`fI}8 z&ZiaU-hNA?#0Dvkp3ZOY(&^A5vkJxc89``t4!+MZr z^=ArG#D+#8+NdVi!_@UmznIOs{a^u20cO7K(-N_2fhcCOR*_11QfK0AlFUF<7TXNR zu40eYc_VR|RS`swj=z=t5{R>wVF0STVaZc3`~5DxWGipoEUieU8Vrl#{7>g<%2xwM zb`^=tn2xrw5%B^7{!5P7yL|jDD#p`m$4Wi*m(713Po)J?s6CDI@@W0LQA1#6y+m?Y z|2F+eA@)65I(nX#-V}`#{mMwuc8`DOGCH;CWNc!OKe9bCB_xa($b@_(De@EpeY!ECHj8OQ7SW$x#@HJx}yzKbyGRIDMh4z(q*3O z3cwok4nk{BHDEi?0HoFEuhq=n?$63f+NzEGF@V3G-Y%4V)A+G!A^CW<8rBc{6M(&z z%`Z3I7+gpd#E|LF?7QUD%A<`rga4lp?J4F7?0M^ZP<65tC-ofgQf>5T*`+Ms&fmV+ zT_-C-&P8y=^?_N@Q15ha<~2U9oqtAAZ;E7EY6#UC`3UIFR)3Tvgli{`7^kkcP_9wiF)w#Zftf z{~b#2)A}`)=~c}d<^f(HFo?7=7ALhylZN3>S3nFB1(US+(f)N2wBV%l=jjP7INXuU zqUS3i;%e?Gi5T|C2Z;KTErzHD&X@bi(F8c#RDT zOR*!}L1mE}ILc=|*=5SY5kx7+#Dk^iX%l6KW@eJ$NWmB0CpXi#*aDMI9)ok<3;C2W3gIh1&eoB%>$ak32z?v%jyxC$KjbXz3ON5eDyt=b(H8D7gPG z7u2jY37n>K3sVNif$rNjE`>0>>OHFk*tV{`U%MPpNa42Nzx?Qx_Iapf8+|?{=ig;G z`SF|11D=#o-}PF2-oC4`wTbuM9xt8G0L~tlo%#xtP(sG0QQ0;5EXV}!Bb%^fGBQ1nrbr5ncjD`Q06dv51HsUw(A{Nl-exkQtc9he zhyF~hJz;V43@8UifhoH0eM9Pncl#etk%$j?ip3#v&|IH#{1;`>os&X5A>L9qN++%& zJZoyWuwX=`dhW833-8uuQpPB_6F(>Q8V>Hkkd){fm1MY=nZ*`eXaVjhnR2!LNT#uM zyX*@N!m?NaZao&=!X6DfN4TFpA2+idy`)29ITfv2e}1+PdIblV00jj&PVIJN2F-0@ z)5F^#n!+33uHl{;IN=#!w`zWdTVgVc!dlBPFYLmKgbLbE1box3DVZ9!6>bCFaI zu0MPOP^hJpq_ExhcvxdtV~>y}0OPGUqnM|sh?N5PWR|0*3q!|4j{zrGklRaV&xJM^X;(Bcr+=cj^Cs7*FD$$) zu34{abW(5fnsY<|uUa1SJo^a>z`XX$9h%*|=C@a`HwH7O&0=ujV4rq@5$4;=3l0X( zIr_ZhzD8jJ4iSdqWKDvOeujf9e)0FaCjNi?ElEp?)~*}c*FL?*(z8u+W`*v5|H{cU z1$H;Ocro{+YOV7ocCRL7uiG`W^DHG*Rqpgs-8|Gtc2oyi#E0dU-p})z3f@W$+-Tw`OB2se%suZisQT4{bXQaUguWy-oHabP3FxXem?&B z=^NJ;M^&!6g0$yMITx`_rz!WNh2&ic zqgtmf2j{4L-IyNNt2tq#Jr!@7ex;uCx1i$HGmBHAU?yH-ra|K*_QExcxI$&Nf!}l{ z@1nE)mZocZbpC9#cIZ}LT5x#{lA@opGI~f8CWc#&s^_zh!rDw9h1Fi5H-9l;Iru~` zxA`uwWK^h&jHt9tqhzn8(UKznQd(|!X*||UGjFzn>nEl8i-ZXJRq3i=b>UqT~q0$J7 z(GIV#clBAkd)*5a8&auqhnmr$F}-x|Cf(HkC%}x2T@;qmamO!u_%vaT z3nf>6Uef-|haErQv~6`A9-=-bSyZ@x*qFR{`7s7e)mxqJ{$G9J>fI-bTTcdFs@iwU z{qEx+h-B~B%L}(ENv~z5aT&b!&Ru2O|Efr-1;96yTIF`mbFt? znyGQ<>Kr@jfTHj(>w&)9>DJHPB#QB3UIuT6t$lrsh({UlTVcB4{=G6%VsfG-!ulWn E52p*L$p8QV literal 0 HcmV?d00001 diff --git a/test/test-outputs/native/win-x64diff.png b/test/test-outputs/native/win-x64diff.png new file mode 100644 index 0000000000000000000000000000000000000000..31c10d44b204c83a194c87942bede7e3210fedc1 GIT binary patch literal 3155 zcmeAS@N?(olHy`uVBq!ia0y~yU=?9tV2b5n1By6{{XPVwSc;uILpXq-h9jkefq{q1 z)5S5QV$R!hjJym69LyWe?$6MBthj~imZw^~>C5-bKrN$SGz3ONU^E0qLtr!nMnhmU m1V%$(Gz3ONV3>tK&Ig7DJtn5_4;>jnKJ#?-b6Mw<&;$UeBpf~f literal 0 HcmV?d00001 diff --git a/wrapper/include/alphaskia.h b/wrapper/include/alphaskia.h index c047ed6..5987f07 100644 --- a/wrapper/include/alphaskia.h +++ b/wrapper/include/alphaskia.h @@ -38,6 +38,8 @@ extern "C" AS_API int32_t alphaskia_image_get_height(alphaskia_image_t image); AS_API uint8_t alphaskia_image_read_pixels(alphaskia_image_t image, uint8_t *pixels, uint64_t row_bytes); AS_API alphaskia_data_t alphaskia_image_encode_png(alphaskia_image_t image); + AS_API alphaskia_image_t alphaskia_image_decode(const uint8_t *data, uint64_t length); + AS_API alphaskia_image_t alphaskia_image_from_pixels(int32_t width, int32_t height, const uint8_t *pixels); AS_API void alphaskia_image_free(alphaskia_image_t image); typedef AS_API void *alphaskia_canvas_t; diff --git a/wrapper/src/AlphaSkiaCanvas.cpp b/wrapper/src/AlphaSkiaCanvas.cpp index db871e8..0d97248 100644 --- a/wrapper/src/AlphaSkiaCanvas.cpp +++ b/wrapper/src/AlphaSkiaCanvas.cpp @@ -412,4 +412,14 @@ void AlphaSkiaCanvas::draw_image(sk_sp image, float x, float y, float w { SkSamplingOptions sampling; surface_->getCanvas()->drawImageRect(image, SkRect::MakeXYWH(x, y, w, h), sampling); +} + +void AlphaSkiaCanvas::draw_pixels(const uint8_t *pixels, uint64_t size) +{ + SkPixmap pixmap; + if (!surface_->getCanvas()->peekPixels(&pixmap)) + { + return; + } + memcpy(pixmap.writable_addr(), pixels, size); } \ No newline at end of file diff --git a/wrapper/src/alphaskia_data.cpp b/wrapper/src/alphaskia_data.cpp index 693aa2d..167a7ba 100644 --- a/wrapper/src/alphaskia_data.cpp +++ b/wrapper/src/alphaskia_data.cpp @@ -7,7 +7,6 @@ extern "C" { sk_sp skData = SkData::MakeWithCopy(data, length); return reinterpret_cast(new sk_sp(skData)); - } AS_API uint8_t * alphaskia_data_get_data(alphaskia_data_t data) diff --git a/wrapper/src/alphaskia_image.cpp b/wrapper/src/alphaskia_image.cpp index deeb379..a36ddf2 100644 --- a/wrapper/src/alphaskia_image.cpp +++ b/wrapper/src/alphaskia_image.cpp @@ -1,8 +1,10 @@ #include "../include/alphaskia.h" #include "../../externals/skia/include/core/SkImage.h" +#include "../../externals/skia/include/core/SkBitmap.h" #include "../../externals/skia/include/core/SkPixmap.h" #include "../../externals/skia/include/core/SkStream.h" #include "../../externals/skia/include/encode/SkPngEncoder.h" +#include "../../externals/skia/include/codec/SkPngDecoder.h" extern "C" { @@ -40,11 +42,26 @@ extern "C" sk_sp raster = (*internal)->makeRasterImage(); SkPixmap pixmap; - if(!(*internal)->peekPixels(&pixmap)) + if (!(*internal)->peekPixels(&pixmap)) { return nullptr; } - SkPngEncoder::Encode(static_cast(&stream), pixmap, options); + SkPngEncoder::Encode(static_cast(&stream), pixmap, options); return reinterpret_cast(new sk_sp(stream.detachAsData())); } + + AS_API alphaskia_image_t alphaskia_image_decode(const uint8_t *data, uint64_t length) + { + sk_sp wrapped_data = SkData::MakeWithCopy(data, length); + sk_sp image = SkImages::DeferredFromEncodedData(wrapped_data, SkAlphaType::kPremul_SkAlphaType); + return reinterpret_cast(new sk_sp(image)); + } + + AS_API alphaskia_image_t alphaskia_image_from_pixels(int32_t width, int32_t height, const uint8_t *pixels) + { + SkPixmap pixmap(SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType), + pixels, sizeof(uint32_t) * width); + sk_sp image = SkImages::RasterFromPixmapCopy(pixmap); + return reinterpret_cast(new sk_sp(image)); + } } \ No newline at end of file From 1133a11bd6dd4609b876ab37da557639b2586f33 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 18:47:37 +0200 Subject: [PATCH 02/38] Setup library tests --- .github/actions/build-alphaskia/action.yml | 15 +- .github/actions/build-skia/action.yml | 2 + .github/workflows/build.yml | 86 +- .nuke/build.schema.json | 2 + build/Build.DotNet.cs | 54 +- build/Build.Java.cs | 35 +- build/Build.LibAlphaSkia.cs | 7 +- build/Build.LibSkia.cs | 16 + build/Build.Node.cs | 70 +- lib/dotnet/AlphaSkia/AlphaSkiaImage.cs | 12 + lib/dotnet/AlphaSkia/NativeMethods.cs | 5 + lib/java/.idea/vcs.xml | 1 - .../alphatab/alphaskia/AlphaSkiaAndroid.java | 2 +- lib/java/build.gradle.kts | 10 - lib/java/jni/src/AlphaSkiaImage.cpp | 9 +- .../alphatab/alphaskia/AlphaSkiaLinux.java | 2 +- .../alphatab/alphaskia/AlphaSkiaMacOs.java | 2 +- .../alphatab/alphaskia/AlphaSkiaImage.java | 4 +- .../alphaskia/MusicSheetRenderTest.java | 188 ----- .../alphatab/alphaskia/RenderFunction.java | 8 - .../alphatab/alphaskia/AlphaSkiaWindows.java | 2 +- lib/node/alphaskia/package.json | 6 +- lib/node/alphaskia/src/AlphaSkiaImage.ts | 14 +- lib/node/alphaskia/src/AlphaSkiaPlatform.ts | 14 +- .../.idea.AlphaSkia.Test/.idea/.gitignore | 13 + .../.idea/.idea.AlphaSkia.Test/.idea/.name | 1 + .../.idea.AlphaSkia.Test/.idea/encodings.xml | 4 + .../.idea/indexLayout.xml | 8 + .../.idea/.idea.AlphaSkia.Test/.idea/vcs.xml | 7 + test/dotnet/AlphaSkia.Test.sln | 16 + .../AlphaSkia.Test/AlphaSkia.Test.csproj | 57 +- .../AlphaTabGeneratedRenderTest.cs | 45 ++ .../AlphaTabGeneratedRenderTest.generated.cs | 752 ++++++++++++++++++ test/dotnet/AlphaSkia.Test/PixelMatch.cs | 302 +++++++ test/dotnet/AlphaSkia.Test/Program.cs | 194 +++++ .../AlphaSkia.Test_/AlphaSkia.Test.csproj | 62 ++ .../AlphaTabGeneratedTest.cs | 0 .../MusicSheetRenderTest.cs | 0 .../Usings.cs | 0 test/dotnet/NuGet.config | 10 + test/dotnet/global.json | 7 + test/java/.idea/.gitignore | 3 + test/java/.idea/.name | 1 + test/java/.idea/gradle.xml | 18 + .../inspectionProfiles/Project_Default.xml | 8 + test/java/.idea/misc.xml | 6 + test/java/.idea/uiDesigner.xml | 124 +++ test/java/.idea/vcs.xml | 7 + test/java/build.gradle.kts | 191 +---- .../gradle/wrapper/gradle-wrapper.properties | 2 +- test/java/local.properties | 20 - test/java/main/build.gradle.kts | 30 - .../alphatab/alphaskia/AlphaSkiaCanvas.java | 247 ------ .../alphaskia/AlphaSkiaColorType.java | 38 - .../net/alphatab/alphaskia/AlphaSkiaData.java | 18 - .../alphatab/alphaskia/AlphaSkiaImage.java | 41 - .../alphatab/alphaskia/AlphaSkiaNative.java | 22 - .../alphatab/alphaskia/AlphaSkiaPlatform.java | 79 -- .../alphaskia/AlphaSkiaTextAlign.java | 29 - .../alphaskia/AlphaSkiaTextBaseline.java | 34 - .../alphatab/alphaskia/AlphaSkiaTypeface.java | 60 -- .../AlphaTabGeneratedRenderTest.java | 752 ------------------ .../alphaskia/MusicSheetRenderTest.java | 188 ----- test/java/settings.gradle.kts | 7 +- .../test}/AlphaTabGeneratedRenderTest.java | 37 +- .../test/AlphaTabGeneratedRenderTestBase.java | 111 +++ .../net/alphatab/alphaskia/test/Main.java | 226 ++++++ .../alphatab/alphaskia/test/PixelMatch.java | 306 +++++++ .../alphaskia/test/PixelMatchColor.java | 3 + .../alphaskia/test/PixelMatchOptions.java | 3 + .../alphaskia/test/PixelMatchResult.java | 3 + .../alphaskia/test}/RenderFunction.java | 4 +- test/native/include/PixelMatch.h | 2 - test/native/src/AlphaSkiaTestBridge.cpp | 12 +- test/native/src/PixelMatch.cpp | 21 +- test/native/src/main.cpp | 7 +- test/node/AlphaTabGeneratedRenderTest.ts | 741 +++++++++++++++++ test/node/AlphaTabGeneratedRenderTestBase.ts | 44 + test/node/PixelMatch.ts | 275 +++++++ test/node/index.ts | 198 +++++ test/node/package-lock.json | 496 ++++++++++++ test/node/package.json | 15 + test/node/tsconfig.json | 33 + .../AlphaSkiaUnitTestGenerator.cs | 72 +- .../test-generator/CSharpTestSourceBuilder.cs | 60 ++ test/test-generator/CppTestSourceBuilder.cs | 65 ++ test/test-generator/JavaTestSourceBuilder.cs | 59 ++ test/test-generator/Program.cs | 29 +- test/test-generator/TestSourceBuilder.cs | 250 ------ .../TypeScriptTestSourceBuilder.cs | 61 ++ test/test-outputs/dotnet/win-x64.png | Bin 0 -> 34635 bytes test/test-outputs/java/win-x64.png | Bin 0 -> 34635 bytes wrapper/src/AlphaSkiaCanvas.cpp | 10 - 93 files changed, 4735 insertions(+), 2377 deletions(-) delete mode 100644 lib/java/main/src/test/java/net/alphatab/alphaskia/MusicSheetRenderTest.java delete mode 100644 lib/java/main/src/test/java/net/alphatab/alphaskia/RenderFunction.java create mode 100644 test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/.gitignore create mode 100644 test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/.name create mode 100644 test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/encodings.xml create mode 100644 test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/indexLayout.xml create mode 100644 test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/vcs.xml create mode 100644 test/dotnet/AlphaSkia.Test.sln create mode 100644 test/dotnet/AlphaSkia.Test/AlphaTabGeneratedRenderTest.cs create mode 100644 test/dotnet/AlphaSkia.Test/AlphaTabGeneratedRenderTest.generated.cs create mode 100644 test/dotnet/AlphaSkia.Test/PixelMatch.cs create mode 100644 test/dotnet/AlphaSkia.Test/Program.cs create mode 100644 test/dotnet/AlphaSkia.Test_/AlphaSkia.Test.csproj rename test/dotnet/{AlphaSkia.Test => AlphaSkia.Test_}/AlphaTabGeneratedTest.cs (100%) rename test/dotnet/{AlphaSkia.Test => AlphaSkia.Test_}/MusicSheetRenderTest.cs (100%) rename test/dotnet/{AlphaSkia.Test => AlphaSkia.Test_}/Usings.cs (100%) create mode 100644 test/dotnet/NuGet.config create mode 100644 test/dotnet/global.json create mode 100644 test/java/.idea/.gitignore create mode 100644 test/java/.idea/.name create mode 100644 test/java/.idea/gradle.xml create mode 100644 test/java/.idea/inspectionProfiles/Project_Default.xml create mode 100644 test/java/.idea/misc.xml create mode 100644 test/java/.idea/uiDesigner.xml create mode 100644 test/java/.idea/vcs.xml delete mode 100644 test/java/local.properties delete mode 100644 test/java/main/build.gradle.kts delete mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaCanvas.java delete mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaColorType.java delete mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaData.java delete mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java delete mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaNative.java delete mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaPlatform.java delete mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextAlign.java delete mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextBaseline.java delete mode 100644 test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTypeface.java delete mode 100644 test/java/main/src/test/java/net/alphatab/alphaskia/AlphaTabGeneratedRenderTest.java delete mode 100644 test/java/main/src/test/java/net/alphatab/alphaskia/MusicSheetRenderTest.java rename {lib/java/main/src/test/java/net/alphatab/alphaskia => test/java/src/main/generated/net/alphatab/alphaskia/test}/AlphaTabGeneratedRenderTest.java (97%) create mode 100644 test/java/src/main/java/net/alphatab/alphaskia/test/AlphaTabGeneratedRenderTestBase.java create mode 100644 test/java/src/main/java/net/alphatab/alphaskia/test/Main.java create mode 100644 test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatch.java create mode 100644 test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatchColor.java create mode 100644 test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatchOptions.java create mode 100644 test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatchResult.java rename test/java/{main/src/test/java/net/alphatab/alphaskia => src/main/java/net/alphatab/alphaskia/test}/RenderFunction.java (82%) create mode 100644 test/node/AlphaTabGeneratedRenderTest.ts create mode 100644 test/node/AlphaTabGeneratedRenderTestBase.ts create mode 100644 test/node/PixelMatch.ts create mode 100644 test/node/index.ts create mode 100644 test/node/package-lock.json create mode 100644 test/node/package.json create mode 100644 test/node/tsconfig.json create mode 100644 test/test-generator/CSharpTestSourceBuilder.cs create mode 100644 test/test-generator/CppTestSourceBuilder.cs create mode 100644 test/test-generator/JavaTestSourceBuilder.cs create mode 100644 test/test-generator/TypeScriptTestSourceBuilder.cs create mode 100644 test/test-outputs/dotnet/win-x64.png create mode 100644 test/test-outputs/java/win-x64.png diff --git a/.github/actions/build-alphaskia/action.yml b/.github/actions/build-alphaskia/action.yml index b705c1d..8b4b4bc 100644 --- a/.github/actions/build-alphaskia/action.yml +++ b/.github/actions/build-alphaskia/action.yml @@ -37,7 +37,20 @@ runs: - run: ${{ steps.build-info.outputs.script }} LibAlphaSkia --target-os ${{ inputs.target-os }} --architecture ${{ inputs.architecture }} --variant ${{ inputs.variant }} shell: ${{ steps.build-info.outputs.shell }} + - run: ${{ steps.build-info.outputs.script }} LibAlphaSkiaTest --target-os ${{ inputs.target-os }} --architecture ${{ inputs.architecture }} --variant ${{ inputs.variant }} + shell: ${{ steps.build-info.outputs.shell }} + - uses: actions/upload-artifact@v3 + if: ${{ always() }} with: name: libalphaskia-${{ inputs.target-os }}-${{ inputs.architecture }}-${{ inputs.variant }} - path: artifacts \ No newline at end of file + path: artifacts + + - uses: actions/upload-artifact@v3 + if: ${{ always() }} + with: + name: libalphaskiatest-${{ inputs.target-os }}-${{ inputs.architecture }}-${{ inputs.variant }} + if-no-files-found: ignore + path: | + dist/libalphaskiatest*/**/*.* + test/test-outputs/**/*.* \ No newline at end of file diff --git a/.github/actions/build-skia/action.yml b/.github/actions/build-skia/action.yml index 2ba4065..46c6e0b 100644 --- a/.github/actions/build-skia/action.yml +++ b/.github/actions/build-skia/action.yml @@ -34,6 +34,7 @@ runs: shell: bash - run: ${{ steps.build-info.outputs.script }} LibSkiaWithCache --target-os ${{ inputs.target-os }} --architecture ${{ inputs.architecture }} --use-cache ${{ inputs.use-cache }} + id: build shell: ${{ steps.build-info.outputs.shell }} - uses: actions/upload-artifact@v3 @@ -42,6 +43,7 @@ runs: path: artifacts - uses: actions/cache/save@v3 + if: ${{ steps.build.outputs.build-skipped != 'true' }} with: path: dist key: libskia-${{ inputs.target-os }}-${{ inputs.architecture }}-static-${{ github.run_id }} \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 41613c2..2f5171c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -204,8 +204,41 @@ jobs: with: name: Nupkgs path: | - lib/dotnet/**/bin/Release/*.nupkg - lib/dotnet/**/bin/Release/*.snupkg + lib/dotnet/AlphaSkia/bin/Release/*.nupkg + lib/dotnet/AlphaTab.Native.Android/bin/Release/*.nupkg + lib/dotnet/AlphaTab.Native.iOS/bin/Release/*.nupkg + lib/dotnet/AlphaTab.Native.Linux/bin/Release/*.nupkg + lib/dotnet/AlphaTab.Native.MacOS/bin/Release/*.nupkg + lib/dotnet/AlphaTab.Native.Windows/bin/Release/*.nupkg + dotnet-test: + strategy: + fail-fast: false + matrix: + include: + - runs-on: ubuntu-latest + architecture: x64 + - runs-on: ubuntu-latest + architecture: x86 + - runs-on: windows-latest + architecture: x64 + - runs-on: windows-latest + architecture: x86 + - runs-on: macos-latest + architecture: x64 + runs-on: ${{ matrix.runs-on }} + needs: [dotnet] + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: Nupkgs + path: dist/NuPkgs + - run: ./build.cmd DotNetTest --architecture ${{ matrix.architecture }} + - uses: actions/upload-artifact@v3 + with: + name: DotNet Test Results + path: | + test/test-outputs/**/*.* java: runs-on: ubuntu-latest @@ -221,6 +254,29 @@ jobs: name: Maven path: | lib/java/dist/**/*.* + java-test: + strategy: + fail-fast: false + matrix: + include: + - runs-on: ubuntu-latest + - runs-on: windows-latest + - runs-on: macos-latest + runs-on: ${{ matrix.runs-on }} + needs: [java] + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: Maven + path: dist/Maven + - run: ./build.cmd JavaTest + - uses: actions/upload-artifact@v3 + with: + name: Java Test Results + path: | + test/test-outputs/**/*.* + node: runs-on: ubuntu-latest @@ -236,3 +292,29 @@ jobs: name: NodeTars path: | lib/node/*/*.tgz + node-test: + strategy: + fail-fast: false + matrix: + runs-on: [ubuntu-latest, windows-latest, macos-latest] + node: [latest, "lts/*"] + architecture: [x64, x86] + runs-on: ${{ matrix.runs-on }} + needs: [node] + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: NodeTars + path: dist/NodeTars + - uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + architecture: ${{ matrix.architecture }} + - run: node -e "console.log(process)" + - run: ./build.cmd NodeTest + - uses: actions/upload-artifact@v3 + with: + name: Node Test Results + path: | + test/test-outputs/**/*.* diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index d5fb218..f5b828a 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -125,6 +125,7 @@ "Java", "JavaBuild", "JavaPack", + "JavaTest", "LibAlphaSkia", "LibAlphaSkiaGitSyncDeps", "LibAlphaSkiaPatchSkiaBuildFiles", @@ -155,6 +156,7 @@ "Java", "JavaBuild", "JavaPack", + "JavaTest", "LibAlphaSkia", "LibAlphaSkiaGitSyncDeps", "LibAlphaSkiaPatchSkiaBuildFiles", diff --git a/build/Build.DotNet.cs b/build/Build.DotNet.cs index 59890f2..5601f08 100644 --- a/build/Build.DotNet.cs +++ b/build/Build.DotNet.cs @@ -1,3 +1,4 @@ +using System; using Nuke.Common; using Nuke.Common.IO; using Nuke.Common.Tooling; @@ -7,34 +8,48 @@ partial class Build { public Target DotNet => _ => _ .DependsOn(DotNetPack); - + public Target DotNetPack => _ => _ - .Unlisted() - .DependsOn(DotNetTest) - .Executes(() => - { - DotNetTasks.DotNetPack(_ => _ - .SetProcessWorkingDirectory(RootDirectory / "lib" / "dotnet") - .SetConfiguration("Release") - ); - }); - public Target DotNetTest => _ => _ .Unlisted() .DependsOn(DotNetBuild) .Executes(() => { - DotNetTasks.DotNetTest(_ => _ + if (Rebuild) + { + (TemporaryDirectory / "packages").DeleteDirectory(); + (RootDirectory / "lib" / "dotnet") + .GetFiles("*.nupkg", int.MaxValue) + .DeleteFiles(); + } + + DotNetTasks.DotNetPack(_ => _ .SetProcessWorkingDirectory(RootDirectory / "lib" / "dotnet") .SetConfiguration("Release") ); + + if (IsLocalBuild) + { + if (Rebuild) + { + (RootDirectory / "dist" / "NuPkgs").DeleteDirectory(); + } + + foreach (var nupkg in (RootDirectory / "lib" / "dotnet").GetFiles("*.nupkg", int.MaxValue)) + { + FileSystemTasks.CopyFile(nupkg, + RootDirectory / "dist" / "NuPkgs" / nupkg.Name, + FileExistsPolicy.OverwriteIfNewer); + } + } }); + public Target DotNetBuild => _ => _ .Unlisted() .DependsOn(PrepareGitHubArtifacts) .Executes(() => { DotNetWriteVersionInfoProps(); - + DotNetTasks.DotNetBuild(_ => _ .SetProcessWorkingDirectory(RootDirectory / "lib" / "dotnet") .SetConfiguration("Release") @@ -57,7 +72,7 @@ void DotNetWriteVersionInfoProps() { semVer = $"{VersionInfo.FileVersion.ToString(3)}"; } - + var props = $""" @@ -78,4 +93,15 @@ void DotNetWriteVersionInfoProps() """; (RootDirectory / "lib" / "dotnet" / "Version.props").WriteAllText(props); } + + public Target DotNetTest => _ => _ + .Executes(() => + { + DotNetTasks.DotNetRun(_ => _ + .SetProcessWorkingDirectory(RootDirectory / "test" / "dotnet") + .SetRuntime(TargetOperatingSystem.Current.RuntimeIdentifier + "-" + + (Architecture ?? Architecture.Current)) + .AddProcessEnvironmentVariable("NUGET_PACKAGES", TemporaryDirectory / "packages") + ); + }); } \ No newline at end of file diff --git a/build/Build.Java.cs b/build/Build.Java.cs index 5e1720d..944e2e9 100644 --- a/build/Build.Java.cs +++ b/build/Build.Java.cs @@ -8,23 +8,35 @@ partial class Build [Parameter] readonly AbsolutePath GradlewExe = GetVariable("GRADLEW_EXE") ?? RootDirectory / "lib" / "java" / $"gradlew{ScriptExtension}"; - [Parameter] - readonly AbsolutePath JavaHome = GetVariable("JAVA_HOME"); + + [Parameter] readonly AbsolutePath JavaHome = GetVariable("JAVA_HOME"); Tool GradlewTool => ToolResolver.GetTool(GradlewExe); public Target Java => _ => _ .DependsOn(JavaPack); - + public Target JavaPack => _ => _ .DependsOn(JavaBuild) .Executes(() => { GradlewTool("publishAllPublicationsToDistPathRepository", workingDirectory: RootDirectory / "lib" / "java"); + + if (IsLocalBuild) + { + if (Rebuild) + { + (RootDirectory / "dist" / "Maven").DeleteDirectory(); + } + + FileSystemTasks.CopyDirectoryRecursively(RootDirectory / "lib" / "java" / "dist", + RootDirectory / "dist" / "Maven", + DirectoryExistsPolicy.Merge, FileExistsPolicy.OverwriteIfNewer); + } }); - - + + public Target JavaBuild => _ => _ .DependsOn(PrepareGitHubArtifacts) .Executes(() => @@ -35,12 +47,19 @@ partial class Build { GradlewTool("clean", workingDirectory: RootDirectory / "lib" / "java"); + (RootDirectory / "lib" / "java" / "dist").DeleteDirectory(); } - + GradlewTool("build", workingDirectory: RootDirectory / "lib" / "java"); }); - + + public Target JavaTest => _ => _ + .Executes(() => + { + GradlewTool("run", workingDirectory: RootDirectory / "test" / "java"); + }); + void JavaWriteVersionInfoProperties() { string semVer; @@ -56,7 +75,7 @@ void JavaWriteVersionInfoProperties() { semVer = $"{VersionInfo.FileVersion.ToString(3)}"; } - + var props = $""" alphaskiaDescription={VersionInfo.Description} alphaskiaAuthorId={VersionInfo.AuthorId} diff --git a/build/Build.LibAlphaSkia.cs b/build/Build.LibAlphaSkia.cs index d768b94..f2619ca 100644 --- a/build/Build.LibAlphaSkia.cs +++ b/build/Build.LibAlphaSkia.cs @@ -379,6 +379,11 @@ void BuildAlphaSkiaTest() GnNinja($"out/{libDir}", buildTarget, gnArgs, gnFlags, SkiaPath); + // copy for artifacts + var distPath = DistBasePath / libDir; + var exePath = outDir / (buildTarget + exeExtension); + FileSystemTasks.CopyFile(exePath, distPath / exePath.Name, FileExistsPolicy.OverwriteIfNewer); + // copy shared lib beside executable var libExtensions = new HashSet(GetLibExtensions(Variant.Shared), StringComparer.OrdinalIgnoreCase); foreach (var file in sharedLibPath.GetFiles().Where(f => libExtensions.Contains(f.Extension))) @@ -389,7 +394,7 @@ void BuildAlphaSkiaTest() // run executable if (LibAlphaSkiaCanRunTests) { - ToolResolver.GetTool(outDir / (buildTarget + exeExtension))( + ToolResolver.GetTool(exePath)( "", workingDirectory: outDir ); diff --git a/build/Build.LibSkia.cs b/build/Build.LibSkia.cs index e8165c2..fa32c4f 100644 --- a/build/Build.LibSkia.cs +++ b/build/Build.LibSkia.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.IO; using Nuke.Common; +using Nuke.Common.CI.GitHubActions; using Nuke.Common.IO; +using Serilog; partial class Build { @@ -44,6 +46,20 @@ partial class Build .Before(SetupDepotTools, LibSkiaPatchSkiaBuildFiles, LibSkiaGitSyncDeps) .Executes(() => { + if (IsGitHubActions) + { + var outputsFile = Environment.GetEnvironmentVariable("GITHUB_OUTPUT"); + if (File.Exists(outputsFile)) + { + File.AppendAllLines(outputsFile, + new[] { $"build-skipped={LibSkiaSkip.Value.ToString().ToLowerInvariant()}\n" }); + } + else + { + Log.Warning("Could not set output parameter, GITHUB_OUTPUT not found"); + } + } + if (LibSkiaSkip.Value) { FileSystemTasks.CopyDirectoryRecursively(DistBasePath, ArtifactBasePath, DirectoryExistsPolicy.Merge, diff --git a/build/Build.Node.cs b/build/Build.Node.cs index c4abd3c..8b3ca9d 100644 --- a/build/Build.Node.cs +++ b/build/Build.Node.cs @@ -1,7 +1,7 @@ +using System.Linq; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Nodes; -using System.Text.Unicode; using Nuke.Common; using Nuke.Common.IO; using Nuke.Common.Tooling; @@ -22,24 +22,51 @@ partial class Build public Target NodePack => _ => _ .Unlisted() - .DependsOn(NodeTest) + .DependsOn(NodeBuild) .Executes(() => { + if (Rebuild) + { + (RootDirectory / "lib" / "node") + .GetFiles("*.tgz", int.MaxValue) + .DeleteFiles(); + } + foreach (var nodePackage in AllNodePackages) { NpmTasks.Npm("pack", nodePackage); } + + if (IsLocalBuild) + { + if (Rebuild) + { + (RootDirectory / "dist" / "NodeTars").DeleteDirectory(); + } + + foreach (var tgz in (RootDirectory / "lib" / "node").GetFiles("*.tgz", int.MaxValue)) + { + FileSystemTasks.CopyFile(tgz, + RootDirectory / "dist" / "NodeTars" / tgz.Name, + FileExistsPolicy.OverwriteIfNewer); + } + PrepareTgzForTest(); + } }); - public Target NodeTest => _ => _ - .Unlisted() - .DependsOn(NodeBuild) - .Executes(() => + void PrepareTgzForTest() + { + foreach (var tgz in (RootDirectory / "dist" / "NodeTars").GetFiles("*.tgz")) { - NpmTasks.NpmRun(_ => _ - .SetProcessWorkingDirectory(RootDirectory / "lib" / "node" / "alphaskia") - .SetCommand("test")); - }); + // coderline-alphaskia-1.0.0-local.0.tgz + var nameWithoutVersion = string.Join("-", + tgz.NameWithoutExtension.Split('-').TakeWhile(p => !char.IsDigit(p[0]))) + + tgz.Extension; + FileSystemTasks.CopyFile(tgz, + RootDirectory / "dist" / "NodeTars" / nameWithoutVersion, + FileExistsPolicy.OverwriteIfNewer); + } + } public Target NodeBuild => _ => _ .Unlisted() @@ -50,10 +77,9 @@ partial class Build if (Rebuild) { - foreach (var nodePackage in AllNodePackages) - { - NpmTasks.Npm("clean", nodePackage); - } + NpmTasks.NpmRun(_ => _ + .SetProcessWorkingDirectory(RootDirectory / "lib" / "node" / "alphaskia") + .SetCommand("clean")); } foreach (var nodePackage in AllNodePackages) @@ -122,6 +148,20 @@ void NodeWritePackageJson() } } + public Target NodeTest => _ => _ + .Executes(() => + { + PrepareTgzForTest(); + // need to delete package-lock.json due to tgz hash mismatch + (RootDirectory / "test" / "node" / "package-lock.json").DeleteFile(); + NpmTasks.NpmInstall(_ => _ + .SetProcessWorkingDirectory(RootDirectory / "test" / "node") + .SetForce(true)); + NpmTasks.NpmRun(_ => _ + .SetProcessWorkingDirectory(RootDirectory / "test" / "node") + .SetCommand("start")); + }); + void CopyNodeAddonsToPackages() { foreach (var subDirectory in DistBasePath.GetDirectories()) @@ -151,7 +191,7 @@ void CopyNodeAddonsToPackages() } FileSystemTasks.CopyDirectoryRecursively(subDirectory, - RootDirectory / "lib" / "node" / packageName / "lib", DirectoryExistsPolicy.Merge, + RootDirectory / "lib" / "node" / packageName / "lib" / subDirectory.Name, DirectoryExistsPolicy.Merge, FileExistsPolicy.OverwriteIfNewer, excludeFile: fi => fi.Extension != ".node"); } } diff --git a/lib/dotnet/AlphaSkia/AlphaSkiaImage.cs b/lib/dotnet/AlphaSkia/AlphaSkiaImage.cs index 8117ce4..98e1ab4 100644 --- a/lib/dotnet/AlphaSkia/AlphaSkiaImage.cs +++ b/lib/dotnet/AlphaSkia/AlphaSkiaImage.cs @@ -45,6 +45,18 @@ public bool ReadPixels(IntPtr pixels, ulong rowBytes) CheckDisposed(); return NativeMethods.alphaskia_image_read_pixels(Handle, pixels, rowBytes) != 0; } + + ///

+ /// Reads the raw pixel data of this image into the given target. + /// + /// The pixels from the image. + public byte[]? ReadPixels() + { + CheckDisposed(); + var rowBytes = Width * sizeof(int); + var pixels = new byte[rowBytes * Height]; + return NativeMethods.alphaskia_image_read_pixels_bytes(Handle, pixels, (ulong)rowBytes) == 0 ? null : pixels; + } /// /// Encodes the image to a PNG. diff --git a/lib/dotnet/AlphaSkia/NativeMethods.cs b/lib/dotnet/AlphaSkia/NativeMethods.cs index d1e1d73..7434f16 100644 --- a/lib/dotnet/AlphaSkia/NativeMethods.cs +++ b/lib/dotnet/AlphaSkia/NativeMethods.cs @@ -68,6 +68,11 @@ public static partial alphaskia_typeface_t alphaskia_typeface_make_from_name(str [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] public static partial int alphaskia_image_read_pixels(alphaskia_image_t image, IntPtr pixels, ulong rowBytes); + + [LibraryImport(AlphaSkiaNativeLibName, EntryPoint = nameof(alphaskia_image_read_pixels))] + [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] + public static partial int alphaskia_image_read_pixels_bytes(alphaskia_image_t image, byte[] pixels, ulong rowBytes); + [LibraryImport(AlphaSkiaNativeLibName)] [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] public static partial alphaskia_data_t alphaskia_image_encode_png(alphaskia_image_t image); diff --git a/lib/java/.idea/vcs.xml b/lib/java/.idea/vcs.xml index def3818..b2bdec2 100644 --- a/lib/java/.idea/vcs.xml +++ b/lib/java/.idea/vcs.xml @@ -1,7 +1,6 @@ - \ No newline at end of file diff --git a/lib/java/android/src/main/java/net/alphatab/alphaskia/AlphaSkiaAndroid.java b/lib/java/android/src/main/java/net/alphatab/alphaskia/AlphaSkiaAndroid.java index ef17a60..fccb261 100644 --- a/lib/java/android/src/main/java/net/alphatab/alphaskia/AlphaSkiaAndroid.java +++ b/lib/java/android/src/main/java/net/alphatab/alphaskia/AlphaSkiaAndroid.java @@ -8,7 +8,7 @@ public final class AlphaSkiaAndroid { * The native libraries needed to run alphaSkia. */ public static final String[] libraries = { - "native/android-" + getCurrentArchitecture() + "/libalphaskiajni.so" + "/native/android-" + getCurrentArchitecture() + "/libalphaskiajni.so" }; private static String getCurrentArchitecture() { diff --git a/lib/java/build.gradle.kts b/lib/java/build.gradle.kts index da9e4a0..8cead64 100644 --- a/lib/java/build.gradle.kts +++ b/lib/java/build.gradle.kts @@ -93,16 +93,6 @@ subprojects { withJavadocJar() } - tasks.withType().configureEach { - systemProperty("alphaskia.library.path", rootProject.projectDir.resolve("../../dist/")) - systemProperty("testdata.path", rootProject.projectDir.resolve("../test/")) - systemProperty("testoutput.path", projectDir.resolve("../test-outputs/")) - testLogging { - exceptionFormat = TestExceptionFormat.FULL - showStackTraces = true - } - } - configure { repositories { // maven { diff --git a/lib/java/jni/src/AlphaSkiaImage.cpp b/lib/java/jni/src/AlphaSkiaImage.cpp index 34713bb..ed0d040 100644 --- a/lib/java/jni/src/AlphaSkiaImage.cpp +++ b/lib/java/jni/src/AlphaSkiaImage.cpp @@ -34,11 +34,14 @@ extern "C" rowBytes * alphaskia_image_get_height(image)); jbyte *bytes = env->GetByteArrayElements(pixels, nullptr); - alphaskia_image_read_pixels(image, reinterpret_cast(bytes), rowBytes); + if ( alphaskia_image_read_pixels(image, reinterpret_cast(bytes), rowBytes) == 0) + { + env->ReleaseByteArrayElements(pixels, bytes, 0); + return nullptr; + } env->ReleaseByteArrayElements(pixels, bytes, 0); - - return nullptr; + return pixels; } JNIEXPORT jbyteArray JNICALL Java_net_alphatab_alphaskia_AlphaSkiaImage_toPng(JNIEnv *env, jobject instance) diff --git a/lib/java/linux/src/main/java/net/alphatab/alphaskia/AlphaSkiaLinux.java b/lib/java/linux/src/main/java/net/alphatab/alphaskia/AlphaSkiaLinux.java index 5bd3f26..66cb9bf 100644 --- a/lib/java/linux/src/main/java/net/alphatab/alphaskia/AlphaSkiaLinux.java +++ b/lib/java/linux/src/main/java/net/alphatab/alphaskia/AlphaSkiaLinux.java @@ -8,7 +8,7 @@ public class AlphaSkiaLinux { * The native libraries needed to run alphaSkia. */ public static final String[] libraries = { - "native/linux-" + getCurrentArchitecture() + "/libalphaskiajni.so" + "/native/linux-" + getCurrentArchitecture() + "/libalphaskiajni.so" }; private static String getCurrentArchitecture() { diff --git a/lib/java/macos/src/main/java/net/alphatab/alphaskia/AlphaSkiaMacOs.java b/lib/java/macos/src/main/java/net/alphatab/alphaskia/AlphaSkiaMacOs.java index 4bd7d91..3c87cf0 100644 --- a/lib/java/macos/src/main/java/net/alphatab/alphaskia/AlphaSkiaMacOs.java +++ b/lib/java/macos/src/main/java/net/alphatab/alphaskia/AlphaSkiaMacOs.java @@ -8,7 +8,7 @@ public final class AlphaSkiaMacOs { * The native libraries needed to run alphaSkia. */ public static final String[] libraries = { - "native/macos-" + getCurrentArchitecture() + "/libalphaskiajni.dylib" + "/native/macos-" + getCurrentArchitecture() + "/libalphaskiajni.dylib" }; private static String getCurrentArchitecture() { diff --git a/lib/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java b/lib/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java index ad1aae9..3fd7734 100644 --- a/lib/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java +++ b/lib/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java @@ -42,7 +42,7 @@ public class AlphaSkiaImage extends AlphaSkiaNative { /** * Decodes the given bytes into an image using supported image formats like PNG. * @param bytes The raw iamge bytes. - * @return The decoded image or null if the image could not be decoded. + * @return The decoded image or {@code null} if the image could not be decoded. */ public static AlphaSkiaImage decode(byte[] bytes) { @@ -58,7 +58,7 @@ public static AlphaSkiaImage decode(byte[] bytes) * @param width The width of the image in pixels. * @param height The height of the image in pixels. * @param pixels The raw pixel bytes. - * @return The decoded image or null if the creation of the image from the pixels failed. + * @return The decoded image or {@code null} if the creation of the image from the pixels failed. */ public static AlphaSkiaImage fromPixels(int width, int height, byte[] pixels) { diff --git a/lib/java/main/src/test/java/net/alphatab/alphaskia/MusicSheetRenderTest.java b/lib/java/main/src/test/java/net/alphatab/alphaskia/MusicSheetRenderTest.java deleted file mode 100644 index 847acb3..0000000 --- a/lib/java/main/src/test/java/net/alphatab/alphaskia/MusicSheetRenderTest.java +++ /dev/null @@ -1,188 +0,0 @@ -package net.alphatab.alphaskia; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -public abstract class MusicSheetRenderTest { - private static AlphaSkiaTypeface _musicTypeface; - private static final int _musicFontSize = 34; - private static final float _renderScale = 1.0f; - - protected static float getRenderScale() { - return _renderScale; - } - - protected static AlphaSkiaTypeface getMusicTypeface() { - return _musicTypeface; - } - - protected static int getMusicFontSize() { - return _musicFontSize; - } - - private static final Map _customTypefaces = - new HashMap<>(); - - private static String customTypefaceKey(String fontFamily, boolean isBold, boolean isItalic) { - return fontFamily.toLowerCase() + "_" + isBold + "_" + isItalic; - } - - @BeforeAll - public static void setup() throws IOException { - var os = System.getProperty("os.name"); - String libName; - String target; - if (os.startsWith("Mac")) { - target = "macos"; - libName = "libalphaskiajni.dylib"; - } else if (os.startsWith("Win")) { - target = "win"; - libName = "libalphaskiajni.dll"; - } else if (os.startsWith("Linux")) { - target = "linux"; - libName = "libalphaskiajni.so"; - } else { - target = os.toLowerCase(); - libName = "libalphaskiajni"; - } - - var jarch = System.getProperty("os.arch"); - var arch = switch (jarch) { - case "x86", "i368", "i486", "i586", "i686" -> "x86"; - case "x86_64", "amd64" -> "x64"; - case "arm" -> "arm"; - case "aarch64" -> "arm64"; - default -> jarch; - }; - - AlphaSkiaPlatform.loadLibrary(new String[]{ - Path.of(System.getProperty("alphaskia.library.path"), "libalphaskiajni-" + target + "-" + arch + "-jni", libName).toAbsolutePath().toString() - }); - - var bravura = readFont("font/bravura/Bravura.ttf"); - - _musicTypeface = AlphaSkiaTypeface.register(bravura); - - _customTypefaces.put(customTypefaceKey("Roboto", false, false), - AlphaSkiaTypeface.register(readFont("font/roboto/Roboto-Regular.ttf"))); - _customTypefaces.put(customTypefaceKey("Roboto", true, false), - AlphaSkiaTypeface.register(readFont("font/roboto/Roboto-Bold.ttf"))); - _customTypefaces.put(customTypefaceKey("Roboto", false, true), - AlphaSkiaTypeface.register(readFont("font/roboto/Roboto-Italic.ttf"))); - _customTypefaces.put(customTypefaceKey("Roboto", true, true), - AlphaSkiaTypeface.register(readFont("font/roboto/Roboto-BoldItalic.ttf"))); - - _customTypefaces.put(customTypefaceKey("PT Serif", false, false), - AlphaSkiaTypeface.register(readFont("font/ptserif/PTSerif-Regular.ttf"))); - _customTypefaces.put(customTypefaceKey("PT Serif", true, false), - AlphaSkiaTypeface.register(readFont("font/ptserif/PTSerif-Bold.ttf"))); - _customTypefaces.put(customTypefaceKey("PT Serif", false, true), - AlphaSkiaTypeface.register(readFont("font/ptserif/PTSerif-Italic.ttf"))); - _customTypefaces.put(customTypefaceKey("PT Serif", true, true), - AlphaSkiaTypeface.register(readFont("font/ptserif/PTSerif-BoldItalic.ttf"))); - } - - protected static AlphaSkiaTypeface getTypeface(String name, boolean isBold, boolean isItalic) { - var face = _customTypefaces.getOrDefault(customTypefaceKey(name, isBold, isItalic), null); - if (face == null) { - throw new IllegalStateException("Unknown font requested: " + name); - } - return face; - } - - - private AlphaSkiaTextAlign textAlign = AlphaSkiaTextAlign.CENTER; - - protected AlphaSkiaTextAlign getTextAlign() { - return textAlign; - } - - protected void setTextAlign(AlphaSkiaTextAlign textAlign) { - this.textAlign = textAlign; - } - - private AlphaSkiaTextBaseline textBaseline = AlphaSkiaTextBaseline.TOP; - - protected AlphaSkiaTextBaseline getTextBaseline() { - return textBaseline; - } - - protected void setTextBaseline(AlphaSkiaTextBaseline textBaseline) { - this.textBaseline = textBaseline; - } - - private AlphaSkiaTypeface typeface; - - protected AlphaSkiaTypeface getTypeface() { - return typeface; - } - - protected void setTypeface(AlphaSkiaTypeface typeface) { - this.typeface = typeface; - } - - private float fontSize; - - public float getFontSize() { - return fontSize; - } - - public void setFontSize(float fontSize) { - this.fontSize = fontSize; - } - - private static byte[] readFont(String relativePath) throws IOException { - var fullPath = Path.of(System.getProperty("testdata.path")).resolve(relativePath); - try (var stream = new FileInputStream(fullPath.toFile())) { - return stream.readAllBytes(); - } - } - - private AlphaSkiaImage renderFullImage() { - try (var canvas = new AlphaSkiaCanvas()) { - var images = Arrays.stream(getAllParts()).map(p -> p.render(canvas)).toArray(AlphaSkiaImage[]::new); - - canvas.beginRender(getTotalWidth(), getTotalHeight()); - - var partPositions = getPartPositions(); - for (var i = 0; i < images.length; i++) { - canvas.drawImage(images[i], partPositions[i][0], partPositions[i][1], images[i].getWidth() / getRenderScale(), images[i].getHeight() / getRenderScale()); - images[i].close(); - } - - return canvas.endRender(); - } - } - - @Test - public void renderTest() throws IOException { - try (var finalImage = renderFullImage()) { - var testOutputPath = Path.of(System.getProperty("testoutput.path")); - Files.createDirectories(testOutputPath); - - var file = testOutputPath.resolve(this.getClass().getSimpleName() +".png").toFile(); - byte[] png = finalImage.toPng(); - - try (var stream = new FileOutputStream(file)) { - stream.write(png); - } - } - - } - - protected abstract int getTotalWidth(); - - protected abstract int getTotalHeight(); - - protected abstract float[][] getPartPositions(); - - protected abstract RenderFunction[] getAllParts(); -} - diff --git a/lib/java/main/src/test/java/net/alphatab/alphaskia/RenderFunction.java b/lib/java/main/src/test/java/net/alphatab/alphaskia/RenderFunction.java deleted file mode 100644 index 44ae5fd..0000000 --- a/lib/java/main/src/test/java/net/alphatab/alphaskia/RenderFunction.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.alphatab.alphaskia; - -import net.alphatab.alphaskia.AlphaSkiaCanvas; -import net.alphatab.alphaskia.AlphaSkiaImage; - -public interface RenderFunction { - AlphaSkiaImage render(AlphaSkiaCanvas canvas); -} \ No newline at end of file diff --git a/lib/java/windows/src/main/java/net/alphatab/alphaskia/AlphaSkiaWindows.java b/lib/java/windows/src/main/java/net/alphatab/alphaskia/AlphaSkiaWindows.java index 593bc11..71f68ff 100644 --- a/lib/java/windows/src/main/java/net/alphatab/alphaskia/AlphaSkiaWindows.java +++ b/lib/java/windows/src/main/java/net/alphatab/alphaskia/AlphaSkiaWindows.java @@ -8,7 +8,7 @@ public final class AlphaSkiaWindows { * The native libraries needed to run alphaSkia. */ public static final String[] libraries = { - "native/windows-" + getCurrentArchitecture() + "/libalphaskiajni.dll" + "/native/win-" + getCurrentArchitecture() + "/libalphaskiajni.dll" }; private static String getCurrentArchitecture() { diff --git a/lib/node/alphaskia/package.json b/lib/node/alphaskia/package.json index e0c3aea..0ffb176 100644 --- a/lib/node/alphaskia/package.json +++ b/lib/node/alphaskia/package.json @@ -4,11 +4,7 @@ "description": "A Skia based rendering backend for alphaTab.", "module": "dist/alphaskia.mjs", "typings": "dist/alphaskia.d.ts", - "exports": { - ".": { - "import": "./dist/alphaskia.mjs" - } - }, + "exports": "./dist/alphaskia.mjs", "engines": { "node": ">=18.0.0" }, diff --git a/lib/node/alphaskia/src/AlphaSkiaImage.ts b/lib/node/alphaskia/src/AlphaSkiaImage.ts index 3e4026a..6ab8e24 100644 --- a/lib/node/alphaskia/src/AlphaSkiaImage.ts +++ b/lib/node/alphaskia/src/AlphaSkiaImage.ts @@ -1,4 +1,3 @@ -import { loadavg } from 'os'; import { AlphaSkiaNative } from './AlphaSkiaNative'; import { AlphaSkiaImageHandle, loadAddon } from './addon'; @@ -62,4 +61,17 @@ export class AlphaSkiaImage extends AlphaSkiaNative { const handle = loadAddon().alphaskia_image_decode(bytes); return !handle ? undefined : new AlphaSkiaImage(handle); } + + + /** + * Creates an image from the raw pixels assuming the default internal pixel format of alphaSkia. + * @param width The width of the image in pixels. + * @param height The height of the image in pixels. + * @param pixels The raw pixel bytes. + * @return The decoded image or {@code undefined} if the creation of the image from the pixels failed. + */ + public static fromPixels(width: number, height: number, pixels: ArrayBuffer): AlphaSkiaImage | undefined { + const handle = loadAddon().alphaskia_image_from_pixels(width, height, pixels); + return handle == 0 ? undefined : new AlphaSkiaImage(handle); + } } \ No newline at end of file diff --git a/lib/node/alphaskia/src/AlphaSkiaPlatform.ts b/lib/node/alphaskia/src/AlphaSkiaPlatform.ts index 05d4d99..acfb476 100644 --- a/lib/node/alphaskia/src/AlphaSkiaPlatform.ts +++ b/lib/node/alphaskia/src/AlphaSkiaPlatform.ts @@ -4,11 +4,17 @@ import * as fs from 'fs'; const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); -const platformMap: any = { +const ridPlatformMap: any = { "win32": "win", "darwin": "macos" }; -const platform = platformMap[process.platform] || process.platform; +const ridPlatform = ridPlatformMap[process.platform] || process.platform; + +const packagePlatformMap: any = { + "win32": "windows", + "darwin": "macos" +}; +const packagePlatform = packagePlatformMap[process.platform] || process.platform; const arch = process.arch; let searchPaths = [ @@ -20,7 +26,7 @@ let searchPaths = [ const nodeModulesIndex = __dirname.indexOf('node_modules'); if (nodeModulesIndex > 0) { - searchPaths.push(path.join(__dirname.substring(0, nodeModulesIndex), 'node_modules', '@coderline', `alphaskia-${platform}`, 'lib')) + searchPaths.push(path.join(__dirname.substring(0, nodeModulesIndex), 'node_modules', '@coderline', `alphaskia-${packagePlatform}`, 'lib')) } /** @@ -42,7 +48,7 @@ export function addSearchPaths(...paths: string[]) { * @returns The resolved path to the native node addon to load for this platform or undefined if it could not be found. */ export function findAddonPath(): string | undefined { - const libDirectory = `libalphaskianode-${platform}-${arch}-node`; + const libDirectory = `libalphaskianode-${ridPlatform}-${arch}-node`; for (const searchPath of searchPaths) { const addonPath = path.join(searchPath, libDirectory, 'libalphaskianode.node'); if (fs.existsSync(addonPath)) { diff --git a/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/.gitignore b/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/.gitignore new file mode 100644 index 0000000..d30967f --- /dev/null +++ b/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/.idea.AlphaSkia.Test.iml +/modules.xml +/contentModel.xml +/projectSettingsUpdater.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/.name b/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/.name new file mode 100644 index 0000000..03c4710 --- /dev/null +++ b/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/.name @@ -0,0 +1 @@ +AlphaSkia.Test \ No newline at end of file diff --git a/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/encodings.xml b/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/indexLayout.xml b/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/vcs.xml b/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/vcs.xml new file mode 100644 index 0000000..def3818 --- /dev/null +++ b/test/dotnet/.idea/.idea.AlphaSkia.Test/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/test/dotnet/AlphaSkia.Test.sln b/test/dotnet/AlphaSkia.Test.sln new file mode 100644 index 0000000..5b8ca10 --- /dev/null +++ b/test/dotnet/AlphaSkia.Test.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlphaSkia.Test", "AlphaSkia.Test\AlphaSkia.Test.csproj", "{D846E24D-7401-457A-9134-8A683AE26EA2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D846E24D-7401-457A-9134-8A683AE26EA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D846E24D-7401-457A-9134-8A683AE26EA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D846E24D-7401-457A-9134-8A683AE26EA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D846E24D-7401-457A-9134-8A683AE26EA2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/test/dotnet/AlphaSkia.Test/AlphaSkia.Test.csproj b/test/dotnet/AlphaSkia.Test/AlphaSkia.Test.csproj index 6ed9dbe..ccbf220 100644 --- a/test/dotnet/AlphaSkia.Test/AlphaSkia.Test.csproj +++ b/test/dotnet/AlphaSkia.Test/AlphaSkia.Test.csproj @@ -1,62 +1,19 @@ + Exe net7.0 enable enable - - false - true - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - diff --git a/test/dotnet/AlphaSkia.Test/AlphaTabGeneratedRenderTest.cs b/test/dotnet/AlphaSkia.Test/AlphaTabGeneratedRenderTest.cs new file mode 100644 index 0000000..f4e5ea1 --- /dev/null +++ b/test/dotnet/AlphaSkia.Test/AlphaTabGeneratedRenderTest.cs @@ -0,0 +1,45 @@ +namespace AlphaSkia.Test; + +partial class AlphaTabGeneratedRenderTest +{ + public static float RenderScale { get; set; } = 1; + public static AlphaSkiaTypeface MusicTypeface { get; set; } = null!; + public static readonly float MusicFontSize = 34; + public static AlphaSkiaTextAlign TextAlign { get; set; } = AlphaSkiaTextAlign.Left; + public static AlphaSkiaTextBaseline TextBaseline { get; set; } = AlphaSkiaTextBaseline.Top; + public static AlphaSkiaTypeface Typeface { get; set; } = null!; + public static float FontSize { get; set; } = 12; + + private static readonly IDictionary CustomTypefaces = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + private static string CustomTypefaceKey(string fontFamily, bool isBold, bool isItalic) + { + return fontFamily.ToLowerInvariant() + "_" + isBold + "_" + isItalic; + } + + private static AlphaSkiaTypeface GetTypeface(string fontFamily, bool isBold, bool isItalic) + { + var key = CustomTypefaceKey(fontFamily, isBold, isItalic); + if (!CustomTypefaces.TryGetValue(key, out var typeface)) + { + throw new InvalidOperationException($"Unknown font requested: {key}"); + } + + return typeface; + } + + public static AlphaSkiaTypeface LoadTypeface(string name, bool isBold, bool isItalic, string filePath) + { + var key = CustomTypefaceKey(name, isBold, isItalic); + Console.WriteLine($"Loading typeface {key} from {filePath}"); + var data = File.ReadAllBytes(filePath); + + Console.WriteLine($"Read {data.Length} bytes from file, decoding typeface"); + var typeface = AlphaSkiaTypeface.Register(data); + + CustomTypefaces[key] = typeface + ?? throw new InvalidOperationException("Could not create typeface from data"); + return typeface; + } +} \ No newline at end of file diff --git a/test/dotnet/AlphaSkia.Test/AlphaTabGeneratedRenderTest.generated.cs b/test/dotnet/AlphaSkia.Test/AlphaTabGeneratedRenderTest.generated.cs new file mode 100644 index 0000000..faf92f1 --- /dev/null +++ b/test/dotnet/AlphaSkia.Test/AlphaTabGeneratedRenderTest.generated.cs @@ -0,0 +1,752 @@ +// +// This file has been auto generated by a unit test in alphaTab. +// +#nullable enable + +namespace AlphaSkia.Test; + +partial class AlphaTabGeneratedRenderTest +{ + private static AlphaSkiaImage? DrawMusicSheetPart1(AlphaSkiaCanvas canvas) + { + canvas.BeginRender(1300, 164, (float)(RenderScale)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(153), (byte)(204), (byte)(30), (byte)(255)); + TextAlign = AlphaSkiaTextAlign.Center; + Typeface = GetTypeface("PT Serif", false, false); + FontSize = (float)(32); + TextAlign = AlphaSkiaTextAlign.Center; + canvas.FillText("Title", Typeface, FontSize, (float)(650), (float)(40), TextAlign, TextBaseline); + TextAlign = AlphaSkiaTextAlign.Center; + Typeface = GetTypeface("PT Serif", false, false); + FontSize = (float)(20); + TextAlign = AlphaSkiaTextAlign.Center; + canvas.FillText("Subtitle", Typeface, FontSize, (float)(650), (float)(72), TextAlign, TextBaseline); + TextAlign = AlphaSkiaTextAlign.Center; + Typeface = GetTypeface("PT Serif", false, false); + FontSize = (float)(20); + TextAlign = AlphaSkiaTextAlign.Center; + canvas.FillText("Artist", Typeface, FontSize, (float)(650), (float)(92), TextAlign, TextBaseline); + TextAlign = AlphaSkiaTextAlign.Center; + Typeface = GetTypeface("PT Serif", false, false); + FontSize = (float)(20); + TextAlign = AlphaSkiaTextAlign.Center; + canvas.FillText("Album", Typeface, FontSize, (float)(650), (float)(112), TextAlign, TextBaseline); + TextAlign = AlphaSkiaTextAlign.Center; + Typeface = GetTypeface("PT Serif", false, false); + FontSize = (float)(15); + TextAlign = AlphaSkiaTextAlign.Right; + canvas.FillText("Music by Music", Typeface, FontSize, (float)(1260), (float)(132), TextAlign, TextBaseline); + TextAlign = AlphaSkiaTextAlign.Center; + Typeface = GetTypeface("PT Serif", false, false); + FontSize = (float)(15); + TextAlign = AlphaSkiaTextAlign.Left; + canvas.FillText("Words by Words", Typeface, FontSize, (float)(40), (float)(132), TextAlign, TextBaseline); + TextAlign = AlphaSkiaTextAlign.Center; + return canvas.EndRender(); + } + private static AlphaSkiaImage? DrawMusicSheetPart2(AlphaSkiaCanvas canvas) + { + canvas.BeginRender(1300, 38, (float)(RenderScale)); + TextAlign = AlphaSkiaTextAlign.Center; + Typeface = GetTypeface("PT Serif", false, true); + FontSize = (float)(12); + TextAlign = AlphaSkiaTextAlign.Left; + canvas.FillText("Guitar Standard Tuning", Typeface, FontSize, (float)(40), (float)(3), TextAlign, TextBaseline); + TextAlign = AlphaSkiaTextAlign.Center; + return canvas.EndRender(); + } + private static AlphaSkiaImage? DrawMusicSheetPart3(AlphaSkiaCanvas canvas) + { + canvas.BeginRender(1300, 6, (float)(RenderScale)); + TextAlign = AlphaSkiaTextAlign.Center; + return canvas.EndRender(); + } + private static AlphaSkiaImage? DrawMusicSheetPart4(AlphaSkiaCanvas canvas) + { + canvas.BeginRender(1300, 373, (float)(RenderScale)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + TextAlign = AlphaSkiaTextAlign.Left; + Typeface = GetTypeface("PT Serif", true, false); + FontSize = (float)(14); + canvas.FillText("\uE1D5", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(156.6767578125), (float)(40), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("= 120", Typeface, FontSize, (float)(169.1767578125), (float)(27), TextAlign, TextBaseline); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); + canvas.FillRect((float)(92), (float)(130), (float)(254), (float)(1.04)); + canvas.FillRect((float)(92), (float)(139), (float)(254), (float)(1.04)); + canvas.FillRect((float)(92), (float)(148), (float)(254), (float)(1.04)); + canvas.FillRect((float)(92), (float)(157), (float)(254), (float)(1.04)); + canvas.FillRect((float)(92), (float)(166), (float)(254), (float)(1.04)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + canvas.FillText("\uE050", MusicTypeface, (float)(MusicFontSize * 1), (float)(94), (float)(157.52), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE084", MusicTypeface, (float)(MusicFontSize * 1), (float)(127), (float)(139), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE084", MusicTypeface, (float)(MusicFontSize * 1), (float)(127), (float)(157), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(200), (byte)(0), (byte)(0), (byte)(255)); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("1", Typeface, FontSize, (float)(141), (float)(118.75), TextAlign, TextBaseline); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(152.1767578125), (float)(148), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE272", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(172.86069962449085), (float)(148), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(180.86069962449085), (float)(149), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.BeginPath(); + canvas.MoveTo((float)(156.6767578125), (float)(144)); + canvas.LineTo((float)(170.855110706305), (float)(133.50723450810483)); + canvas.LineTo((float)(184.23569962449085), (float)(145)); + canvas.Stroke(); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(192.23569962449085), (float)(148), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(220.91964143648167), (float)(144.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.BeginPath(); + canvas.MoveTo((float)(196.73569962449085), (float)(144)); + canvas.LineTo((float)(209.12929655256082), (float)(131.33765133817155)); + canvas.LineTo((float)(224.29464143648167), (float)(140.5)); + canvas.Stroke(); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(232.29464143648167), (float)(148), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE262", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(252.9785832484725), (float)(143.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(260.9785832484725), (float)(144.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.BeginPath(); + canvas.MoveTo((float)(236.79464143648167), (float)(144)); + canvas.LineTo((float)(249.1882383645516), (float)(131.33765133817155)); + canvas.LineTo((float)(264.3535832484725), (float)(140.5)); + canvas.Stroke(); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(272.3535832484725), (float)(148), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(294.5517916242362), (float)(140), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.BeginPath(); + canvas.MoveTo((float)(276.8535832484725), (float)(152)); + canvas.LineTo((float)(292.1060513036615), (float)(156.93783819474976)); + canvas.LineTo((float)(297.9267916242362), (float)(142)); + canvas.Stroke(); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(305.9267916242362), (float)(148), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE262", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(324.625), (float)(139), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(332.625), (float)(140), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.BeginPath(); + canvas.MoveTo((float)(310.4267916242362), (float)(152)); + canvas.LineTo((float)(327.2193887931866), (float)(157.24460932567123)); + canvas.LineTo((float)(336), (float)(142)); + canvas.Stroke(); + canvas.FillRect((float)(345), (float)(130.1), (float)(1), (float)(36)); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(152.1767578125), (float)(148)); + canvas.LineTo((float)(152.1767578125), (float)(175)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(192.23569962449085), (float)(148)); + canvas.LineTo((float)(192.23569962449085), (float)(175)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(232.29464143648167), (float)(148)); + canvas.LineTo((float)(232.29464143648167), (float)(175)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(281.3535832484725), (float)(148)); + canvas.LineTo((float)(281.3535832484725), (float)(121)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.BeginPath(); + canvas.MoveTo((float)(281.3535832484725), (float)(121)); + canvas.LineTo((float)(314.9267916242362), (float)(121)); + canvas.LineTo((float)(314.9267916242362), (float)(125)); + canvas.LineTo((float)(281.3535832484725), (float)(125)); + canvas.ClosePath(); + canvas.Fill(); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(314.9267916242362), (float)(148)); + canvas.LineTo((float)(314.9267916242362), (float)(121)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(100)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); + canvas.FillRect((float)(346), (float)(130), (float)(126), (float)(1.04)); + canvas.FillRect((float)(346), (float)(139), (float)(126), (float)(1.04)); + canvas.FillRect((float)(346), (float)(148), (float)(126), (float)(1.04)); + canvas.FillRect((float)(346), (float)(157), (float)(126), (float)(1.04)); + canvas.FillRect((float)(346), (float)(166), (float)(126), (float)(1.04)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(200), (byte)(0), (byte)(0), (byte)(255)); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("2", Typeface, FontSize, (float)(346), (float)(118.75), TextAlign, TextBaseline); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(357.1767578125), (float)(139), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(378.359547484596), (float)(139), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(399.542337156692), (float)(161.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A3", MusicTypeface, (float)(MusicFontSize * 1), (float)(426.623147703567), (float)(157), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillRect((float)(471), (float)(130.1), (float)(1), (float)(36)); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(366.1767578125), (float)(139)); + canvas.LineTo((float)(366.1767578125), (float)(112)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.BeginPath(); + canvas.MoveTo((float)(366.1767578125), (float)(112)); + canvas.LineTo((float)(387.359547484596), (float)(117)); + canvas.LineTo((float)(387.359547484596), (float)(121)); + canvas.LineTo((float)(366.1767578125), (float)(116)); + canvas.ClosePath(); + canvas.Fill(); + canvas.BeginPath(); + canvas.MoveTo((float)(366.1767578125), (float)(118)); + canvas.LineTo((float)(387.359547484596), (float)(123)); + canvas.LineTo((float)(387.359547484596), (float)(127)); + canvas.LineTo((float)(366.1767578125), (float)(122)); + canvas.ClosePath(); + canvas.Fill(); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(387.359547484596), (float)(139)); + canvas.LineTo((float)(387.359547484596), (float)(117)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.BeginPath(); + canvas.MoveTo((float)(387.359547484596), (float)(117)); + canvas.LineTo((float)(408.542337156692), (float)(122)); + canvas.LineTo((float)(408.542337156692), (float)(126)); + canvas.LineTo((float)(387.359547484596), (float)(121)); + canvas.ClosePath(); + canvas.Fill(); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(408.542337156692), (float)(161.5)); + canvas.LineTo((float)(408.542337156692), (float)(122)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(435.623147703567), (float)(130)); + canvas.LineTo((float)(435.623147703567), (float)(157)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(100)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + TextAlign = AlphaSkiaTextAlign.Center; + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("PT Serif", false, true); + FontSize = (float)(12); + canvas.BeginPath(); + canvas.MoveTo((float)(366.1767578125), (float)(102)); + canvas.LineTo((float)(366.1767578125), (float)(97)); + canvas.LineTo((float)(394.7559555656507), (float)(104)); + canvas.Stroke(); + canvas.BeginPath(); + canvas.MoveTo((float)(407.04394995041633), (float)(107)); + canvas.LineTo((float)(435.623147703567), (float)(115)); + canvas.LineTo((float)(435.623147703567), (float)(120)); + canvas.Stroke(); + canvas.FillText("3", Typeface, FontSize, (float)(400.8999527580335), (float)(106), TextAlign, TextBaseline); + TextAlign = AlphaSkiaTextAlign.Left; + TextBaseline = AlphaSkiaTextBaseline.Top; + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); + canvas.FillRect((float)(472), (float)(130), (float)(206), (float)(1.04)); + canvas.FillRect((float)(472), (float)(139), (float)(206), (float)(1.04)); + canvas.FillRect((float)(472), (float)(148), (float)(206), (float)(1.04)); + canvas.FillRect((float)(472), (float)(157), (float)(206), (float)(1.04)); + canvas.FillRect((float)(472), (float)(166), (float)(206), (float)(1.04)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(200), (byte)(0), (byte)(0), (byte)(255)); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("3", Typeface, FontSize, (float)(472), (float)(118.75), TextAlign, TextBaseline); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(496.32666015625), (float)(157), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(496.32666015625), (float)(143.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(496.32666015625), (float)(134.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.BeginRotate((float)(527.8699951171875), (float)(172), (float)(-90)); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.EndRotate(); + canvas.BeginPath(); + canvas.MoveTo((float)(527.8699951171875), (float)(180)); + canvas.LineTo((float)(531.8699951171875), (float)(172)); + canvas.LineTo((float)(523.8699951171875), (float)(172)); + canvas.ClosePath(); + canvas.Fill(); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); + canvas.FillRect((float)(533.8699951171875), (float)(175), (float)(15), (float)(1)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(536.8699951171875), (float)(175), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(536.8699951171875), (float)(166), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(536.8699951171875), (float)(157), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(536.8699951171875), (float)(143.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(536.8699951171875), (float)(134.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); + canvas.FillRect((float)(574.413330078125), (float)(175), (float)(15), (float)(1)); + canvas.FillRect((float)(574.413330078125), (float)(184), (float)(15), (float)(1)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(577.413330078125), (float)(184), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(577.413330078125), (float)(166), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(577.413330078125), (float)(152.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.BeginRotate((float)(596.9566650390625), (float)(137.5), (float)(90)); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(43.199999999999996), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.EndRotate(); + canvas.BeginPath(); + canvas.MoveTo((float)(596.9566650390625), (float)(129.5)); + canvas.LineTo((float)(600.9566650390625), (float)(137.5)); + canvas.LineTo((float)(592.9566650390625), (float)(137.5)); + canvas.ClosePath(); + canvas.Fill(); + canvas.FillText("\uE262", MusicTypeface, (float)(MusicFontSize * 1), (float)(607.9566650390625), (float)(143.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); + canvas.FillRect((float)(614.9566650390625), (float)(175), (float)(15), (float)(1)); + canvas.FillRect((float)(614.9566650390625), (float)(184), (float)(15), (float)(1)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(617.9566650390625), (float)(184), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(617.9566650390625), (float)(166), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(617.9566650390625), (float)(152.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(617.9566650390625), (float)(143.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(617.9566650390625), (float)(134.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillRect((float)(670), (float)(130.1), (float)(1), (float)(36)); + canvas.FillRect((float)(674), (float)(130.1), (float)(4), (float)(36)); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(496.32666015625), (float)(134.5)); + canvas.LineTo((float)(496.32666015625), (float)(184)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(545.8699951171875), (float)(107.5)); + canvas.LineTo((float)(545.8699951171875), (float)(175)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(586.413330078125), (float)(125.5)); + canvas.LineTo((float)(586.413330078125), (float)(184)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.LineWidth = (float)(0.96); + canvas.BeginPath(); + canvas.MoveTo((float)(626.9566650390625), (float)(107.5)); + canvas.LineTo((float)(626.9566650390625), (float)(184)); + canvas.Stroke(); + canvas.LineWidth = (float)(1); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(100)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); + canvas.FillRect((float)(92), (float)(284), (float)(254), (float)(1.04)); + canvas.FillRect((float)(92), (float)(295), (float)(254), (float)(1.04)); + canvas.FillRect((float)(92), (float)(306), (float)(254), (float)(1.04)); + canvas.FillRect((float)(92), (float)(317), (float)(61.02685546875), (float)(1.04)); + canvas.FillRect((float)(161.32666015625), (float)(317), (float)(31.759140014648438), (float)(1.04)); + canvas.FillRect((float)(201.38560485839844), (float)(317), (float)(31.759140014648438), (float)(1.04)); + canvas.FillRect((float)(241.44454956054688), (float)(317), (float)(31.759124755859375), (float)(1.04)); + canvas.FillRect((float)(281.50347900390625), (float)(317), (float)(25.273406982421875), (float)(1.04)); + canvas.FillRect((float)(315.0766906738281), (float)(317), (float)(30.923309326171875), (float)(1.04)); + canvas.FillRect((float)(92), (float)(328), (float)(254), (float)(1.04)); + canvas.FillRect((float)(92), (float)(339), (float)(254), (float)(1.04)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + canvas.FillText("\uE06D", MusicTypeface, (float)(MusicFontSize * 0.9230769230769231), (float)(97), (float)(311.8), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(163.32666015625), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("9", Typeface, FontSize, (float)(153.02685546875), (float)(317.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + canvas.BeginPath(); + canvas.BeginPath(); + canvas.MoveTo((float)(188.23569962449085), (float)(274.7)); + canvas.LineTo((float)(185.23569962449085), (float)(280.7)); + canvas.LineTo((float)(191.23569962449085), (float)(280.7)); + canvas.ClosePath(); + canvas.Fill(); + canvas.Stroke(); + canvas.MoveTo((float)(162.32666015625), (float)(317.3)); + canvas.BezierCurveTo((float)(175.28117989037042), (float)(317.3), (float)(188.23569962449085), (float)(317.3), (float)(188.23569962449085), (float)(280.7)); + canvas.Stroke(); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("\u00BC", Typeface, FontSize, (float)(183.47495743699085), (float)(260.7), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(203.38560196824085), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("9", Typeface, FontSize, (float)(193.08579728074085), (float)(317.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + canvas.BeginPath(); + canvas.BeginPath(); + canvas.MoveTo((float)(228.29464143648167), (float)(268.7)); + canvas.LineTo((float)(225.29464143648167), (float)(274.7)); + canvas.LineTo((float)(231.29464143648167), (float)(274.7)); + canvas.ClosePath(); + canvas.Fill(); + canvas.Stroke(); + canvas.MoveTo((float)(202.38560196824085), (float)(317.3)); + canvas.BezierCurveTo((float)(215.34012170236127), (float)(317.3), (float)(228.29464143648167), (float)(317.3), (float)(228.29464143648167), (float)(274.7)); + canvas.Stroke(); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("\u00BD", Typeface, FontSize, (float)(223.25142854585667), (float)(254.7), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(243.44454378023167), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("9", Typeface, FontSize, (float)(233.14473909273167), (float)(317.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + canvas.BeginPath(); + canvas.BeginPath(); + canvas.MoveTo((float)(268.3535832484725), (float)(256.7)); + canvas.LineTo((float)(265.3535832484725), (float)(262.7)); + canvas.LineTo((float)(271.3535832484725), (float)(262.7)); + canvas.ClosePath(); + canvas.Fill(); + canvas.Stroke(); + canvas.MoveTo((float)(242.44454378023167), (float)(317.3)); + canvas.BezierCurveTo((float)(255.39906351435206), (float)(317.3), (float)(268.3535832484725), (float)(317.3), (float)(268.3535832484725), (float)(262.7)); + canvas.Stroke(); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("full", Typeface, FontSize, (float)(259.3589543422225), (float)(242.7), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(283.5034855922225), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("9", Typeface, FontSize, (float)(273.2036809047225), (float)(317.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + canvas.BeginPath(); + canvas.BeginPath(); + canvas.MoveTo((float)(301.9267916242362), (float)(244.7)); + canvas.LineTo((float)(298.9267916242362), (float)(250.7)); + canvas.LineTo((float)(304.9267916242362), (float)(250.7)); + canvas.ClosePath(); + canvas.Fill(); + canvas.Stroke(); + canvas.MoveTo((float)(282.5034855922225), (float)(317.3)); + canvas.BezierCurveTo((float)(292.2151386082294), (float)(317.3), (float)(301.9267916242362), (float)(317.3), (float)(301.9267916242362), (float)(250.7)); + canvas.Stroke(); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("1\u00BD", Typeface, FontSize, (float)(293.2336763898612), (float)(230.7), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(317.0766939679862), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("9", Typeface, FontSize, (float)(306.7768892804862), (float)(317.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + canvas.BeginPath(); + canvas.BeginPath(); + canvas.MoveTo((float)(340), (float)(232.7)); + canvas.LineTo((float)(337), (float)(238.7)); + canvas.LineTo((float)(343), (float)(238.7)); + canvas.ClosePath(); + canvas.Fill(); + canvas.Stroke(); + canvas.MoveTo((float)(316.0766939679862), (float)(317.3)); + canvas.BezierCurveTo((float)(328.0383469839931), (float)(317.3), (float)(340), (float)(317.3), (float)(340), (float)(238.7)); + canvas.Stroke(); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("2", Typeface, FontSize, (float)(336.35009765625), (float)(218.7), TextAlign, TextBaseline); + canvas.FillRect((float)(345), (float)(284.3), (float)(1), (float)(54.99999999999994)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); + canvas.FillRect((float)(346), (float)(284), (float)(126), (float)(1.04)); + canvas.FillRect((float)(346), (float)(295), (float)(12.02685546875), (float)(1.04)); + canvas.FillRect((float)(366.32666015625), (float)(295), (float)(12.882984161376953), (float)(1.04)); + canvas.FillRect((float)(387.50944900512695), (float)(295), (float)(84.49055099487305), (float)(1.04)); + canvas.FillRect((float)(346), (float)(306), (float)(126), (float)(1.04)); + canvas.FillRect((float)(346), (float)(317), (float)(54.392433166503906), (float)(1.04)); + canvas.FillRect((float)(408.6922378540039), (float)(317), (float)(18.781005859375), (float)(1.04)); + canvas.FillRect((float)(435.7730484008789), (float)(317), (float)(36.226951599121094), (float)(1.04)); + canvas.FillRect((float)(346), (float)(328), (float)(126), (float)(1.04)); + canvas.FillRect((float)(346), (float)(339), (float)(126), (float)(1.04)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(368.32666015625), (float)(295.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("3", Typeface, FontSize, (float)(358.02685546875), (float)(295.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(389.509449828346), (float)(295.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("3", Typeface, FontSize, (float)(379.209645140846), (float)(295.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(410.692239500442), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("3", Typeface, FontSize, (float)(400.392434812942), (float)(317.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(437.773050047317), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("5", Typeface, FontSize, (float)(427.473245359817), (float)(317.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + canvas.FillRect((float)(471), (float)(284.3), (float)(1), (float)(54.99999999999994)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); + canvas.FillRect((float)(472), (float)(284), (float)(25.1767578125), (float)(1.04)); + canvas.FillRect((float)(505.4765625), (float)(284), (float)(32.2435302734375), (float)(1.04)); + canvas.FillRect((float)(546.0198974609375), (float)(284), (float)(72.786865234375), (float)(1.04)); + canvas.FillRect((float)(627.1065673828125), (float)(284), (float)(50.8934326171875), (float)(1.04)); + canvas.FillRect((float)(472), (float)(295), (float)(25.1767578125), (float)(1.04)); + canvas.FillRect((float)(505.4765625), (float)(295), (float)(32.2435302734375), (float)(1.04)); + canvas.FillRect((float)(546.0198974609375), (float)(295), (float)(72.786865234375), (float)(1.04)); + canvas.FillRect((float)(627.1065673828125), (float)(295), (float)(50.8934326171875), (float)(1.04)); + canvas.FillRect((float)(472), (float)(306), (float)(25.1767578125), (float)(1.04)); + canvas.FillRect((float)(505.4765625), (float)(306), (float)(32.2435302734375), (float)(1.04)); + canvas.FillRect((float)(546.0198974609375), (float)(306), (float)(32.2435302734375), (float)(1.04)); + canvas.FillRect((float)(586.563232421875), (float)(306), (float)(32.2435302734375), (float)(1.04)); + canvas.FillRect((float)(627.1065673828125), (float)(306), (float)(50.8934326171875), (float)(1.04)); + canvas.FillRect((float)(472), (float)(317), (float)(65.7200927734375), (float)(1.04)); + canvas.FillRect((float)(546.0198974609375), (float)(317), (float)(32.2435302734375), (float)(1.04)); + canvas.FillRect((float)(586.563232421875), (float)(317), (float)(32.2435302734375), (float)(1.04)); + canvas.FillRect((float)(627.1065673828125), (float)(317), (float)(50.8934326171875), (float)(1.04)); + canvas.FillRect((float)(472), (float)(328), (float)(65.7200927734375), (float)(1.04)); + canvas.FillRect((float)(546.0198974609375), (float)(328), (float)(32.2435302734375), (float)(1.04)); + canvas.FillRect((float)(586.563232421875), (float)(328), (float)(32.2435302734375), (float)(1.04)); + canvas.FillRect((float)(627.1065673828125), (float)(328), (float)(50.8934326171875), (float)(1.04)); + canvas.FillRect((float)(472), (float)(339), (float)(206), (float)(1.04)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + canvas.BeginPath(); + canvas.MoveTo((float)(488), (float)(275.8)); + canvas.LineTo((float)(488), (float)(312.8)); + canvas.Stroke(); + canvas.BeginPath(); + canvas.MoveTo((float)(488), (float)(275.8)); + canvas.LineTo((float)(492), (float)(283.8)); + canvas.LineTo((float)(484), (float)(283.8)); + canvas.ClosePath(); + canvas.Fill(); + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(507.4765625), (float)(284.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("0", Typeface, FontSize, (float)(497.1767578125), (float)(284.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(507.4765625), (float)(295.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("1", Typeface, FontSize, (float)(497.1767578125), (float)(295.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(507.4765625), (float)(306.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("0", Typeface, FontSize, (float)(497.1767578125), (float)(306.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + canvas.BeginRotate((float)(527.7200927734375), (float)(326.8), (float)(-90)); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(43.199999999999996), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.EndRotate(); + canvas.BeginPath(); + canvas.MoveTo((float)(528), (float)(334.8)); + canvas.LineTo((float)(532), (float)(326.8)); + canvas.LineTo((float)(524), (float)(326.8)); + canvas.ClosePath(); + canvas.Fill(); + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(548.0198974609375), (float)(284.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("0", Typeface, FontSize, (float)(537.7200927734375), (float)(284.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(548.0198974609375), (float)(295.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("1", Typeface, FontSize, (float)(537.7200927734375), (float)(295.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(548.0198974609375), (float)(306.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("0", Typeface, FontSize, (float)(537.7200927734375), (float)(306.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(548.0198974609375), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("2", Typeface, FontSize, (float)(537.7200927734375), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(548.0198974609375), (float)(328.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("3", Typeface, FontSize, (float)(537.7200927734375), (float)(328.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + canvas.BeginPath(); + canvas.MoveTo((float)(569), (float)(297.8)); + canvas.LineTo((float)(569), (float)(334.8)); + canvas.Stroke(); + canvas.BeginPath(); + canvas.MoveTo((float)(569), (float)(297.8)); + canvas.LineTo((float)(573), (float)(305.8)); + canvas.LineTo((float)(565), (float)(305.8)); + canvas.ClosePath(); + canvas.Fill(); + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(588.563232421875), (float)(306.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("2", Typeface, FontSize, (float)(578.263427734375), (float)(306.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(588.563232421875), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("2", Typeface, FontSize, (float)(578.263427734375), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(588.563232421875), (float)(328.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("0", Typeface, FontSize, (float)(578.263427734375), (float)(328.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + canvas.BeginRotate((float)(596.8067626953125), (float)(283.8), (float)(90)); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(43.199999999999996), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.EndRotate(); + canvas.BeginPath(); + canvas.MoveTo((float)(597), (float)(275.8)); + canvas.LineTo((float)(601), (float)(283.8)); + canvas.LineTo((float)(593), (float)(283.8)); + canvas.ClosePath(); + canvas.Fill(); + TextBaseline = AlphaSkiaTextBaseline.Middle; + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(629.1065673828125), (float)(284.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("0", Typeface, FontSize, (float)(618.8067626953125), (float)(284.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(629.1065673828125), (float)(295.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("2", Typeface, FontSize, (float)(618.8067626953125), (float)(295.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(629.1065673828125), (float)(306.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("2", Typeface, FontSize, (float)(618.8067626953125), (float)(306.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(629.1065673828125), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("2", Typeface, FontSize, (float)(618.8067626953125), (float)(317.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(11); + canvas.FillText("", Typeface, FontSize, (float)(629.1065673828125), (float)(328.3), TextAlign, TextBaseline); + Typeface = GetTypeface("Roboto", false, false); + FontSize = (float)(13); + canvas.FillText("0", Typeface, FontSize, (float)(618.8067626953125), (float)(328.3), TextAlign, TextBaseline); + TextBaseline = AlphaSkiaTextBaseline.Top; + canvas.FillRect((float)(670), (float)(284.3), (float)(1), (float)(54.99999999999994)); + canvas.FillRect((float)(674), (float)(284.3), (float)(4), (float)(54.99999999999994)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(153), (byte)(51), (byte)(204), (byte)(100)); + canvas.BeginPath(); + canvas.MoveTo((float)(92), (float)(130.1)); + canvas.LineTo((float)(92), (float)(339.3)); + canvas.Stroke(); + Typeface = GetTypeface("PT Serif", false, true); + FontSize = (float)(12); + canvas.FillText("s.guit.", Typeface, FontSize, (float)(50), (float)(130.1), TextAlign, TextBaseline); + canvas.FillRect((float)(86), (float)(118.1), (float)(3), (float)(233.20000000000002)); + canvas.BeginPath(); + canvas.MoveTo((float)(86), (float)(118.1)); + canvas.BezierCurveTo((float)(86), (float)(118.1), (float)(86), (float)(118.1), (float)(98), (float)(115.1)); + canvas.BezierCurveTo((float)(92), (float)(121.1), (float)(86), (float)(121.1), (float)(86), (float)(121.1)); + canvas.ClosePath(); + canvas.Fill(); + canvas.BeginPath(); + canvas.MoveTo((float)(86), (float)(351.3)); + canvas.BezierCurveTo((float)(86), (float)(351.3), (float)(92), (float)(351.3), (float)(98), (float)(354.3)); + canvas.BezierCurveTo((float)(92), (float)(348.3), (float)(86), (float)(348.3), (float)(86), (float)(348.3)); + canvas.ClosePath(); + canvas.Fill(); + return canvas.EndRender(); + } + private static AlphaSkiaImage? DrawMusicSheetPart5(AlphaSkiaCanvas canvas) + { + canvas.BeginRender(116, 24, (float)(RenderScale)); + canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); + Typeface = GetTypeface("Roboto", true, false); + FontSize = (float)(12); + TextAlign = AlphaSkiaTextAlign.Left; + canvas.FillText("rendered by alphaTab", Typeface, FontSize, (float)(0), (float)(12), TextAlign, TextBaseline); + return canvas.EndRender(); + } + public static float[,] PartPositions => new float[,] { + {(float)0, (float)0, (float)1300, (float)164}, {(float)0, (float)164, (float)1300, (float)38}, {(float)0, (float)202, (float)1300, (float)6}, {(float)0, (float)208, (float)1300, (float)373}, {(float)591.962890625, (float)581, (float)116.07421875, (float)24} + }; + public static int TotalWidth => (int)1300; + public static int TotalHeight => (int)605; + public static Func[] AllParts => new[] + { + DrawMusicSheetPart1, DrawMusicSheetPart2, DrawMusicSheetPart3, DrawMusicSheetPart4, DrawMusicSheetPart5 + }; +} diff --git a/test/dotnet/AlphaSkia.Test/PixelMatch.cs b/test/dotnet/AlphaSkia.Test/PixelMatch.cs new file mode 100644 index 0000000..a7bfffe --- /dev/null +++ b/test/dotnet/AlphaSkia.Test/PixelMatch.cs @@ -0,0 +1,302 @@ +/* + * Based on https://github.com/mapbox/pixelmatch + * ISC License + * Copyright (c) 2019, Mapbox + * + * Permission to use, copy, modify, and/or distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright notice + * and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ +namespace AlphaSkia.Test; + +record struct PixelMatchResult(int TotalPixels, int DifferentPixels, int TransparentPixels); + +record struct PixelMatchColor(byte R, byte G, byte B); + +record struct PixelMatchOptions(double Threshold, PixelMatchColor DiffColor); + +static class PixelMatch +{ + // blend semi-transparent color with white + private static byte Blend(byte c, byte a) + { + return (byte)(255 + (c - 255) * a); + } + + private static double Rgb2Y(byte r, byte g, byte b) + { + return r * 0.29889531 + g * 0.58662247 + b * 0.11448223; + } + + private static double Rgb2I(byte r, byte g, byte b) + { + return r * 0.59597799 - g * 0.2741761 - b * 0.32180189; + } + + private static double Rgb2Q(byte r, byte g, byte b) + { + return r * 0.21147017 - g * 0.52261711 + b * 0.31114694; + } + + private static void DrawPixel(byte[] output, int pos, PixelMatchColor color) + { + output[pos + 0] = color.B; + output[pos + 1] = color.G; + output[pos + 2] = color.R; + output[pos + 3] = 255; + } + + private static double ColorDelta(byte[] img1, byte[] img2, int k, int m, bool yOnly) + { + var r1 = img1[k + 0]; + var g1 = img1[k + 1]; + var b1 = img1[k + 2]; + var a1 = img1[k + 3]; + + var r2 = img2[m + 0]; + var g2 = img2[m + 1]; + var b2 = img2[m + 2]; + var a2 = img2[m + 3]; + + if (a1 == a2 && r1 == r2 && g1 == g2 && b1 == b2) + return 0; + + if (a1 < 255) + { + a1 /= 255; + r1 = Blend(r1, a1); + g1 = Blend(g1, a1); + b1 = Blend(b1, a1); + } + + if (a2 < 255) + { + a2 /= 255; + r2 = Blend(r2, a2); + g2 = Blend(g2, a2); + b2 = Blend(b2, a2); + } + + var y = Rgb2Y(r1, g1, b1) - Rgb2Y(r2, g2, b2); + + if (yOnly) + return y; // brightness difference only + + var i = Rgb2I(r1, g1, b1) - Rgb2I(r2, g2, b2); + var q = Rgb2Q(r1, g1, b1) - Rgb2Q(r2, g2, b2); + + return 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q; + } + + // check if a pixel has 3+ adjacent pixels of the same color. + private static bool HasManySiblings(byte[] img, int x1, int y1, int width, int height) + { + var distance = 1; + var x0 = Math.Max(x1 - distance, 0); + var y0 = Math.Max(y1 - distance, 0); + var x2 = Math.Min(x1 + distance, width - 1); + var y2 = Math.Min(y1 + distance, height - 1); + var pos = (y1 * width + x1) * 4; + var zeroes = x1 == x0 || x1 == x2 || y1 == y0 || y1 == y2 ? 1 : 0; + + // go through 8 adjacent pixels + for (var x = x0; x <= x2; x++) + { + for (var y = y0; y <= y2; y++) + { + if (x == x1 && y == y1) + continue; + + var pos2 = (y * width + x) * 4; + if ( + img[pos] == img[pos2] && + img[pos + 1] == img[pos2 + 1] && + img[pos + 2] == img[pos2 + 2] && + img[pos + 3] == img[pos2 + 3]) + { + zeroes++; + } + + if (zeroes > 2) + return true; + } + } + + return false; + } + + // check if a pixel is likely a part of anti-aliasing; + // based on "Anti-aliased Pixel and Intensity Slope Detector" paper by V. Vysniauskas, 2009 + + private static bool Antialiased(byte[] img, int x1, int y1, int width, int height, byte[] img2) + { + var distance = 1; + var x0 = Math.Max(x1 - distance, 0); + var y0 = Math.Max(y1 - distance, 0); + var x2 = Math.Min(x1 + distance, width - 1); + var y2 = Math.Min(y1 + distance, height - 1); + var pos = (y1 * width + x1) * 4; + var zeroes = x1 == x0 || x1 == x2 || y1 == y0 || y1 == y2 ? 1 : 0; + var min = 0.0; + var max = 0.0; + var minX = 0; + var minY = 0; + var maxX = 0; + var maxY = 0; + + // go through 8 adjacent pixels + for (var x = x0; x <= x2; x++) + { + for (var y = y0; y <= y2; y++) + { + if (x == x1 && y == y1) + { + continue; + } + + // brightness delta between the center pixel and adjacent one + var delta = ColorDelta(img, img, pos, (y * width + x) * 4, true); + + // count the number of equal, darker and brighter adjacent pixels + if (delta == 0) + { + zeroes++; + // if found more than 2 equal siblings, it's definitely not anti-aliasing + if (zeroes > 2) + return false; + + // remember the darkest pixel + } + else if (delta < min) + { + min = delta; + minX = x; + minY = y; + + // remember the brightest pixel + } + else if (delta > max) + { + max = delta; + maxX = x; + maxY = y; + } + } + } + + // if there are no both darker and brighter pixels among siblings, it's not anti-aliasing + if (min == 0 || max == 0) + { + return false; + } + + // if either the darkest or the brightest pixel has 3+ equal siblings in both images + // (definitely not anti-aliased), this pixel is anti-aliased + return ( + (HasManySiblings(img, minX, minY, width, height) && + HasManySiblings(img2, minX, minY, width, height)) || + (HasManySiblings(img, maxX, maxY, width, height) && + HasManySiblings(img2, maxX, maxY, width, height))); + } + + public static PixelMatchResult Match(byte[] img1, byte[] img2, byte[] diffPixels, int width, int height, + PixelMatchOptions options) + { + if (img1.Length != img2.Length || diffPixels.Length != img1.Length) + { + throw new InvalidOperationException("Image sizes do not match"); + } + + if (img1.Length != width * height * sizeof(int)) + { + throw new InvalidOperationException("Image data size does not match width/height."); + } + + // check if images are identical + var len = width * height; + var identical = true; + var transparentPixels = 0; + + for (var i = 0; i < len; i++) + { + var img1R = img1[(i * 4) + 0]; + var img1G = img1[(i * 4) + 1]; + var img1B = img1[(i * 4) + 2]; + var img1A = img1[(i * 4) + 3]; + + var img2R = img2[(i * 4) + 0]; + var img2G = img2[(i * 4) + 1]; + var img2B = img2[(i * 4) + 2]; + var img2A = img2[(i * 4) + 3]; + + if (img1R != img2R || img1G != img2G || img1B != img2B || img1A != img2A) + { + identical = false; + break; + } + + if (img1A == 0) + { + transparentPixels++; + } + } + + if (identical) + { + return new PixelMatchResult(len, 0, transparentPixels); + } + + transparentPixels = 0; + + // maximum acceptable square distance between two colors; + // 35215 is the maximum possible value for the YIQ difference metric + var maxDelta = 35215 * options.Threshold * options.Threshold; + + var diff = 0; + + // compare each pixel of one image against the other one + for (var y = 0; y < height; y++) + { + for (var x = 0; x < width; x++) + { + var pos = (y * width + x) * sizeof(int); + + if (img1[pos + 3] == 0) + { + transparentPixels++; + } + + // squared YUV distance between colors at this pixel position + var delta = ColorDelta(img1, img2, pos, pos, false); + + // the color difference is above the threshold + if (delta > maxDelta) + { + // check it's a real rendering difference or just anti-aliasing + if ((Antialiased(img1, x, y, width, height, img2) || + Antialiased(img2, x, y, width, height, img1))) + { + // match + } + else + { + // found substantial difference not caused by anti-aliasing; draw it as red + DrawPixel(diffPixels, pos, options.DiffColor); + diff++; + } + } + } + } + + // return the number of different pixels + return new PixelMatchResult(len, diff, transparentPixels); + } +} \ No newline at end of file diff --git a/test/dotnet/AlphaSkia.Test/Program.cs b/test/dotnet/AlphaSkia.Test/Program.cs new file mode 100644 index 0000000..d339eaf --- /dev/null +++ b/test/dotnet/AlphaSkia.Test/Program.cs @@ -0,0 +1,194 @@ +using System.Runtime.InteropServices; + +namespace AlphaSkia.Test; + +public class Program +{ + private static string FindRepositoryRoot(string current) + { + if (Directory.Exists(Path.Combine(current, ".nuke"))) + { + return current; + } + + var parent = Path.GetDirectoryName(current); + if (parent == null) + { + throw new InvalidOperationException("Could not find repository root"); + } + + return FindRepositoryRoot(parent); + } + + private static AlphaSkiaImage RenderFullImage() + { + using var fullImageCanvas = new AlphaSkiaCanvas(); + Console.WriteLine( + $"Begin render with size {AlphaTabGeneratedRenderTest.TotalWidth}x{AlphaTabGeneratedRenderTest.TotalHeight} at scale {AlphaTabGeneratedRenderTest.RenderScale}"); + fullImageCanvas.BeginRender(AlphaTabGeneratedRenderTest.TotalWidth, AlphaTabGeneratedRenderTest.TotalHeight, + AlphaTabGeneratedRenderTest.RenderScale); + + using var partialCanvas = new AlphaSkiaCanvas(); + for (var i = 0; i < AlphaTabGeneratedRenderTest.AllParts.Length; i++) + { + Console.WriteLine($"Render part {i}"); + using var part = AlphaTabGeneratedRenderTest.AllParts[i](partialCanvas); + + var x = AlphaTabGeneratedRenderTest.PartPositions[i, 0]; + var y = AlphaTabGeneratedRenderTest.PartPositions[i, 1]; + var w = AlphaTabGeneratedRenderTest.PartPositions[i, 2]; + var h = AlphaTabGeneratedRenderTest.PartPositions[i, 3]; + + Console.WriteLine($"Drawing part {i} into full image at {x}/{y}/{w}/{h}"); + fullImageCanvas.DrawImage(part, x, y, w, h); + } + + var fullImage = fullImageCanvas.EndRender(); + Console.WriteLine("Render of full image completed"); + return fullImage!; + } + + public static int Main() + { + const double tolerancePercent = 1; + try + { + var repositoryRoot = FindRepositoryRoot(Path.GetFullPath(".")); + + // Load all fonts for rendering + Console.WriteLine("Loading fonts"); + var testDataPath = Path.Combine(repositoryRoot, "test", "test-data"); + AlphaTabGeneratedRenderTest.MusicTypeface = AlphaTabGeneratedRenderTest.LoadTypeface("Bravura", false, false, + Path.Combine(testDataPath, "font", "bravura", "Bravura.ttf")); + AlphaTabGeneratedRenderTest.LoadTypeface("Roboto", false, false, + Path.Combine(testDataPath, "font", "roboto", "Roboto-Regular.ttf")); + AlphaTabGeneratedRenderTest.LoadTypeface("Roboto", true, false, + Path.Combine(testDataPath, "font", "roboto", "Roboto-Bold.ttf")); + AlphaTabGeneratedRenderTest.LoadTypeface("Roboto", false, true, + Path.Combine(testDataPath, "font", "roboto", "Roboto-Italic.ttf")); + AlphaTabGeneratedRenderTest.LoadTypeface("Roboto", true, true, + Path.Combine(testDataPath, "font", "roboto", "Roboto-BoldItalic.ttf")); + AlphaTabGeneratedRenderTest.LoadTypeface("PT Serif", false, false, + Path.Combine(testDataPath, "font", "ptserif", "PTSerif-Regular.ttf")); + AlphaTabGeneratedRenderTest.LoadTypeface("PT Serif", true, false, + Path.Combine(testDataPath, "font", "ptserif", "PTSerif-Bold.ttf")); + AlphaTabGeneratedRenderTest.LoadTypeface("PT Serif", false, true, + Path.Combine(testDataPath, "font", "ptserif", "PTSerif-Italic.ttf")); + AlphaTabGeneratedRenderTest.LoadTypeface("PT Serif", true, true, + Path.Combine(testDataPath, "font", "ptserif", "PTSerif-BoldItalic.ttf")); + Console.WriteLine("Fonts loaded"); + + // render full image + Console.WriteLine("Rendering image"); + using var actualImage = RenderFullImage(); + Console.WriteLine("Image rendered"); + + // save png for future reference + Console.WriteLine("Saving image as PNG"); + + var testOutputPath = Path.Combine(repositoryRoot, "test", "test-outputs", "dotnet"); + Directory.CreateDirectory(testOutputPath); + + var testOutputFile = Path.Combine(testOutputPath, AlphaSkiaRid + ".png"); + var pngData = actualImage.ToPng(); + if (pngData == null) + { + throw new InvalidOperationException("Failed to encode final image to png"); + } + + File.WriteAllBytes(testOutputFile, pngData); + Console.WriteLine($"Image saved to {testOutputFile}"); + + // load reference image + var testReferencePath = Path.Combine(testDataPath, "reference", AlphaSkiaRid + ".png"); + Console.WriteLine($"Loading reference image {testReferencePath}"); + + var testReferenceData = File.ReadAllBytes(testReferencePath); + using var expectedImage = AlphaSkiaImage.Decode(testReferenceData); + if (expectedImage == null) + { + throw new InvalidOperationException("Failed to decode reference image"); + } + + Console.WriteLine("Reference image loaded"); + + // compare images + Console.WriteLine("Comparing images"); + var actualWidth = actualImage.Width; + var actualHeight = actualImage.Height; + + var expectedWidth = expectedImage.Width; + var expectedHeight = expectedImage.Height; + if (actualWidth != expectedWidth || actualHeight != expectedHeight) + { + Console.Error.WriteLine( + $"Image sizes do not match: Actual[{actualWidth}x{actualHeight}] Expected[{expectedWidth}x{actualHeight}]"); + return 1; + } + + var actualPixels = actualImage.ReadPixels()!; + var expectedPixels = expectedImage.ReadPixels()!; + var diffPixels = new byte[actualPixels.Length]; + + var compareResult = PixelMatch.Match(actualPixels, + expectedPixels, + diffPixels, + actualWidth, + actualHeight, new PixelMatchOptions + { + Threshold = 0.3, + DiffColor = new PixelMatchColor(255, 0, 0) + }); + + var totalPixels = compareResult.TotalPixels - compareResult.TransparentPixels; + var percentDifference = ((double)compareResult.DifferentPixels / totalPixels) * 100; + var pass = percentDifference < tolerancePercent; + if (!pass) + { + Console.Error.WriteLine( + $"Difference between original and new image is too big: {compareResult.DifferentPixels}/{totalPixels}({percentDifference}%)"); + + // create diff image as PNG + using var diffImage = AlphaSkiaImage.FromPixels(actualWidth, actualHeight, diffPixels)!; + var diffImagePngData = diffImage.ToPng()!; + + var diffOutputPath = Path.Combine(testOutputPath, AlphaSkiaRid + ".diff.png"); + File.WriteAllBytes(diffOutputPath, diffImagePngData); + Console.WriteLine($"Error diff image saved to {diffOutputPath}"); + return 1; + } + + Console.WriteLine( + $"Images match. Total Pixels:{compareResult.TotalPixels}, Transparent Pixels: {compareResult.TransparentPixels}, Percent difference: {percentDifference}%"); + return 0; + } + catch (Exception e) + { + Console.Error.WriteLine($"Failed to run test: {e}"); + return 1; + } + } + + private static readonly string OperatingSystemRid = OperatingSystem.IsWindows() + ? "win" + : OperatingSystem.IsLinux() + ? "linux" + : OperatingSystem.IsMacOS() + ? "maxos" + : OperatingSystem.IsAndroid() + ? "android" + : OperatingSystem.IsIOS() + ? "ios" + : throw new PlatformNotSupportedException("Unsupported test platform"); + + private static readonly string ArchitectureRid = RuntimeInformation.ProcessArchitecture switch + { + Architecture.X64 => "x64", + Architecture.X86 => "x86", + Architecture.Arm => "arm", + Architecture.Arm64 => "arm64", + _ => throw new PlatformNotSupportedException("Unsupported test architecture") + }; + + private static readonly string AlphaSkiaRid = OperatingSystemRid + "-" + ArchitectureRid; +} \ No newline at end of file diff --git a/test/dotnet/AlphaSkia.Test_/AlphaSkia.Test.csproj b/test/dotnet/AlphaSkia.Test_/AlphaSkia.Test.csproj new file mode 100644 index 0000000..6ed9dbe --- /dev/null +++ b/test/dotnet/AlphaSkia.Test_/AlphaSkia.Test.csproj @@ -0,0 +1,62 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/dotnet/AlphaSkia.Test/AlphaTabGeneratedTest.cs b/test/dotnet/AlphaSkia.Test_/AlphaTabGeneratedTest.cs similarity index 100% rename from test/dotnet/AlphaSkia.Test/AlphaTabGeneratedTest.cs rename to test/dotnet/AlphaSkia.Test_/AlphaTabGeneratedTest.cs diff --git a/test/dotnet/AlphaSkia.Test/MusicSheetRenderTest.cs b/test/dotnet/AlphaSkia.Test_/MusicSheetRenderTest.cs similarity index 100% rename from test/dotnet/AlphaSkia.Test/MusicSheetRenderTest.cs rename to test/dotnet/AlphaSkia.Test_/MusicSheetRenderTest.cs diff --git a/test/dotnet/AlphaSkia.Test/Usings.cs b/test/dotnet/AlphaSkia.Test_/Usings.cs similarity index 100% rename from test/dotnet/AlphaSkia.Test/Usings.cs rename to test/dotnet/AlphaSkia.Test_/Usings.cs diff --git a/test/dotnet/NuGet.config b/test/dotnet/NuGet.config new file mode 100644 index 0000000..40d35cf --- /dev/null +++ b/test/dotnet/NuGet.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/test/dotnet/global.json b/test/dotnet/global.json new file mode 100644 index 0000000..aaac9e0 --- /dev/null +++ b/test/dotnet/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "7.0.0", + "rollForward": "latestMinor", + "allowPrerelease": false + } +} \ No newline at end of file diff --git a/test/java/.idea/.gitignore b/test/java/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/test/java/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/test/java/.idea/.name b/test/java/.idea/.name new file mode 100644 index 0000000..19e8e3f --- /dev/null +++ b/test/java/.idea/.name @@ -0,0 +1 @@ +alphaskia.test \ No newline at end of file diff --git a/test/java/.idea/gradle.xml b/test/java/.idea/gradle.xml new file mode 100644 index 0000000..2e197de --- /dev/null +++ b/test/java/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/test/java/.idea/inspectionProfiles/Project_Default.xml b/test/java/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..40a74c5 --- /dev/null +++ b/test/java/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/test/java/.idea/misc.xml b/test/java/.idea/misc.xml new file mode 100644 index 0000000..262040e --- /dev/null +++ b/test/java/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/test/java/.idea/uiDesigner.xml b/test/java/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/test/java/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/java/.idea/vcs.xml b/test/java/.idea/vcs.xml new file mode 100644 index 0000000..def3818 --- /dev/null +++ b/test/java/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/test/java/build.gradle.kts b/test/java/build.gradle.kts index da9e4a0..648ceb3 100644 --- a/test/java/build.gradle.kts +++ b/test/java/build.gradle.kts @@ -1,165 +1,52 @@ -import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import java.io.FileInputStream -import java.util.* - -buildscript { - repositories { - gradlePluginPortal() - google() - mavenCentral() - } +plugins { + id("java") + id("application") + id("idea") } +group = "net.alphatab.alphaskia" +version = "1.0.0-LOCAL" +// Use any latest version +var alphaSkiaVersion = version -var sonatypeSigningKeyId = "" -var sonatypeSigningPassword = "" -var sonatypeSigningKey = "" -var ossrhUsername = "" -var ossrhPassword = "" -var sonatypeStagingProfileId = "" - -var libDescription = "" -var libAuthorId = "" -var libAuthorName = "" -var libOrgUrl = "" -var libCompany = "" -var libVersion = "" -var libProjectUrl = "" -var libGitUrlHttp = "" -var libGitUrlGit = "" -var libLicenseSpdx = "" -var libLicenseUrl = "" -var libIssuesUrl = "" - -val props = Properties() -val propsFile = project.rootProject.file("local.properties") -if (propsFile.exists()) { - FileInputStream(propsFile).use { - props.load(it) - } +// Override with value from CI +val versionEnv = providers.environmentVariable("ALPHASKIA_VERSION") +if (versionEnv.isPresent) { + version = versionEnv.get() + alphaSkiaVersion = versionEnv.get() } -fun loadSetting(envKey: String, propKey: String, setter: (value: String) -> Unit) { - if (props.containsKey(propKey)) { - setter(props.getProperty(propKey)) - } else { - val env = providers - .environmentVariable(envKey) - if (env.isPresent) { - setter(env.get()) - } +repositories { + mavenCentral() + maven { + url = projectDir.resolve("../../dist/Maven/").toURI() + } } -loadSetting("OSSRH_USERNAME", "ossrhUsername") { ossrhUsername = it } -loadSetting("OSSRH_PASSWORD", "ossrhPassword") { ossrhPassword = it } -loadSetting("SONATYPE_STAGING_PROFILE_ID", "sonatypeStagingProfileId") { sonatypeStagingProfileId = it } -loadSetting("SONATYPE_SIGNING_KEY_ID", "sonatypeSigningKeyId") { sonatypeSigningKeyId = it } -loadSetting("SONATYPE_SIGNING_PASSWORD", "sonatypeSigningPassword") { sonatypeSigningPassword = it } -loadSetting("SONATYPE_SIGNING_KEY", "sonatypeSigningKey") { sonatypeSigningKey = it } -loadSetting("ALPHASKIA_DESCRIPTION", "alphaskiaDescription") { libDescription = it } -loadSetting("ALPHASKIA_AUTHOR_ID", "alphaskiaAuthorId") { libAuthorId = it } -loadSetting("ALPHASKIA_AUTHOR_NAME", "alphaskiaAuthorName") { libAuthorName = it } -loadSetting("ALPHASKIA_ORG_URL", "alphaskiaOrgUrl") { libOrgUrl = it } -loadSetting("ALPHASKIA_COMPANY", "alphaskiaCompany") { libCompany = it } -loadSetting("ALPHASKIA_VERSION", "alphaskiaVersion") { libVersion = it } -loadSetting("ALPHASKIA_PROJECT_URL", "alphaskiaProjectUrl") { libProjectUrl = it } -loadSetting("ALPHASKIA_GIT_URL_HTTP", "alphaskiaGitUrlHttp") { libGitUrlHttp = it } -loadSetting("ALPHASKIA_GIT_URL_GIT", "alphaskiaGitUrlGit") { libGitUrlGit = it } -loadSetting("ALPHASKIA_LICENSE_SPDX", "alphaskiaLicenseSpdx") { libLicenseSpdx = it } -loadSetting("ALPHASKIA_LICENSE_URL", "alphaskiaLicenseUrl") { libLicenseUrl = it } -loadSetting("ALPHASKIA_ISSUES_URL", "alphaskiaIssuesUrl") { libIssuesUrl = it } - - - -subprojects { - apply() - apply() - apply(plugin = "maven-publish") - - group = "net.alphatab" - version = libVersion +dependencies { + testImplementation(platform("org.junit:junit-bom:5.9.1")) + testImplementation("org.junit.jupiter:junit-jupiter") - repositories { - google() - mavenCentral() - } - - configure { - toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) - } - withSourcesJar() - withJavadocJar() - } + implementation("net.alphatab:net.alphatab.alphaskia:$alphaSkiaVersion") + implementation("net.alphatab:net.alphatab.alphaskia.macos:$alphaSkiaVersion") + implementation("net.alphatab:net.alphatab.alphaskia.windows:$alphaSkiaVersion") + implementation("net.alphatab:net.alphatab.alphaskia.linux:$alphaSkiaVersion") + implementation("net.alphatab:net.alphatab.alphaskia.android:$alphaSkiaVersion") +} - tasks.withType().configureEach { - systemProperty("alphaskia.library.path", rootProject.projectDir.resolve("../../dist/")) - systemProperty("testdata.path", rootProject.projectDir.resolve("../test/")) - systemProperty("testoutput.path", projectDir.resolve("../test-outputs/")) - testLogging { - exceptionFormat = TestExceptionFormat.FULL - showStackTraces = true - } +val generatedSourcesPath = projectDir.resolve("src/main/generated") +java.sourceSets["main"].java.srcDir(generatedSourcesPath) +idea { + module { + generatedSourceDirs.add(generatedSourcesPath) } +} - configure { - repositories { -// maven { -// name = "sonatype" -// url = uri( -// if (libVersion.endsWith("SNAPSHOT")) -// "https://s01.oss.sonatype.org/content/repositories/snapshots/" -// else -// "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" -// ) -// credentials { -// username = ossrhUsername -// password = ossrhPassword -// } -// } - - maven { - name = "DistPath" - url = rootProject.projectDir.resolve("dist").toURI() - } - } - - publications { - create("mavenJava") { - from(components["java"]) - afterEvaluate { - artifactId = tasks.withType().first().archiveBaseName.get() - - pom { - description = libDescription - url = libProjectUrl - licenses { - license { - name = libLicenseSpdx - url = libLicenseUrl - } - } - developers { - developer { - id = libAuthorId - name = libAuthorName - organization = libCompany - organizationUrl = libOrgUrl - } - } - scm { - url = libGitUrlHttp - connection = "scm:git:$libGitUrlGit" - developerConnection = "scm:git:$libGitUrlGit" - } - issueManagement { - system = "GitHub" - url = libIssuesUrl - } - } - } - } - } - } +tasks.test { + useJUnitPlatform() } + +application { + mainClass = "net.alphatab.alphaskia.test.Main" +} \ No newline at end of file diff --git a/test/java/gradle/wrapper/gradle-wrapper.properties b/test/java/gradle/wrapper/gradle-wrapper.properties index d945a79..33a06b2 100644 --- a/test/java/gradle/wrapper/gradle-wrapper.properties +++ b/test/java/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Fri Sep 29 10:43:19 CEST 2023 +#Sun Oct 15 12:27:03 CEST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip diff --git a/test/java/local.properties b/test/java/local.properties deleted file mode 100644 index 592b3f2..0000000 --- a/test/java/local.properties +++ /dev/null @@ -1,20 +0,0 @@ -## This file must *NOT* be checked into Version Control Systems, -# as it contains information specific to your local configuration. -# -# Location of the SDK. This is only used by Gradle. -# For customization when using a Version Control System, please read the -# header note. -#Sat Oct 14 15:29:14 CEST 2023 -alphaskiaAuthorId=danielku15 -alphaskiaAuthorName=Daniel Kuschny -alphaskiaCompany=CoderLine -alphaskiaDescription=A Skia based rendering backend for alphaTab. -alphaskiaGitUrlGit"=git\://github.com/CoderLine/alphaSkia.git -alphaskiaGitUrlHttp=https\://github.com/CoderLine/alphaSkia.git -alphaskiaIssuesUrl=https\://github.com/CoderLine/alphaSkia/issues -alphaskiaLicenseSpdx=BSD-3-Clause -alphaskiaLicenseUrl=https\://opensource.org/license/bsd-3-clause -alphaskiaOrgUrl=https\://github.com/CoderLine -alphaskiaProjectUrl=https\://github.com/CoderLine/alphaSkia -alphaskiaVersion=1.0.0-LOCAL -sdk.dir=C\:\\Users\\Daniel\\AppData\\Local\\Android\\Sdk diff --git a/test/java/main/build.gradle.kts b/test/java/main/build.gradle.kts deleted file mode 100644 index e8903d4..0000000 --- a/test/java/main/build.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -plugins { - id("java-library") - `maven-publish` - signing -} - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } - withSourcesJar() - withJavadocJar() -} - -tasks.withType().configureEach { - options.headerOutputDirectory = rootProject.projectDir.resolve("jni/include") -} - -dependencies { - testImplementation(platform("org.junit:junit-bom:5.9.1")) - testImplementation("org.junit.jupiter:junit-jupiter") -} - -tasks.test { - useJUnitPlatform() -} - -tasks.jar { - archiveBaseName = "net.alphatab.alphaskia" -} \ No newline at end of file diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaCanvas.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaCanvas.java deleted file mode 100644 index 8991876..0000000 --- a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaCanvas.java +++ /dev/null @@ -1,247 +0,0 @@ -package net.alphatab.alphaskia; - -/** - * A Skia based canvas for rendering images. - */ -public class AlphaSkiaCanvas extends AlphaSkiaNative { - private static AlphaSkiaColorType colorType; - - /** - * Gets the {@link AlphaSkiaColorType} used when generating images. - * AlphaSkia tries to use the native color tpe of the system but this can vary based on OS. - * @return The {@link AlphaSkiaColorType} used when generating images - */ - public static AlphaSkiaColorType getColorType() { - if (colorType == null) { - colorType = AlphaSkiaColorType.fromValue(alphaskiaColorType()); - } - return colorType; - } - - /** - * Gets the color to use for drawing operations in the native canvas. - * See also {@link #rgbaToColor} - * @return The color to use for drawing operations in the native canvas - */ - public native int getColor(); - - /** - * Sets the color to use for drawing operations in the native canvas. - * See also {@link #rgbaToColor} - * @param color The color to use for drawing operations in the native canvas. - */ - public native void setColor(int color); - - /** - * Encodes the given color components into the right format. - * - * @param r The red component of the color. - * @param g The green component of the color. - * @param b The blue component of the color. - * @param a The alpha channel of the color. - * @return The encoded color. - */ - public static int rgbaToColor(byte r, byte g, byte b, byte a) { - return (((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | - (b & 0xFF)); - } - - /** - * Gets the line width to use when drawing strokes and lines. - * @return The line width to use when drawing strokes and lines. - */ - public native float getLineWidth(); - - /** - * Sets the line width to use when drawing strokes and lines. - * @param lineWidth The line width to use when drawing strokes and lines - */ - public native void setLineWidth(float lineWidth); - - /** - * Initializes a new empty canvas to use for drawing. - */ - public AlphaSkiaCanvas() { - super(alphaskiaCanvasAllocate()); - } - - @Override - public native void close(); - - /** - * Starts a new rendering session in the canvas. - * - * @param width The width of the image to produce. - * @param height The height of the image to produce. - */ - public void beginRender(int width, int height) - { - beginRender(width, height, 1); - } - - /** - * Starts a new rendering session in the canvas. - * - * @param width The width of the image to produce. - * @param height The height of the image to produce. - * @param scaleFactor The scale factor for the image (e.g. for high DPI rendering with keeping coordinates). - */ - public native void beginRender(int width, int height, float scaleFactor); - - /** - * Draws the given image into the canvas. - * - * @param image The image to draw. - * @param x The X-coordinate at which to draw the image. - * @param y The Y-coordinate at which to draw the image. - * @param w The target width to which the image should be scaled. - * @param h The target height to which the image should be scaled. - */ - public native void drawImage(AlphaSkiaImage image, float x, float y, float w, float h); - - /** - * Ends the rendering session and provides the rendered result. - * - * @return The rendered result or {@code null} if something went wrong. - */ - public native AlphaSkiaImage endRender(); - - /** - * Fills the given rectangle with the current color. - * - * @param x The X-coordinate of the rectangle - * @param y The Y-coordinate of the rectangle - * @param width The width of the rectangle - * @param height The height of the rectangle - */ - public native void fillRect(float x, float y, float width, float height); - - /** - * Strokes the given rectangle with the current color. - * - * @param x The X-coordinate of the rectangle - * @param y The Y-coordinate of the rectangle - * @param width The width of the rectangle - * @param height The height of the rectangle - */ - public native void strokeRect(float x, float y, float width, float height); - - /** - * Begins a new dynamic path for rendering. - */ - public native void beginPath(); - - /** - * Closes the started path by connecting the last point and the first point. - */ - public native void closePath(); - - /** - * Moves the current position within the current path to the given position. - * - * @param x The X-position where to continue the path. - * @param y The Y-position where to continue the path. - */ - public native void moveTo(float x, float y); - - /** - * Draws a line from the current path position to the given position. - * - * @param x The X-position to which to draw a line. - * @param y The Y-position to which to draw a line. - */ - public native void lineTo(float x, float y); - - /** - * Draws a quadratic curve from the current path position to the given position. - * - * @param cpx The X-position of the control point. - * @param cpy The Y-position of the control point. - * @param x The X-position of the curve end. - * @param y The Y-position of the curve end. - */ - public native void quadraticCurveTo(float cpx, float cpy, float x, float y); - - /** - * Draws a Bézier curve from the current path position to the given position. - * - * @param cp1X The X-position of the first control point. - * @param cp1Y The Y-position of the first control point. - * @param cp2X The X-position of the second control point. - * @param cp2Y The Y-position of the second control point. - * @param x The X-position of the curve end. - * @param y The Y-position of the curve end. - */ - public native void bezierCurveTo(float cp1X, float cp1Y, float cp2X, float cp2Y, float x, float y); - - /** - * Fills a circle with the current color. - * - * @param x The X-position of the circle center. - * @param y The Y-position of the circle center. - * @param radius The circle radius. - */ - public native void fillCircle(float x, float y, float radius); - - /** - * Strokes a circle with the current color and line width. - * - * @param x The X-position of the circle center. - * @param y The Y-position of the circle center. - * @param radius The circle radius. - */ - public native void strokeCircle(float x, float y, float radius); - - /** - * Fills the current path with the current color. - */ - public native void fill(); - - /** - * Strokes the current path with the current color and line width. - */ - public native void stroke(); - - /** - * Fills a text with the current color and provided details. - * - * @param text The text to draw. - * @param typeface The typeface to use for drawing the text. - * @param fontSize The font size to use when drawing the text. - * @param x The X-position where to draw the text to. - * @param y The Y-position where to draw the text to. - * @param textAlign How to align the text at the given position horizontally. - * @param baseline How to align the text at the given position vertically. - */ - public native void fillText(String text, AlphaSkiaTypeface typeface, float fontSize, float x, float y, - AlphaSkiaTextAlign textAlign, - AlphaSkiaTextBaseline baseline); - - /** - * Measures the given text. - * - * @param text The text to measure. - * @param typeface The typeface to use for drawing the text. - * @param fontSize The font size to use when drawing the text. - * @return The horizontal width of the text when it would be drawn. - */ - public native float measureText(String text, AlphaSkiaTypeface typeface, float fontSize); - - /** - * Rotates the canvas allowing angled drawing. . - * - * @param centerX The X-position of the center around which to rotate. - * @param centerY The Y-position of the center around which to rotate. - * @param angle The angle in degrees to rotate. - */ - public native void beginRotate(float centerX, float centerY, float angle); - - /** - * Restores the previous rotation state after {@link #beginRotate} was called. - */ - public native void endRotate(); - - private native static long alphaskiaCanvasAllocate(); - - private static native int alphaskiaColorType(); -} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaColorType.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaColorType.java deleted file mode 100644 index 35b4e30..0000000 --- a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaColorType.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.alphatab.alphaskia; - -/** - * Lists all known color type with which AlphaSkia creates images. - */ -public enum AlphaSkiaColorType { - // NOTE: we internally use kN32_SkColorType which is either kBGRA_8888_SkColorType or kRGBA_8888_SkColorType - // hence we don't need to expose the other SkColorType values - - /** - * pixel with 8 bits for red, green, blue, alpha; in 32-bit word - */ - RGBA_8888(4), - - /** - * pixel with 8 bits for blue, green, red, alpha; in 32-bit word - */ - BGRA_8888(6); - - private final int value; - - int getValue() { - return value; - } - - AlphaSkiaColorType(int value) { - this.value = value; - } - - static AlphaSkiaColorType fromValue(int value) { - if (value == RGBA_8888.getValue()) { - return RGBA_8888; - } else if (value == BGRA_8888.getValue()) { - return BGRA_8888; - } - return null; - } -} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaData.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaData.java deleted file mode 100644 index 0fd16e7..0000000 --- a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaData.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.alphatab.alphaskia; - -class AlphaSkiaData extends AlphaSkiaNative { - AlphaSkiaData(long handle) { - super(handle); - } - - AlphaSkiaData(byte[] raw) { - super(allocateCopy(raw)); - } - - private static native long allocateCopy(byte[] raw); - - @Override - public native void close(); - - public native byte[] toArray(); -} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java deleted file mode 100644 index f072960..0000000 --- a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaImage.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.alphatab.alphaskia; - -/** - * Represents a final rendered image. - */ -public class AlphaSkiaImage extends AlphaSkiaNative { - /** - * Gets the width of the image. - * - * @return the width of the image - */ - public native int getWidth(); - - /** - * Gets the height of the image. - * - * @return the height of the image - */ - public native int getHeight(); - - AlphaSkiaImage(long handle) { - super(handle); - } - - @Override - public native void close(); - - /** - * Reads the raw pixel data of this image as byte array. - * - * @return A copy of the raw pixel data. - */ - public native byte[] readPixels(); - - /** - * Encodes the image to a PNG. - * - * @return The raw PNG bytes for further usage. - */ - public native byte[] toPng(); -} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaNative.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaNative.java deleted file mode 100644 index 49a357d..0000000 --- a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaNative.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.alphatab.alphaskia; - -/** - * The base class for AlphaSkia objects wrapping native Skia objects. - */ -public abstract class AlphaSkiaNative implements AutoCloseable { - static { - if(!AlphaSkiaPlatform.isNativeLibLoaded()) { - throw new IllegalStateException("Initialize the alphaSkia platform first"); - } - } - - long handle; - - AlphaSkiaNative(long handle) { - this.handle = handle; - } - - - @Override - public abstract void close() throws Exception; -} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaPlatform.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaPlatform.java deleted file mode 100644 index 0142ed7..0000000 --- a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaPlatform.java +++ /dev/null @@ -1,79 +0,0 @@ -package net.alphatab.alphaskia; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -/** - * This class provides a way of resolving and loading the native libraries required - * by alphaSkia. - */ -public final class AlphaSkiaPlatform { - private static boolean nativeLibLoaded = false; - - /** - * Gets a value indicating whether the native libraries required by alphaSkia were loaded. - * - * @return {@code true} if the libraries were loaded, otherwise {@code false}. - */ - public static boolean isNativeLibLoaded() { - return nativeLibLoaded; - } - - /** - * Initiates the loading of native libraries from the given file paths assuming they are the correct ones for alphaSkia. - * - * @param nativeLibraryPaths The paths to the files to load. - * @throws IOException Thrown if any of the paths provided point to a non-existent file. - */ - public static void loadLibrary(String[] nativeLibraryPaths) throws IOException { - for (String library : nativeLibraryPaths) { - var file = new File(library); - if (!file.exists()) { - throw new FileNotFoundException("Could not find file: " + library); - } - System.load(library); - } - nativeLibLoaded = true; - } - - /** - * Initiates loading of native libraries from the given platform information class shipped as part of - * individual alphaSkia platform packages. - * - * @param platformInfo The platform info class like {@code net.alphatab.alphaskia.ALphaSkiaWindows} provided - * through individual platform packages. - * @throws IOException Thrown if the resources expected to be provided by the platform package could not be found or extracted. - * @throws IllegalArgumentException Thrown if the provided platform info class is not compliant with the needs of alphaSkia. Indicates typically an incompatibility or a wrong class being provided. - */ - public static void loadLibrary(Class platformInfo) throws IOException { - try { - String[] libraries = (String[]) platformInfo.getDeclaredField("libraries").get(null); - - Path tmpDir = Files.createTempDirectory("alphaskia"); - for (String library : libraries) { - URL resource = platformInfo.getResource(library); - if (resource == null) { - throw new IOException("Resource " + library + " not found"); - } - File tmpLib = tmpDir.resolve(Paths.get(library).getFileName().toString()).toFile(); - tmpLib.deleteOnExit(); - try (InputStream in = resource.openStream()) { - Files.copy(in, tmpLib.toPath()); - } - System.load(tmpLib.getAbsolutePath()); - } - nativeLibLoaded = true; - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new IllegalArgumentException("Type " + platformInfo.getName() + " seems not to be a valid AlphaSkia Target", e); - } - } - - private AlphaSkiaPlatform() { - } -} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextAlign.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextAlign.java deleted file mode 100644 index 7a4473d..0000000 --- a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextAlign.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.alphatab.alphaskia; - -/** - * Lists all text alignments which can be used to draw text. - */ -public enum AlphaSkiaTextAlign { - /** - * The text is drawn left aligned to the provided position. - */ - LEFT(0), - /** - * The text is drawn centered to the provided position. - */ - CENTER(1), - /** - * The text is drawn right aligned to the provided position. - */ - RIGHT(2); - - private final int value; - - int getValue() { - return this.value; - } - - AlphaSkiaTextAlign(int value) { - this.value = value; - } -} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextBaseline.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextBaseline.java deleted file mode 100644 index 10c6181..0000000 --- a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTextBaseline.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.alphatab.alphaskia; - -/** - * Lists all vertical text baseline alignments which can be used to draw text. - */ -public enum AlphaSkiaTextBaseline { - /** - * The text is drawn using the alphabetic baseline. - */ - ALPHABETIC(0), - /** - * The text is top-aligned to the provided position. - */ - TOP(1), - /** - * The test is middle-aligned (vertically centered) to the provided position. - */ - MIDDLE(2), - - /** - * The text is bottom-aligned to the provided position. - */ - BOTTOM(3); - - private final int value; - - int getValue() { - return this.value; - } - - AlphaSkiaTextBaseline(int value) { - this.value = value; - } -} diff --git a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTypeface.java b/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTypeface.java deleted file mode 100644 index 55bffd7..0000000 --- a/test/java/main/src/main/java/net/alphatab/alphaskia/AlphaSkiaTypeface.java +++ /dev/null @@ -1,60 +0,0 @@ -package net.alphatab.alphaskia; - -/** - * Represents a typeface to draw text. - */ -public class AlphaSkiaTypeface extends AlphaSkiaNative { - private final AlphaSkiaData data; - - private AlphaSkiaTypeface(long handle, AlphaSkiaData data) { - super(handle); - this.data = data; - } - - @Override - public void close() { - AlphaSkiaData data = this.data; - if (data != null) { - data.close(); - } - release(handle); - handle = 0; - } - - private native void release(long handle); - - /** - * Register a new custom font from the given binary data containing the data of a font compatible with Skia (e.g. TTF). - * @param data The raw binary data of the font. - * @return The loaded typeface to use for text rendering or {@code null} if the loading failed. - */ - public static AlphaSkiaTypeface register(byte[] data) { - var nativeData = new AlphaSkiaData(data); - var typeface = register(nativeData.handle); - if (typeface == 0) { - nativeData.close(); - return null; - } - return new AlphaSkiaTypeface(typeface, nativeData); - } - - private static native long register(long handle); - - /** - * Creates a typeface using the provided information. - * @param name The name of the typeface. - * @param bold Whether the bold version of the typeface should be loaded. - * @param italic Whether the italic version of the typeface should be loaded. - * @return The typeface if it can be found in the already loaded fonts or the system fonts, otherwise {@code null}. - */ - public static AlphaSkiaTypeface create(String name, boolean bold, boolean italic) { - var typeface = makeFromName(name, bold, italic); - if (typeface == 0) { - return null; - } - - return new AlphaSkiaTypeface(typeface, null); - } - - private static native long makeFromName(String name, boolean bold, boolean italic); -} diff --git a/test/java/main/src/test/java/net/alphatab/alphaskia/AlphaTabGeneratedRenderTest.java b/test/java/main/src/test/java/net/alphatab/alphaskia/AlphaTabGeneratedRenderTest.java deleted file mode 100644 index 862f1a7..0000000 --- a/test/java/main/src/test/java/net/alphatab/alphaskia/AlphaTabGeneratedRenderTest.java +++ /dev/null @@ -1,752 +0,0 @@ -package net.alphatab.alphaskia; - -public class AlphaTabGeneratedRenderTest extends MusicSheetRenderTest -{ - private AlphaSkiaImage drawMusicSheetPart1(AlphaSkiaCanvas canvas) { - canvas.beginRender(1300, 164, (float)(getRenderScale())); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - setTextAlign(AlphaSkiaTextAlign.CENTER); - setTypeface(getTypeface("PT Serif", false, false)); - setFontSize((float)(32)); - setTextAlign(AlphaSkiaTextAlign.CENTER); - canvas.fillText("Title", getTypeface(), getFontSize(), (float)(650), (float)(40), getTextAlign(), getTextBaseline()); - setTextAlign(AlphaSkiaTextAlign.CENTER); - setTypeface(getTypeface("PT Serif", false, false)); - setFontSize((float)(20)); - setTextAlign(AlphaSkiaTextAlign.CENTER); - canvas.fillText("Subtitle", getTypeface(), getFontSize(), (float)(650), (float)(72), getTextAlign(), getTextBaseline()); - setTextAlign(AlphaSkiaTextAlign.CENTER); - setTypeface(getTypeface("PT Serif", false, false)); - setFontSize((float)(20)); - setTextAlign(AlphaSkiaTextAlign.CENTER); - canvas.fillText("Artist", getTypeface(), getFontSize(), (float)(650), (float)(92), getTextAlign(), getTextBaseline()); - setTextAlign(AlphaSkiaTextAlign.CENTER); - setTypeface(getTypeface("PT Serif", false, false)); - setFontSize((float)(20)); - setTextAlign(AlphaSkiaTextAlign.CENTER); - canvas.fillText("Album", getTypeface(), getFontSize(), (float)(650), (float)(112), getTextAlign(), getTextBaseline()); - setTextAlign(AlphaSkiaTextAlign.CENTER); - setTypeface(getTypeface("PT Serif", false, false)); - setFontSize((float)(15)); - setTextAlign(AlphaSkiaTextAlign.RIGHT); - canvas.fillText("Music by Music", getTypeface(), getFontSize(), (float)(1260), (float)(132), getTextAlign(), getTextBaseline()); - setTextAlign(AlphaSkiaTextAlign.CENTER); - setTypeface(getTypeface("PT Serif", false, false)); - setFontSize((float)(15)); - setTextAlign(AlphaSkiaTextAlign.LEFT); - canvas.fillText("Words by Words", getTypeface(), getFontSize(), (float)(40), (float)(132), getTextAlign(), getTextBaseline()); - setTextAlign(AlphaSkiaTextAlign.CENTER); - return canvas.endRender(); - } - private AlphaSkiaImage drawMusicSheetPart2(AlphaSkiaCanvas canvas) { - canvas.beginRender(1300, 38, (float)(getRenderScale())); - setTextAlign(AlphaSkiaTextAlign.CENTER); - setTypeface(getTypeface("PT Serif", false, true)); - setFontSize((float)(12)); - setTextAlign(AlphaSkiaTextAlign.LEFT); - canvas.fillText("Guitar Standard Tuning", getTypeface(), getFontSize(), (float)(40), (float)(3), getTextAlign(), getTextBaseline()); - setTextAlign(AlphaSkiaTextAlign.CENTER); - return canvas.endRender(); - } - private AlphaSkiaImage drawMusicSheetPart3(AlphaSkiaCanvas canvas) { - canvas.beginRender(1300, 6, (float)(getRenderScale())); - setTextAlign(AlphaSkiaTextAlign.CENTER); - return canvas.endRender(); - } - private AlphaSkiaImage drawMusicSheetPart4(AlphaSkiaCanvas canvas) { - canvas.beginRender(1300, 373, (float)(getRenderScale())); - setTextAlign(AlphaSkiaTextAlign.LEFT); - setTypeface(getTypeface("PT Serif", true, false)); - setFontSize((float)(14)); - canvas.fillText("\uE1D5", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(156.6767578125), (float)(40), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("= 120", getTypeface(), getFontSize(), (float)(169.1767578125), (float)(27), getTextAlign(), getTextBaseline()); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); - canvas.fillRect((float)(92), (float)(130), (float)(254), (float)(1.04)); - canvas.fillRect((float)(92), (float)(139), (float)(254), (float)(1.04)); - canvas.fillRect((float)(92), (float)(148), (float)(254), (float)(1.04)); - canvas.fillRect((float)(92), (float)(157), (float)(254), (float)(1.04)); - canvas.fillRect((float)(92), (float)(166), (float)(254), (float)(1.04)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - canvas.fillText("\uE050", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(94), (float)(157.52), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE084", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(127), (float)(139), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE084", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(127), (float)(157), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(200), (byte)(0), (byte)(0), (byte)(255))); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("1", getTypeface(), getFontSize(), (float)(141), (float)(118.75), getTextAlign(), getTextBaseline()); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(152.1767578125), (float)(148), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE272", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(172.86069962449085), (float)(148), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(180.86069962449085), (float)(149), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.beginPath(); - canvas.moveTo((float)(156.6767578125), (float)(144)); - canvas.lineTo((float)(170.855110706305), (float)(133.50723450810483)); - canvas.lineTo((float)(184.23569962449085), (float)(145)); - canvas.stroke(); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(192.23569962449085), (float)(148), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(220.91964143648167), (float)(144.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.beginPath(); - canvas.moveTo((float)(196.73569962449085), (float)(144)); - canvas.lineTo((float)(209.12929655256082), (float)(131.33765133817155)); - canvas.lineTo((float)(224.29464143648167), (float)(140.5)); - canvas.stroke(); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(232.29464143648167), (float)(148), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE262", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(252.9785832484725), (float)(143.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(260.9785832484725), (float)(144.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.beginPath(); - canvas.moveTo((float)(236.79464143648167), (float)(144)); - canvas.lineTo((float)(249.1882383645516), (float)(131.33765133817155)); - canvas.lineTo((float)(264.3535832484725), (float)(140.5)); - canvas.stroke(); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(272.3535832484725), (float)(148), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(294.5517916242362), (float)(140), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.beginPath(); - canvas.moveTo((float)(276.8535832484725), (float)(152)); - canvas.lineTo((float)(292.1060513036615), (float)(156.93783819474976)); - canvas.lineTo((float)(297.9267916242362), (float)(142)); - canvas.stroke(); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(305.9267916242362), (float)(148), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE262", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(324.625), (float)(139), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 0.75), (float)(332.625), (float)(140), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.beginPath(); - canvas.moveTo((float)(310.4267916242362), (float)(152)); - canvas.lineTo((float)(327.2193887931866), (float)(157.24460932567123)); - canvas.lineTo((float)(336), (float)(142)); - canvas.stroke(); - canvas.fillRect((float)(345), (float)(130.1), (float)(1), (float)(36)); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(152.1767578125), (float)(148)); - canvas.lineTo((float)(152.1767578125), (float)(175)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(192.23569962449085), (float)(148)); - canvas.lineTo((float)(192.23569962449085), (float)(175)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(232.29464143648167), (float)(148)); - canvas.lineTo((float)(232.29464143648167), (float)(175)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(281.3535832484725), (float)(148)); - canvas.lineTo((float)(281.3535832484725), (float)(121)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.beginPath(); - canvas.moveTo((float)(281.3535832484725), (float)(121)); - canvas.lineTo((float)(314.9267916242362), (float)(121)); - canvas.lineTo((float)(314.9267916242362), (float)(125)); - canvas.lineTo((float)(281.3535832484725), (float)(125)); - canvas.closePath(); - canvas.fill(); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(314.9267916242362), (float)(148)); - canvas.lineTo((float)(314.9267916242362), (float)(121)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(100))); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); - canvas.fillRect((float)(346), (float)(130), (float)(126), (float)(1.04)); - canvas.fillRect((float)(346), (float)(139), (float)(126), (float)(1.04)); - canvas.fillRect((float)(346), (float)(148), (float)(126), (float)(1.04)); - canvas.fillRect((float)(346), (float)(157), (float)(126), (float)(1.04)); - canvas.fillRect((float)(346), (float)(166), (float)(126), (float)(1.04)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(200), (byte)(0), (byte)(0), (byte)(255))); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("2", getTypeface(), getFontSize(), (float)(346), (float)(118.75), getTextAlign(), getTextBaseline()); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(357.1767578125), (float)(139), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(378.359547484596), (float)(139), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(399.542337156692), (float)(161.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A3", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(426.623147703567), (float)(157), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillRect((float)(471), (float)(130.1), (float)(1), (float)(36)); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(366.1767578125), (float)(139)); - canvas.lineTo((float)(366.1767578125), (float)(112)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.beginPath(); - canvas.moveTo((float)(366.1767578125), (float)(112)); - canvas.lineTo((float)(387.359547484596), (float)(117)); - canvas.lineTo((float)(387.359547484596), (float)(121)); - canvas.lineTo((float)(366.1767578125), (float)(116)); - canvas.closePath(); - canvas.fill(); - canvas.beginPath(); - canvas.moveTo((float)(366.1767578125), (float)(118)); - canvas.lineTo((float)(387.359547484596), (float)(123)); - canvas.lineTo((float)(387.359547484596), (float)(127)); - canvas.lineTo((float)(366.1767578125), (float)(122)); - canvas.closePath(); - canvas.fill(); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(387.359547484596), (float)(139)); - canvas.lineTo((float)(387.359547484596), (float)(117)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.beginPath(); - canvas.moveTo((float)(387.359547484596), (float)(117)); - canvas.lineTo((float)(408.542337156692), (float)(122)); - canvas.lineTo((float)(408.542337156692), (float)(126)); - canvas.lineTo((float)(387.359547484596), (float)(121)); - canvas.closePath(); - canvas.fill(); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(408.542337156692), (float)(161.5)); - canvas.lineTo((float)(408.542337156692), (float)(122)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(435.623147703567), (float)(130)); - canvas.lineTo((float)(435.623147703567), (float)(157)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(100))); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - setTextAlign(AlphaSkiaTextAlign.CENTER); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("PT Serif", false, true)); - setFontSize((float)(12)); - canvas.beginPath(); - canvas.moveTo((float)(366.1767578125), (float)(102)); - canvas.lineTo((float)(366.1767578125), (float)(97)); - canvas.lineTo((float)(394.7559555656507), (float)(104)); - canvas.stroke(); - canvas.beginPath(); - canvas.moveTo((float)(407.04394995041633), (float)(107)); - canvas.lineTo((float)(435.623147703567), (float)(115)); - canvas.lineTo((float)(435.623147703567), (float)(120)); - canvas.stroke(); - canvas.fillText("3", getTypeface(), getFontSize(), (float)(400.8999527580335), (float)(106), getTextAlign(), getTextBaseline()); - setTextAlign(AlphaSkiaTextAlign.LEFT); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); - canvas.fillRect((float)(472), (float)(130), (float)(206), (float)(1.04)); - canvas.fillRect((float)(472), (float)(139), (float)(206), (float)(1.04)); - canvas.fillRect((float)(472), (float)(148), (float)(206), (float)(1.04)); - canvas.fillRect((float)(472), (float)(157), (float)(206), (float)(1.04)); - canvas.fillRect((float)(472), (float)(166), (float)(206), (float)(1.04)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(200), (byte)(0), (byte)(0), (byte)(255))); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("3", getTypeface(), getFontSize(), (float)(472), (float)(118.75), getTextAlign(), getTextBaseline()); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(496.32666015625), (float)(157), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(496.32666015625), (float)(143.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(496.32666015625), (float)(134.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.beginRotate((float)(527.8699951171875), (float)(172), (float)(-90)); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.endRotate(); - canvas.beginPath(); - canvas.moveTo((float)(527.8699951171875), (float)(180)); - canvas.lineTo((float)(531.8699951171875), (float)(172)); - canvas.lineTo((float)(523.8699951171875), (float)(172)); - canvas.closePath(); - canvas.fill(); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); - canvas.fillRect((float)(533.8699951171875), (float)(175), (float)(15), (float)(1)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(536.8699951171875), (float)(175), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(536.8699951171875), (float)(166), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(536.8699951171875), (float)(157), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(536.8699951171875), (float)(143.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(536.8699951171875), (float)(134.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); - canvas.fillRect((float)(574.413330078125), (float)(175), (float)(15), (float)(1)); - canvas.fillRect((float)(574.413330078125), (float)(184), (float)(15), (float)(1)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(577.413330078125), (float)(184), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(577.413330078125), (float)(166), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(577.413330078125), (float)(152.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.beginRotate((float)(596.9566650390625), (float)(137.5), (float)(90)); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(43.199999999999996), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.endRotate(); - canvas.beginPath(); - canvas.moveTo((float)(596.9566650390625), (float)(129.5)); - canvas.lineTo((float)(600.9566650390625), (float)(137.5)); - canvas.lineTo((float)(592.9566650390625), (float)(137.5)); - canvas.closePath(); - canvas.fill(); - canvas.fillText("\uE262", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(607.9566650390625), (float)(143.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); - canvas.fillRect((float)(614.9566650390625), (float)(175), (float)(15), (float)(1)); - canvas.fillRect((float)(614.9566650390625), (float)(184), (float)(15), (float)(1)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(617.9566650390625), (float)(184), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(617.9566650390625), (float)(166), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(617.9566650390625), (float)(152.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(617.9566650390625), (float)(143.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uE0A4", getMusicTypeface(), (float)(getMusicFontSize() * 1), (float)(617.9566650390625), (float)(134.5), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillRect((float)(670), (float)(130.1), (float)(1), (float)(36)); - canvas.fillRect((float)(674), (float)(130.1), (float)(4), (float)(36)); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(496.32666015625), (float)(134.5)); - canvas.lineTo((float)(496.32666015625), (float)(184)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(545.8699951171875), (float)(107.5)); - canvas.lineTo((float)(545.8699951171875), (float)(175)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(586.413330078125), (float)(125.5)); - canvas.lineTo((float)(586.413330078125), (float)(184)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.setLineWidth((float)(0.96)); - canvas.beginPath(); - canvas.moveTo((float)(626.9566650390625), (float)(107.5)); - canvas.lineTo((float)(626.9566650390625), (float)(184)); - canvas.stroke(); - canvas.setLineWidth((float)(1)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(100))); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); - canvas.fillRect((float)(92), (float)(284), (float)(254), (float)(1.04)); - canvas.fillRect((float)(92), (float)(295), (float)(254), (float)(1.04)); - canvas.fillRect((float)(92), (float)(306), (float)(254), (float)(1.04)); - canvas.fillRect((float)(92), (float)(317), (float)(61.02685546875), (float)(1.04)); - canvas.fillRect((float)(161.32666015625), (float)(317), (float)(31.759140014648438), (float)(1.04)); - canvas.fillRect((float)(201.38560485839844), (float)(317), (float)(31.759140014648438), (float)(1.04)); - canvas.fillRect((float)(241.44454956054688), (float)(317), (float)(31.759124755859375), (float)(1.04)); - canvas.fillRect((float)(281.50347900390625), (float)(317), (float)(25.273406982421875), (float)(1.04)); - canvas.fillRect((float)(315.0766906738281), (float)(317), (float)(30.923309326171875), (float)(1.04)); - canvas.fillRect((float)(92), (float)(328), (float)(254), (float)(1.04)); - canvas.fillRect((float)(92), (float)(339), (float)(254), (float)(1.04)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - canvas.fillText("\uE06D", getMusicTypeface(), (float)(getMusicFontSize() * 0.9230769230769231), (float)(97), (float)(311.8), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(163.32666015625), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("9", getTypeface(), getFontSize(), (float)(153.02685546875), (float)(317.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - canvas.beginPath(); - canvas.beginPath(); - canvas.moveTo((float)(188.23569962449085), (float)(274.7)); - canvas.lineTo((float)(185.23569962449085), (float)(280.7)); - canvas.lineTo((float)(191.23569962449085), (float)(280.7)); - canvas.closePath(); - canvas.fill(); - canvas.stroke(); - canvas.moveTo((float)(162.32666015625), (float)(317.3)); - canvas.bezierCurveTo((float)(175.28117989037042), (float)(317.3), (float)(188.23569962449085), (float)(317.3), (float)(188.23569962449085), (float)(280.7)); - canvas.stroke(); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("\u00BC", getTypeface(), getFontSize(), (float)(183.47495743699085), (float)(260.7), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(203.38560196824085), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("9", getTypeface(), getFontSize(), (float)(193.08579728074085), (float)(317.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - canvas.beginPath(); - canvas.beginPath(); - canvas.moveTo((float)(228.29464143648167), (float)(268.7)); - canvas.lineTo((float)(225.29464143648167), (float)(274.7)); - canvas.lineTo((float)(231.29464143648167), (float)(274.7)); - canvas.closePath(); - canvas.fill(); - canvas.stroke(); - canvas.moveTo((float)(202.38560196824085), (float)(317.3)); - canvas.bezierCurveTo((float)(215.34012170236127), (float)(317.3), (float)(228.29464143648167), (float)(317.3), (float)(228.29464143648167), (float)(274.7)); - canvas.stroke(); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("\u00BD", getTypeface(), getFontSize(), (float)(223.25142854585667), (float)(254.7), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(243.44454378023167), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("9", getTypeface(), getFontSize(), (float)(233.14473909273167), (float)(317.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - canvas.beginPath(); - canvas.beginPath(); - canvas.moveTo((float)(268.3535832484725), (float)(256.7)); - canvas.lineTo((float)(265.3535832484725), (float)(262.7)); - canvas.lineTo((float)(271.3535832484725), (float)(262.7)); - canvas.closePath(); - canvas.fill(); - canvas.stroke(); - canvas.moveTo((float)(242.44454378023167), (float)(317.3)); - canvas.bezierCurveTo((float)(255.39906351435206), (float)(317.3), (float)(268.3535832484725), (float)(317.3), (float)(268.3535832484725), (float)(262.7)); - canvas.stroke(); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("full", getTypeface(), getFontSize(), (float)(259.3589543422225), (float)(242.7), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(283.5034855922225), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("9", getTypeface(), getFontSize(), (float)(273.2036809047225), (float)(317.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - canvas.beginPath(); - canvas.beginPath(); - canvas.moveTo((float)(301.9267916242362), (float)(244.7)); - canvas.lineTo((float)(298.9267916242362), (float)(250.7)); - canvas.lineTo((float)(304.9267916242362), (float)(250.7)); - canvas.closePath(); - canvas.fill(); - canvas.stroke(); - canvas.moveTo((float)(282.5034855922225), (float)(317.3)); - canvas.bezierCurveTo((float)(292.2151386082294), (float)(317.3), (float)(301.9267916242362), (float)(317.3), (float)(301.9267916242362), (float)(250.7)); - canvas.stroke(); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("1\u00BD", getTypeface(), getFontSize(), (float)(293.2336763898612), (float)(230.7), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(317.0766939679862), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("9", getTypeface(), getFontSize(), (float)(306.7768892804862), (float)(317.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - canvas.beginPath(); - canvas.beginPath(); - canvas.moveTo((float)(340), (float)(232.7)); - canvas.lineTo((float)(337), (float)(238.7)); - canvas.lineTo((float)(343), (float)(238.7)); - canvas.closePath(); - canvas.fill(); - canvas.stroke(); - canvas.moveTo((float)(316.0766939679862), (float)(317.3)); - canvas.bezierCurveTo((float)(328.0383469839931), (float)(317.3), (float)(340), (float)(317.3), (float)(340), (float)(238.7)); - canvas.stroke(); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("2", getTypeface(), getFontSize(), (float)(336.35009765625), (float)(218.7), getTextAlign(), getTextBaseline()); - canvas.fillRect((float)(345), (float)(284.3), (float)(1), (float)(54.99999999999994)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); - canvas.fillRect((float)(346), (float)(284), (float)(126), (float)(1.04)); - canvas.fillRect((float)(346), (float)(295), (float)(12.02685546875), (float)(1.04)); - canvas.fillRect((float)(366.32666015625), (float)(295), (float)(12.882984161376953), (float)(1.04)); - canvas.fillRect((float)(387.50944900512695), (float)(295), (float)(84.49055099487305), (float)(1.04)); - canvas.fillRect((float)(346), (float)(306), (float)(126), (float)(1.04)); - canvas.fillRect((float)(346), (float)(317), (float)(54.392433166503906), (float)(1.04)); - canvas.fillRect((float)(408.6922378540039), (float)(317), (float)(18.781005859375), (float)(1.04)); - canvas.fillRect((float)(435.7730484008789), (float)(317), (float)(36.226951599121094), (float)(1.04)); - canvas.fillRect((float)(346), (float)(328), (float)(126), (float)(1.04)); - canvas.fillRect((float)(346), (float)(339), (float)(126), (float)(1.04)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(368.32666015625), (float)(295.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("3", getTypeface(), getFontSize(), (float)(358.02685546875), (float)(295.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(389.509449828346), (float)(295.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("3", getTypeface(), getFontSize(), (float)(379.209645140846), (float)(295.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(410.692239500442), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("3", getTypeface(), getFontSize(), (float)(400.392434812942), (float)(317.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(437.773050047317), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("5", getTypeface(), getFontSize(), (float)(427.473245359817), (float)(317.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - canvas.fillRect((float)(471), (float)(284.3), (float)(1), (float)(54.99999999999994)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255))); - canvas.fillRect((float)(472), (float)(284), (float)(25.1767578125), (float)(1.04)); - canvas.fillRect((float)(505.4765625), (float)(284), (float)(32.2435302734375), (float)(1.04)); - canvas.fillRect((float)(546.0198974609375), (float)(284), (float)(72.786865234375), (float)(1.04)); - canvas.fillRect((float)(627.1065673828125), (float)(284), (float)(50.8934326171875), (float)(1.04)); - canvas.fillRect((float)(472), (float)(295), (float)(25.1767578125), (float)(1.04)); - canvas.fillRect((float)(505.4765625), (float)(295), (float)(32.2435302734375), (float)(1.04)); - canvas.fillRect((float)(546.0198974609375), (float)(295), (float)(72.786865234375), (float)(1.04)); - canvas.fillRect((float)(627.1065673828125), (float)(295), (float)(50.8934326171875), (float)(1.04)); - canvas.fillRect((float)(472), (float)(306), (float)(25.1767578125), (float)(1.04)); - canvas.fillRect((float)(505.4765625), (float)(306), (float)(32.2435302734375), (float)(1.04)); - canvas.fillRect((float)(546.0198974609375), (float)(306), (float)(32.2435302734375), (float)(1.04)); - canvas.fillRect((float)(586.563232421875), (float)(306), (float)(32.2435302734375), (float)(1.04)); - canvas.fillRect((float)(627.1065673828125), (float)(306), (float)(50.8934326171875), (float)(1.04)); - canvas.fillRect((float)(472), (float)(317), (float)(65.7200927734375), (float)(1.04)); - canvas.fillRect((float)(546.0198974609375), (float)(317), (float)(32.2435302734375), (float)(1.04)); - canvas.fillRect((float)(586.563232421875), (float)(317), (float)(32.2435302734375), (float)(1.04)); - canvas.fillRect((float)(627.1065673828125), (float)(317), (float)(50.8934326171875), (float)(1.04)); - canvas.fillRect((float)(472), (float)(328), (float)(65.7200927734375), (float)(1.04)); - canvas.fillRect((float)(546.0198974609375), (float)(328), (float)(32.2435302734375), (float)(1.04)); - canvas.fillRect((float)(586.563232421875), (float)(328), (float)(32.2435302734375), (float)(1.04)); - canvas.fillRect((float)(627.1065673828125), (float)(328), (float)(50.8934326171875), (float)(1.04)); - canvas.fillRect((float)(472), (float)(339), (float)(206), (float)(1.04)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - canvas.beginPath(); - canvas.moveTo((float)(488), (float)(275.8)); - canvas.lineTo((float)(488), (float)(312.8)); - canvas.stroke(); - canvas.beginPath(); - canvas.moveTo((float)(488), (float)(275.8)); - canvas.lineTo((float)(492), (float)(283.8)); - canvas.lineTo((float)(484), (float)(283.8)); - canvas.closePath(); - canvas.fill(); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(507.4765625), (float)(284.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("0", getTypeface(), getFontSize(), (float)(497.1767578125), (float)(284.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(507.4765625), (float)(295.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("1", getTypeface(), getFontSize(), (float)(497.1767578125), (float)(295.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(507.4765625), (float)(306.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("0", getTypeface(), getFontSize(), (float)(497.1767578125), (float)(306.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - canvas.beginRotate((float)(527.7200927734375), (float)(326.8), (float)(-90)); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(43.199999999999996), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.endRotate(); - canvas.beginPath(); - canvas.moveTo((float)(528), (float)(334.8)); - canvas.lineTo((float)(532), (float)(326.8)); - canvas.lineTo((float)(524), (float)(326.8)); - canvas.closePath(); - canvas.fill(); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(548.0198974609375), (float)(284.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("0", getTypeface(), getFontSize(), (float)(537.7200927734375), (float)(284.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(548.0198974609375), (float)(295.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("1", getTypeface(), getFontSize(), (float)(537.7200927734375), (float)(295.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(548.0198974609375), (float)(306.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("0", getTypeface(), getFontSize(), (float)(537.7200927734375), (float)(306.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(548.0198974609375), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("2", getTypeface(), getFontSize(), (float)(537.7200927734375), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(548.0198974609375), (float)(328.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("3", getTypeface(), getFontSize(), (float)(537.7200927734375), (float)(328.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - canvas.beginPath(); - canvas.moveTo((float)(569), (float)(297.8)); - canvas.lineTo((float)(569), (float)(334.8)); - canvas.stroke(); - canvas.beginPath(); - canvas.moveTo((float)(569), (float)(297.8)); - canvas.lineTo((float)(573), (float)(305.8)); - canvas.lineTo((float)(565), (float)(305.8)); - canvas.closePath(); - canvas.fill(); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(588.563232421875), (float)(306.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("2", getTypeface(), getFontSize(), (float)(578.263427734375), (float)(306.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(588.563232421875), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("2", getTypeface(), getFontSize(), (float)(578.263427734375), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(588.563232421875), (float)(328.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("0", getTypeface(), getFontSize(), (float)(578.263427734375), (float)(328.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - canvas.beginRotate((float)(596.8067626953125), (float)(283.8), (float)(90)); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.fillText("\uEAA4", getMusicTypeface(), (float)(getMusicFontSize() * 1.2), (float)(43.199999999999996), (float)(10.799999999999999), AlphaSkiaTextAlign.LEFT, AlphaSkiaTextBaseline.ALPHABETIC); - canvas.endRotate(); - canvas.beginPath(); - canvas.moveTo((float)(597), (float)(275.8)); - canvas.lineTo((float)(601), (float)(283.8)); - canvas.lineTo((float)(593), (float)(283.8)); - canvas.closePath(); - canvas.fill(); - setTextBaseline(AlphaSkiaTextBaseline.MIDDLE); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(629.1065673828125), (float)(284.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("0", getTypeface(), getFontSize(), (float)(618.8067626953125), (float)(284.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(629.1065673828125), (float)(295.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("2", getTypeface(), getFontSize(), (float)(618.8067626953125), (float)(295.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(629.1065673828125), (float)(306.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("2", getTypeface(), getFontSize(), (float)(618.8067626953125), (float)(306.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(629.1065673828125), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("2", getTypeface(), getFontSize(), (float)(618.8067626953125), (float)(317.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(11)); - canvas.fillText("", getTypeface(), getFontSize(), (float)(629.1065673828125), (float)(328.3), getTextAlign(), getTextBaseline()); - setTypeface(getTypeface("Roboto", false, false)); - setFontSize((float)(13)); - canvas.fillText("0", getTypeface(), getFontSize(), (float)(618.8067626953125), (float)(328.3), getTextAlign(), getTextBaseline()); - setTextBaseline(AlphaSkiaTextBaseline.TOP); - canvas.fillRect((float)(670), (float)(284.3), (float)(1), (float)(54.99999999999994)); - canvas.fillRect((float)(674), (float)(284.3), (float)(4), (float)(54.99999999999994)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(34), (byte)(34), (byte)(17), (byte)(255))); - canvas.beginPath(); - canvas.moveTo((float)(92), (float)(130.1)); - canvas.lineTo((float)(92), (float)(339.3)); - canvas.stroke(); - setTypeface(getTypeface("PT Serif", false, true)); - setFontSize((float)(12)); - canvas.fillText("s.guit.", getTypeface(), getFontSize(), (float)(50), (float)(130.1), getTextAlign(), getTextBaseline()); - canvas.fillRect((float)(86), (float)(118.1), (float)(3), (float)(233.20000000000002)); - canvas.beginPath(); - canvas.moveTo((float)(86), (float)(118.1)); - canvas.bezierCurveTo((float)(86), (float)(118.1), (float)(86), (float)(118.1), (float)(98), (float)(115.1)); - canvas.bezierCurveTo((float)(92), (float)(121.1), (float)(86), (float)(121.1), (float)(86), (float)(121.1)); - canvas.closePath(); - canvas.fill(); - canvas.beginPath(); - canvas.moveTo((float)(86), (float)(351.3)); - canvas.bezierCurveTo((float)(86), (float)(351.3), (float)(92), (float)(351.3), (float)(98), (float)(354.3)); - canvas.bezierCurveTo((float)(92), (float)(348.3), (float)(86), (float)(348.3), (float)(86), (float)(348.3)); - canvas.closePath(); - canvas.fill(); - return canvas.endRender(); - } - private AlphaSkiaImage drawMusicSheetPart5(AlphaSkiaCanvas canvas) { - canvas.beginRender(116, 24, (float)(getRenderScale())); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); - setTypeface(getTypeface("Roboto", true, false)); - setFontSize((float)(12)); - setTextAlign(AlphaSkiaTextAlign.LEFT); - canvas.fillText("rendered by alphaTab", getTypeface(), getFontSize(), (float)(0), (float)(12), getTextAlign(), getTextBaseline()); - return canvas.endRender(); - } - @Override - protected float[][] getPartPositions() - { - return new float[][] - { - {(float)0, (float)0}, {(float)0, (float)164}, {(float)0, (float)202}, {(float)0, (float)208}, {(float)591.962890625, (float)581} - }; - } - @Override - protected int getTotalWidth() { return (int)1300; } - @Override - protected int getTotalHeight() { return (int)605; } - @Override - protected RenderFunction[] getAllParts() - { - return new RenderFunction[] - { - this::drawMusicSheetPart1, this::drawMusicSheetPart2, this::drawMusicSheetPart3, this::drawMusicSheetPart4, this::drawMusicSheetPart5 - }; - } -} diff --git a/test/java/main/src/test/java/net/alphatab/alphaskia/MusicSheetRenderTest.java b/test/java/main/src/test/java/net/alphatab/alphaskia/MusicSheetRenderTest.java deleted file mode 100644 index 847acb3..0000000 --- a/test/java/main/src/test/java/net/alphatab/alphaskia/MusicSheetRenderTest.java +++ /dev/null @@ -1,188 +0,0 @@ -package net.alphatab.alphaskia; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -public abstract class MusicSheetRenderTest { - private static AlphaSkiaTypeface _musicTypeface; - private static final int _musicFontSize = 34; - private static final float _renderScale = 1.0f; - - protected static float getRenderScale() { - return _renderScale; - } - - protected static AlphaSkiaTypeface getMusicTypeface() { - return _musicTypeface; - } - - protected static int getMusicFontSize() { - return _musicFontSize; - } - - private static final Map _customTypefaces = - new HashMap<>(); - - private static String customTypefaceKey(String fontFamily, boolean isBold, boolean isItalic) { - return fontFamily.toLowerCase() + "_" + isBold + "_" + isItalic; - } - - @BeforeAll - public static void setup() throws IOException { - var os = System.getProperty("os.name"); - String libName; - String target; - if (os.startsWith("Mac")) { - target = "macos"; - libName = "libalphaskiajni.dylib"; - } else if (os.startsWith("Win")) { - target = "win"; - libName = "libalphaskiajni.dll"; - } else if (os.startsWith("Linux")) { - target = "linux"; - libName = "libalphaskiajni.so"; - } else { - target = os.toLowerCase(); - libName = "libalphaskiajni"; - } - - var jarch = System.getProperty("os.arch"); - var arch = switch (jarch) { - case "x86", "i368", "i486", "i586", "i686" -> "x86"; - case "x86_64", "amd64" -> "x64"; - case "arm" -> "arm"; - case "aarch64" -> "arm64"; - default -> jarch; - }; - - AlphaSkiaPlatform.loadLibrary(new String[]{ - Path.of(System.getProperty("alphaskia.library.path"), "libalphaskiajni-" + target + "-" + arch + "-jni", libName).toAbsolutePath().toString() - }); - - var bravura = readFont("font/bravura/Bravura.ttf"); - - _musicTypeface = AlphaSkiaTypeface.register(bravura); - - _customTypefaces.put(customTypefaceKey("Roboto", false, false), - AlphaSkiaTypeface.register(readFont("font/roboto/Roboto-Regular.ttf"))); - _customTypefaces.put(customTypefaceKey("Roboto", true, false), - AlphaSkiaTypeface.register(readFont("font/roboto/Roboto-Bold.ttf"))); - _customTypefaces.put(customTypefaceKey("Roboto", false, true), - AlphaSkiaTypeface.register(readFont("font/roboto/Roboto-Italic.ttf"))); - _customTypefaces.put(customTypefaceKey("Roboto", true, true), - AlphaSkiaTypeface.register(readFont("font/roboto/Roboto-BoldItalic.ttf"))); - - _customTypefaces.put(customTypefaceKey("PT Serif", false, false), - AlphaSkiaTypeface.register(readFont("font/ptserif/PTSerif-Regular.ttf"))); - _customTypefaces.put(customTypefaceKey("PT Serif", true, false), - AlphaSkiaTypeface.register(readFont("font/ptserif/PTSerif-Bold.ttf"))); - _customTypefaces.put(customTypefaceKey("PT Serif", false, true), - AlphaSkiaTypeface.register(readFont("font/ptserif/PTSerif-Italic.ttf"))); - _customTypefaces.put(customTypefaceKey("PT Serif", true, true), - AlphaSkiaTypeface.register(readFont("font/ptserif/PTSerif-BoldItalic.ttf"))); - } - - protected static AlphaSkiaTypeface getTypeface(String name, boolean isBold, boolean isItalic) { - var face = _customTypefaces.getOrDefault(customTypefaceKey(name, isBold, isItalic), null); - if (face == null) { - throw new IllegalStateException("Unknown font requested: " + name); - } - return face; - } - - - private AlphaSkiaTextAlign textAlign = AlphaSkiaTextAlign.CENTER; - - protected AlphaSkiaTextAlign getTextAlign() { - return textAlign; - } - - protected void setTextAlign(AlphaSkiaTextAlign textAlign) { - this.textAlign = textAlign; - } - - private AlphaSkiaTextBaseline textBaseline = AlphaSkiaTextBaseline.TOP; - - protected AlphaSkiaTextBaseline getTextBaseline() { - return textBaseline; - } - - protected void setTextBaseline(AlphaSkiaTextBaseline textBaseline) { - this.textBaseline = textBaseline; - } - - private AlphaSkiaTypeface typeface; - - protected AlphaSkiaTypeface getTypeface() { - return typeface; - } - - protected void setTypeface(AlphaSkiaTypeface typeface) { - this.typeface = typeface; - } - - private float fontSize; - - public float getFontSize() { - return fontSize; - } - - public void setFontSize(float fontSize) { - this.fontSize = fontSize; - } - - private static byte[] readFont(String relativePath) throws IOException { - var fullPath = Path.of(System.getProperty("testdata.path")).resolve(relativePath); - try (var stream = new FileInputStream(fullPath.toFile())) { - return stream.readAllBytes(); - } - } - - private AlphaSkiaImage renderFullImage() { - try (var canvas = new AlphaSkiaCanvas()) { - var images = Arrays.stream(getAllParts()).map(p -> p.render(canvas)).toArray(AlphaSkiaImage[]::new); - - canvas.beginRender(getTotalWidth(), getTotalHeight()); - - var partPositions = getPartPositions(); - for (var i = 0; i < images.length; i++) { - canvas.drawImage(images[i], partPositions[i][0], partPositions[i][1], images[i].getWidth() / getRenderScale(), images[i].getHeight() / getRenderScale()); - images[i].close(); - } - - return canvas.endRender(); - } - } - - @Test - public void renderTest() throws IOException { - try (var finalImage = renderFullImage()) { - var testOutputPath = Path.of(System.getProperty("testoutput.path")); - Files.createDirectories(testOutputPath); - - var file = testOutputPath.resolve(this.getClass().getSimpleName() +".png").toFile(); - byte[] png = finalImage.toPng(); - - try (var stream = new FileOutputStream(file)) { - stream.write(png); - } - } - - } - - protected abstract int getTotalWidth(); - - protected abstract int getTotalHeight(); - - protected abstract float[][] getPartPositions(); - - protected abstract RenderFunction[] getAllParts(); -} - diff --git a/test/java/settings.gradle.kts b/test/java/settings.gradle.kts index 86cb29e..53526d9 100644 --- a/test/java/settings.gradle.kts +++ b/test/java/settings.gradle.kts @@ -1,7 +1,2 @@ -rootProject.name = "AlphaSkia" +rootProject.name = "alphaskia.test" -include("main") -include("linux") -include("windows") -include("android") -include("macos") diff --git a/lib/java/main/src/test/java/net/alphatab/alphaskia/AlphaTabGeneratedRenderTest.java b/test/java/src/main/generated/net/alphatab/alphaskia/test/AlphaTabGeneratedRenderTest.java similarity index 97% rename from lib/java/main/src/test/java/net/alphatab/alphaskia/AlphaTabGeneratedRenderTest.java rename to test/java/src/main/generated/net/alphatab/alphaskia/test/AlphaTabGeneratedRenderTest.java index 862f1a7..3f2b96d 100644 --- a/lib/java/main/src/test/java/net/alphatab/alphaskia/AlphaTabGeneratedRenderTest.java +++ b/test/java/src/main/generated/net/alphatab/alphaskia/test/AlphaTabGeneratedRenderTest.java @@ -1,10 +1,12 @@ -package net.alphatab.alphaskia; +package net.alphatab.alphaskia.test; -public class AlphaTabGeneratedRenderTest extends MusicSheetRenderTest +import net.alphatab.alphaskia.*; + +public class AlphaTabGeneratedRenderTest extends AlphaTabGeneratedRenderTestBase { - private AlphaSkiaImage drawMusicSheetPart1(AlphaSkiaCanvas canvas) { + private static AlphaSkiaImage drawMusicSheetPart1(AlphaSkiaCanvas canvas) { canvas.beginRender(1300, 164, (float)(getRenderScale())); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(153), (byte)(204), (byte)(30), (byte)(255))); setTextAlign(AlphaSkiaTextAlign.CENTER); setTypeface(getTypeface("PT Serif", false, false)); setFontSize((float)(32)); @@ -38,7 +40,7 @@ private AlphaSkiaImage drawMusicSheetPart1(AlphaSkiaCanvas canvas) { setTextAlign(AlphaSkiaTextAlign.CENTER); return canvas.endRender(); } - private AlphaSkiaImage drawMusicSheetPart2(AlphaSkiaCanvas canvas) { + private static AlphaSkiaImage drawMusicSheetPart2(AlphaSkiaCanvas canvas) { canvas.beginRender(1300, 38, (float)(getRenderScale())); setTextAlign(AlphaSkiaTextAlign.CENTER); setTypeface(getTypeface("PT Serif", false, true)); @@ -48,13 +50,14 @@ private AlphaSkiaImage drawMusicSheetPart2(AlphaSkiaCanvas canvas) { setTextAlign(AlphaSkiaTextAlign.CENTER); return canvas.endRender(); } - private AlphaSkiaImage drawMusicSheetPart3(AlphaSkiaCanvas canvas) { + private static AlphaSkiaImage drawMusicSheetPart3(AlphaSkiaCanvas canvas) { canvas.beginRender(1300, 6, (float)(getRenderScale())); setTextAlign(AlphaSkiaTextAlign.CENTER); return canvas.endRender(); } - private AlphaSkiaImage drawMusicSheetPart4(AlphaSkiaCanvas canvas) { + private static AlphaSkiaImage drawMusicSheetPart4(AlphaSkiaCanvas canvas) { canvas.beginRender(1300, 373, (float)(getRenderScale())); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); setTextAlign(AlphaSkiaTextAlign.LEFT); setTypeface(getTypeface("PT Serif", true, false)); setFontSize((float)(14)); @@ -697,7 +700,7 @@ private AlphaSkiaImage drawMusicSheetPart4(AlphaSkiaCanvas canvas) { setTextBaseline(AlphaSkiaTextBaseline.TOP); canvas.fillRect((float)(670), (float)(284.3), (float)(1), (float)(54.99999999999994)); canvas.fillRect((float)(674), (float)(284.3), (float)(4), (float)(54.99999999999994)); - canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(34), (byte)(34), (byte)(17), (byte)(255))); + canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(153), (byte)(51), (byte)(204), (byte)(100))); canvas.beginPath(); canvas.moveTo((float)(92), (float)(130.1)); canvas.lineTo((float)(92), (float)(339.3)); @@ -720,7 +723,7 @@ private AlphaSkiaImage drawMusicSheetPart4(AlphaSkiaCanvas canvas) { canvas.fill(); return canvas.endRender(); } - private AlphaSkiaImage drawMusicSheetPart5(AlphaSkiaCanvas canvas) { + private static AlphaSkiaImage drawMusicSheetPart5(AlphaSkiaCanvas canvas) { canvas.beginRender(116, 24, (float)(getRenderScale())); canvas.setColor(AlphaSkiaCanvas.rgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255))); setTypeface(getTypeface("Roboto", true, false)); @@ -729,24 +732,20 @@ private AlphaSkiaImage drawMusicSheetPart5(AlphaSkiaCanvas canvas) { canvas.fillText("rendered by alphaTab", getTypeface(), getFontSize(), (float)(0), (float)(12), getTextAlign(), getTextBaseline()); return canvas.endRender(); } - @Override - protected float[][] getPartPositions() + public static float[][] getPartPositions() { return new float[][] { - {(float)0, (float)0}, {(float)0, (float)164}, {(float)0, (float)202}, {(float)0, (float)208}, {(float)591.962890625, (float)581} + {(float)0, (float)0, (float)1300, (float)164}, {(float)0, (float)164, (float)1300, (float)38}, {(float)0, (float)202, (float)1300, (float)6}, {(float)0, (float)208, (float)1300, (float)373}, {(float)591.962890625, (float)581, (float)116.07421875, (float)24} }; } - @Override - protected int getTotalWidth() { return (int)1300; } - @Override - protected int getTotalHeight() { return (int)605; } - @Override - protected RenderFunction[] getAllParts() + public static int getTotalWidth() { return (int)1300; } + public static int getTotalHeight() { return (int)605; } + public static RenderFunction[] getAllParts() { return new RenderFunction[] { - this::drawMusicSheetPart1, this::drawMusicSheetPart2, this::drawMusicSheetPart3, this::drawMusicSheetPart4, this::drawMusicSheetPart5 + AlphaTabGeneratedRenderTest::drawMusicSheetPart1, AlphaTabGeneratedRenderTest::drawMusicSheetPart2, AlphaTabGeneratedRenderTest::drawMusicSheetPart3, AlphaTabGeneratedRenderTest::drawMusicSheetPart4, AlphaTabGeneratedRenderTest::drawMusicSheetPart5 }; } } diff --git a/test/java/src/main/java/net/alphatab/alphaskia/test/AlphaTabGeneratedRenderTestBase.java b/test/java/src/main/java/net/alphatab/alphaskia/test/AlphaTabGeneratedRenderTestBase.java new file mode 100644 index 0000000..89484c4 --- /dev/null +++ b/test/java/src/main/java/net/alphatab/alphaskia/test/AlphaTabGeneratedRenderTestBase.java @@ -0,0 +1,111 @@ +package net.alphatab.alphaskia.test; + +import net.alphatab.alphaskia.AlphaSkiaTextAlign; +import net.alphatab.alphaskia.AlphaSkiaTextBaseline; +import net.alphatab.alphaskia.AlphaSkiaTypeface; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.TreeMap; + +public class AlphaTabGeneratedRenderTestBase { + private static float renderScale = 1; + + public static float getRenderScale() { + return renderScale; + } + + public static void setRenderScale(float renderScale) { + AlphaTabGeneratedRenderTestBase.renderScale = renderScale; + } + + private static AlphaSkiaTypeface musicTypeface; + + public static AlphaSkiaTypeface getMusicTypeface() { + return musicTypeface; + } + + public static void setMusicTypeface(AlphaSkiaTypeface musicTypeface) { + AlphaTabGeneratedRenderTestBase.musicTypeface = musicTypeface; + } + + public static float getMusicFontSize() { + return 34; + } + + private static AlphaSkiaTextAlign textAlign = AlphaSkiaTextAlign.LEFT; + + public static AlphaSkiaTextAlign getTextAlign() { + return textAlign; + } + + public static void setTextAlign(AlphaSkiaTextAlign textAlign) { + AlphaTabGeneratedRenderTestBase.textAlign = textAlign; + } + + private static AlphaSkiaTextBaseline textBaseline = AlphaSkiaTextBaseline.TOP; + + public static AlphaSkiaTextBaseline getTextBaseline() { + return textBaseline; + } + + public static void setTextBaseline(AlphaSkiaTextBaseline textBaseline) { + AlphaTabGeneratedRenderTestBase.textBaseline = textBaseline; + } + + + private static AlphaSkiaTypeface typeface; + + public static AlphaSkiaTypeface getTypeface() { + return typeface; + } + + public static void setTypeface(AlphaSkiaTypeface typeface) { + AlphaTabGeneratedRenderTestBase.typeface = typeface; + } + + private static float fontSize = 12; + + public static float getFontSize() { + return fontSize; + } + + public static void setFontSize(float fontSize) { + AlphaTabGeneratedRenderTestBase.fontSize = fontSize; + } + + + private static final Map customTypefaces = + new TreeMap(String.CASE_INSENSITIVE_ORDER); + + private static String customTypefaceKey(String fontFamily, boolean isBold, boolean isItalic) { + return fontFamily.toLowerCase() + "_" + isBold + "_" + isItalic; + } + + protected static AlphaSkiaTypeface getTypeface(String fontFamily, boolean isBold, boolean isItalic) { + var key = customTypefaceKey(fontFamily, isBold, isItalic); + var typeface = customTypefaces.get(key); + if (typeface == null) { + throw new IllegalStateException("Unknown font requested: " + key); + } + + return typeface; + } + + public static AlphaSkiaTypeface loadTypeface(String name, boolean isBold, boolean isItalic, Path filePath) throws IOException { + var key = customTypefaceKey(name, isBold, isItalic); + System.out.println("Loading typeface " + key + " from " + filePath); + var data = Files.readAllBytes(filePath); + + System.out.println("Read " + data.length + " bytes from file, decoding typeface"); + var typeface = AlphaSkiaTypeface.register(data); + + if (typeface == null) { + throw new IllegalStateException("Could not create typeface from data"); + } + customTypefaces.put(key, typeface); + return typeface; + } +} diff --git a/test/java/src/main/java/net/alphatab/alphaskia/test/Main.java b/test/java/src/main/java/net/alphatab/alphaskia/test/Main.java new file mode 100644 index 0000000..2ec84b4 --- /dev/null +++ b/test/java/src/main/java/net/alphatab/alphaskia/test/Main.java @@ -0,0 +1,226 @@ +package net.alphatab.alphaskia.test; + +import net.alphatab.alphaskia.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class Main { + private static Path findRepositoryRoot(Path current) { + if (current.resolve(".nuke").toFile().exists()) { + return current; + } + + var parent = current.getParent(); + if (parent == null) { + throw new IllegalStateException("Could not find repository root"); + } + + return findRepositoryRoot(parent); + } + + private static AlphaSkiaImage renderFullImage() { + try (var fullImageCanvas = new AlphaSkiaCanvas()) { + System.out.println( + "Begin render with size " + AlphaTabGeneratedRenderTest.getTotalWidth() + "x" + AlphaTabGeneratedRenderTest.getTotalHeight() + " at scale " + AlphaTabGeneratedRenderTest.getRenderScale()); + fullImageCanvas.beginRender(AlphaTabGeneratedRenderTest.getTotalWidth(), AlphaTabGeneratedRenderTest.getTotalHeight(), + AlphaTabGeneratedRenderTest.getRenderScale()); + + try (var partialCanvas = new AlphaSkiaCanvas()) { + var allParts = AlphaTabGeneratedRenderTest.getAllParts(); + var partPositions = AlphaTabGeneratedRenderTest.getPartPositions(); + for (var i = 0; i < allParts.length; i++) { + System.out.println("Render part " + i); + try (var part = allParts[i].render(partialCanvas)) { + float x = partPositions[i][0]; + float y = partPositions[i][1]; + float w = partPositions[i][2]; + float h = partPositions[i][3]; + + System.out.println("Drawing part " + i + " into full image at " + x + "/" + y + "/" + w + "/" + h); + fullImageCanvas.drawImage(part, x, y, w, h); + } + } + } + + var fullImage = fullImageCanvas.endRender(); + System.out.println("Render of full image completed"); + return fullImage; + } + } + + public static void main(String[] args) { + System.exit(mainWithExitCode()); + } + + private static int mainWithExitCode() { + try { + initializeAlphaSkia(); + double tolerancePercent = 1; + + var repositoryRoot = findRepositoryRoot(Paths.get(".").toAbsolutePath()); + + // Load all fonts for rendering + System.out.println("Loading fonts"); + var testDataPath = repositoryRoot.resolve("test/test-data"); + AlphaTabGeneratedRenderTest.setMusicTypeface(AlphaTabGeneratedRenderTest.loadTypeface("Bravura", false, false, + testDataPath.resolve("font/bravura/Bravura.ttf"))); + AlphaTabGeneratedRenderTest.loadTypeface("Roboto", false, false, + testDataPath.resolve("font/roboto/Roboto-Regular.ttf")); + AlphaTabGeneratedRenderTest.loadTypeface("Roboto", true, false, + testDataPath.resolve("font/roboto/Roboto-Bold.ttf")); + AlphaTabGeneratedRenderTest.loadTypeface("Roboto", false, true, + testDataPath.resolve("font/roboto/Roboto-Italic.ttf")); + AlphaTabGeneratedRenderTest.loadTypeface("Roboto", true, true, + testDataPath.resolve("font/roboto/Roboto-BoldItalic.ttf")); + AlphaTabGeneratedRenderTest.loadTypeface("PT Serif", false, false, + testDataPath.resolve("font/ptserif/PTSerif-Regular.ttf")); + AlphaTabGeneratedRenderTest.loadTypeface("PT Serif", true, false, + testDataPath.resolve("font/ptserif/PTSerif-Bold.ttf")); + AlphaTabGeneratedRenderTest.loadTypeface("PT Serif", false, true, + testDataPath.resolve("font/ptserif/PTSerif-Italic.ttf")); + AlphaTabGeneratedRenderTest.loadTypeface("PT Serif", true, true, + testDataPath.resolve("font/ptserif/PTSerif-BoldItalic.ttf")); + System.out.println("Fonts loaded"); + + // render full image + System.out.println("Rendering image"); + try (var actualImage = renderFullImage()) { + System.out.println("Image rendered"); + + // save png for future reference + System.out.println("Saving image as PNG"); + + var testOutputPath = repositoryRoot.resolve("test/test-outputs/java"); + //noinspection ResultOfMethodCallIgnored + testOutputPath.toFile().mkdirs(); + + var testOutputFile = testOutputPath.resolve(getAlphaSkiaRid() + ".png"); + var pngData = actualImage.toPng(); + if (pngData == null) { + throw new IllegalStateException("Failed to encode final image to png"); + } + + Files.write(testOutputFile, pngData); + System.out.println("Image saved to " + testOutputFile); + + // load reference image + var testReferencePath = testDataPath.resolve("reference/" + getAlphaSkiaRid() + ".png"); + System.out.println("Loading reference image " + testReferencePath); + + var testReferenceData = Files.readAllBytes(testReferencePath); + try (var expectedImage = AlphaSkiaImage.decode(testReferenceData)) { + if (expectedImage == null) { + throw new IllegalStateException("Failed to decode reference image"); + } + + System.out.println("Reference image loaded"); + + // compare images + System.out.println("Comparing images"); + var actualWidth = actualImage.getWidth(); + var actualHeight = actualImage.getHeight(); + + var expectedWidth = expectedImage.getWidth(); + var expectedHeight = expectedImage.getHeight(); + if (actualWidth != expectedWidth || actualHeight != expectedHeight) { + System.err.println( + "Image sizes do not match: Actual[" + actualWidth + "x" + actualHeight + "] Expected[" + expectedWidth + "x" + actualHeight + "]"); + return 1; + } + + var actualPixels = actualImage.readPixels(); + var expectedPixels = expectedImage.readPixels(); + var diffPixels = new byte[actualPixels.length]; + + var compareResult = PixelMatch.match(actualPixels, + expectedPixels, + diffPixels, + actualWidth, + actualHeight, + new PixelMatchOptions(0.3, new PixelMatchColor((byte) 255, (byte) 0, (byte) 0)) + ); + + var totalPixels = compareResult.totalPixels() - compareResult.transparentPixels(); + var percentDifference = ((double) compareResult.differentPixels() / totalPixels) * 100; + var pass = percentDifference < tolerancePercent; + if (!pass) { + System.err.println( + "Difference between original and new image is too big: " + compareResult.differentPixels() + "/" + totalPixels + "(" + percentDifference + "%)"); + + // create diff image as PNG + try (var diffImage = AlphaSkiaImage.fromPixels(actualWidth, actualHeight, diffPixels)) { + var diffImagePngData = diffImage.toPng(); + + var diffOutputPath = testOutputPath.resolve(getAlphaSkiaRid() + ".diff.png"); + Files.write(diffOutputPath, diffImagePngData); + System.out.println("Error diff image saved to " + diffOutputPath); + return 1; + } + } + + System.out.println( + "Images match. Total Pixels:" + compareResult.totalPixels() + ", Transparent Pixels: " + compareResult.transparentPixels() + ", Percent difference: " + percentDifference + "%"); + return 0; + } + } + } catch (Exception e) { + System.err.println("Failed to run test: " + e); + return 1; + } + } + + private static void initializeAlphaSkia() throws IOException { + switch(getOperatingSystemRid()) { + case "macos": + AlphaSkiaPlatform.loadLibrary(AlphaSkiaMacOs.class); + break; + case "win": + AlphaSkiaPlatform.loadLibrary(AlphaSkiaWindows.class); + break; + case "android": + AlphaSkiaPlatform.loadLibrary(AlphaSkiaAndroid.class); + break; + case "linux": + AlphaSkiaPlatform.loadLibrary(AlphaSkiaLinux.class); + break; + } + } + + private static String getOperatingSystemRid() { + var os = System.getProperty("os.name"); + if (os.startsWith("Mac")) { + return "macos"; + } + if (os.startsWith("Win")) { + return "win"; + } + if (os.startsWith("Linux")) { + // https://developer.android.com/reference/java/lang/System#getProperties() + if ("The Android Project".equalsIgnoreCase(System.getProperty("java.specification.vendor"))) { + return "android"; + } + + return "linux"; + } + + throw new IllegalStateException("Unsupported OS: " + os); + } + + private static String getArchitectureRid() { + var jarch = System.getProperty("os.arch"); + return switch (jarch) { + case "x86", "i368", "i486", "i586", "i686" -> "x86"; + case "x86_64", "amd64" -> "x64"; + case "arm" -> "arm"; + case "aarch64" -> "arm64"; + default -> throw new IllegalStateException("Unsupported Architecture: " + jarch); + }; + } + + private static String getAlphaSkiaRid() { + return getOperatingSystemRid() + "-" + getArchitectureRid(); + } +} \ No newline at end of file diff --git a/test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatch.java b/test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatch.java new file mode 100644 index 0000000..019b0f1 --- /dev/null +++ b/test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatch.java @@ -0,0 +1,306 @@ +/* + * Based on https://github.com/mapbox/pixelmatch + * ISC License + * Copyright (c) 2019, Mapbox + * + * Permission to use, copy, modify, and/or distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright notice + * and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ +package net.alphatab.alphaskia.test; + + +final class PixelMatch { + // blend semi-transparent color with white + private static byte blend(byte c, byte a) + { + return (byte)(255 + (c - 255) * a); + } + + private static double rgb2Y(byte r, byte g, byte b) + { + return r * 0.29889531 + g * 0.58662247 + b * 0.11448223; + } + + private static double rgb2I(byte r, byte g, byte b) + { + return r * 0.59597799 - g * 0.2741761 - b * 0.32180189; + } + + private static double rgb2Q(byte r, byte g, byte b) + { + return r * 0.21147017 - g * 0.52261711 + b * 0.31114694; + } + + private static void drawPixel(byte[] output, int pos, PixelMatchColor color) + { + //noinspection PointlessArithmeticExpression + output[pos + 0] = color.b(); + output[pos + 1] = color.g(); + output[pos + 2] = color.r(); + output[pos + 3] = (byte)255; + } + + private static double colorDelta(byte[] img1, byte[] img2, int k, int m, boolean yOnly) + { + //noinspection PointlessArithmeticExpression + var r1 = img1[k + 0]; + var g1 = img1[k + 1]; + var b1 = img1[k + 2]; + var a1 = img1[k + 3]; + + //noinspection PointlessArithmeticExpression + var r2 = img2[m + 0]; + var g2 = img2[m + 1]; + var b2 = img2[m + 2]; + var a2 = img2[m + 3]; + + if (a1 == a2 && r1 == r2 && g1 == g2 && b1 == b2) + return 0; + + if (a1 < (byte)255) + { + a1 /= (byte)255; + r1 = blend(r1, a1); + g1 = blend(g1, a1); + b1 = blend(b1, a1); + } + + if (a2 < (byte)255) + { + a2 /= (byte)255; + r2 = blend(r2, a2); + g2 = blend(g2, a2); + b2 = blend(b2, a2); + } + + var y = rgb2Y(r1, g1, b1) - rgb2Y(r2, g2, b2); + + if (yOnly) + return y; // brightness difference only + + var i = rgb2I(r1, g1, b1) - rgb2I(r2, g2, b2); + var q = rgb2Q(r1, g1, b1) - rgb2Q(r2, g2, b2); + + return 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q; + } + + // check if a pixel has 3+ adjacent pixels of the same color. + private static boolean hasManySiblings(byte[] img, int x1, int y1, int width, int height) + { + var distance = 1; + var x0 = Math.max(x1 - distance, 0); + var y0 = Math.max(y1 - distance, 0); + var x2 = Math.min(x1 + distance, width - 1); + var y2 = Math.min(y1 + distance, height - 1); + var pos = (y1 * width + x1) * 4; + var zeroes = x1 == x0 || x1 == x2 || y1 == y0 || y1 == y2 ? 1 : 0; + + // go through 8 adjacent pixels + for (var x = x0; x <= x2; x++) + { + for (var y = y0; y <= y2; y++) + { + if (x == x1 && y == y1) + continue; + + var pos2 = (y * width + x) * 4; + if ( + img[pos] == img[pos2] && + img[pos + 1] == img[pos2 + 1] && + img[pos + 2] == img[pos2 + 2] && + img[pos + 3] == img[pos2 + 3]) + { + zeroes++; + } + + if (zeroes > 2) + return true; + } + } + + return false; + } + + // check if a pixel is likely a part of anti-aliasing; + // based on "Anti-aliased Pixel and Intensity Slope Detector" paper by V. Vysniauskas, 2009 + + private static boolean Antialiased(byte[] img, int x1, int y1, int width, int height, byte[] img2) + { + var distance = 1; + var x0 = Math.max(x1 - distance, 0); + var y0 = Math.max(y1 - distance, 0); + var x2 = Math.min(x1 + distance, width - 1); + var y2 = Math.min(y1 + distance, height - 1); + var pos = (y1 * width + x1) * 4; + var zeroes = x1 == x0 || x1 == x2 || y1 == y0 || y1 == y2 ? 1 : 0; + var min = 0.0; + var max = 0.0; + var minX = 0; + var minY = 0; + var maxX = 0; + var maxY = 0; + + // go through 8 adjacent pixels + for (var x = x0; x <= x2; x++) + { + for (var y = y0; y <= y2; y++) + { + if (x == x1 && y == y1) + { + continue; + } + + // brightness delta between the center pixel and adjacent one + var delta = colorDelta(img, img, pos, (y * width + x) * 4, true); + + // count the number of equal, darker and brighter adjacent pixels + if (delta == 0) + { + zeroes++; + // if found more than 2 equal siblings, it's definitely not anti-aliasing + if (zeroes > 2) + return false; + + // remember the darkest pixel + } + else if (delta < min) + { + min = delta; + minX = x; + minY = y; + + // remember the brightest pixel + } + else if (delta > max) + { + max = delta; + maxX = x; + maxY = y; + } + } + } + + // if there are no both darker and brighter pixels among siblings, it's not anti-aliasing + if (min == 0 || max == 0) + { + return false; + } + + // if either the darkest or the brightest pixel has 3+ equal siblings in both images + // (definitely not anti-aliased), this pixel is anti-aliased + return ( + (hasManySiblings(img, minX, minY, width, height) && + hasManySiblings(img2, minX, minY, width, height)) || + (hasManySiblings(img, maxX, maxY, width, height) && + hasManySiblings(img2, maxX, maxY, width, height))); + } + + public static PixelMatchResult match(byte[] img1, byte[] img2, byte[] diffPixels, int width, int height, + PixelMatchOptions options) + { + if (img1.length != img2.length || diffPixels.length != img1.length) + { + throw new IllegalStateException("Image sizes do not match"); + } + + if (img1.length != width * height * 4) + { + throw new IllegalStateException("Image data size does not match width/height."); + } + + // check if images are identical + var len = width * height; + var identical = true; + var transparentPixels = 0; + + for (var i = 0; i < len; i++) + { + //noinspection PointlessArithmeticExpression + var img1R = img1[(i * 4) + 0]; + var img1G = img1[(i * 4) + 1]; + var img1B = img1[(i * 4) + 2]; + var img1A = img1[(i * 4) + 3]; + + //noinspection PointlessArithmeticExpression + var img2R = img2[(i * 4) + 0]; + var img2G = img2[(i * 4) + 1]; + var img2B = img2[(i * 4) + 2]; + var img2A = img2[(i * 4) + 3]; + + if (img1R != img2R || img1G != img2G || img1B != img2B || img1A != img2A) + { + identical = false; + break; + } + + if (img1A == 0) + { + transparentPixels++; + } + } + + if (identical) + { + return new PixelMatchResult(len, 0, transparentPixels); + } + + transparentPixels = 0; + + // maximum acceptable square distance between two colors; + // 35215 is the maximum possible value for the YIQ difference metric + var maxDelta = 35215 * options.threshold() * options.threshold(); + + var diff = 0; + + // compare each pixel of one image against the other one + for (var y = 0; y < height; y++) + { + for (var x = 0; x < width; x++) + { + var pos = (y * width + x) * 4; + + if (img1[pos + 3] == 0) + { + transparentPixels++; + } + + // squared YUV distance between colors at this pixel position + var delta = colorDelta(img1, img2, pos, pos, false); + + // the color difference is above the threshold + if (delta > maxDelta) + { + // check it's a real rendering difference or just anti-aliasing + //noinspection StatementWithEmptyBody + if ((Antialiased(img1, x, y, width, height, img2) || + Antialiased(img2, x, y, width, height, img1))) + { + // match + } + else + { + // found substantial difference not caused by anti-aliasing; draw it as red + drawPixel(diffPixels, pos, options.diffColor()); + diff++; + } + } + } + } + + // return the number of different pixels + return new PixelMatchResult(len, diff, transparentPixels); + } + + private PixelMatch() { } +} + + diff --git a/test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatchColor.java b/test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatchColor.java new file mode 100644 index 0000000..86f55d6 --- /dev/null +++ b/test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatchColor.java @@ -0,0 +1,3 @@ +package net.alphatab.alphaskia.test; + +public record PixelMatchColor(byte r, byte g, byte b) { } diff --git a/test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatchOptions.java b/test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatchOptions.java new file mode 100644 index 0000000..de44509 --- /dev/null +++ b/test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatchOptions.java @@ -0,0 +1,3 @@ +package net.alphatab.alphaskia.test; + +public record PixelMatchOptions(double threshold, PixelMatchColor diffColor) { } \ No newline at end of file diff --git a/test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatchResult.java b/test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatchResult.java new file mode 100644 index 0000000..f8e2737 --- /dev/null +++ b/test/java/src/main/java/net/alphatab/alphaskia/test/PixelMatchResult.java @@ -0,0 +1,3 @@ +package net.alphatab.alphaskia.test; + +public record PixelMatchResult(int totalPixels, int differentPixels, int transparentPixels) { } diff --git a/test/java/main/src/test/java/net/alphatab/alphaskia/RenderFunction.java b/test/java/src/main/java/net/alphatab/alphaskia/test/RenderFunction.java similarity index 82% rename from test/java/main/src/test/java/net/alphatab/alphaskia/RenderFunction.java rename to test/java/src/main/java/net/alphatab/alphaskia/test/RenderFunction.java index 44ae5fd..2a7dfd6 100644 --- a/test/java/main/src/test/java/net/alphatab/alphaskia/RenderFunction.java +++ b/test/java/src/main/java/net/alphatab/alphaskia/test/RenderFunction.java @@ -1,8 +1,8 @@ -package net.alphatab.alphaskia; +package net.alphatab.alphaskia.test; import net.alphatab.alphaskia.AlphaSkiaCanvas; import net.alphatab.alphaskia.AlphaSkiaImage; public interface RenderFunction { AlphaSkiaImage render(AlphaSkiaCanvas canvas); -} \ No newline at end of file +} diff --git a/test/native/include/PixelMatch.h b/test/native/include/PixelMatch.h index 822ae25..38be104 100644 --- a/test/native/include/PixelMatch.h +++ b/test/native/include/PixelMatch.h @@ -36,9 +36,7 @@ typedef struct typedef struct { double threshold; - double alpha; pixel_match_color_t diff_color; - bool diff_mask; } pixel_match_options_t; pixel_match_result_t pixel_match(const std::vector& actual_pixels, diff --git a/test/native/src/AlphaSkiaTestBridge.cpp b/test/native/src/AlphaSkiaTestBridge.cpp index bbbcb65..cf8367f 100644 --- a/test/native/src/AlphaSkiaTestBridge.cpp +++ b/test/native/src/AlphaSkiaTestBridge.cpp @@ -11,7 +11,7 @@ alphaskia_typeface_t typeface = nullptr; alphaskia_typeface_t music_typeface = nullptr; float music_font_size = 34; float render_scale = 1; -float font_size = 0.0f; +float font_size = 12.0f; static std::map custom_type_faces; @@ -48,24 +48,24 @@ void read_file(std::string file_path, std::vector &data) alphaskia_typeface_t alphaskia_load_typeface(const char *name, bool is_bold, bool is_italic, std::string file_path) { auto key = custom_typeface_key(name, is_bold, is_italic); - std::cout << "Loading font " << key << " from " << file_path << std::endl; + std::cout << "Loading typeface " << key << " from " << file_path << std::endl; std::vector data; read_file(file_path, data); alphaskia_data_t font_data = alphaskia_data_new_copy(data.data(), data.size()); if (!font_data) { - throw std::exception(("Could allocate graphic font data " + file_path).c_str()); + throw std::exception(("Could allocate typeface data " + file_path).c_str()); } - std::cout << "Read " << data.size() << " bytes from file, decoding font" << std::endl; + std::cout << "Read " << data.size() << " bytes from file, decoding typeface" << std::endl; alphaskia_typeface_t typeface = alphaskia_typeface_register(font_data); if (!typeface) { alphaskia_data_free(font_data); - throw std::exception("Could not create font from data"); + throw std::exception("Could not create typeface from data"); } - std::cout << "Font " << key << " loaded and registered" << std::endl; + std::cout << "Typeface " << key << " loaded and registered" << std::endl; custom_type_faces[key] = typeface; return typeface; } \ No newline at end of file diff --git a/test/native/src/PixelMatch.cpp b/test/native/src/PixelMatch.cpp index c14171f..501fefc 100644 --- a/test/native/src/PixelMatch.cpp +++ b/test/native/src/PixelMatch.cpp @@ -1,3 +1,20 @@ +/** + * Based on https://github.com/mapbox/pixelmatch + * ISC License + * Copyright (c) 2019, Mapbox + * + * Permission to use, copy, modify, and/or distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright notice + * and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ #include "../include/PixelMatch.h" #include #include @@ -124,8 +141,8 @@ bool antialiased(const std::vector &img, int32_t x1, int32_t y1, int32_ const int32_t y2 = std::min(y1 + distance, height - 1); const int32_t pos = (y1 * width + x1) * 4; int32_t zeroes = x1 == x0 || x1 == x2 || y1 == y0 || y1 == y2 ? 1 : 0; - int32_t min = 0; - int32_t max = 0; + double min = 0; + double max = 0; int32_t minX = 0; int32_t minY = 0; int32_t maxX = 0; diff --git a/test/native/src/main.cpp b/test/native/src/main.cpp index ec78d2b..1c30c0c 100644 --- a/test/native/src/main.cpp +++ b/test/native/src/main.cpp @@ -122,6 +122,10 @@ int main() std::vector test_reference_data; read_file(test_reference_path.generic_string(), test_reference_data); alphaskia_image_t expected_image = alphaskia_image_decode(test_reference_data.data(), test_reference_data.size()); + if (!expected_image) + { + throw std::exception("Failed to decode reference image"); + } std::cout << "Reference image loaded" << std::endl; // compare images @@ -156,8 +160,7 @@ int main() actual_width, actual_height, {/* threshold: */ 0.3, - /* alpha: */ 1, - /* diff_color: */ {255, 0, 0}, true}); + /* diff_color: */ {255, 0, 0}}); int32_t totalPixels = compare_result.total_pixels - compare_result.transparent_pixels; double percentDifference = ((double)compare_result.different_pixels / totalPixels) * 100; diff --git a/test/node/AlphaTabGeneratedRenderTest.ts b/test/node/AlphaTabGeneratedRenderTest.ts new file mode 100644 index 0000000..c25f231 --- /dev/null +++ b/test/node/AlphaTabGeneratedRenderTest.ts @@ -0,0 +1,741 @@ +import TestBase from './AlphaTabGeneratedRenderTestBase'; +import { AlphaSkiaTextBaseline, AlphaSkiaTextAlign, AlphaSkiaCanvas, AlphaSkiaImage } from '@coderline/alphaskia'; + +function drawMusicSheetPart1(canvas: AlphaSkiaCanvas): AlphaSkiaImage { + canvas.beginRender(1300, 164, TestBase.renderScale); + canvas.color = AlphaSkiaCanvas.rgbaToColor(153, 204, 30, 255); + TestBase.textAlign = AlphaSkiaTextAlign.Center; + TestBase.typeface = TestBase.getTypeface("PT Serif", false, false); + TestBase.fontSize = 32; + TestBase.textAlign = AlphaSkiaTextAlign.Center; + canvas.fillText("Title", TestBase.typeface, TestBase.fontSize, 650, 40, TestBase.textAlign, TestBase.textBaseline); + TestBase.textAlign = AlphaSkiaTextAlign.Center; + TestBase.typeface = TestBase.getTypeface("PT Serif", false, false); + TestBase.fontSize = 20; + TestBase.textAlign = AlphaSkiaTextAlign.Center; + canvas.fillText("Subtitle", TestBase.typeface, TestBase.fontSize, 650, 72, TestBase.textAlign, TestBase.textBaseline); + TestBase.textAlign = AlphaSkiaTextAlign.Center; + TestBase.typeface = TestBase.getTypeface("PT Serif", false, false); + TestBase.fontSize = 20; + TestBase.textAlign = AlphaSkiaTextAlign.Center; + canvas.fillText("Artist", TestBase.typeface, TestBase.fontSize, 650, 92, TestBase.textAlign, TestBase.textBaseline); + TestBase.textAlign = AlphaSkiaTextAlign.Center; + TestBase.typeface = TestBase.getTypeface("PT Serif", false, false); + TestBase.fontSize = 20; + TestBase.textAlign = AlphaSkiaTextAlign.Center; + canvas.fillText("Album", TestBase.typeface, TestBase.fontSize, 650, 112, TestBase.textAlign, TestBase.textBaseline); + TestBase.textAlign = AlphaSkiaTextAlign.Center; + TestBase.typeface = TestBase.getTypeface("PT Serif", false, false); + TestBase.fontSize = 15; + TestBase.textAlign = AlphaSkiaTextAlign.Right; + canvas.fillText("Music by Music", TestBase.typeface, TestBase.fontSize, 1260, 132, TestBase.textAlign, TestBase.textBaseline); + TestBase.textAlign = AlphaSkiaTextAlign.Center; + TestBase.typeface = TestBase.getTypeface("PT Serif", false, false); + TestBase.fontSize = 15; + TestBase.textAlign = AlphaSkiaTextAlign.Left; + canvas.fillText("Words by Words", TestBase.typeface, TestBase.fontSize, 40, 132, TestBase.textAlign, TestBase.textBaseline); + TestBase.textAlign = AlphaSkiaTextAlign.Center; + return canvas.endRender(); +} +function drawMusicSheetPart2(canvas: AlphaSkiaCanvas): AlphaSkiaImage { + canvas.beginRender(1300, 38, TestBase.renderScale); + TestBase.textAlign = AlphaSkiaTextAlign.Center; + TestBase.typeface = TestBase.getTypeface("PT Serif", false, true); + TestBase.fontSize = 12; + TestBase.textAlign = AlphaSkiaTextAlign.Left; + canvas.fillText("Guitar Standard Tuning", TestBase.typeface, TestBase.fontSize, 40, 3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textAlign = AlphaSkiaTextAlign.Center; + return canvas.endRender(); +} +function drawMusicSheetPart3(canvas: AlphaSkiaCanvas): AlphaSkiaImage { + canvas.beginRender(1300, 6, TestBase.renderScale); + TestBase.textAlign = AlphaSkiaTextAlign.Center; + return canvas.endRender(); +} +function drawMusicSheetPart4(canvas: AlphaSkiaCanvas): AlphaSkiaImage { + canvas.beginRender(1300, 373, TestBase.renderScale); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + TestBase.textAlign = AlphaSkiaTextAlign.Left; + TestBase.typeface = TestBase.getTypeface("PT Serif", true, false); + TestBase.fontSize = 14; + canvas.fillText("\uE1D5", TestBase.musicTypeface, TestBase.musicFontSize * 0.75, 156.6767578125, 40, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("= 120", TestBase.typeface, TestBase.fontSize, 169.1767578125, 27, TestBase.textAlign, TestBase.textBaseline); + canvas.color = AlphaSkiaCanvas.rgbaToColor(165, 165, 165, 255); + canvas.fillRect(92, 130, 254, 1.04); + canvas.fillRect(92, 139, 254, 1.04); + canvas.fillRect(92, 148, 254, 1.04); + canvas.fillRect(92, 157, 254, 1.04); + canvas.fillRect(92, 166, 254, 1.04); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + canvas.fillText("\uE050", TestBase.musicTypeface, TestBase.musicFontSize * 1, 94, 157.52, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE084", TestBase.musicTypeface, TestBase.musicFontSize * 1, 127, 139, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE084", TestBase.musicTypeface, TestBase.musicFontSize * 1, 127, 157, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.color = AlphaSkiaCanvas.rgbaToColor(200, 0, 0, 255); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("1", TestBase.typeface, TestBase.fontSize, 141, 118.75, TestBase.textAlign, TestBase.textBaseline); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 152.1767578125, 148, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE272", TestBase.musicTypeface, TestBase.musicFontSize * 0.75, 172.86069962449085, 148, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 0.75, 180.86069962449085, 149, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.beginPath(); + canvas.moveTo(156.6767578125, 144); + canvas.lineTo(170.855110706305, 133.50723450810483); + canvas.lineTo(184.23569962449085, 145); + canvas.stroke(); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 192.23569962449085, 148, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 0.75, 220.91964143648167, 144.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.beginPath(); + canvas.moveTo(196.73569962449085, 144); + canvas.lineTo(209.12929655256082, 131.33765133817155); + canvas.lineTo(224.29464143648167, 140.5); + canvas.stroke(); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 232.29464143648167, 148, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE262", TestBase.musicTypeface, TestBase.musicFontSize * 0.75, 252.9785832484725, 143.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 0.75, 260.9785832484725, 144.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.beginPath(); + canvas.moveTo(236.79464143648167, 144); + canvas.lineTo(249.1882383645516, 131.33765133817155); + canvas.lineTo(264.3535832484725, 140.5); + canvas.stroke(); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 272.3535832484725, 148, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 0.75, 294.5517916242362, 140, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.beginPath(); + canvas.moveTo(276.8535832484725, 152); + canvas.lineTo(292.1060513036615, 156.93783819474976); + canvas.lineTo(297.9267916242362, 142); + canvas.stroke(); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 305.9267916242362, 148, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE262", TestBase.musicTypeface, TestBase.musicFontSize * 0.75, 324.625, 139, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 0.75, 332.625, 140, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.beginPath(); + canvas.moveTo(310.4267916242362, 152); + canvas.lineTo(327.2193887931866, 157.24460932567123); + canvas.lineTo(336, 142); + canvas.stroke(); + canvas.fillRect(345, 130.1, 1, 36); + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(152.1767578125, 148); + canvas.lineTo(152.1767578125, 175); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(192.23569962449085, 148); + canvas.lineTo(192.23569962449085, 175); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(232.29464143648167, 148); + canvas.lineTo(232.29464143648167, 175); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(281.3535832484725, 148); + canvas.lineTo(281.3535832484725, 121); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.beginPath(); + canvas.moveTo(281.3535832484725, 121); + canvas.lineTo(314.9267916242362, 121); + canvas.lineTo(314.9267916242362, 125); + canvas.lineTo(281.3535832484725, 125); + canvas.closePath(); + canvas.fill(); + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(314.9267916242362, 148); + canvas.lineTo(314.9267916242362, 121); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 100); + canvas.color = AlphaSkiaCanvas.rgbaToColor(165, 165, 165, 255); + canvas.fillRect(346, 130, 126, 1.04); + canvas.fillRect(346, 139, 126, 1.04); + canvas.fillRect(346, 148, 126, 1.04); + canvas.fillRect(346, 157, 126, 1.04); + canvas.fillRect(346, 166, 126, 1.04); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + canvas.color = AlphaSkiaCanvas.rgbaToColor(200, 0, 0, 255); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("2", TestBase.typeface, TestBase.fontSize, 346, 118.75, TestBase.textAlign, TestBase.textBaseline); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 357.1767578125, 139, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 378.359547484596, 139, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 399.542337156692, 161.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A3", TestBase.musicTypeface, TestBase.musicFontSize * 1, 426.623147703567, 157, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillRect(471, 130.1, 1, 36); + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(366.1767578125, 139); + canvas.lineTo(366.1767578125, 112); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.beginPath(); + canvas.moveTo(366.1767578125, 112); + canvas.lineTo(387.359547484596, 117); + canvas.lineTo(387.359547484596, 121); + canvas.lineTo(366.1767578125, 116); + canvas.closePath(); + canvas.fill(); + canvas.beginPath(); + canvas.moveTo(366.1767578125, 118); + canvas.lineTo(387.359547484596, 123); + canvas.lineTo(387.359547484596, 127); + canvas.lineTo(366.1767578125, 122); + canvas.closePath(); + canvas.fill(); + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(387.359547484596, 139); + canvas.lineTo(387.359547484596, 117); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.beginPath(); + canvas.moveTo(387.359547484596, 117); + canvas.lineTo(408.542337156692, 122); + canvas.lineTo(408.542337156692, 126); + canvas.lineTo(387.359547484596, 121); + canvas.closePath(); + canvas.fill(); + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(408.542337156692, 161.5); + canvas.lineTo(408.542337156692, 122); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(435.623147703567, 130); + canvas.lineTo(435.623147703567, 157); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 100); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + TestBase.textAlign = AlphaSkiaTextAlign.Center; + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("PT Serif", false, true); + TestBase.fontSize = 12; + canvas.beginPath(); + canvas.moveTo(366.1767578125, 102); + canvas.lineTo(366.1767578125, 97); + canvas.lineTo(394.7559555656507, 104); + canvas.stroke(); + canvas.beginPath(); + canvas.moveTo(407.04394995041633, 107); + canvas.lineTo(435.623147703567, 115); + canvas.lineTo(435.623147703567, 120); + canvas.stroke(); + canvas.fillText("3", TestBase.typeface, TestBase.fontSize, 400.8999527580335, 106, TestBase.textAlign, TestBase.textBaseline); + TestBase.textAlign = AlphaSkiaTextAlign.Left; + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + canvas.color = AlphaSkiaCanvas.rgbaToColor(165, 165, 165, 255); + canvas.fillRect(472, 130, 206, 1.04); + canvas.fillRect(472, 139, 206, 1.04); + canvas.fillRect(472, 148, 206, 1.04); + canvas.fillRect(472, 157, 206, 1.04); + canvas.fillRect(472, 166, 206, 1.04); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + canvas.color = AlphaSkiaCanvas.rgbaToColor(200, 0, 0, 255); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("3", TestBase.typeface, TestBase.fontSize, 472, 118.75, TestBase.textAlign, TestBase.textBaseline); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 496.32666015625, 157, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 496.32666015625, 143.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 496.32666015625, 134.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.beginRotate(527.8699951171875, 172, -90); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 0, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 10.799999999999999, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 21.599999999999998, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 32.4, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.endRotate(); + canvas.beginPath(); + canvas.moveTo(527.8699951171875, 180); + canvas.lineTo(531.8699951171875, 172); + canvas.lineTo(523.8699951171875, 172); + canvas.closePath(); + canvas.fill(); + canvas.color = AlphaSkiaCanvas.rgbaToColor(165, 165, 165, 255); + canvas.fillRect(533.8699951171875, 175, 15, 1); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 536.8699951171875, 175, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 536.8699951171875, 166, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 536.8699951171875, 157, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 536.8699951171875, 143.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 536.8699951171875, 134.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.color = AlphaSkiaCanvas.rgbaToColor(165, 165, 165, 255); + canvas.fillRect(574.413330078125, 175, 15, 1); + canvas.fillRect(574.413330078125, 184, 15, 1); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 577.413330078125, 184, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 577.413330078125, 166, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 577.413330078125, 152.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.beginRotate(596.9566650390625, 137.5, 90); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 0, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 10.799999999999999, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 21.599999999999998, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 32.4, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 43.199999999999996, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.endRotate(); + canvas.beginPath(); + canvas.moveTo(596.9566650390625, 129.5); + canvas.lineTo(600.9566650390625, 137.5); + canvas.lineTo(592.9566650390625, 137.5); + canvas.closePath(); + canvas.fill(); + canvas.fillText("\uE262", TestBase.musicTypeface, TestBase.musicFontSize * 1, 607.9566650390625, 143.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.color = AlphaSkiaCanvas.rgbaToColor(165, 165, 165, 255); + canvas.fillRect(614.9566650390625, 175, 15, 1); + canvas.fillRect(614.9566650390625, 184, 15, 1); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 617.9566650390625, 184, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 617.9566650390625, 166, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 617.9566650390625, 152.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 617.9566650390625, 143.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uE0A4", TestBase.musicTypeface, TestBase.musicFontSize * 1, 617.9566650390625, 134.5, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillRect(670, 130.1, 1, 36); + canvas.fillRect(674, 130.1, 4, 36); + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(496.32666015625, 134.5); + canvas.lineTo(496.32666015625, 184); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(545.8699951171875, 107.5); + canvas.lineTo(545.8699951171875, 175); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(586.413330078125, 125.5); + canvas.lineTo(586.413330078125, 184); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.lineWidth = 0.96; + canvas.beginPath(); + canvas.moveTo(626.9566650390625, 107.5); + canvas.lineTo(626.9566650390625, 184); + canvas.stroke(); + canvas.lineWidth = 1; + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 100); + canvas.color = AlphaSkiaCanvas.rgbaToColor(165, 165, 165, 255); + canvas.fillRect(92, 284, 254, 1.04); + canvas.fillRect(92, 295, 254, 1.04); + canvas.fillRect(92, 306, 254, 1.04); + canvas.fillRect(92, 317, 61.02685546875, 1.04); + canvas.fillRect(161.32666015625, 317, 31.759140014648438, 1.04); + canvas.fillRect(201.38560485839844, 317, 31.759140014648438, 1.04); + canvas.fillRect(241.44454956054688, 317, 31.759124755859375, 1.04); + canvas.fillRect(281.50347900390625, 317, 25.273406982421875, 1.04); + canvas.fillRect(315.0766906738281, 317, 30.923309326171875, 1.04); + canvas.fillRect(92, 328, 254, 1.04); + canvas.fillRect(92, 339, 254, 1.04); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + canvas.fillText("\uE06D", TestBase.musicTypeface, TestBase.musicFontSize * 0.9230769230769231, 97, 311.8, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 163.32666015625, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("9", TestBase.typeface, TestBase.fontSize, 153.02685546875, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + canvas.beginPath(); + canvas.beginPath(); + canvas.moveTo(188.23569962449085, 274.7); + canvas.lineTo(185.23569962449085, 280.7); + canvas.lineTo(191.23569962449085, 280.7); + canvas.closePath(); + canvas.fill(); + canvas.stroke(); + canvas.moveTo(162.32666015625, 317.3); + canvas.bezierCurveTo(175.28117989037042, 317.3, 188.23569962449085, 317.3, 188.23569962449085, 280.7); + canvas.stroke(); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("\u00BC", TestBase.typeface, TestBase.fontSize, 183.47495743699085, 260.7, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 203.38560196824085, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("9", TestBase.typeface, TestBase.fontSize, 193.08579728074085, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + canvas.beginPath(); + canvas.beginPath(); + canvas.moveTo(228.29464143648167, 268.7); + canvas.lineTo(225.29464143648167, 274.7); + canvas.lineTo(231.29464143648167, 274.7); + canvas.closePath(); + canvas.fill(); + canvas.stroke(); + canvas.moveTo(202.38560196824085, 317.3); + canvas.bezierCurveTo(215.34012170236127, 317.3, 228.29464143648167, 317.3, 228.29464143648167, 274.7); + canvas.stroke(); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("\u00BD", TestBase.typeface, TestBase.fontSize, 223.25142854585667, 254.7, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 243.44454378023167, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("9", TestBase.typeface, TestBase.fontSize, 233.14473909273167, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + canvas.beginPath(); + canvas.beginPath(); + canvas.moveTo(268.3535832484725, 256.7); + canvas.lineTo(265.3535832484725, 262.7); + canvas.lineTo(271.3535832484725, 262.7); + canvas.closePath(); + canvas.fill(); + canvas.stroke(); + canvas.moveTo(242.44454378023167, 317.3); + canvas.bezierCurveTo(255.39906351435206, 317.3, 268.3535832484725, 317.3, 268.3535832484725, 262.7); + canvas.stroke(); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("full", TestBase.typeface, TestBase.fontSize, 259.3589543422225, 242.7, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 283.5034855922225, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("9", TestBase.typeface, TestBase.fontSize, 273.2036809047225, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + canvas.beginPath(); + canvas.beginPath(); + canvas.moveTo(301.9267916242362, 244.7); + canvas.lineTo(298.9267916242362, 250.7); + canvas.lineTo(304.9267916242362, 250.7); + canvas.closePath(); + canvas.fill(); + canvas.stroke(); + canvas.moveTo(282.5034855922225, 317.3); + canvas.bezierCurveTo(292.2151386082294, 317.3, 301.9267916242362, 317.3, 301.9267916242362, 250.7); + canvas.stroke(); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("1\u00BD", TestBase.typeface, TestBase.fontSize, 293.2336763898612, 230.7, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 317.0766939679862, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("9", TestBase.typeface, TestBase.fontSize, 306.7768892804862, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + canvas.beginPath(); + canvas.beginPath(); + canvas.moveTo(340, 232.7); + canvas.lineTo(337, 238.7); + canvas.lineTo(343, 238.7); + canvas.closePath(); + canvas.fill(); + canvas.stroke(); + canvas.moveTo(316.0766939679862, 317.3); + canvas.bezierCurveTo(328.0383469839931, 317.3, 340, 317.3, 340, 238.7); + canvas.stroke(); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("2", TestBase.typeface, TestBase.fontSize, 336.35009765625, 218.7, TestBase.textAlign, TestBase.textBaseline); + canvas.fillRect(345, 284.3, 1, 54.99999999999994); + canvas.color = AlphaSkiaCanvas.rgbaToColor(165, 165, 165, 255); + canvas.fillRect(346, 284, 126, 1.04); + canvas.fillRect(346, 295, 12.02685546875, 1.04); + canvas.fillRect(366.32666015625, 295, 12.882984161376953, 1.04); + canvas.fillRect(387.50944900512695, 295, 84.49055099487305, 1.04); + canvas.fillRect(346, 306, 126, 1.04); + canvas.fillRect(346, 317, 54.392433166503906, 1.04); + canvas.fillRect(408.6922378540039, 317, 18.781005859375, 1.04); + canvas.fillRect(435.7730484008789, 317, 36.226951599121094, 1.04); + canvas.fillRect(346, 328, 126, 1.04); + canvas.fillRect(346, 339, 126, 1.04); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 368.32666015625, 295.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("3", TestBase.typeface, TestBase.fontSize, 358.02685546875, 295.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 389.509449828346, 295.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("3", TestBase.typeface, TestBase.fontSize, 379.209645140846, 295.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 410.692239500442, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("3", TestBase.typeface, TestBase.fontSize, 400.392434812942, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 437.773050047317, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("5", TestBase.typeface, TestBase.fontSize, 427.473245359817, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + canvas.fillRect(471, 284.3, 1, 54.99999999999994); + canvas.color = AlphaSkiaCanvas.rgbaToColor(165, 165, 165, 255); + canvas.fillRect(472, 284, 25.1767578125, 1.04); + canvas.fillRect(505.4765625, 284, 32.2435302734375, 1.04); + canvas.fillRect(546.0198974609375, 284, 72.786865234375, 1.04); + canvas.fillRect(627.1065673828125, 284, 50.8934326171875, 1.04); + canvas.fillRect(472, 295, 25.1767578125, 1.04); + canvas.fillRect(505.4765625, 295, 32.2435302734375, 1.04); + canvas.fillRect(546.0198974609375, 295, 72.786865234375, 1.04); + canvas.fillRect(627.1065673828125, 295, 50.8934326171875, 1.04); + canvas.fillRect(472, 306, 25.1767578125, 1.04); + canvas.fillRect(505.4765625, 306, 32.2435302734375, 1.04); + canvas.fillRect(546.0198974609375, 306, 32.2435302734375, 1.04); + canvas.fillRect(586.563232421875, 306, 32.2435302734375, 1.04); + canvas.fillRect(627.1065673828125, 306, 50.8934326171875, 1.04); + canvas.fillRect(472, 317, 65.7200927734375, 1.04); + canvas.fillRect(546.0198974609375, 317, 32.2435302734375, 1.04); + canvas.fillRect(586.563232421875, 317, 32.2435302734375, 1.04); + canvas.fillRect(627.1065673828125, 317, 50.8934326171875, 1.04); + canvas.fillRect(472, 328, 65.7200927734375, 1.04); + canvas.fillRect(546.0198974609375, 328, 32.2435302734375, 1.04); + canvas.fillRect(586.563232421875, 328, 32.2435302734375, 1.04); + canvas.fillRect(627.1065673828125, 328, 50.8934326171875, 1.04); + canvas.fillRect(472, 339, 206, 1.04); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + canvas.beginPath(); + canvas.moveTo(488, 275.8); + canvas.lineTo(488, 312.8); + canvas.stroke(); + canvas.beginPath(); + canvas.moveTo(488, 275.8); + canvas.lineTo(492, 283.8); + canvas.lineTo(484, 283.8); + canvas.closePath(); + canvas.fill(); + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 507.4765625, 284.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("0", TestBase.typeface, TestBase.fontSize, 497.1767578125, 284.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 507.4765625, 295.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("1", TestBase.typeface, TestBase.fontSize, 497.1767578125, 295.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 507.4765625, 306.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("0", TestBase.typeface, TestBase.fontSize, 497.1767578125, 306.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + canvas.beginRotate(527.7200927734375, 326.8, -90); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 0, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 10.799999999999999, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 21.599999999999998, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 32.4, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 43.199999999999996, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.endRotate(); + canvas.beginPath(); + canvas.moveTo(528, 334.8); + canvas.lineTo(532, 326.8); + canvas.lineTo(524, 326.8); + canvas.closePath(); + canvas.fill(); + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 548.0198974609375, 284.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("0", TestBase.typeface, TestBase.fontSize, 537.7200927734375, 284.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 548.0198974609375, 295.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("1", TestBase.typeface, TestBase.fontSize, 537.7200927734375, 295.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 548.0198974609375, 306.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("0", TestBase.typeface, TestBase.fontSize, 537.7200927734375, 306.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 548.0198974609375, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("2", TestBase.typeface, TestBase.fontSize, 537.7200927734375, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 548.0198974609375, 328.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("3", TestBase.typeface, TestBase.fontSize, 537.7200927734375, 328.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + canvas.beginPath(); + canvas.moveTo(569, 297.8); + canvas.lineTo(569, 334.8); + canvas.stroke(); + canvas.beginPath(); + canvas.moveTo(569, 297.8); + canvas.lineTo(573, 305.8); + canvas.lineTo(565, 305.8); + canvas.closePath(); + canvas.fill(); + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 588.563232421875, 306.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("2", TestBase.typeface, TestBase.fontSize, 578.263427734375, 306.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 588.563232421875, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("2", TestBase.typeface, TestBase.fontSize, 578.263427734375, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 588.563232421875, 328.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("0", TestBase.typeface, TestBase.fontSize, 578.263427734375, 328.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + canvas.beginRotate(596.8067626953125, 283.8, 90); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 0, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 10.799999999999999, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 21.599999999999998, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 32.4, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.fillText("\uEAA4", TestBase.musicTypeface, TestBase.musicFontSize * 1.2, 43.199999999999996, 10.799999999999999, AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); + canvas.endRotate(); + canvas.beginPath(); + canvas.moveTo(597, 275.8); + canvas.lineTo(601, 283.8); + canvas.lineTo(593, 283.8); + canvas.closePath(); + canvas.fill(); + TestBase.textBaseline = AlphaSkiaTextBaseline.Middle; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 629.1065673828125, 284.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("0", TestBase.typeface, TestBase.fontSize, 618.8067626953125, 284.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 629.1065673828125, 295.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("2", TestBase.typeface, TestBase.fontSize, 618.8067626953125, 295.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 629.1065673828125, 306.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("2", TestBase.typeface, TestBase.fontSize, 618.8067626953125, 306.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 629.1065673828125, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("2", TestBase.typeface, TestBase.fontSize, 618.8067626953125, 317.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 11; + canvas.fillText("", TestBase.typeface, TestBase.fontSize, 629.1065673828125, 328.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.typeface = TestBase.getTypeface("Roboto", false, false); + TestBase.fontSize = 13; + canvas.fillText("0", TestBase.typeface, TestBase.fontSize, 618.8067626953125, 328.3, TestBase.textAlign, TestBase.textBaseline); + TestBase.textBaseline = AlphaSkiaTextBaseline.Top; + canvas.fillRect(670, 284.3, 1, 54.99999999999994); + canvas.fillRect(674, 284.3, 4, 54.99999999999994); + canvas.color = AlphaSkiaCanvas.rgbaToColor(153, 51, 204, 100); + canvas.beginPath(); + canvas.moveTo(92, 130.1); + canvas.lineTo(92, 339.3); + canvas.stroke(); + TestBase.typeface = TestBase.getTypeface("PT Serif", false, true); + TestBase.fontSize = 12; + canvas.fillText("s.guit.", TestBase.typeface, TestBase.fontSize, 50, 130.1, TestBase.textAlign, TestBase.textBaseline); + canvas.fillRect(86, 118.1, 3, 233.20000000000002); + canvas.beginPath(); + canvas.moveTo(86, 118.1); + canvas.bezierCurveTo(86, 118.1, 86, 118.1, 98, 115.1); + canvas.bezierCurveTo(92, 121.1, 86, 121.1, 86, 121.1); + canvas.closePath(); + canvas.fill(); + canvas.beginPath(); + canvas.moveTo(86, 351.3); + canvas.bezierCurveTo(86, 351.3, 92, 351.3, 98, 354.3); + canvas.bezierCurveTo(92, 348.3, 86, 348.3, 86, 348.3); + canvas.closePath(); + canvas.fill(); + return canvas.endRender(); +} +function drawMusicSheetPart5(canvas: AlphaSkiaCanvas): AlphaSkiaImage { + canvas.beginRender(116, 24, TestBase.renderScale); + canvas.color = AlphaSkiaCanvas.rgbaToColor(0, 0, 0, 255); + TestBase.typeface = TestBase.getTypeface("Roboto", true, false); + TestBase.fontSize = 12; + TestBase.textAlign = AlphaSkiaTextAlign.Left; + canvas.fillText("rendered by alphaTab", TestBase.typeface, TestBase.fontSize, 0, 12, TestBase.textAlign, TestBase.textBaseline); + return canvas.endRender(); +} +export default { + partPositions: [ + [0, 0, 1300, 164], [0, 164, 1300, 38], [0, 202, 1300, 6], [0, 208, 1300, 373], [591.962890625, 581, 116.07421875, 24] + ], + totalWidth: 1300, + totalHeight: 605, + allParts: [ + drawMusicSheetPart1, drawMusicSheetPart2, drawMusicSheetPart3, drawMusicSheetPart4, drawMusicSheetPart5 + ] +}; diff --git a/test/node/AlphaTabGeneratedRenderTestBase.ts b/test/node/AlphaTabGeneratedRenderTestBase.ts new file mode 100644 index 0000000..e02cf81 --- /dev/null +++ b/test/node/AlphaTabGeneratedRenderTestBase.ts @@ -0,0 +1,44 @@ +import { AlphaSkiaTextAlign, AlphaSkiaTextBaseline, AlphaSkiaTypeface } from "@coderline/alphaskia"; +import * as fs from 'fs'; + +const customTypefaces = new Map(); +function customTypefaceKey(fontFamily: string, isBold: boolean, isItalic: boolean) { + return `${fontFamily.toLocaleLowerCase()}_${isBold}_${isItalic}`; +} + +function getTypeface(fontFamily: string, isBold: boolean, isItalic: boolean): AlphaSkiaTypeface { + const key = customTypefaceKey(fontFamily, isBold, isItalic); + const typeface = customTypefaces.get(key); + if (!typeface) { + throw new Error(`Unknown font requested: ${key}`); + } + return typeface; +} + +function loadTypeface(fontFamily: string, isBold: boolean, isItalic: boolean, filePath: string): AlphaSkiaTypeface { + const key = customTypefaceKey(fontFamily, isBold, isItalic); + console.log(`Loading typeface ${key} from ${filePath}`); + const data = fs.readFileSync(filePath); + + console.log(`Read ${data.length} bytes from file, decoding typeface`); + + const typeface = AlphaSkiaTypeface.register(data.buffer); + if (!typeface) { + throw new Error("Could not create typeface from data") + } + + customTypefaces.set(key, typeface); + return typeface; +} + +export default { + renderScale: 1, + musicTypeface: null! as AlphaSkiaTypeface, + musicFontSize: 34, + textAlign: AlphaSkiaTextAlign.Left, + textBaseline: AlphaSkiaTextBaseline.Top, + typeface: null! as AlphaSkiaTypeface, + fontSize: 12, + getTypeface, + loadTypeface +}; \ No newline at end of file diff --git a/test/node/PixelMatch.ts b/test/node/PixelMatch.ts new file mode 100644 index 0000000..5d35b9c --- /dev/null +++ b/test/node/PixelMatch.ts @@ -0,0 +1,275 @@ +/** + * Based on https://github.com/mapbox/pixelmatch + * ISC License + * Copyright (c) 2019, Mapbox + * + * Permission to use, copy, modify, and/or distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright notice + * and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +export interface PixelMatchColor { + r: number; + g: number; + b: number +} + +export interface PixelMatchOptions { + threshold: number; + diffColor: PixelMatchColor; +} + +export interface PixelMatchResult { + totalPixels: number; + differentPixels: number; + transparentPixels: number; +} + +// blend semi-transparent color with white +function blend(c: number, a: number) { + return 255 + (c - 255) * a; +} +function rgb2y(r: number, g: number, b: number) { + return r * 0.29889531 + g * 0.58662247 + b * 0.11448223; +} +function rgb2i(r: number, g: number, b: number) { + return r * 0.59597799 - g * 0.2741761 - b * 0.32180189; +} +function rgb2q(r: number, g: number, b: number) { + return r * 0.21147017 - g * 0.52261711 + b * 0.31114694; +} + +function drawPixel(output: Uint8Array, pos: number, color: PixelMatchColor) { + output[pos + 0] = color.r; + output[pos + 1] = color.g; + output[pos + 2] = color.b; + output[pos + 3] = 255; +} + +function colorDelta(img1: Uint8Array, img2: Uint8Array, k: number, m: number, yOnly: boolean = false) { + let r1 = img1[k + 0]; + let g1 = img1[k + 1]; + let b1 = img1[k + 2]; + let a1 = img1[k + 3]; + + let r2 = img2[m + 0]; + let g2 = img2[m + 1]; + let b2 = img2[m + 2]; + let a2 = img2[m + 3]; + + if (a1 === a2 && r1 === r2 && g1 === g2 && b1 === b2) return 0; + + if (a1 < 255) { + a1 /= 255; + r1 = blend(r1, a1); + g1 = blend(g1, a1); + b1 = blend(b1, a1); + } + + if (a2 < 255) { + a2 /= 255; + r2 = blend(r2, a2); + g2 = blend(g2, a2); + b2 = blend(b2, a2); + } + + const y = rgb2y(r1, g1, b1) - rgb2y(r2, g2, b2); + + if (yOnly) return y; // brightness difference only + + const i = rgb2i(r1, g1, b1) - rgb2i(r2, g2, b2); + const q = rgb2q(r1, g1, b1) - rgb2q(r2, g2, b2); + + return 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q; +} + +// check if a pixel has 3+ adjacent pixels of the same color. +function hasManySiblings(img: Uint8Array, x1: number, y1: number, width: number, height: number) { + const distance = 1; + const x0 = Math.max(x1 - distance, 0); + const y0 = Math.max(y1 - distance, 0); + const x2 = Math.min(x1 + distance, width - 1); + const y2 = Math.min(y1 + distance, height - 1); + const pos = (y1 * width + x1) * 4; + let zeroes = x1 === x0 || x1 === x2 || y1 === y0 || y1 === y2 ? 1 : 0; + + // go through 8 adjacent pixels + for (let x = x0; x <= x2; x++) { + for (let y = y0; y <= y2; y++) { + if (x === x1 && y === y1) continue; + + const pos2 = (y * width + x) * 4; + if ( + img[pos] === img[pos2] && + img[pos + 1] === img[pos2 + 1] && + img[pos + 2] === img[pos2 + 2] && + img[pos + 3] === img[pos2 + 3] + ) { + zeroes++; + } + + if (zeroes > 2) return true; + } + } + + return false; +} + +// check if a pixel is likely a part of anti-aliasing; +// based on "Anti-aliased Pixel and Intensity Slope Detector" paper by V. Vysniauskas, 2009 + +function antialiased(img: Uint8Array, x1: number, y1: number, width: number, height: number, img2: Uint8Array) { + const distance = 1; + const x0 = Math.max(x1 - distance, 0); + const y0 = Math.max(y1 - distance, 0); + const x2 = Math.min(x1 + distance, width - 1); + const y2 = Math.min(y1 + distance, height - 1); + const pos = (y1 * width + x1) * 4; + let zeroes = x1 === x0 || x1 === x2 || y1 === y0 || y1 === y2 ? 1 : 0; + let min = 0; + let max = 0; + let minX = 0; + let minY = 0; + let maxX = 0; + let maxY = 0; + + // go through 8 adjacent pixels + for (let x = x0; x <= x2; x++) { + for (let y = y0; y <= y2; y++) { + if (x === x1 && y === y1) continue; + + // brightness delta between the center pixel and adjacent one + const delta = colorDelta(img, img, pos, (y * width + x) * 4, true); + + // count the number of equal, darker and brighter adjacent pixels + if (delta === 0) { + zeroes++; + // if found more than 2 equal siblings, it's definitely not anti-aliasing + if (zeroes > 2) return false; + + // remember the darkest pixel + } else if (delta < min) { + min = delta; + minX = x; + minY = y; + + // remember the brightest pixel + } else if (delta > max) { + max = delta; + maxX = x; + maxY = y; + } + } + } + + // if there are no both darker and brighter pixels among siblings, it's not anti-aliasing + if (min === 0 || max === 0) return false; + + // if either the darkest or the brightest pixel has 3+ equal siblings in both images + // (definitely not anti-aliased), this pixel is anti-aliased + return ( + (hasManySiblings(img, minX, minY, width, height) && + hasManySiblings(img2, minX, minY, width, height)) || + (hasManySiblings(img, maxX, maxY, width, height) && + hasManySiblings(img2, maxX, maxY, width, height)) + ); +} + + +export function match( + img1: Uint8Array, + img2: Uint8Array, + output: Uint8Array, + width: number, + height: number, + options: PixelMatchOptions +): PixelMatchResult { + if (img1.length !== img2.length || (output && output.length !== img1.length)) { + throw new Error(`Image sizes do not match`); + } + + if (img1.length !== width * height * 4) throw new Error('Image data size does not match width/height.'); + + // check if images are identical + const len = width * height; + let identical = true; + let transparentPixels = 0; + + for (let i = 0; i < len; i++) { + const img1r = img1[(i * 4) + 0]; + const img1g = img1[(i * 4) + 1]; + const img1b = img1[(i * 4) + 2]; + const img1a = img1[(i * 4) + 3]; + + const img2r = img2[(i * 4) + 0]; + const img2g = img2[(i * 4) + 1]; + const img2b = img2[(i * 4) + 2]; + const img2a = img2[(i * 4) + 3]; + + if (img1r !== img2r || img1g !== img2g || img1b !== img2b || img1a !== img2a) { + identical = false; + break; + } + if (img1a === 0) { + transparentPixels++; + } + } + if (identical) { + return { + totalPixels: len, + differentPixels: 0, + transparentPixels: transparentPixels + }; + } + + transparentPixels = 0; + + // maximum acceptable square distance between two colors; + // 35215 is the maximum possible value for the YIQ difference metric + const maxDelta = 35215 * options.threshold! * options.threshold!; + + let diff = 0; + + // compare each pixel of one image against the other one + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const pos = (y * width + x) * 4; + + if (img1[pos + 3] === 0) { + transparentPixels++; + } + + // squared YUV distance between colors at this pixel position + const delta = colorDelta(img1, img2, pos, pos); + + // the color difference is above the threshold + if (delta > maxDelta) { + // check it's a real rendering difference or just anti-aliasing + if ((antialiased(img1, x, y, width, height, img2) || + antialiased(img2, x, y, width, height, img1)) + ) { + // match + } else { + // found substantial difference not caused by anti-aliasing; draw it as red + drawPixel(output, pos, options.diffColor); + diff++; + } + } + } + } + + // return the number of different pixels + return { + totalPixels: len, + differentPixels: diff, + transparentPixels: transparentPixels + }; +} \ No newline at end of file diff --git a/test/node/index.ts b/test/node/index.ts new file mode 100644 index 0000000..1a3747b --- /dev/null +++ b/test/node/index.ts @@ -0,0 +1,198 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; + +import { AlphaSkiaCanvas, AlphaSkiaImage } from '@coderline/alphaskia'; + +import TestBase from './AlphaTabGeneratedRenderTestBase' +import Test from './AlphaTabGeneratedRenderTest' +import { match } from 'PixelMatch'; + + +function findRepositoryRoot(current: string): string { + if (fs.existsSync(path.join(current, ".nuke"))) { + return current; + } + + const parent = path.resolve(current, '..'); + if (parent == current) { + throw new Error("could not find repository root"); + } + + return findRepositoryRoot(parent); +} + +function renderFullImage(): AlphaSkiaImage { + using fullImageCanvas = new AlphaSkiaCanvas(); + console.log(`Begin render with size ${Test.totalWidth}x${Test.totalHeight} at scale ${TestBase.renderScale}`); + fullImageCanvas.beginRender(Test.totalWidth, Test.totalHeight, TestBase.renderScale); + + using partialCanvas = new AlphaSkiaCanvas(); + for (let i = 0; i < Test.allParts.length; i++) { + console.log(`Render part ${i}`); + using part = Test.allParts[i](partialCanvas); + + const x = Test.partPositions[i][0]; + const y = Test.partPositions[i][1]; + const w = Test.partPositions[i][2]; + const h = Test.partPositions[i][3]; + + console.log(`Drawing part ${i} into full image at ${x}/${y}/${w}/${h}`); + fullImageCanvas.drawImage(part, x, y, w, h); + } + + const fullImage = fullImageCanvas.endRender(); + console.log("Render of full image completed"); + return fullImage!; +} + +let operatingSystemRid; +switch (os.type()) { + case "Linux": + operatingSystemRid = "linux"; + break; + case "Darwin": + operatingSystemRid = "macos"; + break; + case "Windows_NT": + operatingSystemRid = "win"; + break; + default: + throw new Error("Unsupported test platform"); +} + +let architectureRid; +switch (process.arch) { + case "arm": + architectureRid = "arm"; + break; + case "arm64": + architectureRid = "arm64"; + break; + case "x64": + architectureRid = "x64"; + break; + case "ia32": + architectureRid = "x86"; + break; + default: + throw new Error("Unsupported test platform"); + break; +} + +const alphaSkiaRid = operatingSystemRid + "-" + architectureRid; + + +function main(): number { + const tolerancePercent = 1; + try { + const repositoryRoot = findRepositoryRoot(path.resolve('.')); + + // Load all fonts for rendering + console.log("Loading fonts"); + let testDataPath = path.join(repositoryRoot, "test", "test-data"); + TestBase.musicTypeface = TestBase.loadTypeface("Bravura", false, false, + path.join(testDataPath, "font", "bravura", "Bravura.ttf")); + TestBase.loadTypeface("Roboto", false, false, + path.join(testDataPath, "font", "roboto", "Roboto-Regular.ttf")); + TestBase.loadTypeface("Roboto", true, false, + path.join(testDataPath, "font", "roboto", "Roboto-Bold.ttf")); + TestBase.loadTypeface("Roboto", false, true, + path.join(testDataPath, "font", "roboto", "Roboto-Italic.ttf")); + TestBase.loadTypeface("Roboto", true, true, + path.join(testDataPath, "font", "roboto", "Roboto-BoldItalic.ttf")); + TestBase.loadTypeface("PT Serif", false, false, + path.join(testDataPath, "font", "ptserif", "PTSerif-Regular.ttf")); + TestBase.loadTypeface("PT Serif", true, false, + path.join(testDataPath, "font", "ptserif", "PTSerif-Bold.ttf")); + TestBase.loadTypeface("PT Serif", false, true, + path.join(testDataPath, "font", "ptserif", "PTSerif-Italic.ttf")); + TestBase.loadTypeface("PT Serif", true, true, + path.join(testDataPath, "font", "ptserif", "PTSerif-BoldItalic.ttf")); + console.log("Fonts loaded"); + + // render full image + console.log("Rendering image"); + using actualImage = renderFullImage(); + console.log("Image rendered"); + + // save png for future reference + console.log("Saving image as PNG"); + + let testOutputPath = path.join(repositoryRoot, "test", "test-outputs", "dotnet"); + fs.mkdirSync(testOutputPath, { recursive: true }) + + let testOutputFile = path.join(testOutputPath, alphaSkiaRid + ".png"); + let pngData = actualImage.toPng(); + if (!pngData) { + throw new Error("Failed to encode final image to png"); + } + + fs.writeFileSync(testOutputFile, new Uint8Array(pngData)); + console.log(`Image saved to ${testOutputFile}`); + + // load reference image + let testReferencePath = path.join(testDataPath, "reference", alphaSkiaRid + ".png"); + console.log(`Loading reference image ${testReferencePath}`); + + let testReferenceData = fs.readFileSync(testReferencePath); + using expectedImage = AlphaSkiaImage.decode(testReferenceData.buffer); + if (expectedImage == null) { + throw new Error("Failed to decode reference image"); + } + + console.log("Reference image loaded"); + + // compare images + console.log("Comparing images"); + let actualWidth = actualImage.width; + let actualHeight = actualImage.height; + + let expectedWidth = expectedImage.width; + let expectedHeight = expectedImage.height; + if (actualWidth != expectedWidth || actualHeight != expectedHeight) { + console.error( + `Image sizes do not match: Actual[${actualWidth}x${actualHeight}] Expected[${expectedWidth}x${actualHeight}]`); + return 1; + } + + let actualPixels = new Uint8Array(actualImage.readPixels()!); + let expectedPixels = new Uint8Array(expectedImage.readPixels()!); + let diffPixels = new Uint8Array(actualPixels.byteLength); + + let compareResult = match(actualPixels, + expectedPixels, + diffPixels, + actualWidth, + actualHeight, + { + threshold: 0.3, + diffColor: { r: 255, g: 0, b: 0 } + }); + + let totalPixels = compareResult.totalPixels - compareResult.transparentPixels; + let percentDifference = (compareResult.differentPixels / totalPixels) * 100; + let pass = percentDifference < tolerancePercent; + if (!pass) { + console.error(`Difference between original and new image is too big: ${compareResult.differentPixels}/${totalPixels}(${percentDifference}%)`); + + // create diff image as PNG + using diffImage = AlphaSkiaImage.fromPixels(actualWidth, actualHeight, diffPixels)!; + let diffImagePngData = diffImage.toPng()!; + + let diffOutputPath = path.join(testOutputPath, alphaSkiaRid + ".diff.png"); + fs.writeFileSync(diffOutputPath, diffImagePngData); + console.log(`Error diff image saved to ${diffOutputPath}`); + return 1; + } + + console.log(`Images match. Total Pixels:${compareResult.totalPixels}, Transparent Pixels: ${compareResult.transparentPixels}, Percent difference: ${percentDifference}%`); + return 0; + } + catch (e) { + console.error(`Failed to run test`, e); + return 1; + } +} + +process.exit(main()); \ No newline at end of file diff --git a/test/node/package-lock.json b/test/node/package-lock.json new file mode 100644 index 0000000..a0b1363 --- /dev/null +++ b/test/node/package-lock.json @@ -0,0 +1,496 @@ +{ + "name": "alphaskiatest", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "alphaskiatest", + "version": "1.0.0", + "dependencies": { + "@coderline/alphaskia": "file:../../dist/NodeTars/coderline-alphaskia.tgz", + "@coderline/alphaskia-linux": "file:../../dist/NodeTars/coderline-alphaskia-linux.tgz", + "@coderline/alphaskia-macos": "file:../../dist/NodeTars/coderline-alphaskia-macos.tgz", + "@coderline/alphaskia-windows": "file:../../dist/NodeTars/coderline-alphaskia-windows.tgz", + "tsx": "^3.13.0" + } + }, + "node_modules/@coderline/alphaskia": { + "version": "1.0.0-local.0", + "resolved": "file:../../dist/NodeTars/coderline-alphaskia.tgz", + "integrity": "sha512-uWa+Nf1klIV0Hrwpy0herWbxVkkN8xli9V4SlTx2IMMw/w854zron/otogMHmwPEw1kvGHQw9zaZucd96lFLyg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "@coderline/alphaskia-linux": "^1.0.0", + "@coderline/alphaskia-macos": "^1.0.0", + "@coderline/alphaskia-windows": "^1.0.0" + } + }, + "node_modules/@coderline/alphaskia-linux": { + "version": "1.0.0-local.0", + "resolved": "file:../../dist/NodeTars/coderline-alphaskia-linux.tgz", + "integrity": "sha512-fsJLwL0q+TK99H1kZPj4OHE+q9v0MNXAqD+4tSLbB01M2VHDKd9oDjenK+yaJOXSzflBC1lVMONTjulzz1FoLQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@coderline/alphaskia-macos": { + "version": "1.0.0-local.0", + "resolved": "file:../../dist/NodeTars/coderline-alphaskia-macos.tgz", + "integrity": "sha512-HtlkmftKtG2YHPkkeaVPjjFK6ElgTNMC8TscIIhbNDzdLgA6d09/byCzyh1K8+/yFY8omv8zq/WGfeeclYRTjQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@coderline/alphaskia-windows": { + "version": "1.0.0-local.0", + "resolved": "file:../../dist/NodeTars/coderline-alphaskia-windows.tgz", + "integrity": "sha512-4w5ZbWxacjEOoSjT8NJL5RuOepcTbIHljH0DAYY03b97hzbc8PT/XL6eXBnGhNL6U7tIEMBlUg+PfthANHChgg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/tsx": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.13.0.tgz", + "integrity": "sha512-rjmRpTu3as/5fjNq/kOkOtihgLxuIz6pbKdj9xwP4J5jOLkBxw/rjN5ANw+KyrrOXV5uB7HC8+SrrSJxT65y+A==", + "dependencies": { + "esbuild": "~0.18.20", + "get-tsconfig": "^4.7.2", + "source-map-support": "^0.5.21" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + } + } +} diff --git a/test/node/package.json b/test/node/package.json new file mode 100644 index 0000000..0598dab --- /dev/null +++ b/test/node/package.json @@ -0,0 +1,15 @@ +{ + "name": "alphaskiatest", + "version": "1.0.0", + "private": true, + "scripts": { + "start": "tsx index.ts" + }, + "dependencies": { + "@coderline/alphaskia": "file:../../dist/NodeTars/coderline-alphaskia.tgz", + "@coderline/alphaskia-linux": "file:../../dist/NodeTars/coderline-alphaskia-linux.tgz", + "@coderline/alphaskia-macos": "file:../../dist/NodeTars/coderline-alphaskia-macos.tgz", + "@coderline/alphaskia-windows": "file:../../dist/NodeTars/coderline-alphaskia-windows.tgz", + "tsx": "^3.13.0" + } +} diff --git a/test/node/tsconfig.json b/test/node/tsconfig.json new file mode 100644 index 0000000..42f6fd4 --- /dev/null +++ b/test/node/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "target": "ES2022", + "module": "ES2022", + "lib": [ + "es2015", + "es2016", + "es2017", + "es2021" + ], + "strict": true, + "allowSyntheticDefaultImports": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "emitDecoratorMetadata": false, + "noImplicitAny": true, + "noImplicitThis": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noImplicitOverride": true, + "sourceMap": true, + "declaration": true, + "incremental": true, + "declarationDir": "dist/types", + "outDir": "dist/lib", + "baseUrl": ".", + "typeRoots": [ + "types", + "node_modules/@types" + ] + }, +} \ No newline at end of file diff --git a/test/test-generator/AlphaSkiaUnitTestGenerator.cs b/test/test-generator/AlphaSkiaUnitTestGenerator.cs index a6e304d..e4bc2fa 100644 --- a/test/test-generator/AlphaSkiaUnitTestGenerator.cs +++ b/test/test-generator/AlphaSkiaUnitTestGenerator.cs @@ -1,5 +1,4 @@ -using System.Diagnostics.Tracing; -using AlphaTab; +using AlphaTab; using AlphaTab.Importer; using AlphaTab.Model; using AlphaTab.Rendering; @@ -89,22 +88,25 @@ public static string GenerateCSharp() sourceCode.WriteLine("// "); sourceCode.WriteLine("// This file has been auto generated by a unit test in alphaTab."); sourceCode.WriteLine("// "); - sourceCode.WriteLine("namespace AlphaSkia.Test;\n"); - sourceCode.WriteLine("public class AlphaTabGeneratedRenderTest : MusicSheetRenderTest"); + sourceCode.WriteLine("#nullable enable"); + sourceCode.WriteLine(); + sourceCode.WriteLine("namespace AlphaSkia.Test;"); + sourceCode.WriteLine(); + sourceCode.WriteLine("partial class AlphaTabGeneratedRenderTest"); sourceCode.BeginBlock(); sourceCode.Suspend(); var result = GenerateTestCode(settings); sourceCode.Resume(); - sourceCode.WriteLine("protected override float[,] PartPositions => new float[,] {"); + sourceCode.WriteLine("public static float[,] PartPositions => new float[,] {"); sourceCode.WriteLine(string.Join(", ", result.partialPositions.Select(p => "{" + string.Join(", ", p.Select(v => $"(float){v}")) + "}"))); sourceCode.WriteLine(" };"); - sourceCode.WriteLine($"protected override int TotalWidth => (int){result.totalWidth};"); - sourceCode.WriteLine($"protected override int TotalHeight => (int){result.totalHeight};"); - sourceCode.WriteLine("protected override Func[] AllParts => new[]"); + sourceCode.WriteLine($"public static int TotalWidth => (int){result.totalWidth};"); + sourceCode.WriteLine($"public static int TotalHeight => (int){result.totalHeight};"); + sourceCode.WriteLine("public static Func[] AllParts => new[]"); sourceCode.BeginBlock(); var parts = result.partialPositions.Select((_, i) => $"DrawMusicSheetPart{i + 1}"); sourceCode.WriteLine(string.Join(", ", parts)); @@ -132,17 +134,18 @@ public static string GenerateJava() ); sourceCode.Resume(); - sourceCode.WriteLine("package net.alphatab.alphaskia;"); + sourceCode.WriteLine("package net.alphatab.alphaskia.test;"); + sourceCode.WriteLine(); + sourceCode.WriteLine("import net.alphatab.alphaskia.*;"); sourceCode.WriteLine(); - sourceCode.WriteLine("public class AlphaTabGeneratedRenderTest extends MusicSheetRenderTest"); + sourceCode.WriteLine("public class AlphaTabGeneratedRenderTest extends AlphaTabGeneratedRenderTestBase"); sourceCode.BeginBlock(); sourceCode.Suspend(); var result = GenerateTestCode(settings); sourceCode.Resume(); - sourceCode.WriteLine("@Override"); - sourceCode.WriteLine("protected float[][] getPartPositions()"); + sourceCode.WriteLine("public static float[][] getPartPositions()"); sourceCode.BeginBlock(); { sourceCode.WriteLine("return new float[][]"); @@ -155,18 +158,15 @@ public static string GenerateJava() } sourceCode.EndBlock(); - sourceCode.WriteLine("@Override"); - sourceCode.WriteLine($"protected int getTotalWidth() {{ return (int){result.totalWidth}; }}"); - sourceCode.WriteLine("@Override"); - sourceCode.WriteLine($"protected int getTotalHeight() {{ return (int){result.totalHeight}; }}"); - sourceCode.WriteLine("@Override"); - sourceCode.WriteLine("protected RenderFunction[] getAllParts()"); + sourceCode.WriteLine($"public static int getTotalWidth() {{ return (int){result.totalWidth}; }}"); + sourceCode.WriteLine($"public static int getTotalHeight() {{ return (int){result.totalHeight}; }}"); + sourceCode.WriteLine("public static RenderFunction[] getAllParts()"); sourceCode.BeginBlock(); { sourceCode.WriteLine("return new RenderFunction[]"); sourceCode.BeginBlock(); { - var parts = result.partialPositions.Select((_, i) => $"this::drawMusicSheetPart{i + 1}"); + var parts = result.partialPositions.Select((_, i) => $"AlphaTabGeneratedRenderTest::drawMusicSheetPart{i + 1}"); sourceCode.WriteLine(string.Join(", ", parts)); } sourceCode.EndBlock(true); @@ -196,35 +196,31 @@ public static string GenerateTypeScript() sourceCode.Resume(); - sourceCode.WriteLine("import { MusicSheetRenderTest } from './MusicSheetRenderTest'; "); - sourceCode.WriteLine( - "import { AlphaSkiaTextBaseline, AlphaSkiaTextAlign, AlphaSkiaCanvas, AlphaSkiaImage } from 'src/alphaskia'; "); - sourceCode.WriteLine(); - - sourceCode.Write("describe('AlphaTabGeneratedRenderTest', function() "); - sourceCode.BeginBlock(); - sourceCode.WriteLine("const test = new MusicSheetRenderTest();"); - sourceCode.WriteLine("before(function () { test.before(); })"); + sourceCode.WriteLine("import TestBase from './AlphaTabGeneratedRenderTestBase'; "); + sourceCode.WriteLine("import { AlphaSkiaTextBaseline, AlphaSkiaTextAlign, AlphaSkiaCanvas, AlphaSkiaImage } from '@coderline/alphaskia'; "); sourceCode.WriteLine(); + sourceCode.Suspend(); var result = GenerateTestCode(settings); sourceCode.Resume(); - sourceCode.WriteLine("test.partPositions = ["); + sourceCode.Write("export default "); + sourceCode.BeginBlock(); + + sourceCode.WriteLine("partPositions: ["); sourceCode.WriteLine(" " + string.Join(", ", result.partialPositions.Select(p => "[" + string.Join(", ", p) + "]"))); - sourceCode.WriteLine("];"); - sourceCode.WriteLine($"test.totalWidth = {result.totalWidth};"); - sourceCode.WriteLine($"test.totalHeight = {result.totalHeight};"); - sourceCode.WriteLine($"test.allParts = ["); + sourceCode.WriteLine("],"); + sourceCode.WriteLine($"totalWidth: {result.totalWidth},"); + sourceCode.WriteLine($"totalHeight: {result.totalHeight},"); + sourceCode.WriteLine($"allParts: ["); var parts = result.partialPositions.Select((_, i) => $"drawMusicSheetPart{i + 1}"); sourceCode.WriteLine(string.Join(", ", parts)); - sourceCode.WriteLine("];"); - sourceCode.WriteLine(); - sourceCode.WriteLine("it('render', function() { test.render('AlphaTabGeneratedRenderTest') } )"); - sourceCode.EndBlock(); - sourceCode.WriteLine(");"); + sourceCode.WriteLine("]"); + + sourceCode.EndBlock(true); + sourceCode.Suspend(); return sourceCode.ToString(); diff --git a/test/test-generator/CSharpTestSourceBuilder.cs b/test/test-generator/CSharpTestSourceBuilder.cs new file mode 100644 index 0000000..a01045f --- /dev/null +++ b/test/test-generator/CSharpTestSourceBuilder.cs @@ -0,0 +1,60 @@ +namespace TestGenerator; + +class CSharpTestSourceBuilder : TestSourceBuilder +{ + public override void WritePartialRenderMethod(string methodName) + { + WriteLine($"private static AlphaSkiaImage? {ToPascalCase(methodName)}(AlphaSkiaCanvas canvas)"); + } + + public override string MakeCallTestMethod(string methodName, string args, bool statement) + { + return MakeCallMethod($"{ToPascalCase(methodName)}", args, statement); + } + + public override string MakeCallHelperMethod(string methodName, string args, bool statement) + { + return MakeCallMethod($"AlphaSkiaCanvas.{ToPascalCase(methodName)}", args, statement); + } + + public override string MakeCallCanvasMethod(string methodName, string args, bool statement) + { + return MakeCallMethod($"canvas.{ToPascalCase(methodName)}", args, statement); + } + + public override string MakeCastToFloat(string value) + { + return $"(float)({value})"; + } + + public override string MakeCastToByte(double value) + { + return $"(byte)({value})"; + } + + private string MakeCallMethod(string methodName, string args = "", bool statement = true, + bool notNull = false) + { + return $"{methodName}({args}){(notNull ? "!" : "")}{(statement ? ";" : "")}"; + } + + public override void WriteSetTestProperty(string property, string value) + { + WriteLine($"{ToPascalCase(property)} = {value};"); + } + + public override string MakeTestGetProperty(string property) + { + return ToPascalCase(property); + } + + public override string MakeEnumAccess(string type, string field) + { + return $"{type}.{ToPascalCase(field)}"; + } + + public override void WriteSetCanvasProperty(string property, string value) + { + WriteLine($"canvas.{ToPascalCase(property)} = {value};"); + } +} \ No newline at end of file diff --git a/test/test-generator/CppTestSourceBuilder.cs b/test/test-generator/CppTestSourceBuilder.cs new file mode 100644 index 0000000..ba4c61c --- /dev/null +++ b/test/test-generator/CppTestSourceBuilder.cs @@ -0,0 +1,65 @@ +namespace TestGenerator; + +class CppTestSourceBuilder : TestSourceBuilder +{ + public override void WritePartialRenderMethod(string methodName) + { + Write($"alphaskia_image_t {ToSnakeCase(methodName)}(alphaskia_canvas_t canvas) "); + } + + public override string MakeCallTestMethod(string methodName, string args, bool statement) + { + return MakeCallMethod($"alphaskia_{ToSnakeCase(methodName)}", args, statement); + } + + public override string MakeCallHelperMethod(string methodName, string args, bool statement) + { + return MakeCallMethod($"alphaskia_{ToSnakeCase(methodName)}", args, statement); + } + + public override string MakeCallCanvasMethod(string methodName, string args, bool statement) + { + args = args.Length > 0 ? "canvas, " + args : "canvas"; + return MakeCallMethod($"alphaskia_canvas_{ToSnakeCase(methodName)}", args, statement); + } + + private string MakeCallMethod(string methodName, string args = "", bool statement = true) + { + return $"{methodName}({args}){(statement ? ";" : "")}"; + } + + public override string MakeCastToFloat(string value) + { + return $"static_cast({value})"; + } + + public override string MakeCastToByte(double value) + { + return $"static_cast({(byte)value})"; + } + + public override void WriteSetTestProperty(string property, string value) + { + WriteLine($"{ToSnakeCase(property)} = {value};"); + } + + public override string MakeTestGetProperty(string property) + { + return $"{ToSnakeCase(property)}"; + } + + public override string MakeEnumAccess(string type, string field) + { + return $"{ToSnakeCase(type)}_{field.ToLower()}"; + } + + public override void WriteSetCanvasProperty(string property, string value) + { + WriteLine($"alphaskia_canvas_set_{ToSnakeCase(property)}(canvas, {value});"); + } + + public override string EncodeString(string text) + { + return "u" + base.EncodeString(text); + } +} \ No newline at end of file diff --git a/test/test-generator/JavaTestSourceBuilder.cs b/test/test-generator/JavaTestSourceBuilder.cs new file mode 100644 index 0000000..443b285 --- /dev/null +++ b/test/test-generator/JavaTestSourceBuilder.cs @@ -0,0 +1,59 @@ +namespace TestGenerator; + +class JavaTestSourceBuilder : TestSourceBuilder +{ + public override void WritePartialRenderMethod(string methodName) + { + Write($"private static AlphaSkiaImage {ToCamelCase(methodName)}(AlphaSkiaCanvas canvas) "); + } + + public override string MakeCallTestMethod(string methodName, string args, bool statement) + { + return MakeCallMethod($"{ToCamelCase(methodName)}", args, statement); + } + + public override string MakeCallHelperMethod(string methodName, string args, bool statement) + { + return MakeCallMethod($"AlphaSkiaCanvas.{ToCamelCase(methodName)}", args, statement); + } + + public override string MakeCallCanvasMethod(string methodName, string args, bool statement) + { + return MakeCallMethod($"canvas.{ToCamelCase(methodName)}", args, statement); + } + + public override string MakeCastToFloat(string value) + { + return $"(float)({value})"; + } + + public override string MakeCastToByte(double value) + { + return $"(byte)({value})"; + } + + private string MakeCallMethod(string methodName, string args = "", bool statement = true) + { + return $"{methodName}({args}){(statement ? ";" : "")}"; + } + + public override void WriteSetTestProperty(string property, string value) + { + WriteLine($"set{ToPascalCase(property)}({value});"); + } + + public override string MakeTestGetProperty(string property) + { + return $"get{ToPascalCase(property)}()"; + } + + public override string MakeEnumAccess(string type, string field) + { + return $"{type}.{ToSnakeCase(field).ToUpperInvariant()}"; + } + + public override void WriteSetCanvasProperty(string property, string value) + { + WriteLine($"canvas.set{ToPascalCase(property)}({value});"); + } +} \ No newline at end of file diff --git a/test/test-generator/Program.cs b/test/test-generator/Program.cs index 9956ccf..df7bcd9 100644 --- a/test/test-generator/Program.cs +++ b/test/test-generator/Program.cs @@ -10,20 +10,25 @@ var nativeTestFileCpp = Path.Combine(rootDir, "test", "native", "src", "AlphaTabGeneratedTest.cpp"); var nativeTestFileH = Path.Combine(rootDir, "test", "native", "include", "AlphaTabGeneratedTest.h"); +Directory.CreateDirectory(Path.GetDirectoryName(nativeTestFileCpp)!); File.WriteAllText(nativeTestFileCpp, AlphaSkiaUnitTestGenerator.GenerateCppSource(out var headerSource)); Console.WriteLine("Updated C++ source in {0}", nativeTestFileCpp); +Directory.CreateDirectory(Path.GetDirectoryName(nativeTestFileH)!); File.WriteAllText(nativeTestFileH, headerSource); Console.WriteLine("Updated C++ Header in {0}", nativeTestFileH); -// var csharpTestFile = Path.Combine(rootDir, "test", "dotnet", "AlphaSkia.Test", "AlphaTabGeneratedTest.cs"); -// File.WriteAllText(csharpTestFile, AlphaSkiaUnitTestGenerator.GenerateCSharp()); -// Console.WriteLine("Updated C# source in {0}", csharpTestFile); -// -// var javaTestFile = Path.Combine(rootDir, "test", "src", "main", "java", "net", "alphatab", "alphaskia", -// "AlphaTabGeneratedRenderTest.java"); -// File.WriteAllText(javaTestFile, AlphaSkiaUnitTestGenerator.GenerateJava()); -// Console.WriteLine("Updated Java source in {0}", javaTestFile); -// -// var typeScriptTestFile = Path.Combine(rootDir, "test", "node", "src", "AlphaTabGeneratedRenderTest.ts"); -// File.WriteAllText(typeScriptTestFile, AlphaSkiaUnitTestGenerator.GenerateTypeScript()); -// Console.WriteLine("Updated TypeScript source in {0}", typeScriptTestFile); \ No newline at end of file +var csharpTestFile = Path.Combine(rootDir, "test", "dotnet", "AlphaSkia.Test", "AlphaTabGeneratedRenderTest.generated.cs"); +Directory.CreateDirectory(Path.GetDirectoryName(csharpTestFile)!); +File.WriteAllText(csharpTestFile, AlphaSkiaUnitTestGenerator.GenerateCSharp()); +Console.WriteLine("Updated C# source in {0}", csharpTestFile); + +var javaTestFile = Path.Combine(rootDir, "test", "java", "src", "main", "generated", "net", "alphatab", "alphaskia", "test", + "AlphaTabGeneratedRenderTest.java"); +Directory.CreateDirectory(Path.GetDirectoryName(javaTestFile)!); +File.WriteAllText(javaTestFile, AlphaSkiaUnitTestGenerator.GenerateJava()); +Console.WriteLine("Updated Java source in {0}", javaTestFile); + +var typeScriptTestFile = Path.Combine(rootDir, "test", "node", "AlphaTabGeneratedRenderTest.ts"); +Directory.CreateDirectory(Path.GetDirectoryName(typeScriptTestFile)!); +File.WriteAllText(typeScriptTestFile, AlphaSkiaUnitTestGenerator.GenerateTypeScript()); +Console.WriteLine("Updated TypeScript source in {0}", javaTestFile); \ No newline at end of file diff --git a/test/test-generator/TestSourceBuilder.cs b/test/test-generator/TestSourceBuilder.cs index 297d7c7..37c0554 100644 --- a/test/test-generator/TestSourceBuilder.cs +++ b/test/test-generator/TestSourceBuilder.cs @@ -132,254 +132,4 @@ public void Suspend() { _suspended = true; } -} - -class CSharpTestSourceBuilder : TestSourceBuilder -{ - public override void WritePartialRenderMethod(string methodName) - { - WriteLine($"private AlphaSkiaImage? {ToPascalCase(methodName)}(AlphaSkiaCanvas canvas)"); - } - - public override string MakeCallTestMethod(string methodName, string args, bool statement) - { - return MakeCallMethod($"{ToPascalCase(methodName)}", args, statement); - } - - public override string MakeCallHelperMethod(string methodName, string args, bool statement) - { - return MakeCallMethod($"AlphaSkiaCanvas.{ToPascalCase(methodName)}", args, statement); - } - - public override string MakeCallCanvasMethod(string methodName, string args, bool statement) - { - return MakeCallMethod($"canvas.{ToPascalCase(methodName)}", args, statement); - } - - public override string MakeCastToFloat(string value) - { - return $"(float)({value})"; - } - - public override string MakeCastToByte(double value) - { - return $"(byte)({value})"; - } - - private string MakeCallMethod(string methodName, string args = "", bool statement = true, - bool notNull = false) - { - return $"{methodName}({args}){(notNull ? "!" : "")}{(statement ? ";" : "")}"; - } - - private string MakePropertyAccess(string expression, string member) - { - return $"{expression}.{ToPascalCase(member)}"; - } - - public override void WriteSetTestProperty(string property, string value) - { - WriteLine($"{ToPascalCase(property)} = {value};"); - } - - public override string MakeTestGetProperty(string property) - { - return ToPascalCase(property); - } - - public override string MakeEnumAccess(string type, string field) - { - return $"{type}.{ToPascalCase(field)}"; - } - - public override void WriteSetCanvasProperty(string property, string value) - { - WriteLine($"{MakePropertyAccess("canvas", property)} = {value};"); - } -} - -class JavaTestSourceBuilder : TestSourceBuilder -{ - public override void WritePartialRenderMethod(string methodName) - { - Write($"private AlphaSkiaImage {ToCamelCase(methodName)}(AlphaSkiaCanvas canvas) "); - } - - public override string MakeCallTestMethod(string methodName, string args, bool statement) - { - return MakeCallMethod($"{ToCamelCase(methodName)}", args, statement); - } - - public override string MakeCallCanvasMethod(string methodName, string args, bool statement) - { - return MakeCallMethod($"canvas.{ToCamelCase(methodName)}", args, statement); - } - - public override string MakeCallHelperMethod(string methodName, string args, bool statement) - { - return MakeCallMethod($"AlphaSkiaCanvas.{ToCamelCase(methodName)}", args, statement); - } - - private string MakeCallMethod(string methodName, string args = "", bool statement = true) - { - return $"{methodName}({args}){(statement ? ";" : "")}"; - } - - public override string MakeCastToFloat(string value) - { - return $"(float)({value})"; - } - - public override string MakeCastToByte(double value) - { - return $"(byte)({value})"; - } - - public override void WriteSetTestProperty(string property, string value) - { - WriteLine($"set{ToPascalCase(property)}({value});"); - } - - public override string MakeTestGetProperty(string property) - { - return $"get{ToPascalCase(property)}()"; - } - - public override string MakeEnumAccess(string type, string field) - { - return $"{type}.{field.ToUpperInvariant()}"; - } - - public override void WriteSetCanvasProperty(string property, string value) - { - WriteLine($"canvas.set{ToPascalCase(property)}({value});"); - } -} - -class TypeScriptTestSourceBuilder : TestSourceBuilder -{ - public override void WritePartialRenderMethod(string methodName) - { - Write($"function {ToCamelCase(methodName)}(canvas: AlphaSkiaCanvas): AlphaSkiaImage "); - } - - public override string MakeCallTestMethod(string methodName, string args, bool statement) - { - return MakeCallMethod($"test.{ToCamelCase(methodName)}", args, statement); - } - - public override string MakeCallHelperMethod(string methodName, string args, bool statement) - { - return MakeCallMethod($"AlphaSkiaCanvas.{ToCamelCase(methodName)}", args, statement); - } - - public override string MakeCallCanvasMethod(string methodName, string args, bool statement) - { - return MakeCallMethod($"canvas.{ToCamelCase(methodName)}", args, statement); - } - - private string MakeCallMethod(string methodName, string args = "", bool statement = true, - bool notNull = false) - { - return $"{methodName}({args}){(notNull ? "!" : "")}{(statement ? ";" : "")}"; - } - - public override string MakeCastToFloat(string value) - { - return value; - } - - public override string MakeCastToByte(double value) - { - return ((int)value).ToString(); - } - - private string MakePropertyAccess(string expression, string member) - { - return $"{expression}.{ToCamelCase(member)}"; - } - - public override void WriteSetTestProperty(string property, string value) - { - WriteLine($"test.{ToCamelCase(property)} = {value};"); - } - - public override string MakeTestGetProperty(string property) - { - return $"test.{ToCamelCase(property)}"; - } - - public override string MakeEnumAccess(string type, string field) - { - return $"{type}.{ToPascalCase(field)}"; - } - - public override void WriteSetCanvasProperty(string property, string value) - { - WriteLine($"{MakePropertyAccess("canvas", property)} = {value};"); - } -} - -class CppTestSourceBuilder : TestSourceBuilder -{ - public override void WritePartialRenderMethod(string methodName) - { - Write($"alphaskia_image_t {ToSnakeCase(methodName)}(alphaskia_canvas_t canvas) "); - } - - public override string MakeCallTestMethod(string methodName, string args, bool statement) - { - return MakeCallMethod($"alphaskia_{ToSnakeCase(methodName)}", args, statement); - } - - public override string MakeCallHelperMethod(string methodName, string args, bool statement) - { - return MakeCallMethod($"alphaskia_{ToSnakeCase(methodName)}", args, statement); - } - - public override string MakeCallCanvasMethod(string methodName, string args, bool statement) - { - args = args.Length > 0 ? "canvas, " + args : "canvas"; - return MakeCallMethod($"alphaskia_canvas_{ToSnakeCase(methodName)}", args, statement); - } - - private string MakeCallMethod(string methodName, string args = "", bool statement = true) - { - return $"{methodName}({args}){(statement ? ";" : "")}"; - } - - public override string MakeCastToFloat(string value) - { - return $"static_cast({value})"; - } - - public override string MakeCastToByte(double value) - { - return $"static_cast({(byte)value})"; - } - - public override void WriteSetTestProperty(string property, string value) - { - WriteLine($"{ToSnakeCase(property)} = {value};"); - } - - public override string MakeTestGetProperty(string property) - { - return $"{ToSnakeCase(property)}"; - } - - public override string MakeEnumAccess(string type, string field) - { - return $"{ToSnakeCase(type)}_{field.ToLower()}"; - } - - public override void WriteSetCanvasProperty(string property, string value) - { - WriteLine($"alphaskia_canvas_set_{ToSnakeCase(property)}(canvas, {value});"); - } - - public override string EncodeString(string text) - { - return "u" + base.EncodeString(text); - } } \ No newline at end of file diff --git a/test/test-generator/TypeScriptTestSourceBuilder.cs b/test/test-generator/TypeScriptTestSourceBuilder.cs new file mode 100644 index 0000000..465db7c --- /dev/null +++ b/test/test-generator/TypeScriptTestSourceBuilder.cs @@ -0,0 +1,61 @@ +namespace TestGenerator; + +class TypeScriptTestSourceBuilder : TestSourceBuilder +{ + public override void WritePartialRenderMethod(string methodName) + { + Write($"function {ToCamelCase(methodName)}(canvas: AlphaSkiaCanvas): AlphaSkiaImage "); + } + + public override string MakeCallTestMethod(string methodName, string args, bool statement) + { + return MakeCallMethod($"TestBase.{ToCamelCase(methodName)}", args, statement); + } + + public override string MakeCallHelperMethod(string methodName, string args, bool statement) + { + return MakeCallMethod($"AlphaSkiaCanvas.{ToCamelCase(methodName)}", args, statement); + } + + public override string MakeCallCanvasMethod(string methodName, string args, bool statement) + { + return MakeCallMethod($"canvas.{ToCamelCase(methodName)}", args, statement); + } + + public override string MakeCastToFloat(string value) + { + return value; + } + + public override string MakeCastToByte(double value) + { + return ((int)value).ToString(); + } + + + private string MakeCallMethod(string methodName, string args = "", bool statement = true, + bool notNull = false) + { + return $"{methodName}({args}){(notNull ? "!" : "")}{(statement ? ";" : "")}"; + } + + public override void WriteSetTestProperty(string property, string value) + { + WriteLine($"TestBase.{ToCamelCase(property)} = {value};"); + } + + public override string MakeTestGetProperty(string property) + { + return $"TestBase.{ToCamelCase(property)}"; + } + + public override string MakeEnumAccess(string type, string field) + { + return $"{type}.{ToPascalCase(field)}"; + } + + public override void WriteSetCanvasProperty(string property, string value) + { + WriteLine($"canvas.{ToCamelCase(property)} = {value};"); + } +} \ No newline at end of file diff --git a/test/test-outputs/dotnet/win-x64.png b/test/test-outputs/dotnet/win-x64.png new file mode 100644 index 0000000000000000000000000000000000000000..81479f137ae9f61bffb346684ed8ba0f2ccc9a06 GIT binary patch literal 34635 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 E52p*L$p8QV literal 0 HcmV?d00001 diff --git a/test/test-outputs/java/win-x64.png b/test/test-outputs/java/win-x64.png new file mode 100644 index 0000000000000000000000000000000000000000..81479f137ae9f61bffb346684ed8ba0f2ccc9a06 GIT binary patch literal 34635 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 E52p*L$p8QV literal 0 HcmV?d00001 diff --git a/wrapper/src/AlphaSkiaCanvas.cpp b/wrapper/src/AlphaSkiaCanvas.cpp index 0d97248..db871e8 100644 --- a/wrapper/src/AlphaSkiaCanvas.cpp +++ b/wrapper/src/AlphaSkiaCanvas.cpp @@ -412,14 +412,4 @@ void AlphaSkiaCanvas::draw_image(sk_sp image, float x, float y, float w { SkSamplingOptions sampling; surface_->getCanvas()->drawImageRect(image, SkRect::MakeXYWH(x, y, w, h), sampling); -} - -void AlphaSkiaCanvas::draw_pixels(const uint8_t *pixels, uint64_t size) -{ - SkPixmap pixmap; - if (!surface_->getCanvas()->peekPixels(&pixmap)) - { - return; - } - memcpy(pixmap.writable_addr(), pixels, size); } \ No newline at end of file From 65ede5144c14f698624d69329fadd0675af06c8c Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 19:27:07 +0200 Subject: [PATCH 03/38] Adjust used exceptions --- test/native/include/AlphaSkiaTestBridge.h | 1 + test/native/src/AlphaSkiaTestBridge.cpp | 8 ++++---- test/native/src/PixelMatch.cpp | 4 ++-- test/native/src/main.cpp | 10 +++++----- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/test/native/include/AlphaSkiaTestBridge.h b/test/native/include/AlphaSkiaTestBridge.h index 6def37a..d238b67 100644 --- a/test/native/include/AlphaSkiaTestBridge.h +++ b/test/native/include/AlphaSkiaTestBridge.h @@ -2,6 +2,7 @@ #include "../../../wrapper/include/alphaskia.h" +#include #include #include #include diff --git a/test/native/src/AlphaSkiaTestBridge.cpp b/test/native/src/AlphaSkiaTestBridge.cpp index cf8367f..8fd5ae0 100644 --- a/test/native/src/AlphaSkiaTestBridge.cpp +++ b/test/native/src/AlphaSkiaTestBridge.cpp @@ -26,7 +26,7 @@ alphaskia_typeface_t alphaskia_get_typeface(const char *name, bool is_bold, bool auto it = custom_type_faces.find(key); if (it == custom_type_faces.end()) { - throw std::exception(("Unknown font requested: " + key).c_str()); + throw std::logic_error("Unknown font requested: " + key); } return it->second; } @@ -36,7 +36,7 @@ void read_file(std::string file_path, std::vector &data) std::ifstream in(file_path, std::ios::binary | std::ios::ate); if (!in) { - throw std::exception(("Could not open file " + file_path).c_str()); + throw std::logic_error("Could not open file " + file_path); } std::streampos size = in.tellg(); @@ -55,7 +55,7 @@ alphaskia_typeface_t alphaskia_load_typeface(const char *name, bool is_bold, boo alphaskia_data_t font_data = alphaskia_data_new_copy(data.data(), data.size()); if (!font_data) { - throw std::exception(("Could allocate typeface data " + file_path).c_str()); + throw std::logic_error("Could allocate typeface data " + file_path); } std::cout << "Read " << data.size() << " bytes from file, decoding typeface" << std::endl; @@ -63,7 +63,7 @@ alphaskia_typeface_t alphaskia_load_typeface(const char *name, bool is_bold, boo if (!typeface) { alphaskia_data_free(font_data); - throw std::exception("Could not create typeface from data"); + throw std::logic_error("Could not create typeface from data"); } std::cout << "Typeface " << key << " loaded and registered" << std::endl; custom_type_faces[key] = typeface; diff --git a/test/native/src/PixelMatch.cpp b/test/native/src/PixelMatch.cpp index 501fefc..1844899 100644 --- a/test/native/src/PixelMatch.cpp +++ b/test/native/src/PixelMatch.cpp @@ -212,12 +212,12 @@ pixel_match_result_t pixel_match(const std::vector &img1, { if (img1.size() != img2.size() || diff_pixels.size() != img1.size()) { - throw std::exception("Image sizes do not match"); + throw std::logic_error("Image sizes do not match"); } if (img1.size() != width * height * sizeof(uint32_t)) { - throw std::exception("Image data size does not match width/height."); + throw std::logic_error("Image data size does not match width/height."); } // check if images are identical diff --git a/test/native/src/main.cpp b/test/native/src/main.cpp index 1c30c0c..94caf9a 100644 --- a/test/native/src/main.cpp +++ b/test/native/src/main.cpp @@ -21,7 +21,7 @@ std::filesystem::path find_repository_root(std::filesystem::path current) if (!current.has_parent_path()) { - throw std::exception("Could not find repository root"); + throw std::logic_error("Could not find repository root"); } return find_repository_root(current.parent_path()); @@ -63,7 +63,7 @@ void write_data_to_file_and_free(alphaskia_data_t data, std::string path) if (!stream) { alphaskia_data_free(data); - throw std::exception(("Could not open file " + path).c_str()); + throw std::logic_error("Could not open file " + path); } uint8_t *data_raw = alphaskia_data_get_data(data); @@ -111,7 +111,7 @@ int main() alphaskia_data_t png_data = alphaskia_image_encode_png(actual_image); if (!png_data) { - throw std::exception("Failed to encode final image to png"); + throw std::logic_error("Failed to encode final image to png"); } write_data_to_file_and_free(png_data, test_output_file.generic_string()); std::cout << "Image saved to " << test_output_file.generic_string() << std::endl; @@ -124,7 +124,7 @@ int main() alphaskia_image_t expected_image = alphaskia_image_decode(test_reference_data.data(), test_reference_data.size()); if (!expected_image) { - throw std::exception("Failed to decode reference image"); + throw std::logic_error("Failed to decode reference image"); } std::cout << "Reference image loaded" << std::endl; @@ -183,7 +183,7 @@ int main() std::cout << "Images match. Total Pixels: " << compare_result.total_pixels << ", Transparent Pixels: " << compare_result.transparent_pixels << ", Percent difference:" << percentDifference << "%" << std::endl; return 0; } - catch (std::exception &e) + catch (const std::exception &e) { std::cerr << "Failed to run test: " << e.what() << std::endl; return 1; From 00f341db517c083456b02549d9c11a2dd07375fb Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 19:40:57 +0200 Subject: [PATCH 04/38] Remove exceptions --- test/native/src/AlphaSkiaTestBridge.cpp | 12 +++++++---- test/native/src/PixelMatch.cpp | 6 ++++-- test/native/src/main.cpp | 27 +++++++++++++++++-------- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/test/native/src/AlphaSkiaTestBridge.cpp b/test/native/src/AlphaSkiaTestBridge.cpp index 8fd5ae0..388e08b 100644 --- a/test/native/src/AlphaSkiaTestBridge.cpp +++ b/test/native/src/AlphaSkiaTestBridge.cpp @@ -26,7 +26,8 @@ alphaskia_typeface_t alphaskia_get_typeface(const char *name, bool is_bold, bool auto it = custom_type_faces.find(key); if (it == custom_type_faces.end()) { - throw std::logic_error("Unknown font requested: " + key); + std::cerr << "Unknown font requested: " << key << std::endl; + return nullptr; } return it->second; } @@ -36,7 +37,8 @@ void read_file(std::string file_path, std::vector &data) std::ifstream in(file_path, std::ios::binary | std::ios::ate); if (!in) { - throw std::logic_error("Could not open file " + file_path); + std::cerr << "Could not open file " << file_path << std::endl; + return; } std::streampos size = in.tellg(); @@ -55,7 +57,8 @@ alphaskia_typeface_t alphaskia_load_typeface(const char *name, bool is_bold, boo alphaskia_data_t font_data = alphaskia_data_new_copy(data.data(), data.size()); if (!font_data) { - throw std::logic_error("Could allocate typeface data " + file_path); + std::cerr << "Could allocate typeface data " << file_path << std::endl; + return nullptr; } std::cout << "Read " << data.size() << " bytes from file, decoding typeface" << std::endl; @@ -63,7 +66,8 @@ alphaskia_typeface_t alphaskia_load_typeface(const char *name, bool is_bold, boo if (!typeface) { alphaskia_data_free(font_data); - throw std::logic_error("Could not create typeface from data"); + std::cerr << "Could not create typeface from data " << file_path << std::endl; + return nullptr; } std::cout << "Typeface " << key << " loaded and registered" << std::endl; custom_type_faces[key] = typeface; diff --git a/test/native/src/PixelMatch.cpp b/test/native/src/PixelMatch.cpp index 1844899..5320727 100644 --- a/test/native/src/PixelMatch.cpp +++ b/test/native/src/PixelMatch.cpp @@ -212,12 +212,14 @@ pixel_match_result_t pixel_match(const std::vector &img1, { if (img1.size() != img2.size() || diff_pixels.size() != img1.size()) { - throw std::logic_error("Image sizes do not match"); + std::cerr << "Image sizes do not match" << std::endl; + return {0, 0xFFFF, 0}; } if (img1.size() != width * height * sizeof(uint32_t)) { - throw std::logic_error("Image data size does not match width/height."); + std::cerr << "Image data size does not match width/height." << std::endl; + return {0, 0xFFFF, 0}; } // check if images are identical diff --git a/test/native/src/main.cpp b/test/native/src/main.cpp index 94caf9a..827a25d 100644 --- a/test/native/src/main.cpp +++ b/test/native/src/main.cpp @@ -12,7 +12,7 @@ #define STRINGIFY(s) _STRINGIFY(s) #define _STRINGIFY(s) #s -std::filesystem::path find_repository_root(std::filesystem::path current) +std::filesystem::path find_repository_root(std::filesystem::path current, bool &success) { if (std::filesystem::exists(current / ".nuke")) { @@ -21,10 +21,12 @@ std::filesystem::path find_repository_root(std::filesystem::path current) if (!current.has_parent_path()) { - throw std::logic_error("Could not find repository root"); + std::cerr << "Could not find repository root" << std::endl; + success = false; + return std::filesystem::path(); } - return find_repository_root(current.parent_path()); + return find_repository_root(current.parent_path(), success); } alphaskia_image_t render_full_image() @@ -57,13 +59,14 @@ alphaskia_image_t render_full_image() return full_image; } -void write_data_to_file_and_free(alphaskia_data_t data, std::string path) +bool write_data_to_file_and_free(alphaskia_data_t data, std::string path) { std::ofstream stream(path, std::ios::binary); if (!stream) { alphaskia_data_free(data); - throw std::logic_error("Could not open file " + path); + std::cerr << "Could not open file " << path << std::endl; + return false; } uint8_t *data_raw = alphaskia_data_get_data(data); @@ -73,6 +76,7 @@ void write_data_to_file_and_free(alphaskia_data_t data, std::string path) stream.close(); alphaskia_data_free(data); + return true; } int main() @@ -80,7 +84,12 @@ int main() const double tolerance_percent = 1; try { - std::filesystem::path repository_root = find_repository_root(std::filesystem::current_path()); + bool found(false); + std::filesystem::path repository_root = find_repository_root(std::filesystem::current_path(), found); + if (!found) + { + return 1; + } // Load all fonts for rendering std::cout << "Loading fonts" << std::endl; @@ -111,7 +120,8 @@ int main() alphaskia_data_t png_data = alphaskia_image_encode_png(actual_image); if (!png_data) { - throw std::logic_error("Failed to encode final image to png"); + std::cerr << "Failed to encode final image to png" << std::endl; + return 1; } write_data_to_file_and_free(png_data, test_output_file.generic_string()); std::cout << "Image saved to " << test_output_file.generic_string() << std::endl; @@ -124,7 +134,8 @@ int main() alphaskia_image_t expected_image = alphaskia_image_decode(test_reference_data.data(), test_reference_data.size()); if (!expected_image) { - throw std::logic_error("Failed to decode reference image"); + std::cerr << "Failed to decode reference image" << std::endl; + return 1; } std::cout << "Reference image loaded" << std::endl; From b37e5cd5fe01e021f2c15201bf44f7233296f1a5 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 19:43:55 +0200 Subject: [PATCH 05/38] Add include for int types --- test/native/include/PixelMatch.h | 1 + 1 file changed, 1 insertion(+) diff --git a/test/native/include/PixelMatch.h b/test/native/include/PixelMatch.h index 38be104..1200ca4 100644 --- a/test/native/include/PixelMatch.h +++ b/test/native/include/PixelMatch.h @@ -18,6 +18,7 @@ #pragma once #include +#include typedef struct { From 9aa9458f19e4cb8500d4395461ee5efe398a07f3 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 19:46:16 +0200 Subject: [PATCH 06/38] Avoid duplicate target execution --- .github/actions/build-alphaskia/action.yml | 5 +---- build/Build.LibAlphaSkia.cs | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/actions/build-alphaskia/action.yml b/.github/actions/build-alphaskia/action.yml index 8b4b4bc..eb05cb5 100644 --- a/.github/actions/build-alphaskia/action.yml +++ b/.github/actions/build-alphaskia/action.yml @@ -34,10 +34,7 @@ runs: fi shell: bash - - run: ${{ steps.build-info.outputs.script }} LibAlphaSkia --target-os ${{ inputs.target-os }} --architecture ${{ inputs.architecture }} --variant ${{ inputs.variant }} - shell: ${{ steps.build-info.outputs.shell }} - - - run: ${{ steps.build-info.outputs.script }} LibAlphaSkiaTest --target-os ${{ inputs.target-os }} --architecture ${{ inputs.architecture }} --variant ${{ inputs.variant }} + - run: ${{ steps.build-info.outputs.script }} LibAlphaSkia LibAlphaSkiaTest --target-os ${{ inputs.target-os }} --architecture ${{ inputs.architecture }} --variant ${{ inputs.variant }} shell: ${{ steps.build-info.outputs.shell }} - uses: actions/upload-artifact@v3 diff --git a/build/Build.LibAlphaSkia.cs b/build/Build.LibAlphaSkia.cs index f2619ca..9dd8ff2 100644 --- a/build/Build.LibAlphaSkia.cs +++ b/build/Build.LibAlphaSkia.cs @@ -33,6 +33,7 @@ partial class Build public Target LibAlphaSkiaTest => _ => _ .DependsOn(PrepareGitHubArtifacts, LibAlphaSkiaGitSyncDeps, LibAlphaSkiaPatchSkiaBuildFiles) + .After(LibAlphaSkia) .Requires(() => Architecture) .Requires(() => TargetOs) .Executes(BuildAlphaSkiaTest); From ddbc08235d5bc62a3d3542138c6dec99d4a58dbb Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 19:54:03 +0200 Subject: [PATCH 07/38] Make linux dependency install a target --- build/Build.LibAlphaSkia.cs | 4 +- build/Build.LibSkia.cs | 2 +- build/Build.Linux.cs | 121 ++++++++++++++++++++---------------- build/Build.Native.cs | 5 -- 4 files changed, 70 insertions(+), 62 deletions(-) diff --git a/build/Build.LibAlphaSkia.cs b/build/Build.LibAlphaSkia.cs index 9dd8ff2..bf52823 100644 --- a/build/Build.LibAlphaSkia.cs +++ b/build/Build.LibAlphaSkia.cs @@ -25,14 +25,14 @@ partial class Build }); public Target LibAlphaSkia => _ => _ - .DependsOn(PrepareGitHubArtifacts, LibAlphaSkiaGitSyncDeps, LibAlphaSkiaPatchSkiaBuildFiles) + .DependsOn(PrepareGitHubArtifacts, LibAlphaSkiaGitSyncDeps, LibAlphaSkiaPatchSkiaBuildFiles, InstallDependenciesLinux) .Requires(() => Architecture) .Requires(() => Variant) .Requires(() => TargetOs) .Executes(BuildAlphaSkia); public Target LibAlphaSkiaTest => _ => _ - .DependsOn(PrepareGitHubArtifacts, LibAlphaSkiaGitSyncDeps, LibAlphaSkiaPatchSkiaBuildFiles) + .DependsOn(PrepareGitHubArtifacts, LibAlphaSkiaGitSyncDeps, LibAlphaSkiaPatchSkiaBuildFiles, InstallDependenciesLinux) .After(LibAlphaSkia) .Requires(() => Architecture) .Requires(() => TargetOs) diff --git a/build/Build.LibSkia.cs b/build/Build.LibSkia.cs index fa32c4f..469fddf 100644 --- a/build/Build.LibSkia.cs +++ b/build/Build.LibSkia.cs @@ -73,7 +73,7 @@ partial class Build .Triggers(LibSkia); public Target LibSkia => _ => _ - .DependsOn(LibSkiaGitSyncDeps, LibSkiaPatchSkiaBuildFiles) + .DependsOn(LibSkiaGitSyncDeps, LibSkiaPatchSkiaBuildFiles, InstallDependenciesLinux) .OnlyWhenStatic(() => !LibSkiaSkip.Value) .Requires(() => Architecture) .Requires(() => TargetOs) diff --git a/build/Build.Linux.cs b/build/Build.Linux.cs index 7c1aabc..2badecb 100644 --- a/build/Build.Linux.cs +++ b/build/Build.Linux.cs @@ -2,73 +2,86 @@ using System.IO; using System.Linq; using System.Text; +using Nuke.Common; using Nuke.Common.IO; using Nuke.Common.Tooling; partial class Build { - void InstallDependenciesLinux() - { - var installDependencies = new StringBuilder(); - installDependencies.AppendLine("#!/bin/bash"); - installDependencies.AppendLine("set -e"); - - // Using aptitude here because github actions runners have quite some packages preinstalled - // which leads to many version conflicts after adding more sources. aptitude can resolve those - // conflicts easier - installDependencies.AppendLine("echo Install Aptitude"); - installDependencies.AppendLine("apt-get update"); - installDependencies.AppendLine("apt-get install -y aptitude"); - - // Main system build tools - installDependencies.AppendLine("apt-get install -y build-essential"); - - // Cross compilation build tools if needed - var linuxArch = Architecture.LinuxArch; - if (Architecture != Architecture.X64 && TargetOs == TargetOperatingSystem.Linux) + Target InstallDependenciesLinux => _ => _ + .Unlisted() + .After(PrepareGitHubArtifacts, + LibAlphaSkiaGitSyncDeps, LibAlphaSkiaPatchSkiaBuildFiles, + LibSkiaGitSyncDeps, LibSkiaPatchSkiaBuildFiles) + .Before(LibSkia, LibAlphaSkia, LibAlphaSkiaTest) + .Executes(() => { - installDependencies.AppendLine($"echo Adding Arch {linuxArch}"); - installDependencies.AppendLine($"dpkg --add-architecture {linuxArch}"); + var installDependencies = new StringBuilder(); + installDependencies.AppendLine("#!/bin/bash"); + installDependencies.AppendLine("set -e"); + + // Using aptitude here because github actions runners have quite some packages preinstalled + // which leads to many version conflicts after adding more sources. aptitude can resolve those + // conflicts easier + installDependencies.AppendLine("echo Install Aptitude"); + installDependencies.AppendLine("apt-get update"); + installDependencies.AppendLine("apt-get install -y aptitude"); - installDependencies.AppendLine("echo Modifying sources.list"); - installDependencies.AppendLine("sed -i \"s/deb /deb [arch=amd64,i386] /\" /etc/apt/sources.list"); - installDependencies.AppendLine("sed -i \"s/deb-src /deb-src [arch=amd64,i386] /\" /etc/apt/sources.list"); + // Main system build tools + installDependencies.AppendLine("apt-get install -y build-essential"); - if (Architecture == Architecture.Arm || Architecture == Architecture.Arm64) + // Cross compilation build tools if needed + var linuxArch = Architecture.LinuxArch; + if (Architecture != Architecture.X64 && TargetOs == TargetOperatingSystem.Linux) { - installDependencies.AppendLine($"echo Adding new {linuxArch} sources"); + installDependencies.AppendLine($"echo Adding Arch {linuxArch}"); + installDependencies.AppendLine($"dpkg --add-architecture {linuxArch}"); + + installDependencies.AppendLine("echo Modifying sources.list"); + installDependencies.AppendLine("sed -i \"s/deb /deb [arch=amd64,i386] /\" /etc/apt/sources.list"); + installDependencies.AppendLine( + "sed -i \"s/deb-src /deb-src [arch=amd64,i386] /\" /etc/apt/sources.list"); - installDependencies.AppendLine($"echo 'deb [arch={linuxArch}] http://ports.ubuntu.com/ubuntu-ports/ jammy main multiverse universe' >> /etc/apt/sources.list"); - installDependencies.AppendLine($"echo 'deb [arch={linuxArch}] http://ports.ubuntu.com/ubuntu-ports/ jammy-security main multiverse universe' >> /etc/apt/sources.list"); - installDependencies.AppendLine($"echo 'deb [arch={linuxArch}] http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main multiverse universe' >> /etc/apt/sources.list"); - installDependencies.AppendLine($"echo 'deb [arch={linuxArch}] http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main multiverse universe' >> /etc/apt/sources.list"); + if (Architecture == Architecture.Arm || Architecture == Architecture.Arm64) + { + installDependencies.AppendLine($"echo Adding new {linuxArch} sources"); + + installDependencies.AppendLine( + $"echo 'deb [arch={linuxArch}] http://ports.ubuntu.com/ubuntu-ports/ jammy main multiverse universe' >> /etc/apt/sources.list"); + installDependencies.AppendLine( + $"echo 'deb [arch={linuxArch}] http://ports.ubuntu.com/ubuntu-ports/ jammy-security main multiverse universe' >> /etc/apt/sources.list"); + installDependencies.AppendLine( + $"echo 'deb [arch={linuxArch}] http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main multiverse universe' >> /etc/apt/sources.list"); + installDependencies.AppendLine( + $"echo 'deb [arch={linuxArch}] http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main multiverse universe' >> /etc/apt/sources.list"); + } + else + { + installDependencies.AppendLine($"echo No additional package sources for {linuxArch}"); + } + + installDependencies.AppendLine("echo Updating Packages"); + installDependencies.AppendLine("apt-get update"); + installDependencies.AppendLine("echo Installing main build tools"); + installDependencies.AppendLine( + $"aptitude install -y crossbuild-essential-{linuxArch} libstdc++-11-dev-{linuxArch}-cross"); } else { - installDependencies.AppendLine($"echo No additional package sources for {linuxArch}"); - + // no cross compilation packages + linuxArch = ""; } - - installDependencies.AppendLine("echo Updating Packages"); - installDependencies.AppendLine("apt-get update"); - installDependencies.AppendLine("echo Installing main build tools"); - installDependencies.AppendLine($"aptitude install -y crossbuild-essential-{linuxArch} libstdc++-11-dev-{linuxArch}-cross"); - } - else - { - // no cross compilation packages - linuxArch = ""; - } - - // dependent libraries - var linuxArchSuffix = string.IsNullOrEmpty(linuxArch) ? "" : $":{linuxArch}"; - installDependencies.AppendLine("echo Installing libs"); - installDependencies.AppendLine($"aptitude install -y libfontconfig-dev{linuxArchSuffix} libgl1-mesa-dev{linuxArchSuffix} libglu1-mesa-dev{linuxArchSuffix} freeglut3-dev{linuxArchSuffix}"); - - var scriptFile = TemporaryDirectory / "install_dependencies.sh"; - File.WriteAllText(scriptFile, installDependencies.ToString()); - ToolResolver.GetPathTool("sudo")($"bash {scriptFile}"); - } + + // dependent libraries + var linuxArchSuffix = string.IsNullOrEmpty(linuxArch) ? "" : $":{linuxArch}"; + installDependencies.AppendLine("echo Installing libs"); + installDependencies.AppendLine( + $"aptitude install -y libfontconfig-dev{linuxArchSuffix} libgl1-mesa-dev{linuxArchSuffix} libglu1-mesa-dev{linuxArchSuffix} freeglut3-dev{linuxArchSuffix}"); + + var scriptFile = TemporaryDirectory / "install_dependencies.sh"; + File.WriteAllText(scriptFile, installDependencies.ToString()); + ToolResolver.GetPathTool("sudo")($"bash {scriptFile}"); + }); void SetClangLinux(Dictionary gnArgs) { @@ -77,7 +90,7 @@ void SetClangLinux(Dictionary gnArgs) var crossCompileToolchainArch = Architecture.LinuxCrossToolchain; var crossCompileTargetArch = Architecture.LinuxCrossTargetArch; - + if (!string.IsNullOrEmpty(crossCompileToolchainArch) && TargetOs == TargetOperatingSystem.Linux) { var sysroot = $"/usr/{crossCompileToolchainArch}"; diff --git a/build/Build.Native.cs b/build/Build.Native.cs index 6cd08af..666899f 100644 --- a/build/Build.Native.cs +++ b/build/Build.Native.cs @@ -294,11 +294,6 @@ string QuoteValue(string value) Dictionary PrepareNativeBuild(Variant variant) { - if (OperatingSystem.IsLinux() && IsGitHubActions) - { - InstallDependenciesLinux(); - } - var gnArgs = new Dictionary(TargetOs.SkiaGnArgs) { ["cc"] = "clang", From 798d058f77557015b06d2d6a8fa2413f56669a61 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 19:54:41 +0200 Subject: [PATCH 08/38] Remove exception handling --- test/native/src/main.cpp | 216 +++++++++++++++++++-------------------- 1 file changed, 104 insertions(+), 112 deletions(-) diff --git a/test/native/src/main.cpp b/test/native/src/main.cpp index 827a25d..b59888d 100644 --- a/test/native/src/main.cpp +++ b/test/native/src/main.cpp @@ -82,121 +82,113 @@ bool write_data_to_file_and_free(alphaskia_data_t data, std::string path) int main() { const double tolerance_percent = 1; - try + bool found(false); + std::filesystem::path repository_root = find_repository_root(std::filesystem::current_path(), found); + if (!found) { - bool found(false); - std::filesystem::path repository_root = find_repository_root(std::filesystem::current_path(), found); - if (!found) - { - return 1; - } - - // Load all fonts for rendering - std::cout << "Loading fonts" << std::endl; - std::filesystem::path test_data_path = repository_root / "test" / "test-data"; - music_typeface = alphaskia_load_typeface("Bravura", false, false, (test_data_path / "font" / "bravura" / "Bravura.ttf").generic_string()); - alphaskia_load_typeface("Roboto", false, false, (test_data_path / "font" / "roboto" / "Roboto-Regular.ttf").generic_string()); - alphaskia_load_typeface("Roboto", true, false, (test_data_path / "font" / "roboto" / "Roboto-Bold.ttf").generic_string()); - alphaskia_load_typeface("Roboto", false, true, (test_data_path / "font" / "roboto" / "Roboto-Italic.ttf").generic_string()); - alphaskia_load_typeface("Roboto", true, true, (test_data_path / "font" / "roboto" / "Roboto-BoldItalic.ttf").generic_string()); - alphaskia_load_typeface("PT Serif", false, false, (test_data_path / "font" / "ptserif" / "PTSerif-Regular.ttf").generic_string()); - alphaskia_load_typeface("PT Serif", true, false, (test_data_path / "font" / "ptserif" / "PTSerif-Bold.ttf").generic_string()); - alphaskia_load_typeface("PT Serif", false, true, (test_data_path / "font" / "ptserif" / "PTSerif-Italic.ttf").generic_string()); - alphaskia_load_typeface("PT Serif", true, true, (test_data_path / "font" / "ptserif" / "PTSerif-BoldItalic.ttf").generic_string()); - std::cout << "Fonts loaded" << std::endl; - - // render full image - std::cout << "Rendering image" << std::endl; - alphaskia_image_t actual_image = render_full_image(); - std::cout << "Image rendered" << std::endl; - - // save png for future reference - std::cout << "Saving image as PNG" << std::endl; - - std::filesystem::path test_output_path = repository_root / "test" / "test-outputs" / "native"; - std::filesystem::create_directories(test_output_path); - - std::filesystem::path test_output_file = test_output_path / (std::string(STRINGIFY(ALPHASKIA_RID)) + ".png"); - alphaskia_data_t png_data = alphaskia_image_encode_png(actual_image); - if (!png_data) - { - std::cerr << "Failed to encode final image to png" << std::endl; - return 1; - } - write_data_to_file_and_free(png_data, test_output_file.generic_string()); - std::cout << "Image saved to " << test_output_file.generic_string() << std::endl; - - // load reference image - std::filesystem::path test_reference_path = test_data_path / "reference" / (std::string(STRINGIFY(ALPHASKIA_RID)) + ".png"); - std::cout << "Loading reference image " << test_reference_path.generic_string() << std::endl; - std::vector test_reference_data; - read_file(test_reference_path.generic_string(), test_reference_data); - alphaskia_image_t expected_image = alphaskia_image_decode(test_reference_data.data(), test_reference_data.size()); - if (!expected_image) - { - std::cerr << "Failed to decode reference image" << std::endl; - return 1; - } - std::cout << "Reference image loaded" << std::endl; - - // compare images - std::cout << "Comparing images" << std::endl; - auto actual_width = alphaskia_image_get_width(actual_image); - auto actual_height = alphaskia_image_get_height(actual_image); - - auto expected_width = alphaskia_image_get_width(expected_image); - auto expected_height = alphaskia_image_get_height(expected_image); - if (actual_width != expected_width || actual_height != expected_height) - { - std::cerr << "Image sizes do not match: Actual[" << actual_width << "x" << actual_height << "] Expected[" << expected_width << "x" << actual_height << "]" << std::endl; - return 1; - } - - std::vector actual_pixels; - std::vector expected_pixels; - std::vector diff_pixels; - - size_t row_bytes = actual_width * sizeof(uint32_t); - size_t pixel_bytes = row_bytes * actual_height; - actual_pixels.resize(pixel_bytes); - expected_pixels.resize(pixel_bytes); - diff_pixels.resize(pixel_bytes); - - alphaskia_image_read_pixels(actual_image, actual_pixels.data(), row_bytes); - alphaskia_image_read_pixels(expected_image, expected_pixels.data(), row_bytes); - - auto compare_result = pixel_match(actual_pixels, - expected_pixels, - diff_pixels, - actual_width, - actual_height, - {/* threshold: */ 0.3, - /* diff_color: */ {255, 0, 0}}); - - int32_t totalPixels = compare_result.total_pixels - compare_result.transparent_pixels; - double percentDifference = ((double)compare_result.different_pixels / totalPixels) * 100; - bool pass = percentDifference < tolerance_percent; - if (!pass) - { - std::cerr << "Difference between original and new image is too big: " << compare_result.different_pixels << "/" << totalPixels << "(" << percentDifference << "%)"; - - // create diff image as PNG - alphaskia_image_t diff_image = alphaskia_image_from_pixels(actual_width, actual_height, diff_pixels.data()); - alphaskia_data_t diff_image_png_data = alphaskia_image_encode_png(diff_image); - alphaskia_image_free(diff_image); - - auto diff_output_path = test_output_path / (std::string(STRINGIFY(ALPHASKIA_RID)) + ".diff.png"); - write_data_to_file_and_free(diff_image_png_data, diff_output_path.generic_string()); - std::cout << "Error diff image saved to " << diff_output_path.generic_string() << std::endl; - return 1; - } - - std::cout << "Images match. Total Pixels: " << compare_result.total_pixels << ", Transparent Pixels: " << compare_result.transparent_pixels << ", Percent difference:" << percentDifference << "%" << std::endl; - return 0; + return 1; + } + + // Load all fonts for rendering + std::cout << "Loading fonts" << std::endl; + std::filesystem::path test_data_path = repository_root / "test" / "test-data"; + music_typeface = alphaskia_load_typeface("Bravura", false, false, (test_data_path / "font" / "bravura" / "Bravura.ttf").generic_string()); + alphaskia_load_typeface("Roboto", false, false, (test_data_path / "font" / "roboto" / "Roboto-Regular.ttf").generic_string()); + alphaskia_load_typeface("Roboto", true, false, (test_data_path / "font" / "roboto" / "Roboto-Bold.ttf").generic_string()); + alphaskia_load_typeface("Roboto", false, true, (test_data_path / "font" / "roboto" / "Roboto-Italic.ttf").generic_string()); + alphaskia_load_typeface("Roboto", true, true, (test_data_path / "font" / "roboto" / "Roboto-BoldItalic.ttf").generic_string()); + alphaskia_load_typeface("PT Serif", false, false, (test_data_path / "font" / "ptserif" / "PTSerif-Regular.ttf").generic_string()); + alphaskia_load_typeface("PT Serif", true, false, (test_data_path / "font" / "ptserif" / "PTSerif-Bold.ttf").generic_string()); + alphaskia_load_typeface("PT Serif", false, true, (test_data_path / "font" / "ptserif" / "PTSerif-Italic.ttf").generic_string()); + alphaskia_load_typeface("PT Serif", true, true, (test_data_path / "font" / "ptserif" / "PTSerif-BoldItalic.ttf").generic_string()); + std::cout << "Fonts loaded" << std::endl; + + // render full image + std::cout << "Rendering image" << std::endl; + alphaskia_image_t actual_image = render_full_image(); + std::cout << "Image rendered" << std::endl; + + // save png for future reference + std::cout << "Saving image as PNG" << std::endl; + + std::filesystem::path test_output_path = repository_root / "test" / "test-outputs" / "native"; + std::filesystem::create_directories(test_output_path); + + std::filesystem::path test_output_file = test_output_path / (std::string(STRINGIFY(ALPHASKIA_RID)) + ".png"); + alphaskia_data_t png_data = alphaskia_image_encode_png(actual_image); + if (!png_data) + { + std::cerr << "Failed to encode final image to png" << std::endl; + return 1; } - catch (const std::exception &e) + write_data_to_file_and_free(png_data, test_output_file.generic_string()); + std::cout << "Image saved to " << test_output_file.generic_string() << std::endl; + + // load reference image + std::filesystem::path test_reference_path = test_data_path / "reference" / (std::string(STRINGIFY(ALPHASKIA_RID)) + ".png"); + std::cout << "Loading reference image " << test_reference_path.generic_string() << std::endl; + std::vector test_reference_data; + read_file(test_reference_path.generic_string(), test_reference_data); + alphaskia_image_t expected_image = alphaskia_image_decode(test_reference_data.data(), test_reference_data.size()); + if (!expected_image) { - std::cerr << "Failed to run test: " << e.what() << std::endl; + std::cerr << "Failed to decode reference image" << std::endl; return 1; } + std::cout << "Reference image loaded" << std::endl; + + // compare images + std::cout << "Comparing images" << std::endl; + auto actual_width = alphaskia_image_get_width(actual_image); + auto actual_height = alphaskia_image_get_height(actual_image); + + auto expected_width = alphaskia_image_get_width(expected_image); + auto expected_height = alphaskia_image_get_height(expected_image); + if (actual_width != expected_width || actual_height != expected_height) + { + std::cerr << "Image sizes do not match: Actual[" << actual_width << "x" << actual_height << "] Expected[" << expected_width << "x" << actual_height << "]" << std::endl; + return 1; + } + + std::vector actual_pixels; + std::vector expected_pixels; + std::vector diff_pixels; + + size_t row_bytes = actual_width * sizeof(uint32_t); + size_t pixel_bytes = row_bytes * actual_height; + actual_pixels.resize(pixel_bytes); + expected_pixels.resize(pixel_bytes); + diff_pixels.resize(pixel_bytes); + + alphaskia_image_read_pixels(actual_image, actual_pixels.data(), row_bytes); + alphaskia_image_read_pixels(expected_image, expected_pixels.data(), row_bytes); + + auto compare_result = pixel_match(actual_pixels, + expected_pixels, + diff_pixels, + actual_width, + actual_height, + {/* threshold: */ 0.3, + /* diff_color: */ {255, 0, 0}}); + + int32_t totalPixels = compare_result.total_pixels - compare_result.transparent_pixels; + double percentDifference = ((double)compare_result.different_pixels / totalPixels) * 100; + bool pass = percentDifference < tolerance_percent; + if (!pass) + { + std::cerr << "Difference between original and new image is too big: " << compare_result.different_pixels << "/" << totalPixels << "(" << percentDifference << "%)"; + + // create diff image as PNG + alphaskia_image_t diff_image = alphaskia_image_from_pixels(actual_width, actual_height, diff_pixels.data()); + alphaskia_data_t diff_image_png_data = alphaskia_image_encode_png(diff_image); + alphaskia_image_free(diff_image); + + auto diff_output_path = test_output_path / (std::string(STRINGIFY(ALPHASKIA_RID)) + ".diff.png"); + write_data_to_file_and_free(diff_image_png_data, diff_output_path.generic_string()); + std::cout << "Error diff image saved to " << diff_output_path.generic_string() << std::endl; + return 1; + } + + std::cout << "Images match. Total Pixels: " << compare_result.total_pixels << ", Transparent Pixels: " << compare_result.transparent_pixels << ", Percent difference:" << percentDifference << "%" << std::endl; + return 0; } \ No newline at end of file From 447d1d620477c1f72b7f298c1e1492b3204a6570 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 19:58:47 +0200 Subject: [PATCH 09/38] Add msising condition for dependency install --- build/Build.Linux.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/Build.Linux.cs b/build/Build.Linux.cs index 2badecb..6b3dd4f 100644 --- a/build/Build.Linux.cs +++ b/build/Build.Linux.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -10,6 +11,7 @@ partial class Build { Target InstallDependenciesLinux => _ => _ .Unlisted() + .OnlyWhenStatic(()=> OperatingSystem.IsLinux() && IsGitHubActions) .After(PrepareGitHubArtifacts, LibAlphaSkiaGitSyncDeps, LibAlphaSkiaPatchSkiaBuildFiles, LibSkiaGitSyncDeps, LibSkiaPatchSkiaBuildFiles) From 47a66ab946bb561b99010b0b24d4158b62f41627 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 20:03:05 +0200 Subject: [PATCH 10/38] Adjust some compilation settings --- build/Build.LibAlphaSkia.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/build/Build.LibAlphaSkia.cs b/build/Build.LibAlphaSkia.cs index bf52823..081842b 100644 --- a/build/Build.LibAlphaSkia.cs +++ b/build/Build.LibAlphaSkia.cs @@ -178,7 +178,7 @@ partial class Build libs = [ "libalphaskia.dll.lib" ] } else { - libs = [ "libalphaskia" ] + libs = [ "alphaskia" ] } } """; @@ -361,6 +361,12 @@ void BuildAlphaSkiaTest() var sharedLibPath = DistBasePath / GetLibDirectory("libalphaskia", variant: Variant.Shared); var gnFlags = new Dictionary(); + if (!sharedLibPath.DirectoryExists()) + { + throw new IOException(sharedLibPath + " is fully missing, needed for linking!"); + } + + Log.Debug("Available libs for linking: " + string.Join(", ", sharedLibPath.GetFiles().Select(f => f.Name))); AppendToFlagList(gnArgs, "extra_cflags", $"'-DALPHASKIA_RID={TargetOs.RuntimeIdentifier}-{Architecture}'"); if (TargetOs == TargetOperatingSystem.Windows) From 78cbfdb660ccc862247b5b260c197ddc26ec0f31 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 20:11:40 +0200 Subject: [PATCH 11/38] Only build tests for shared variants --- build/Build.LibAlphaSkia.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/build/Build.LibAlphaSkia.cs b/build/Build.LibAlphaSkia.cs index 081842b..fad4237 100644 --- a/build/Build.LibAlphaSkia.cs +++ b/build/Build.LibAlphaSkia.cs @@ -35,6 +35,7 @@ partial class Build .DependsOn(PrepareGitHubArtifacts, LibAlphaSkiaGitSyncDeps, LibAlphaSkiaPatchSkiaBuildFiles, InstallDependenciesLinux) .After(LibAlphaSkia) .Requires(() => Architecture) + .OnlyWhenStatic(() => Variant == Variant.Shared) .Requires(() => TargetOs) .Executes(BuildAlphaSkiaTest); @@ -357,8 +358,14 @@ void CopyBuildOutputTo(AbsolutePath path) void BuildAlphaSkiaTest() { - var gnArgs = PrepareNativeBuild(Variant.Shared); - var sharedLibPath = DistBasePath / GetLibDirectory("libalphaskia", variant: Variant.Shared); + if (Variant != Variant.Shared) + { + Log.Information("Skipping build of BuildAlphaSkiaTest, not relevant for variant {Variant}", Variant); + return; + } + + var gnArgs = PrepareNativeBuild(Variant); + var sharedLibPath = DistBasePath / GetLibDirectory("libalphaskia", variant: Variant); var gnFlags = new Dictionary(); if (!sharedLibPath.DirectoryExists()) @@ -380,7 +387,7 @@ void BuildAlphaSkiaTest() } var buildTarget = "libalphaskiatest"; - var libDir = GetLibDirectory(buildTarget, TargetOs, Architecture, Variant.Shared); + var libDir = GetLibDirectory(buildTarget, TargetOs, Architecture, Variant); var outDir = SkiaPath / "out" / libDir; var exeExtension = GetExeExtension(); @@ -392,7 +399,7 @@ void BuildAlphaSkiaTest() FileSystemTasks.CopyFile(exePath, distPath / exePath.Name, FileExistsPolicy.OverwriteIfNewer); // copy shared lib beside executable - var libExtensions = new HashSet(GetLibExtensions(Variant.Shared), StringComparer.OrdinalIgnoreCase); + var libExtensions = new HashSet(GetLibExtensions(Variant), StringComparer.OrdinalIgnoreCase); foreach (var file in sharedLibPath.GetFiles().Where(f => libExtensions.Contains(f.Extension))) { FileSystemTasks.CopyFile(file, outDir / file.Name, FileExistsPolicy.OverwriteIfNewer); From 1b9ff80bb44941d73910771bb49b67130c1fa784 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 20:14:35 +0200 Subject: [PATCH 12/38] Remove test outputs --- .gitignore | 3 ++- test/test-outputs/dotnet/win-x64.png | Bin 34635 -> 0 bytes test/test-outputs/java/win-x64.png | Bin 34635 -> 0 bytes test/test-outputs/native/win-x64.diff.png | Bin 5798 -> 0 bytes test/test-outputs/native/win-x64.png | Bin 34635 -> 0 bytes test/test-outputs/native/win-x64diff.png | Bin 3155 -> 0 bytes 6 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 test/test-outputs/dotnet/win-x64.png delete mode 100644 test/test-outputs/java/win-x64.png delete mode 100644 test/test-outputs/native/win-x64.diff.png delete mode 100644 test/test-outputs/native/win-x64.png delete mode 100644 test/test-outputs/native/win-x64diff.png diff --git a/.gitignore b/.gitignore index 2cb66b3..9299872 100644 --- a/.gitignore +++ b/.gitignore @@ -718,4 +718,5 @@ fabric.properties /lib/java/local.properties /lib/node/**/test-outputs /lib/node/*/*.tgz -/lib/dotnet/Version.props \ No newline at end of file +/lib/dotnet/Version.props +/test/test-outputs \ No newline at end of file diff --git a/test/test-outputs/dotnet/win-x64.png b/test/test-outputs/dotnet/win-x64.png deleted file mode 100644 index 81479f137ae9f61bffb346684ed8ba0f2ccc9a06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34635 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 E52p*L$p8QV diff --git a/test/test-outputs/java/win-x64.png b/test/test-outputs/java/win-x64.png deleted file mode 100644 index 81479f137ae9f61bffb346684ed8ba0f2ccc9a06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34635 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 E52p*L$p8QV diff --git a/test/test-outputs/native/win-x64.diff.png b/test/test-outputs/native/win-x64.diff.png deleted file mode 100644 index 3bb574a1f9973fbad0d8236467b973040a7a2b45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5798 zcmeHLc~q0f79X~Y??b>0~^Hy8;R~JkYZH6Nd#c7H_>i` z`=KmxOJ8WwW=-r#hJj?d{;7?9o??G3xx2>GHSeyR^}+DVBYm}36rudzj~Z^CcJ1wD z0dP}K-~+c8?>(n`u)dW#1sk%3Zeg(}}l7V>3yL;Lx#b zp*jFH7whZ*wqNCUDs-brnwDa? zAIf1I0IBH?j@vXqjxS?y2H-IEOjxa9r@PfZ9VoqgdDu%41Z3Lq0k9khyee@^95-uo z9uEnL(w`A$X@(Z{?~<*+(J}9eyXf0#0LX7W)K}W>wSHh7nMluSntpkmQ5g@wixHWF zri5w~g40M+0swyFbmTJOYrIb~WF>=0b6S2G5GAr2jL$m(J5 z13>5IT!BJzzVXl_C!sF6~M8 zs;yv#m6@$&4No0+kP;3&V~WwTKR|67jH}AP1|N;?=VbBISV?K1mVYs_%AgN`aLZI+wO&wqsBZ<2DZX4LbE#~B|MFOe$DgGz0!GAf z#yVa5(Wls_1}YgfL7|uuk3tih3XV+iq`fdICcL6d z@_jh-PEl$mly_H6Mo>xk^J(>iJ5KEvJh$u~j(lC(wf~&&anZ1_bg08Pl^rudV&1#<^yN+l%mT+B3g?` z(d)e`QeVXj=)!y)^zlw7uaePRLF9-u?y+fFh1I<$%!^;UtbCN0=|)T4_iOB|^Z9}I zDU!bAr?irkEVOBJ&x{nS8!5T|7enb!wxp2ulwK~%VM$LCh7=C&UFb3KkCw?zpIb}` z&9bGLHHF%$P$t;or%xHdOPIOO=HDWEY`8whBCj&bY=S?hV3lCx*o~N0H@YYMIsEc*p zH*>x3e2>Wp;+xT)wYx>9xaCsjsNedr5oi=lYCNCA7o2a7X?Cd!TeC$~V{9W;9v0sxl#$+n2dqZRE{!>?S@ke9G{v2$7Mj}kYA z3TB*8Llb-oJ2~nqZt=0y`7ieBiE+b`I6t|d?KSomvtyu)Sz^1W?OQ%PBymTqC zaZ5VwSEk=6V|2xNWjpXI%4%JjO*Y z)2qX#@qjeZ(%89_i*9P@}#%J`Lg1IP7 z%F;6!)LEL>-nbjZ?*T=7AHU?Jd|5PCEi)KY8%hjtL^0h)J&Y?^j6=9b-t@uKqU}D* zgDKf{vUmtycl4WTU1cQ|siA31aI;~(^zJqUwjYGzKY*!*3|Jh0JD$tEGK&!7OWonu zXASRptc?XeoWkz-XZ}1{A%6tby#K{4?`2I2PrK6D2`-+#etmsk(o@&U1Cw(y1eo5R zO@Mbb0L-X4iY)u~?f|yC(B;g3&boRN={I9q`BAT`f*2IW*~rSe?Yw9g8~+7@v0=uW zH+hX*9azAB$zWsK9!;}&e=huB``&|GRod7NJ$QXn$J8>pI;uO|G$c!t7o%)<*U>kn%Act7$hY|u7&HG-4_o(CVP1tvn3dws91r%I5or!izv?tjyHtsz9 E8_1)#L;wH) diff --git a/test/test-outputs/native/win-x64.png b/test/test-outputs/native/win-x64.png deleted file mode 100644 index 81479f137ae9f61bffb346684ed8ba0f2ccc9a06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34635 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 E52p*L$p8QV diff --git a/test/test-outputs/native/win-x64diff.png b/test/test-outputs/native/win-x64diff.png deleted file mode 100644 index 31c10d44b204c83a194c87942bede7e3210fedc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3155 zcmeAS@N?(olHy`uVBq!ia0y~yU=?9tV2b5n1By6{{XPVwSc;uILpXq-h9jkefq{q1 z)5S5QV$R!hjJym69LyWe?$6MBthj~imZw^~>C5-bKrN$SGz3ONU^E0qLtr!nMnhmU m1V%$(Gz3ONV3>tK&Ig7DJtn5_4;>jnKJ#?-b6Mw<&;$UeBpf~f From 676f3766372a8e62dec83388b6be2e92f20cd295 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 20:16:31 +0200 Subject: [PATCH 13/38] Mark root as found --- test/native/src/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/native/src/main.cpp b/test/native/src/main.cpp index b59888d..52648ed 100644 --- a/test/native/src/main.cpp +++ b/test/native/src/main.cpp @@ -16,6 +16,8 @@ std::filesystem::path find_repository_root(std::filesystem::path current, bool & { if (std::filesystem::exists(current / ".nuke")) { + std::cerr << "Repository root found at " << current << std::endl; + success = true; return current; } From 01708070cea40a8aabd36a0497fc242b9d63f9d8 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 20:22:41 +0200 Subject: [PATCH 14/38] Set min macOS version --- build/Build.MacOs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Build.MacOs.cs b/build/Build.MacOs.cs index 1e94fa4..0e28241 100644 --- a/build/Build.MacOs.cs +++ b/build/Build.MacOs.cs @@ -4,7 +4,7 @@ partial class Build { void SetClangMacOs(Dictionary gnArgs) { - AppendToFlagList(gnArgs, "extra_cflags", "'-DHAVE_ARC4RANDOM_BUF', '-stdlib=libc++'"); + AppendToFlagList(gnArgs, "extra_cflags", "'-DHAVE_ARC4RANDOM_BUF', '-stdlib=libc++', '-mmacosx-version-min=10.15'"); AppendToFlagList(gnArgs, "extra_ldflags", "'-stdlib=libc++'"); } } \ No newline at end of file From a24f50c0334a62b43bd378824e8d59f4175f0a1b Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 20:32:51 +0200 Subject: [PATCH 15/38] Update reference test checks --- build/Build.LibAlphaSkia.cs | 2 +- test/dotnet/AlphaSkia.Test/Program.cs | 19 +- .../AlphaSkia.Test_/AlphaSkia.Test.csproj | 62 -- .../AlphaSkia.Test_/AlphaTabGeneratedTest.cs | 749 ------------------ .../AlphaSkia.Test_/MusicSheetRenderTest.cs | 116 --- test/dotnet/AlphaSkia.Test_/Usings.cs | 1 - .../net/alphatab/alphaskia/test/Main.java | 21 +- test/native/src/main.cpp | 12 +- test/node/index.ts | 35 +- test/test-data/reference/linux.png | Bin 0 -> 41322 bytes .../reference/{win-x64.png => win.png} | Bin 11 files changed, 23 insertions(+), 994 deletions(-) delete mode 100644 test/dotnet/AlphaSkia.Test_/AlphaSkia.Test.csproj delete mode 100644 test/dotnet/AlphaSkia.Test_/AlphaTabGeneratedTest.cs delete mode 100644 test/dotnet/AlphaSkia.Test_/MusicSheetRenderTest.cs delete mode 100644 test/dotnet/AlphaSkia.Test_/Usings.cs create mode 100644 test/test-data/reference/linux.png rename test/test-data/reference/{win-x64.png => win.png} (100%) diff --git a/build/Build.LibAlphaSkia.cs b/build/Build.LibAlphaSkia.cs index fad4237..41ba901 100644 --- a/build/Build.LibAlphaSkia.cs +++ b/build/Build.LibAlphaSkia.cs @@ -374,7 +374,7 @@ void BuildAlphaSkiaTest() } Log.Debug("Available libs for linking: " + string.Join(", ", sharedLibPath.GetFiles().Select(f => f.Name))); - AppendToFlagList(gnArgs, "extra_cflags", $"'-DALPHASKIA_RID={TargetOs.RuntimeIdentifier}-{Architecture}'"); + AppendToFlagList(gnArgs, "extra_cflags", $"'-DALPHASKIA_TEST_RID={TargetOs.RuntimeIdentifier}'"); if (TargetOs == TargetOperatingSystem.Windows) { diff --git a/test/dotnet/AlphaSkia.Test/Program.cs b/test/dotnet/AlphaSkia.Test/Program.cs index d339eaf..00e722b 100644 --- a/test/dotnet/AlphaSkia.Test/Program.cs +++ b/test/dotnet/AlphaSkia.Test/Program.cs @@ -89,7 +89,7 @@ public static int Main() var testOutputPath = Path.Combine(repositoryRoot, "test", "test-outputs", "dotnet"); Directory.CreateDirectory(testOutputPath); - var testOutputFile = Path.Combine(testOutputPath, AlphaSkiaRid + ".png"); + var testOutputFile = Path.Combine(testOutputPath, AlphaSkiaTestRid + ".png"); var pngData = actualImage.ToPng(); if (pngData == null) { @@ -100,7 +100,7 @@ public static int Main() Console.WriteLine($"Image saved to {testOutputFile}"); // load reference image - var testReferencePath = Path.Combine(testDataPath, "reference", AlphaSkiaRid + ".png"); + var testReferencePath = Path.Combine(testDataPath, "reference", AlphaSkiaTestRid + ".png"); Console.WriteLine($"Loading reference image {testReferencePath}"); var testReferenceData = File.ReadAllBytes(testReferencePath); @@ -152,7 +152,7 @@ public static int Main() using var diffImage = AlphaSkiaImage.FromPixels(actualWidth, actualHeight, diffPixels)!; var diffImagePngData = diffImage.ToPng()!; - var diffOutputPath = Path.Combine(testOutputPath, AlphaSkiaRid + ".diff.png"); + var diffOutputPath = Path.Combine(testOutputPath, AlphaSkiaTestRid + ".diff.png"); File.WriteAllBytes(diffOutputPath, diffImagePngData); Console.WriteLine($"Error diff image saved to {diffOutputPath}"); return 1; @@ -169,7 +169,7 @@ public static int Main() } } - private static readonly string OperatingSystemRid = OperatingSystem.IsWindows() + private static readonly string AlphaSkiaTestRid = OperatingSystem.IsWindows() ? "win" : OperatingSystem.IsLinux() ? "linux" @@ -180,15 +180,4 @@ public static int Main() : OperatingSystem.IsIOS() ? "ios" : throw new PlatformNotSupportedException("Unsupported test platform"); - - private static readonly string ArchitectureRid = RuntimeInformation.ProcessArchitecture switch - { - Architecture.X64 => "x64", - Architecture.X86 => "x86", - Architecture.Arm => "arm", - Architecture.Arm64 => "arm64", - _ => throw new PlatformNotSupportedException("Unsupported test architecture") - }; - - private static readonly string AlphaSkiaRid = OperatingSystemRid + "-" + ArchitectureRid; } \ No newline at end of file diff --git a/test/dotnet/AlphaSkia.Test_/AlphaSkia.Test.csproj b/test/dotnet/AlphaSkia.Test_/AlphaSkia.Test.csproj deleted file mode 100644 index 6ed9dbe..0000000 --- a/test/dotnet/AlphaSkia.Test_/AlphaSkia.Test.csproj +++ /dev/null @@ -1,62 +0,0 @@ - - - - net7.0 - enable - enable - - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/dotnet/AlphaSkia.Test_/AlphaTabGeneratedTest.cs b/test/dotnet/AlphaSkia.Test_/AlphaTabGeneratedTest.cs deleted file mode 100644 index 38e93c0..0000000 --- a/test/dotnet/AlphaSkia.Test_/AlphaTabGeneratedTest.cs +++ /dev/null @@ -1,749 +0,0 @@ -// -// This file has been auto generated by a unit test in alphaTab. -// -namespace AlphaSkia.Test; - -public class AlphaTabGeneratedRenderTest : MusicSheetRenderTest -{ - private AlphaSkiaImage DrawMusicSheetPart1(AlphaSkiaCanvas canvas) - { - canvas.BeginRender(1300, 164, (float)(RenderScale)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - TextAlign = AlphaSkiaTextAlign.Center; - Typeface = GetTypeface("PT Serif", false, false); - FontSize = (float)(32); - TextAlign = AlphaSkiaTextAlign.Center; - canvas.FillText("Title", Typeface, FontSize, (float)(650), (float)(40), TextAlign, TextBaseline); - TextAlign = AlphaSkiaTextAlign.Center; - Typeface = GetTypeface("PT Serif", false, false); - FontSize = (float)(20); - TextAlign = AlphaSkiaTextAlign.Center; - canvas.FillText("Subtitle", Typeface, FontSize, (float)(650), (float)(72), TextAlign, TextBaseline); - TextAlign = AlphaSkiaTextAlign.Center; - Typeface = GetTypeface("PT Serif", false, false); - FontSize = (float)(20); - TextAlign = AlphaSkiaTextAlign.Center; - canvas.FillText("Artist", Typeface, FontSize, (float)(650), (float)(92), TextAlign, TextBaseline); - TextAlign = AlphaSkiaTextAlign.Center; - Typeface = GetTypeface("PT Serif", false, false); - FontSize = (float)(20); - TextAlign = AlphaSkiaTextAlign.Center; - canvas.FillText("Album", Typeface, FontSize, (float)(650), (float)(112), TextAlign, TextBaseline); - TextAlign = AlphaSkiaTextAlign.Center; - Typeface = GetTypeface("PT Serif", false, false); - FontSize = (float)(15); - TextAlign = AlphaSkiaTextAlign.Right; - canvas.FillText("Music by Music", Typeface, FontSize, (float)(1260), (float)(132), TextAlign, TextBaseline); - TextAlign = AlphaSkiaTextAlign.Center; - Typeface = GetTypeface("PT Serif", false, false); - FontSize = (float)(15); - TextAlign = AlphaSkiaTextAlign.Left; - canvas.FillText("Words by Words", Typeface, FontSize, (float)(40), (float)(132), TextAlign, TextBaseline); - TextAlign = AlphaSkiaTextAlign.Center; - return canvas.EndRender()!; - } - private AlphaSkiaImage DrawMusicSheetPart2(AlphaSkiaCanvas canvas) - { - canvas.BeginRender(1300, 38, (float)(RenderScale)); - TextAlign = AlphaSkiaTextAlign.Center; - Typeface = GetTypeface("PT Serif", false, true); - FontSize = (float)(12); - TextAlign = AlphaSkiaTextAlign.Left; - canvas.FillText("Guitar Standard Tuning", Typeface, FontSize, (float)(40), (float)(3), TextAlign, TextBaseline); - TextAlign = AlphaSkiaTextAlign.Center; - return canvas.EndRender()!; - } - private AlphaSkiaImage DrawMusicSheetPart3(AlphaSkiaCanvas canvas) - { - canvas.BeginRender(1300, 6, (float)(RenderScale)); - TextAlign = AlphaSkiaTextAlign.Center; - return canvas.EndRender()!; - } - private AlphaSkiaImage DrawMusicSheetPart4(AlphaSkiaCanvas canvas) - { - canvas.BeginRender(1300, 373, (float)(RenderScale)); - TextAlign = AlphaSkiaTextAlign.Left; - Typeface = GetTypeface("PT Serif", true, false); - FontSize = (float)(14); - canvas.FillText("\uE1D5", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(156.6767578125), (float)(40), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("= 120", Typeface, FontSize, (float)(169.1767578125), (float)(27), TextAlign, TextBaseline); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); - canvas.FillRect((float)(92), (float)(130), (float)(254), (float)(1.04)); - canvas.FillRect((float)(92), (float)(139), (float)(254), (float)(1.04)); - canvas.FillRect((float)(92), (float)(148), (float)(254), (float)(1.04)); - canvas.FillRect((float)(92), (float)(157), (float)(254), (float)(1.04)); - canvas.FillRect((float)(92), (float)(166), (float)(254), (float)(1.04)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - canvas.FillText("\uE050", MusicTypeface, (float)(MusicFontSize * 1), (float)(94), (float)(157.52), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE084", MusicTypeface, (float)(MusicFontSize * 1), (float)(127), (float)(139), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE084", MusicTypeface, (float)(MusicFontSize * 1), (float)(127), (float)(157), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(200), (byte)(0), (byte)(0), (byte)(255)); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("1", Typeface, FontSize, (float)(141), (float)(118.75), TextAlign, TextBaseline); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(152.1767578125), (float)(148), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE272", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(172.86069962449085), (float)(148), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(180.86069962449085), (float)(149), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.BeginPath(); - canvas.MoveTo((float)(156.6767578125), (float)(144)); - canvas.LineTo((float)(170.855110706305), (float)(133.50723450810483)); - canvas.LineTo((float)(184.23569962449085), (float)(145)); - canvas.Stroke(); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(192.23569962449085), (float)(148), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(220.91964143648167), (float)(144.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.BeginPath(); - canvas.MoveTo((float)(196.73569962449085), (float)(144)); - canvas.LineTo((float)(209.12929655256082), (float)(131.33765133817155)); - canvas.LineTo((float)(224.29464143648167), (float)(140.5)); - canvas.Stroke(); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(232.29464143648167), (float)(148), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE262", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(252.9785832484725), (float)(143.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(260.9785832484725), (float)(144.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.BeginPath(); - canvas.MoveTo((float)(236.79464143648167), (float)(144)); - canvas.LineTo((float)(249.1882383645516), (float)(131.33765133817155)); - canvas.LineTo((float)(264.3535832484725), (float)(140.5)); - canvas.Stroke(); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(272.3535832484725), (float)(148), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(294.5517916242362), (float)(140), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.BeginPath(); - canvas.MoveTo((float)(276.8535832484725), (float)(152)); - canvas.LineTo((float)(292.1060513036615), (float)(156.93783819474976)); - canvas.LineTo((float)(297.9267916242362), (float)(142)); - canvas.Stroke(); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(305.9267916242362), (float)(148), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE262", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(324.625), (float)(139), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 0.75), (float)(332.625), (float)(140), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.BeginPath(); - canvas.MoveTo((float)(310.4267916242362), (float)(152)); - canvas.LineTo((float)(327.2193887931866), (float)(157.24460932567123)); - canvas.LineTo((float)(336), (float)(142)); - canvas.Stroke(); - canvas.FillRect((float)(345), (float)(130.1), (float)(1), (float)(36)); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(152.1767578125), (float)(148)); - canvas.LineTo((float)(152.1767578125), (float)(175)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(192.23569962449085), (float)(148)); - canvas.LineTo((float)(192.23569962449085), (float)(175)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(232.29464143648167), (float)(148)); - canvas.LineTo((float)(232.29464143648167), (float)(175)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(281.3535832484725), (float)(148)); - canvas.LineTo((float)(281.3535832484725), (float)(121)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.BeginPath(); - canvas.MoveTo((float)(281.3535832484725), (float)(121)); - canvas.LineTo((float)(314.9267916242362), (float)(121)); - canvas.LineTo((float)(314.9267916242362), (float)(125)); - canvas.LineTo((float)(281.3535832484725), (float)(125)); - canvas.ClosePath(); - canvas.Fill(); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(314.9267916242362), (float)(148)); - canvas.LineTo((float)(314.9267916242362), (float)(121)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(100)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); - canvas.FillRect((float)(346), (float)(130), (float)(126), (float)(1.04)); - canvas.FillRect((float)(346), (float)(139), (float)(126), (float)(1.04)); - canvas.FillRect((float)(346), (float)(148), (float)(126), (float)(1.04)); - canvas.FillRect((float)(346), (float)(157), (float)(126), (float)(1.04)); - canvas.FillRect((float)(346), (float)(166), (float)(126), (float)(1.04)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(200), (byte)(0), (byte)(0), (byte)(255)); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("2", Typeface, FontSize, (float)(346), (float)(118.75), TextAlign, TextBaseline); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(357.1767578125), (float)(139), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(378.359547484596), (float)(139), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(399.542337156692), (float)(161.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A3", MusicTypeface, (float)(MusicFontSize * 1), (float)(426.623147703567), (float)(157), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillRect((float)(471), (float)(130.1), (float)(1), (float)(36)); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(366.1767578125), (float)(139)); - canvas.LineTo((float)(366.1767578125), (float)(112)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.BeginPath(); - canvas.MoveTo((float)(366.1767578125), (float)(112)); - canvas.LineTo((float)(387.359547484596), (float)(117)); - canvas.LineTo((float)(387.359547484596), (float)(121)); - canvas.LineTo((float)(366.1767578125), (float)(116)); - canvas.ClosePath(); - canvas.Fill(); - canvas.BeginPath(); - canvas.MoveTo((float)(366.1767578125), (float)(118)); - canvas.LineTo((float)(387.359547484596), (float)(123)); - canvas.LineTo((float)(387.359547484596), (float)(127)); - canvas.LineTo((float)(366.1767578125), (float)(122)); - canvas.ClosePath(); - canvas.Fill(); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(387.359547484596), (float)(139)); - canvas.LineTo((float)(387.359547484596), (float)(117)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.BeginPath(); - canvas.MoveTo((float)(387.359547484596), (float)(117)); - canvas.LineTo((float)(408.542337156692), (float)(122)); - canvas.LineTo((float)(408.542337156692), (float)(126)); - canvas.LineTo((float)(387.359547484596), (float)(121)); - canvas.ClosePath(); - canvas.Fill(); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(408.542337156692), (float)(161.5)); - canvas.LineTo((float)(408.542337156692), (float)(122)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(435.623147703567), (float)(130)); - canvas.LineTo((float)(435.623147703567), (float)(157)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(100)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - TextAlign = AlphaSkiaTextAlign.Center; - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("PT Serif", false, true); - FontSize = (float)(12); - canvas.BeginPath(); - canvas.MoveTo((float)(366.1767578125), (float)(102)); - canvas.LineTo((float)(366.1767578125), (float)(97)); - canvas.LineTo((float)(394.7559555656507), (float)(104)); - canvas.Stroke(); - canvas.BeginPath(); - canvas.MoveTo((float)(407.04394995041633), (float)(107)); - canvas.LineTo((float)(435.623147703567), (float)(115)); - canvas.LineTo((float)(435.623147703567), (float)(120)); - canvas.Stroke(); - canvas.FillText("3", Typeface, FontSize, (float)(400.8999527580335), (float)(106), TextAlign, TextBaseline); - TextAlign = AlphaSkiaTextAlign.Left; - TextBaseline = AlphaSkiaTextBaseline.Top; - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); - canvas.FillRect((float)(472), (float)(130), (float)(206), (float)(1.04)); - canvas.FillRect((float)(472), (float)(139), (float)(206), (float)(1.04)); - canvas.FillRect((float)(472), (float)(148), (float)(206), (float)(1.04)); - canvas.FillRect((float)(472), (float)(157), (float)(206), (float)(1.04)); - canvas.FillRect((float)(472), (float)(166), (float)(206), (float)(1.04)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(200), (byte)(0), (byte)(0), (byte)(255)); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("3", Typeface, FontSize, (float)(472), (float)(118.75), TextAlign, TextBaseline); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(496.32666015625), (float)(157), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(496.32666015625), (float)(143.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(496.32666015625), (float)(134.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.BeginRotate((float)(527.8699951171875), (float)(172), (float)(-90)); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.EndRotate(); - canvas.BeginPath(); - canvas.MoveTo((float)(527.8699951171875), (float)(180)); - canvas.LineTo((float)(531.8699951171875), (float)(172)); - canvas.LineTo((float)(523.8699951171875), (float)(172)); - canvas.ClosePath(); - canvas.Fill(); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); - canvas.FillRect((float)(533.8699951171875), (float)(175), (float)(15), (float)(1)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(536.8699951171875), (float)(175), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(536.8699951171875), (float)(166), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(536.8699951171875), (float)(157), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(536.8699951171875), (float)(143.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(536.8699951171875), (float)(134.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); - canvas.FillRect((float)(574.413330078125), (float)(175), (float)(15), (float)(1)); - canvas.FillRect((float)(574.413330078125), (float)(184), (float)(15), (float)(1)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(577.413330078125), (float)(184), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(577.413330078125), (float)(166), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(577.413330078125), (float)(152.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.BeginRotate((float)(596.9566650390625), (float)(137.5), (float)(90)); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(43.199999999999996), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.EndRotate(); - canvas.BeginPath(); - canvas.MoveTo((float)(596.9566650390625), (float)(129.5)); - canvas.LineTo((float)(600.9566650390625), (float)(137.5)); - canvas.LineTo((float)(592.9566650390625), (float)(137.5)); - canvas.ClosePath(); - canvas.Fill(); - canvas.FillText("\uE262", MusicTypeface, (float)(MusicFontSize * 1), (float)(607.9566650390625), (float)(143.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); - canvas.FillRect((float)(614.9566650390625), (float)(175), (float)(15), (float)(1)); - canvas.FillRect((float)(614.9566650390625), (float)(184), (float)(15), (float)(1)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(617.9566650390625), (float)(184), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(617.9566650390625), (float)(166), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(617.9566650390625), (float)(152.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(617.9566650390625), (float)(143.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uE0A4", MusicTypeface, (float)(MusicFontSize * 1), (float)(617.9566650390625), (float)(134.5), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillRect((float)(670), (float)(130.1), (float)(1), (float)(36)); - canvas.FillRect((float)(674), (float)(130.1), (float)(4), (float)(36)); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(496.32666015625), (float)(134.5)); - canvas.LineTo((float)(496.32666015625), (float)(184)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(545.8699951171875), (float)(107.5)); - canvas.LineTo((float)(545.8699951171875), (float)(175)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(586.413330078125), (float)(125.5)); - canvas.LineTo((float)(586.413330078125), (float)(184)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.LineWidth = (float)(0.96); - canvas.BeginPath(); - canvas.MoveTo((float)(626.9566650390625), (float)(107.5)); - canvas.LineTo((float)(626.9566650390625), (float)(184)); - canvas.Stroke(); - canvas.LineWidth = (float)(1); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(100)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); - canvas.FillRect((float)(92), (float)(284), (float)(254), (float)(1.04)); - canvas.FillRect((float)(92), (float)(295), (float)(254), (float)(1.04)); - canvas.FillRect((float)(92), (float)(306), (float)(254), (float)(1.04)); - canvas.FillRect((float)(92), (float)(317), (float)(61.02685546875), (float)(1.04)); - canvas.FillRect((float)(161.32666015625), (float)(317), (float)(31.759140014648438), (float)(1.04)); - canvas.FillRect((float)(201.38560485839844), (float)(317), (float)(31.759140014648438), (float)(1.04)); - canvas.FillRect((float)(241.44454956054688), (float)(317), (float)(31.759124755859375), (float)(1.04)); - canvas.FillRect((float)(281.50347900390625), (float)(317), (float)(25.273406982421875), (float)(1.04)); - canvas.FillRect((float)(315.0766906738281), (float)(317), (float)(30.923309326171875), (float)(1.04)); - canvas.FillRect((float)(92), (float)(328), (float)(254), (float)(1.04)); - canvas.FillRect((float)(92), (float)(339), (float)(254), (float)(1.04)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - canvas.FillText("\uE06D", MusicTypeface, (float)(MusicFontSize * 0.9230769230769231), (float)(97), (float)(311.8), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(163.32666015625), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("9", Typeface, FontSize, (float)(153.02685546875), (float)(317.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - canvas.BeginPath(); - canvas.BeginPath(); - canvas.MoveTo((float)(188.23569962449085), (float)(274.7)); - canvas.LineTo((float)(185.23569962449085), (float)(280.7)); - canvas.LineTo((float)(191.23569962449085), (float)(280.7)); - canvas.ClosePath(); - canvas.Fill(); - canvas.Stroke(); - canvas.MoveTo((float)(162.32666015625), (float)(317.3)); - canvas.BezierCurveTo((float)(175.28117989037042), (float)(317.3), (float)(188.23569962449085), (float)(317.3), (float)(188.23569962449085), (float)(280.7)); - canvas.Stroke(); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("\u00BC", Typeface, FontSize, (float)(183.47495743699085), (float)(260.7), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(203.38560196824085), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("9", Typeface, FontSize, (float)(193.08579728074085), (float)(317.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - canvas.BeginPath(); - canvas.BeginPath(); - canvas.MoveTo((float)(228.29464143648167), (float)(268.7)); - canvas.LineTo((float)(225.29464143648167), (float)(274.7)); - canvas.LineTo((float)(231.29464143648167), (float)(274.7)); - canvas.ClosePath(); - canvas.Fill(); - canvas.Stroke(); - canvas.MoveTo((float)(202.38560196824085), (float)(317.3)); - canvas.BezierCurveTo((float)(215.34012170236127), (float)(317.3), (float)(228.29464143648167), (float)(317.3), (float)(228.29464143648167), (float)(274.7)); - canvas.Stroke(); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("\u00BD", Typeface, FontSize, (float)(223.25142854585667), (float)(254.7), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(243.44454378023167), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("9", Typeface, FontSize, (float)(233.14473909273167), (float)(317.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - canvas.BeginPath(); - canvas.BeginPath(); - canvas.MoveTo((float)(268.3535832484725), (float)(256.7)); - canvas.LineTo((float)(265.3535832484725), (float)(262.7)); - canvas.LineTo((float)(271.3535832484725), (float)(262.7)); - canvas.ClosePath(); - canvas.Fill(); - canvas.Stroke(); - canvas.MoveTo((float)(242.44454378023167), (float)(317.3)); - canvas.BezierCurveTo((float)(255.39906351435206), (float)(317.3), (float)(268.3535832484725), (float)(317.3), (float)(268.3535832484725), (float)(262.7)); - canvas.Stroke(); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("full", Typeface, FontSize, (float)(259.3589543422225), (float)(242.7), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(283.5034855922225), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("9", Typeface, FontSize, (float)(273.2036809047225), (float)(317.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - canvas.BeginPath(); - canvas.BeginPath(); - canvas.MoveTo((float)(301.9267916242362), (float)(244.7)); - canvas.LineTo((float)(298.9267916242362), (float)(250.7)); - canvas.LineTo((float)(304.9267916242362), (float)(250.7)); - canvas.ClosePath(); - canvas.Fill(); - canvas.Stroke(); - canvas.MoveTo((float)(282.5034855922225), (float)(317.3)); - canvas.BezierCurveTo((float)(292.2151386082294), (float)(317.3), (float)(301.9267916242362), (float)(317.3), (float)(301.9267916242362), (float)(250.7)); - canvas.Stroke(); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("1\u00BD", Typeface, FontSize, (float)(293.2336763898612), (float)(230.7), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(317.0766939679862), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("9", Typeface, FontSize, (float)(306.7768892804862), (float)(317.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - canvas.BeginPath(); - canvas.BeginPath(); - canvas.MoveTo((float)(340), (float)(232.7)); - canvas.LineTo((float)(337), (float)(238.7)); - canvas.LineTo((float)(343), (float)(238.7)); - canvas.ClosePath(); - canvas.Fill(); - canvas.Stroke(); - canvas.MoveTo((float)(316.0766939679862), (float)(317.3)); - canvas.BezierCurveTo((float)(328.0383469839931), (float)(317.3), (float)(340), (float)(317.3), (float)(340), (float)(238.7)); - canvas.Stroke(); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("2", Typeface, FontSize, (float)(336.35009765625), (float)(218.7), TextAlign, TextBaseline); - canvas.FillRect((float)(345), (float)(284.3), (float)(1), (float)(54.99999999999994)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); - canvas.FillRect((float)(346), (float)(284), (float)(126), (float)(1.04)); - canvas.FillRect((float)(346), (float)(295), (float)(12.02685546875), (float)(1.04)); - canvas.FillRect((float)(366.32666015625), (float)(295), (float)(12.882984161376953), (float)(1.04)); - canvas.FillRect((float)(387.50944900512695), (float)(295), (float)(84.49055099487305), (float)(1.04)); - canvas.FillRect((float)(346), (float)(306), (float)(126), (float)(1.04)); - canvas.FillRect((float)(346), (float)(317), (float)(54.392433166503906), (float)(1.04)); - canvas.FillRect((float)(408.6922378540039), (float)(317), (float)(18.781005859375), (float)(1.04)); - canvas.FillRect((float)(435.7730484008789), (float)(317), (float)(36.226951599121094), (float)(1.04)); - canvas.FillRect((float)(346), (float)(328), (float)(126), (float)(1.04)); - canvas.FillRect((float)(346), (float)(339), (float)(126), (float)(1.04)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(368.32666015625), (float)(295.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("3", Typeface, FontSize, (float)(358.02685546875), (float)(295.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(389.509449828346), (float)(295.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("3", Typeface, FontSize, (float)(379.209645140846), (float)(295.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(410.692239500442), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("3", Typeface, FontSize, (float)(400.392434812942), (float)(317.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(437.773050047317), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("5", Typeface, FontSize, (float)(427.473245359817), (float)(317.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - canvas.FillRect((float)(471), (float)(284.3), (float)(1), (float)(54.99999999999994)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(165), (byte)(165), (byte)(165), (byte)(255)); - canvas.FillRect((float)(472), (float)(284), (float)(25.1767578125), (float)(1.04)); - canvas.FillRect((float)(505.4765625), (float)(284), (float)(32.2435302734375), (float)(1.04)); - canvas.FillRect((float)(546.0198974609375), (float)(284), (float)(72.786865234375), (float)(1.04)); - canvas.FillRect((float)(627.1065673828125), (float)(284), (float)(50.8934326171875), (float)(1.04)); - canvas.FillRect((float)(472), (float)(295), (float)(25.1767578125), (float)(1.04)); - canvas.FillRect((float)(505.4765625), (float)(295), (float)(32.2435302734375), (float)(1.04)); - canvas.FillRect((float)(546.0198974609375), (float)(295), (float)(72.786865234375), (float)(1.04)); - canvas.FillRect((float)(627.1065673828125), (float)(295), (float)(50.8934326171875), (float)(1.04)); - canvas.FillRect((float)(472), (float)(306), (float)(25.1767578125), (float)(1.04)); - canvas.FillRect((float)(505.4765625), (float)(306), (float)(32.2435302734375), (float)(1.04)); - canvas.FillRect((float)(546.0198974609375), (float)(306), (float)(32.2435302734375), (float)(1.04)); - canvas.FillRect((float)(586.563232421875), (float)(306), (float)(32.2435302734375), (float)(1.04)); - canvas.FillRect((float)(627.1065673828125), (float)(306), (float)(50.8934326171875), (float)(1.04)); - canvas.FillRect((float)(472), (float)(317), (float)(65.7200927734375), (float)(1.04)); - canvas.FillRect((float)(546.0198974609375), (float)(317), (float)(32.2435302734375), (float)(1.04)); - canvas.FillRect((float)(586.563232421875), (float)(317), (float)(32.2435302734375), (float)(1.04)); - canvas.FillRect((float)(627.1065673828125), (float)(317), (float)(50.8934326171875), (float)(1.04)); - canvas.FillRect((float)(472), (float)(328), (float)(65.7200927734375), (float)(1.04)); - canvas.FillRect((float)(546.0198974609375), (float)(328), (float)(32.2435302734375), (float)(1.04)); - canvas.FillRect((float)(586.563232421875), (float)(328), (float)(32.2435302734375), (float)(1.04)); - canvas.FillRect((float)(627.1065673828125), (float)(328), (float)(50.8934326171875), (float)(1.04)); - canvas.FillRect((float)(472), (float)(339), (float)(206), (float)(1.04)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - canvas.BeginPath(); - canvas.MoveTo((float)(488), (float)(275.8)); - canvas.LineTo((float)(488), (float)(312.8)); - canvas.Stroke(); - canvas.BeginPath(); - canvas.MoveTo((float)(488), (float)(275.8)); - canvas.LineTo((float)(492), (float)(283.8)); - canvas.LineTo((float)(484), (float)(283.8)); - canvas.ClosePath(); - canvas.Fill(); - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(507.4765625), (float)(284.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("0", Typeface, FontSize, (float)(497.1767578125), (float)(284.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(507.4765625), (float)(295.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("1", Typeface, FontSize, (float)(497.1767578125), (float)(295.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(507.4765625), (float)(306.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("0", Typeface, FontSize, (float)(497.1767578125), (float)(306.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - canvas.BeginRotate((float)(527.7200927734375), (float)(326.8), (float)(-90)); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(43.199999999999996), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.EndRotate(); - canvas.BeginPath(); - canvas.MoveTo((float)(528), (float)(334.8)); - canvas.LineTo((float)(532), (float)(326.8)); - canvas.LineTo((float)(524), (float)(326.8)); - canvas.ClosePath(); - canvas.Fill(); - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(548.0198974609375), (float)(284.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("0", Typeface, FontSize, (float)(537.7200927734375), (float)(284.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(548.0198974609375), (float)(295.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("1", Typeface, FontSize, (float)(537.7200927734375), (float)(295.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(548.0198974609375), (float)(306.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("0", Typeface, FontSize, (float)(537.7200927734375), (float)(306.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(548.0198974609375), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("2", Typeface, FontSize, (float)(537.7200927734375), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(548.0198974609375), (float)(328.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("3", Typeface, FontSize, (float)(537.7200927734375), (float)(328.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - canvas.BeginPath(); - canvas.MoveTo((float)(569), (float)(297.8)); - canvas.LineTo((float)(569), (float)(334.8)); - canvas.Stroke(); - canvas.BeginPath(); - canvas.MoveTo((float)(569), (float)(297.8)); - canvas.LineTo((float)(573), (float)(305.8)); - canvas.LineTo((float)(565), (float)(305.8)); - canvas.ClosePath(); - canvas.Fill(); - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(588.563232421875), (float)(306.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("2", Typeface, FontSize, (float)(578.263427734375), (float)(306.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(588.563232421875), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("2", Typeface, FontSize, (float)(578.263427734375), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(588.563232421875), (float)(328.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("0", Typeface, FontSize, (float)(578.263427734375), (float)(328.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - canvas.BeginRotate((float)(596.8067626953125), (float)(283.8), (float)(90)); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(0), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(10.799999999999999), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(21.599999999999998), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(32.4), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.FillText("\uEAA4", MusicTypeface, (float)(MusicFontSize * 1.2), (float)(43.199999999999996), (float)(10.799999999999999), AlphaSkiaTextAlign.Left, AlphaSkiaTextBaseline.Alphabetic); - canvas.EndRotate(); - canvas.BeginPath(); - canvas.MoveTo((float)(597), (float)(275.8)); - canvas.LineTo((float)(601), (float)(283.8)); - canvas.LineTo((float)(593), (float)(283.8)); - canvas.ClosePath(); - canvas.Fill(); - TextBaseline = AlphaSkiaTextBaseline.Middle; - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(629.1065673828125), (float)(284.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("0", Typeface, FontSize, (float)(618.8067626953125), (float)(284.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(629.1065673828125), (float)(295.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("2", Typeface, FontSize, (float)(618.8067626953125), (float)(295.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(629.1065673828125), (float)(306.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("2", Typeface, FontSize, (float)(618.8067626953125), (float)(306.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(629.1065673828125), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("2", Typeface, FontSize, (float)(618.8067626953125), (float)(317.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(11); - canvas.FillText("", Typeface, FontSize, (float)(629.1065673828125), (float)(328.3), TextAlign, TextBaseline); - Typeface = GetTypeface("Roboto", false, false); - FontSize = (float)(13); - canvas.FillText("0", Typeface, FontSize, (float)(618.8067626953125), (float)(328.3), TextAlign, TextBaseline); - TextBaseline = AlphaSkiaTextBaseline.Top; - canvas.FillRect((float)(670), (float)(284.3), (float)(1), (float)(54.99999999999994)); - canvas.FillRect((float)(674), (float)(284.3), (float)(4), (float)(54.99999999999994)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(34), (byte)(34), (byte)(17), (byte)(255)); - canvas.BeginPath(); - canvas.MoveTo((float)(92), (float)(130.1)); - canvas.LineTo((float)(92), (float)(339.3)); - canvas.Stroke(); - Typeface = GetTypeface("PT Serif", false, true); - FontSize = (float)(12); - canvas.FillText("s.guit.", Typeface, FontSize, (float)(50), (float)(130.1), TextAlign, TextBaseline); - canvas.FillRect((float)(86), (float)(118.1), (float)(3), (float)(233.20000000000002)); - canvas.BeginPath(); - canvas.MoveTo((float)(86), (float)(118.1)); - canvas.BezierCurveTo((float)(86), (float)(118.1), (float)(86), (float)(118.1), (float)(98), (float)(115.1)); - canvas.BezierCurveTo((float)(92), (float)(121.1), (float)(86), (float)(121.1), (float)(86), (float)(121.1)); - canvas.ClosePath(); - canvas.Fill(); - canvas.BeginPath(); - canvas.MoveTo((float)(86), (float)(351.3)); - canvas.BezierCurveTo((float)(86), (float)(351.3), (float)(92), (float)(351.3), (float)(98), (float)(354.3)); - canvas.BezierCurveTo((float)(92), (float)(348.3), (float)(86), (float)(348.3), (float)(86), (float)(348.3)); - canvas.ClosePath(); - canvas.Fill(); - return canvas.EndRender()!; - } - private AlphaSkiaImage DrawMusicSheetPart5(AlphaSkiaCanvas canvas) - { - canvas.BeginRender(116, 24, (float)(RenderScale)); - canvas.Color = AlphaSkiaCanvas.RgbaToColor((byte)(0), (byte)(0), (byte)(0), (byte)(255)); - Typeface = GetTypeface("Roboto", true, false); - FontSize = (float)(12); - TextAlign = AlphaSkiaTextAlign.Left; - canvas.FillText("rendered by alphaTab", Typeface, FontSize, (float)(0), (float)(12), TextAlign, TextBaseline); - return canvas.EndRender()!; - } - protected override float[,] PartPositions => new float[,] { - {(float)0, (float)0}, {(float)0, (float)164}, {(float)0, (float)202}, {(float)0, (float)208}, {(float)591.962890625, (float)581} - }; - protected override int TotalWidth => (int)1300; - protected override int TotalHeight => (int)605; - protected override Func[] AllParts => new[] - { - DrawMusicSheetPart1, DrawMusicSheetPart2, DrawMusicSheetPart3, DrawMusicSheetPart4, DrawMusicSheetPart5 - }; -} diff --git a/test/dotnet/AlphaSkia.Test_/MusicSheetRenderTest.cs b/test/dotnet/AlphaSkia.Test_/MusicSheetRenderTest.cs deleted file mode 100644 index 92f21cb..0000000 --- a/test/dotnet/AlphaSkia.Test_/MusicSheetRenderTest.cs +++ /dev/null @@ -1,116 +0,0 @@ -namespace AlphaSkia.Test; - -public abstract class MusicSheetRenderTest -{ - protected static AlphaSkiaTypeface MusicTypeface { get; private set; } = null!; - protected static float RenderScale { get; private set; } = 1; - protected static int MusicFontSize { get; private set; } = 34; - - private static readonly IDictionary CustomTypefaces = - new Dictionary(StringComparer.OrdinalIgnoreCase); - - - private static string CustomTypefaceKey(string fontFamily, bool isBold, bool isItalic) - { - return fontFamily.ToLowerInvariant() + "_" + isBold + "_" + isItalic; - } - - [OneTimeSetUp] - public static void Setup() - { - AlphaSkiaPlatform.LoadLibrary(); - - var bravura = ReadFont("font/bravura/Bravura.ttf"); - - MusicTypeface = AlphaSkiaTypeface.Register(bravura) ?? - throw new InvalidOperationException("Could not load bravura font"); - - CustomTypefaces[CustomTypefaceKey("Roboto", false, false)] = - AlphaSkiaTypeface.Register(ReadFont("font/roboto/Roboto-Regular.ttf")) ?? - throw new InvalidOperationException("Could not load font"); - CustomTypefaces[CustomTypefaceKey("Roboto", true, false)] = - AlphaSkiaTypeface.Register(ReadFont("font/roboto/Roboto-Bold.ttf")) ?? - throw new InvalidOperationException("Could not load font"); - CustomTypefaces[CustomTypefaceKey("Roboto", false, true)] = - AlphaSkiaTypeface.Register(ReadFont("font/roboto/Roboto-Italic.ttf")) ?? - throw new InvalidOperationException("Could not load font"); - CustomTypefaces[CustomTypefaceKey("Roboto", true, true)] = - AlphaSkiaTypeface.Register(ReadFont("font/roboto/Roboto-BoldItalic.ttf")) ?? - throw new InvalidOperationException("Could not load font"); - - CustomTypefaces[CustomTypefaceKey("PT Serif", false, false)] = - AlphaSkiaTypeface.Register(ReadFont("font/ptserif/PTSerif-Regular.ttf")) ?? - throw new InvalidOperationException("Could not load font"); - CustomTypefaces[CustomTypefaceKey("PT Serif", true, false)] = - AlphaSkiaTypeface.Register(ReadFont("font/ptserif/PTSerif-Bold.ttf")) ?? - throw new InvalidOperationException("Could not load font"); - CustomTypefaces[CustomTypefaceKey("PT Serif", false, true)] = - AlphaSkiaTypeface.Register(ReadFont("font/ptserif/PTSerif-Italic.ttf")) ?? - throw new InvalidOperationException("Could not load font"); - CustomTypefaces[CustomTypefaceKey("PT Serif", true, true)] = - AlphaSkiaTypeface.Register(ReadFont("font/ptserif/PTSerif-BoldItalic.ttf")) ?? - throw new InvalidOperationException("Could not load font"); - } - - protected static AlphaSkiaTypeface GetTypeface(string name, bool isBold, bool isItalic) - { - if (!CustomTypefaces.TryGetValue(CustomTypefaceKey(name, isBold, isItalic), out var face)) - { - throw new InvalidOperationException("Unknown font requested: " + name); - } - - return face; - } - - - protected AlphaSkiaTextAlign TextAlign { get; set; } = AlphaSkiaTextAlign.Center; - protected AlphaSkiaTextBaseline TextBaseline { get; set; } = AlphaSkiaTextBaseline.Top; - protected AlphaSkiaTypeface Typeface { get; set; } = null!; - protected float FontSize { get; set; } - - private static byte[] ReadFont(string relativePath) - { - var resourceName = "AlphaSkia.Test." + relativePath.Replace("/", "."); - using var stream = typeof(MusicSheetRenderTest).Assembly.GetManifestResourceStream(resourceName); - if (stream == null) - { - throw new IOException("Could not find resource " + relativePath); - } - - var data = new byte[stream.Length]; - stream.ReadExactly(data); - return data; - } - - private AlphaSkiaImage RenderFullImage() - { - using var canvas = new AlphaSkiaCanvas(); - - var images = AllParts.Select(p => p(canvas)).ToArray(); - - canvas.BeginRender(TotalWidth, TotalHeight, RenderScale); - - for (var i = 0; i < images.Length; i++) - { - canvas.DrawImage(images[i], PartPositions[i, 0], PartPositions[i, 1], images[i].Width / RenderScale, images[i].Height / RenderScale); - images[i].Dispose(); - } - - return canvas.EndRender()!; - } - - [Test] - public void RenderTest() - { - using var finalImage = RenderFullImage(); - var fileName = GetType().Name; - File.WriteAllBytes(Path.Combine(TestContext.CurrentContext.WorkDirectory, fileName + ".png"), - finalImage.ToPng()!); - } - - protected abstract int TotalWidth { get; } - protected abstract int TotalHeight { get; } - protected abstract float[,] PartPositions { get; } - - protected abstract Func[] AllParts { get; } -} \ No newline at end of file diff --git a/test/dotnet/AlphaSkia.Test_/Usings.cs b/test/dotnet/AlphaSkia.Test_/Usings.cs deleted file mode 100644 index cefced4..0000000 --- a/test/dotnet/AlphaSkia.Test_/Usings.cs +++ /dev/null @@ -1 +0,0 @@ -global using NUnit.Framework; \ No newline at end of file diff --git a/test/java/src/main/java/net/alphatab/alphaskia/test/Main.java b/test/java/src/main/java/net/alphatab/alphaskia/test/Main.java index 2ec84b4..c7be174 100644 --- a/test/java/src/main/java/net/alphatab/alphaskia/test/Main.java +++ b/test/java/src/main/java/net/alphatab/alphaskia/test/Main.java @@ -97,7 +97,7 @@ private static int mainWithExitCode() { //noinspection ResultOfMethodCallIgnored testOutputPath.toFile().mkdirs(); - var testOutputFile = testOutputPath.resolve(getAlphaSkiaRid() + ".png"); + var testOutputFile = testOutputPath.resolve(getAlphaSkiaTestRid() + ".png"); var pngData = actualImage.toPng(); if (pngData == null) { throw new IllegalStateException("Failed to encode final image to png"); @@ -107,7 +107,7 @@ private static int mainWithExitCode() { System.out.println("Image saved to " + testOutputFile); // load reference image - var testReferencePath = testDataPath.resolve("reference/" + getAlphaSkiaRid() + ".png"); + var testReferencePath = testDataPath.resolve("reference/" + getAlphaSkiaTestRid() + ".png"); System.out.println("Loading reference image " + testReferencePath); var testReferenceData = Files.readAllBytes(testReferencePath); @@ -154,7 +154,7 @@ private static int mainWithExitCode() { try (var diffImage = AlphaSkiaImage.fromPixels(actualWidth, actualHeight, diffPixels)) { var diffImagePngData = diffImage.toPng(); - var diffOutputPath = testOutputPath.resolve(getAlphaSkiaRid() + ".diff.png"); + var diffOutputPath = testOutputPath.resolve(getAlphaSkiaTestRid() + ".diff.png"); Files.write(diffOutputPath, diffImagePngData); System.out.println("Error diff image saved to " + diffOutputPath); return 1; @@ -209,18 +209,7 @@ private static String getOperatingSystemRid() { throw new IllegalStateException("Unsupported OS: " + os); } - private static String getArchitectureRid() { - var jarch = System.getProperty("os.arch"); - return switch (jarch) { - case "x86", "i368", "i486", "i586", "i686" -> "x86"; - case "x86_64", "amd64" -> "x64"; - case "arm" -> "arm"; - case "aarch64" -> "arm64"; - default -> throw new IllegalStateException("Unsupported Architecture: " + jarch); - }; - } - - private static String getAlphaSkiaRid() { - return getOperatingSystemRid() + "-" + getArchitectureRid(); + private static String getAlphaSkiaTestRid() { + return getOperatingSystemRid(); } } \ No newline at end of file diff --git a/test/native/src/main.cpp b/test/native/src/main.cpp index 52648ed..1c079b5 100644 --- a/test/native/src/main.cpp +++ b/test/native/src/main.cpp @@ -5,8 +5,8 @@ #include #include -#ifndef ALPHASKIA_RID -#error "Missing alphaSkia runtime identifier, please specify ALPHASKIA_RID"; +#ifndef ALPHASKIA_TEST_RID +#error "Missing alphaSkia runtime identifier, please specify ALPHASKIA_TEST_RID"; #endif #define STRINGIFY(s) _STRINGIFY(s) @@ -16,7 +16,7 @@ std::filesystem::path find_repository_root(std::filesystem::path current, bool & { if (std::filesystem::exists(current / ".nuke")) { - std::cerr << "Repository root found at " << current << std::endl; + std::cout << "Repository root found at " << current << std::endl; success = true; return current; } @@ -116,7 +116,7 @@ int main() std::filesystem::path test_output_path = repository_root / "test" / "test-outputs" / "native"; std::filesystem::create_directories(test_output_path); - std::filesystem::path test_output_file = test_output_path / (std::string(STRINGIFY(ALPHASKIA_RID)) + ".png"); + std::filesystem::path test_output_file = test_output_path / (std::string(STRINGIFY(ALPHASKIA_TEST_RID)) + ".png"); alphaskia_data_t png_data = alphaskia_image_encode_png(actual_image); if (!png_data) { @@ -127,7 +127,7 @@ int main() std::cout << "Image saved to " << test_output_file.generic_string() << std::endl; // load reference image - std::filesystem::path test_reference_path = test_data_path / "reference" / (std::string(STRINGIFY(ALPHASKIA_RID)) + ".png"); + std::filesystem::path test_reference_path = test_data_path / "reference" / (std::string(STRINGIFY(ALPHASKIA_TEST_RID)) + ".png"); std::cout << "Loading reference image " << test_reference_path.generic_string() << std::endl; std::vector test_reference_data; read_file(test_reference_path.generic_string(), test_reference_data); @@ -185,7 +185,7 @@ int main() alphaskia_data_t diff_image_png_data = alphaskia_image_encode_png(diff_image); alphaskia_image_free(diff_image); - auto diff_output_path = test_output_path / (std::string(STRINGIFY(ALPHASKIA_RID)) + ".diff.png"); + auto diff_output_path = test_output_path / (std::string(STRINGIFY(ALPHASKIA_TEST_RID)) + ".diff.png"); write_data_to_file_and_free(diff_image_png_data, diff_output_path.generic_string()); std::cout << "Error diff image saved to " << diff_output_path.generic_string() << std::endl; return 1; diff --git a/test/node/index.ts b/test/node/index.ts index 1a3747b..236bacd 100644 --- a/test/node/index.ts +++ b/test/node/index.ts @@ -46,42 +46,21 @@ function renderFullImage(): AlphaSkiaImage { return fullImage!; } -let operatingSystemRid; +let alphaSkiaTestRid: string; switch (os.type()) { case "Linux": - operatingSystemRid = "linux"; + alphaSkiaTestRid = "linux"; break; case "Darwin": - operatingSystemRid = "macos"; + alphaSkiaTestRid = "macos"; break; case "Windows_NT": - operatingSystemRid = "win"; + alphaSkiaTestRid = "win"; break; default: throw new Error("Unsupported test platform"); } -let architectureRid; -switch (process.arch) { - case "arm": - architectureRid = "arm"; - break; - case "arm64": - architectureRid = "arm64"; - break; - case "x64": - architectureRid = "x64"; - break; - case "ia32": - architectureRid = "x86"; - break; - default: - throw new Error("Unsupported test platform"); - break; -} - -const alphaSkiaRid = operatingSystemRid + "-" + architectureRid; - function main(): number { const tolerancePercent = 1; @@ -122,7 +101,7 @@ function main(): number { let testOutputPath = path.join(repositoryRoot, "test", "test-outputs", "dotnet"); fs.mkdirSync(testOutputPath, { recursive: true }) - let testOutputFile = path.join(testOutputPath, alphaSkiaRid + ".png"); + let testOutputFile = path.join(testOutputPath, alphaSkiaTestRid + ".png"); let pngData = actualImage.toPng(); if (!pngData) { throw new Error("Failed to encode final image to png"); @@ -132,7 +111,7 @@ function main(): number { console.log(`Image saved to ${testOutputFile}`); // load reference image - let testReferencePath = path.join(testDataPath, "reference", alphaSkiaRid + ".png"); + let testReferencePath = path.join(testDataPath, "reference", alphaSkiaTestRid + ".png"); console.log(`Loading reference image ${testReferencePath}`); let testReferenceData = fs.readFileSync(testReferencePath); @@ -180,7 +159,7 @@ function main(): number { using diffImage = AlphaSkiaImage.fromPixels(actualWidth, actualHeight, diffPixels)!; let diffImagePngData = diffImage.toPng()!; - let diffOutputPath = path.join(testOutputPath, alphaSkiaRid + ".diff.png"); + let diffOutputPath = path.join(testOutputPath, alphaSkiaTestRid + ".diff.png"); fs.writeFileSync(diffOutputPath, diffImagePngData); console.log(`Error diff image saved to ${diffOutputPath}`); return 1; diff --git a/test/test-data/reference/linux.png b/test/test-data/reference/linux.png new file mode 100644 index 0000000000000000000000000000000000000000..85454e55f5e572b4ffb7cca7bd4256e649c60e0d GIT binary patch literal 41322 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 Date: Sun, 15 Oct 2023 20:38:53 +0200 Subject: [PATCH 16/38] Set macOS version only for macOS target OS compilation --- build/Build.MacOs.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/build/Build.MacOs.cs b/build/Build.MacOs.cs index 0e28241..ee6ed43 100644 --- a/build/Build.MacOs.cs +++ b/build/Build.MacOs.cs @@ -4,7 +4,14 @@ partial class Build { void SetClangMacOs(Dictionary gnArgs) { - AppendToFlagList(gnArgs, "extra_cflags", "'-DHAVE_ARC4RANDOM_BUF', '-stdlib=libc++', '-mmacosx-version-min=10.15'"); + if (TargetOs == TargetOperatingSystem.MacOs) + { + AppendToFlagList(gnArgs, "extra_cflags", "'-DHAVE_ARC4RANDOM_BUF', '-stdlib=libc++', '-mmacosx-version-min=10.15'"); + } + else + { + AppendToFlagList(gnArgs, "extra_cflags", "'-DHAVE_ARC4RANDOM_BUF', '-stdlib=libc++'"); + } AppendToFlagList(gnArgs, "extra_ldflags", "'-stdlib=libc++'"); } } \ No newline at end of file From 0935d4832e8dd7caadab682decfb937b21fcbcc6 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 20:47:13 +0200 Subject: [PATCH 17/38] Patch macOS version target in skia build config --- build/Build.LibAlphaSkia.cs | 1 + build/Build.LibSkia.cs | 1 + build/Build.MacOs.cs | 12 +++++++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/build/Build.LibAlphaSkia.cs b/build/Build.LibAlphaSkia.cs index 41ba901..ced9e4f 100644 --- a/build/Build.LibAlphaSkia.cs +++ b/build/Build.LibAlphaSkia.cs @@ -185,6 +185,7 @@ partial class Build """; PatchSkiaFile(SkiaPath / "BUILD.gn", buildNew); PatchSkiaToolchain(); + PatchSkiaMacOsVersion(); }); diff --git a/build/Build.LibSkia.cs b/build/Build.LibSkia.cs index 469fddf..dec2fa2 100644 --- a/build/Build.LibSkia.cs +++ b/build/Build.LibSkia.cs @@ -118,6 +118,7 @@ partial class Build } PatchSkiaToolchain(); + PatchSkiaMacOsVersion(); }); void BuildSkia() diff --git a/build/Build.MacOs.cs b/build/Build.MacOs.cs index ee6ed43..0500b43 100644 --- a/build/Build.MacOs.cs +++ b/build/Build.MacOs.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Nuke.Common.IO; partial class Build { @@ -6,7 +7,7 @@ void SetClangMacOs(Dictionary gnArgs) { if (TargetOs == TargetOperatingSystem.MacOs) { - AppendToFlagList(gnArgs, "extra_cflags", "'-DHAVE_ARC4RANDOM_BUF', '-stdlib=libc++', '-mmacosx-version-min=10.15'"); + AppendToFlagList(gnArgs, "extra_cflags", "'-DHAVE_ARC4RANDOM_BUF', '-stdlib=libc++'"); } else { @@ -14,4 +15,13 @@ void SetClangMacOs(Dictionary gnArgs) } AppendToFlagList(gnArgs, "extra_ldflags", "'-stdlib=libc++'"); } + + void PatchSkiaMacOsVersion() + { + // Skia has hard-coded x86_64-apple-macos10.13 as target, we want it slightly newer as x86_64-apple-macos10.15 + // to use some more C++ types + var buildFile = SkiaPath / "gn" / "skia" / "BUILD.gn"; + var source = buildFile.ReadAllText(); + buildFile.WriteAllText(source.Replace("x86_64-apple-macos10.13", "x86_64-apple-macos10.15")); + } } \ No newline at end of file From 71f8c4a240d5729d7e20f944e74181f1d99cda03 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 20:57:02 +0200 Subject: [PATCH 18/38] Add old file writing for comparison --- test/native/src/main.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/native/src/main.cpp b/test/native/src/main.cpp index 1c079b5..c2fdfae 100644 --- a/test/native/src/main.cpp +++ b/test/native/src/main.cpp @@ -188,6 +188,16 @@ int main() auto diff_output_path = test_output_path / (std::string(STRINGIFY(ALPHASKIA_TEST_RID)) + ".diff.png"); write_data_to_file_and_free(diff_image_png_data, diff_output_path.generic_string()); std::cout << "Error diff image saved to " << diff_output_path.generic_string() << std::endl; + + // for the sake of comparing directly, we also store the old image (we had cases where linux detected differences on the exact same file) + alphaskia_image_t old_image = alphaskia_image_from_pixels(actual_width, actual_height, expected_pixels.data()); + alphaskia_data_t old_image_png_data = alphaskia_image_encode_png(old_image); + alphaskia_image_free(old_image); + + auto old_output_path = test_output_path / (std::string(STRINGIFY(ALPHASKIA_TEST_RID)) + ".old.png"); + write_data_to_file_and_free(old_image_png_data, old_output_path.generic_string()); + std::cout << "Error old image saved to " << old_output_path.generic_string() << std::endl; + return 1; } From 474ba0ab4b222b77142afaea937c869ebd09b6c4 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 20:58:30 +0200 Subject: [PATCH 19/38] Add macOS test output as reference --- test/test-data/reference/macos.png | Bin 0 -> 44070 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/test-data/reference/macos.png diff --git a/test/test-data/reference/macos.png b/test/test-data/reference/macos.png new file mode 100644 index 0000000000000000000000000000000000000000..12f3f115a5cb349950ff6ff940dd3c7b135840f7 GIT binary patch literal 44070 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

JAs~)x_IQ>k*n_FAp9+lRC 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 literal 0 HcmV?d00001 From 4dc80f2b79f2ba25ac2d03666578d6384413f7ea Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 21:28:31 +0200 Subject: [PATCH 20/38] Alternative image loading hoping for deterministic pixels on linux --- .nuke/build.schema.json | 2 ++ wrapper/src/alphaskia_image.cpp | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index f5b828a..00b0b5f 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -122,6 +122,7 @@ "DotNetBuild", "DotNetPack", "DotNetTest", + "InstallDependenciesLinux", "Java", "JavaBuild", "JavaPack", @@ -153,6 +154,7 @@ "DotNetBuild", "DotNetPack", "DotNetTest", + "InstallDependenciesLinux", "Java", "JavaBuild", "JavaPack", diff --git a/wrapper/src/alphaskia_image.cpp b/wrapper/src/alphaskia_image.cpp index a36ddf2..ecbffa3 100644 --- a/wrapper/src/alphaskia_image.cpp +++ b/wrapper/src/alphaskia_image.cpp @@ -53,7 +53,12 @@ extern "C" AS_API alphaskia_image_t alphaskia_image_decode(const uint8_t *data, uint64_t length) { sk_sp wrapped_data = SkData::MakeWithCopy(data, length); - sk_sp image = SkImages::DeferredFromEncodedData(wrapped_data, SkAlphaType::kPremul_SkAlphaType); + std::unique_ptr codec = SkCodec::MakeFromData(wrapped_data); + + // create desired color type + SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType).makeAlphaType(kPremul_SkAlphaType); + sk_sp image = std::get<0>(codec->getImage(info)); + return reinterpret_cast(new sk_sp(image)); } From 5c50dc7ca1804cdb747209bf30f317e3cab015ff Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 21:56:00 +0200 Subject: [PATCH 21/38] GHA test fixes --- build/Build.DotNet.cs | 2 +- build/Build.Java.cs | 44 ++++++++++++++++++++++++++------------ build/Build.Node.cs | 27 +++++++++++++++++++++++ test/java/build.gradle.kts | 2 +- 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/build/Build.DotNet.cs b/build/Build.DotNet.cs index 5601f08..a8eb0e5 100644 --- a/build/Build.DotNet.cs +++ b/build/Build.DotNet.cs @@ -98,7 +98,7 @@ void DotNetWriteVersionInfoProps() .Executes(() => { DotNetTasks.DotNetRun(_ => _ - .SetProcessWorkingDirectory(RootDirectory / "test" / "dotnet") + .SetProcessWorkingDirectory(RootDirectory / "test" / "dotnet" / "AlphaSkia.Test") .SetRuntime(TargetOperatingSystem.Current.RuntimeIdentifier + "-" + (Architecture ?? Architecture.Current)) .AddProcessEnvironmentVariable("NUGET_PACKAGES", TemporaryDirectory / "packages") diff --git a/build/Build.Java.cs b/build/Build.Java.cs index 944e2e9..8e2cdcc 100644 --- a/build/Build.Java.cs +++ b/build/Build.Java.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Nuke.Common; using Nuke.Common.IO; using Nuke.Common.Tooling; @@ -57,24 +58,17 @@ partial class Build public Target JavaTest => _ => _ .Executes(() => { - GradlewTool("run", workingDirectory: RootDirectory / "test" / "java"); + GradlewTool("run", + environmentVariables: new Dictionary + { + ["ALPHASKIA_TEST_VERSION"] = JavaVersion + }, + workingDirectory: RootDirectory / "test" / "java"); }); void JavaWriteVersionInfoProperties() { - string semVer; - if (IsLocalBuild) - { - semVer = $"{VersionInfo.FileVersion.ToString(3)}-LOCAL"; - } - else if (!IsReleaseBuild) - { - semVer = $"{VersionInfo.FileVersion.ToString(3)}-SNAPSHOT"; - } - else - { - semVer = $"{VersionInfo.FileVersion.ToString(3)}"; - } + var semVer = JavaVersion; var props = $""" alphaskiaDescription={VersionInfo.Description} @@ -92,4 +86,26 @@ void JavaWriteVersionInfoProperties() """; (RootDirectory / "lib" / "java" / "local.properties").WriteAllText(props); } + + string JavaVersion + { + get + { + string semVer; + if (IsLocalBuild) + { + semVer = $"{VersionInfo.FileVersion.ToString(3)}-LOCAL"; + } + else if (!IsReleaseBuild) + { + semVer = $"{VersionInfo.FileVersion.ToString(3)}-SNAPSHOT"; + } + else + { + semVer = $"{VersionInfo.FileVersion.ToString(3)}"; + } + + return semVer; + } + } } \ No newline at end of file diff --git a/build/Build.Node.cs b/build/Build.Node.cs index 8b3ca9d..3f1a6f5 100644 --- a/build/Build.Node.cs +++ b/build/Build.Node.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Text.Encodings.Web; using System.Text.Json; @@ -6,6 +7,7 @@ using Nuke.Common.IO; using Nuke.Common.Tooling; using Nuke.Common.Tools.Npm; +using Serilog; partial class Build { @@ -56,6 +58,25 @@ partial class Build void PrepareTgzForTest() { + if (IsGitHubActions) + { + // flatten tar directory first + Log.Information("Flattening TGZ files from github"); + foreach (var tgz in (RootDirectory / "dist" / "NodeTars").GetFiles("*.tgz", 3)) + { + FileSystemTasks.MoveFile(tgz, + RootDirectory / "dist" / "NodeTars" / tgz.Name, + FileExistsPolicy.OverwriteIfNewer); + } + + Log.Information("Folder flattened to: " + string.Join(", ", + (RootDirectory / "dist" / "NodeTars").GetFiles("*.tgz", 3).Select(f => f.Name))); + } + + + + Log.Information("Preparing TGZ files for test by creating copy without version"); + var files = new List(); foreach (var tgz in (RootDirectory / "dist" / "NodeTars").GetFiles("*.tgz")) { // coderline-alphaskia-1.0.0-local.0.tgz @@ -65,7 +86,10 @@ void PrepareTgzForTest() FileSystemTasks.CopyFile(tgz, RootDirectory / "dist" / "NodeTars" / nameWithoutVersion, FileExistsPolicy.OverwriteIfNewer); + files.Add(nameWithoutVersion); } + + Log.Information("Preparing TGZ files done {Files}", string.Join(", ", files)); } public Target NodeBuild => _ => _ @@ -152,8 +176,11 @@ void NodeWritePackageJson() .Executes(() => { PrepareTgzForTest(); + // need to delete package-lock.json due to tgz hash mismatch + Log.Information("Deleting package-lock.json to avoid integrity checks failing"); (RootDirectory / "test" / "node" / "package-lock.json").DeleteFile(); + NpmTasks.NpmInstall(_ => _ .SetProcessWorkingDirectory(RootDirectory / "test" / "node") .SetForce(true)); diff --git a/test/java/build.gradle.kts b/test/java/build.gradle.kts index 648ceb3..332f511 100644 --- a/test/java/build.gradle.kts +++ b/test/java/build.gradle.kts @@ -10,7 +10,7 @@ version = "1.0.0-LOCAL" var alphaSkiaVersion = version // Override with value from CI -val versionEnv = providers.environmentVariable("ALPHASKIA_VERSION") +val versionEnv = providers.environmentVariable("ALPHASKIA_TEST_VERSION") if (versionEnv.isPresent) { version = versionEnv.get() alphaSkiaVersion = versionEnv.get() From 20c8c1ca94497c1f7a4c5130af7b32f13a68d0f7 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 22:29:27 +0200 Subject: [PATCH 22/38] Fix some folder structure issues GitHub Actions artifact packing & extraction pain! --- build/Build.DotNet.cs | 10 ++++++++++ build/Build.Node.cs | 37 +++++++++++++++++++++++++++---------- test/java/build.gradle.kts | 6 ++++++ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/build/Build.DotNet.cs b/build/Build.DotNet.cs index a8eb0e5..7a78de9 100644 --- a/build/Build.DotNet.cs +++ b/build/Build.DotNet.cs @@ -3,6 +3,7 @@ using Nuke.Common.IO; using Nuke.Common.Tooling; using Nuke.Common.Tools.DotNet; +using Serilog; partial class Build { @@ -97,6 +98,15 @@ void DotNetWriteVersionInfoProps() public Target DotNetTest => _ => _ .Executes(() => { + // flatten tar directory first + Log.Information("Flattening NuPkgs files from github"); + foreach (var nupkg in (RootDirectory / "dist" / "NuPkgs").GetFiles("*.nupkg", 3)) + { + FileSystemTasks.MoveFile(nupkg, + RootDirectory / "dist" / "NuPkgs" / nupkg.Name, + FileExistsPolicy.OverwriteIfNewer); + } + DotNetTasks.DotNetRun(_ => _ .SetProcessWorkingDirectory(RootDirectory / "test" / "dotnet" / "AlphaSkia.Test") .SetRuntime(TargetOperatingSystem.Current.RuntimeIdentifier + "-" + diff --git a/build/Build.Node.cs b/build/Build.Node.cs index 3f1a6f5..3e104a0 100644 --- a/build/Build.Node.cs +++ b/build/Build.Node.cs @@ -33,12 +33,12 @@ partial class Build .GetFiles("*.tgz", int.MaxValue) .DeleteFiles(); } - + foreach (var nodePackage in AllNodePackages) { NpmTasks.Npm("pack", nodePackage); } - + if (IsLocalBuild) { if (Rebuild) @@ -52,6 +52,7 @@ partial class Build RootDirectory / "dist" / "NodeTars" / tgz.Name, FileExistsPolicy.OverwriteIfNewer); } + PrepareTgzForTest(); } }); @@ -72,9 +73,8 @@ void PrepareTgzForTest() Log.Information("Folder flattened to: " + string.Join(", ", (RootDirectory / "dist" / "NodeTars").GetFiles("*.tgz", 3).Select(f => f.Name))); } - - + Log.Information("Preparing TGZ files for test by creating copy without version"); var files = new List(); foreach (var tgz in (RootDirectory / "dist" / "NodeTars").GetFiles("*.tgz")) @@ -94,9 +94,10 @@ void PrepareTgzForTest() public Target NodeBuild => _ => _ .Unlisted() - .DependsOn(PrepareGitHubArtifacts) .Executes(() => { + PrepareNodeModuleStructure(); + NodeWritePackageJson(); if (Rebuild) @@ -105,13 +106,13 @@ void PrepareTgzForTest() .SetProcessWorkingDirectory(RootDirectory / "lib" / "node" / "alphaskia") .SetCommand("clean")); } - + foreach (var nodePackage in AllNodePackages) { NpmTasks.NpmInstall(_ => _.SetProcessWorkingDirectory(nodePackage)); } - + CopyNodeAddonsToPackages(); NpmTasks.NpmRun(_ => _ @@ -119,6 +120,22 @@ void PrepareTgzForTest() .SetCommand("build")); }); + void PrepareNodeModuleStructure() + { + if (IsGitHubActions) + { + // flatten //libalphaskianode.node + // to /libalphaskianode.node + foreach (var subdir in DistBasePath.GetDirectories().Where(d => d.Name.EndsWith("-node"))) + { + if ((subdir / "libalphaskia.node").FileExists()) + { + FileSystemTasks.MoveDirectoryToDirectory(subdir, DistBasePath); + } + } + } + } + void NodeWritePackageJson() { string semVer; @@ -163,7 +180,7 @@ void NodeWritePackageJson() ["url"] = VersionInfo.GitUrlHttp, ["directory"] = RootDirectory.GetRelativePathTo(jsonPath.Parent).ToUnixRelativePath().ToString() }; - + jsonPath.WriteAllText(jsonContent.ToJsonString(new JsonSerializerOptions { WriteIndented = true, @@ -176,11 +193,11 @@ void NodeWritePackageJson() .Executes(() => { PrepareTgzForTest(); - + // need to delete package-lock.json due to tgz hash mismatch Log.Information("Deleting package-lock.json to avoid integrity checks failing"); (RootDirectory / "test" / "node" / "package-lock.json").DeleteFile(); - + NpmTasks.NpmInstall(_ => _ .SetProcessWorkingDirectory(RootDirectory / "test" / "node") .SetForce(true)); diff --git a/test/java/build.gradle.kts b/test/java/build.gradle.kts index 332f511..85f4e43 100644 --- a/test/java/build.gradle.kts +++ b/test/java/build.gradle.kts @@ -35,6 +35,12 @@ dependencies { implementation("net.alphatab:net.alphatab.alphaskia.android:$alphaSkiaVersion") } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + val generatedSourcesPath = projectDir.resolve("src/main/generated") java.sourceSets["main"].java.srcDir(generatedSourcesPath) idea { From fd2ed34785c4da776ef79935d096532cbd98460f Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 15 Oct 2023 22:51:46 +0200 Subject: [PATCH 23/38] More adaptions for tests --- .github/workflows/build.yml | 13 +++++-------- build/Build.DotNet.cs | 2 +- build/Build.Node.cs | 24 +++--------------------- build/TargetOperatingSystem.cs | 13 +++++++++---- 4 files changed, 18 insertions(+), 34 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2f5171c..725bc92 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -204,12 +204,7 @@ jobs: with: name: Nupkgs path: | - lib/dotnet/AlphaSkia/bin/Release/*.nupkg - lib/dotnet/AlphaTab.Native.Android/bin/Release/*.nupkg - lib/dotnet/AlphaTab.Native.iOS/bin/Release/*.nupkg - lib/dotnet/AlphaTab.Native.Linux/bin/Release/*.nupkg - lib/dotnet/AlphaTab.Native.MacOS/bin/Release/*.nupkg - lib/dotnet/AlphaTab.Native.Windows/bin/Release/*.nupkg + lib/dotnet/**/*.nupkg dotnet-test: strategy: fail-fast: false @@ -217,8 +212,6 @@ jobs: include: - runs-on: ubuntu-latest architecture: x64 - - runs-on: ubuntu-latest - architecture: x86 - runs-on: windows-latest architecture: x64 - runs-on: windows-latest @@ -266,6 +259,10 @@ jobs: needs: [java] steps: - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' - uses: actions/download-artifact@v3 with: name: Maven diff --git a/build/Build.DotNet.cs b/build/Build.DotNet.cs index 7a78de9..54a8001 100644 --- a/build/Build.DotNet.cs +++ b/build/Build.DotNet.cs @@ -109,7 +109,7 @@ void DotNetWriteVersionInfoProps() DotNetTasks.DotNetRun(_ => _ .SetProcessWorkingDirectory(RootDirectory / "test" / "dotnet" / "AlphaSkia.Test") - .SetRuntime(TargetOperatingSystem.Current.RuntimeIdentifier + "-" + + .SetRuntime(TargetOperatingSystem.Current.DotNetRid + "-" + (Architecture ?? Architecture.Current)) .AddProcessEnvironmentVariable("NUGET_PACKAGES", TemporaryDirectory / "packages") ); diff --git a/build/Build.Node.cs b/build/Build.Node.cs index 3e104a0..db758a8 100644 --- a/build/Build.Node.cs +++ b/build/Build.Node.cs @@ -96,8 +96,6 @@ void PrepareTgzForTest() .Unlisted() .Executes(() => { - PrepareNodeModuleStructure(); - NodeWritePackageJson(); if (Rebuild) @@ -119,23 +117,7 @@ void PrepareTgzForTest() .SetProcessWorkingDirectory(RootDirectory / "lib" / "node" / "alphaskia") .SetCommand("build")); }); - - void PrepareNodeModuleStructure() - { - if (IsGitHubActions) - { - // flatten //libalphaskianode.node - // to /libalphaskianode.node - foreach (var subdir in DistBasePath.GetDirectories().Where(d => d.Name.EndsWith("-node"))) - { - if ((subdir / "libalphaskia.node").FileExists()) - { - FileSystemTasks.MoveDirectoryToDirectory(subdir, DistBasePath); - } - } - } - } - + void NodeWritePackageJson() { string semVer; @@ -223,11 +205,11 @@ void CopyNodeAddonsToPackages() } else if (parts[1] == TargetOperatingSystem.Linux.RuntimeIdentifier) { - packageName = "alphaskia-windows"; + packageName = "alphaskia-linux"; } else if (parts[1] == TargetOperatingSystem.MacOs.RuntimeIdentifier) { - packageName = "alphaskia-windows"; + packageName = "alphaskia-macos"; } else { diff --git a/build/TargetOperatingSystem.cs b/build/TargetOperatingSystem.cs index f2621ca..a4a427c 100644 --- a/build/TargetOperatingSystem.cs +++ b/build/TargetOperatingSystem.cs @@ -14,7 +14,8 @@ public class TargetOperatingSystem : Enumeration { ["skia_enable_fontmgr_win_gdi"] = "false" }, - RuntimeIdentifier = "win" + RuntimeIdentifier = "win", + DotNetRid = "win" }; public static readonly TargetOperatingSystem Linux = new() @@ -25,7 +26,8 @@ public class TargetOperatingSystem : Enumeration { ["skia_use_system_freetype2"] = "false" }, - RuntimeIdentifier = "linux" + RuntimeIdentifier = "linux", + DotNetRid = "linux" }; public static readonly TargetOperatingSystem Android = new() @@ -36,7 +38,8 @@ public class TargetOperatingSystem : Enumeration { ["skia_use_system_freetype2"] = "false" }, - RuntimeIdentifier = "android" + RuntimeIdentifier = "android", + DotNetRid = "android" }; public static readonly TargetOperatingSystem MacOs = new() @@ -49,7 +52,8 @@ public class TargetOperatingSystem : Enumeration ["skia_use_fonthost_mac"] = "true", ["skia_use_metal"] = "true" }, - RuntimeIdentifier = "macos" + RuntimeIdentifier = "macos", + DotNetRid = "osx" }; // ReSharper disable once InconsistentNaming @@ -105,4 +109,5 @@ public static TargetOperatingSystem Current public Dictionary SkiaGnArgs { get; } = new(); public string RuntimeIdentifier { get; private init; } + public object DotNetRid { get; private init; } } \ No newline at end of file From fe30f3dddcded7657868437245759752b8e7b25a Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 21 Oct 2023 14:58:54 +0200 Subject: [PATCH 24/38] Reorganize artifacts --- .github/actions/build-alphaskia/action.yml | 9 +- .github/actions/setup-java-home/action.yml | 10 +++ .github/workflows/build.yml | 18 ++-- build/Build.DotNet.cs | 9 -- build/Build.Node.cs | 16 ---- build/Build.cs | 98 +++++++++++++++++----- 6 files changed, 99 insertions(+), 61 deletions(-) create mode 100644 .github/actions/setup-java-home/action.yml diff --git a/.github/actions/build-alphaskia/action.yml b/.github/actions/build-alphaskia/action.yml index eb05cb5..bb57304 100644 --- a/.github/actions/build-alphaskia/action.yml +++ b/.github/actions/build-alphaskia/action.yml @@ -18,9 +18,12 @@ inputs: runs: using: "composite" steps: + - uses: ./.github/actions/setup-java-home + if: ${{ matrix.variant == "jni" }} + - uses: actions/download-artifact@v3 with: - path: dist + path: dist/.organize/libskia-${{ inputs.target-os }}-${{ inputs.architecture }}-static name: libskia-${{ inputs.target-os }}-${{ inputs.architecture }}-static - id: build-info @@ -49,5 +52,5 @@ runs: name: libalphaskiatest-${{ inputs.target-os }}-${{ inputs.architecture }}-${{ inputs.variant }} if-no-files-found: ignore path: | - dist/libalphaskiatest*/**/*.* - test/test-outputs/**/*.* \ No newline at end of file + dist/libalphaskiatest-${{ inputs.target-os }}-${{ inputs.architecture }}-${{ inputs.variant }}/ + test/test-outputs/ \ No newline at end of file diff --git a/.github/actions/setup-java-home/action.yml b/.github/actions/setup-java-home/action.yml new file mode 100644 index 0000000..59eb790 --- /dev/null +++ b/.github/actions/setup-java-home/action.yml @@ -0,0 +1,10 @@ +name: 'Setup JAVA_HOME' +description: 'Setup JAVA_HOME from the default runner image setup' +runs: + using: "composite" + steps: + - run: + if [[ -z "${JAVA_HOME}" ]]; then + echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV + fi + shell: bash \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 725bc92..1579c16 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -198,7 +198,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/download-artifact@v3 with: - path: dist + path: dist/.organize - run: ./build.cmd DotNet - uses: actions/upload-artifact@v3 with: @@ -225,7 +225,7 @@ jobs: - uses: actions/download-artifact@v3 with: name: Nupkgs - path: dist/NuPkgs + path: dist/.organize/nupkgs - run: ./build.cmd DotNetTest --architecture ${{ matrix.architecture }} - uses: actions/upload-artifact@v3 with: @@ -237,10 +237,11 @@ jobs: runs-on: ubuntu-latest needs: [windows-alphaskia, linux-alphaskia, android-alphaskia, macos-alphaskia] steps: + - uses: ./.github/actions/setup-java-home - uses: actions/checkout@v3 - uses: actions/download-artifact@v3 with: - path: dist + path: dist/.organize - run: ./build.cmd Java - uses: actions/upload-artifact@v3 with: @@ -258,15 +259,12 @@ jobs: runs-on: ${{ matrix.runs-on }} needs: [java] steps: + - uses: ./.github/actions/setup-java-home - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '17' - uses: actions/download-artifact@v3 with: name: Maven - path: dist/Maven + path: dist/.organize/maven - run: ./build.cmd JavaTest - uses: actions/upload-artifact@v3 with: @@ -282,7 +280,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/download-artifact@v3 with: - path: dist + path: dist/.organize - run: ./build.cmd Node - uses: actions/upload-artifact@v3 with: @@ -303,7 +301,7 @@ jobs: - uses: actions/download-artifact@v3 with: name: NodeTars - path: dist/NodeTars + path: dist/.organize/node - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} diff --git a/build/Build.DotNet.cs b/build/Build.DotNet.cs index 54a8001..b660ff1 100644 --- a/build/Build.DotNet.cs +++ b/build/Build.DotNet.cs @@ -98,15 +98,6 @@ void DotNetWriteVersionInfoProps() public Target DotNetTest => _ => _ .Executes(() => { - // flatten tar directory first - Log.Information("Flattening NuPkgs files from github"); - foreach (var nupkg in (RootDirectory / "dist" / "NuPkgs").GetFiles("*.nupkg", 3)) - { - FileSystemTasks.MoveFile(nupkg, - RootDirectory / "dist" / "NuPkgs" / nupkg.Name, - FileExistsPolicy.OverwriteIfNewer); - } - DotNetTasks.DotNetRun(_ => _ .SetProcessWorkingDirectory(RootDirectory / "test" / "dotnet" / "AlphaSkia.Test") .SetRuntime(TargetOperatingSystem.Current.DotNetRid + "-" + diff --git a/build/Build.Node.cs b/build/Build.Node.cs index db758a8..6512aa5 100644 --- a/build/Build.Node.cs +++ b/build/Build.Node.cs @@ -59,22 +59,6 @@ partial class Build void PrepareTgzForTest() { - if (IsGitHubActions) - { - // flatten tar directory first - Log.Information("Flattening TGZ files from github"); - foreach (var tgz in (RootDirectory / "dist" / "NodeTars").GetFiles("*.tgz", 3)) - { - FileSystemTasks.MoveFile(tgz, - RootDirectory / "dist" / "NodeTars" / tgz.Name, - FileExistsPolicy.OverwriteIfNewer); - } - - Log.Information("Folder flattened to: " + string.Join(", ", - (RootDirectory / "dist" / "NodeTars").GetFiles("*.tgz", 3).Select(f => f.Name))); - } - - Log.Information("Preparing TGZ files for test by creating copy without version"); var files = new List(); foreach (var tgz in (RootDirectory / "dist" / "NodeTars").GetFiles("*.tgz")) diff --git a/build/Build.cs b/build/Build.cs index ec7a221..e0cfe68 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Nuke.Common; using Nuke.Common.IO; using Serilog; @@ -31,10 +32,10 @@ partial class Build : NukeBuild static readonly bool IsGitHubActions = GetVariable("GITHUB_ACTIONS"); static readonly AbsolutePath DistBasePath = RootDirectory / "dist"; static readonly AbsolutePath ArtifactBasePath = RootDirectory / "artifacts"; - + [Parameter] readonly bool IsReleaseBuild = GetVariable("IS_RELEASE_BUILD") ?? false; [Parameter] readonly bool Rebuild; - + public static int Main() => Execute(); static void AppendToFlagList( @@ -53,42 +54,93 @@ static void AppendToFlagList( gnArgs[key] = $"[ {flags}{value} ]"; } + static readonly HashSet AllLibExtensions = new(StringComparer.OrdinalIgnoreCase) + { + ".dll", + ".lib", + ".so", + ".dylib", + ".a", + ".exe" + }; + Target PrepareGitHubArtifacts => _ => _ .OnlyWhenStatic(() => IsGitHubActions) .Executes(() => { - // We auto download all artifacts of all dependencies - // which results in a nested structure like - // dist// - // but we want them at dist/ - var dist = DistBasePath; + // The Github artifact actions are really messy as you can hardly control the folder structure + // you end up with, this action tries to consolidate things to our knowledge. + var dist = DistBasePath / ".organize"; if (!dist.DirectoryExists()) { - Log.Debug("Skipping GitHub Artifact preparation, no dependencies"); - - // nothing to do + Log.Debug("Skipping GitHub Artifact preparation, no dependencies to organize"); return; } - var includeDir = dist / "include"; - if (includeDir.DirectoryExists()) - { - // seems we have a proper structure already - return; - } - - foreach (var artifactDir in dist.GetDirectories()) + foreach (var subDir in dist.GlobDirectories("**")) { - Log.Information("Flattening artifact dir {artifactDir}", artifactDir); + Log.Information("Flattening folder {Folder}", subDir); - foreach (var artifactSubDir in artifactDir.GetDirectories()) + // known include path + if (subDir.Name == "include") { - FileSystemTasks.MoveDirectoryToDirectory(artifactSubDir, dist, DirectoryExistsPolicy.Merge, + FileSystemTasks.MoveDirectoryToDirectory(subDir, DistBasePath, DirectoryExistsPolicy.Merge, FileExistsPolicy.OverwriteIfNewer); + Log.Information(" Treated as header include dir"); + } + else + { + var moved = false; + + foreach (var file in subDir.GetFiles()) + { + if (AllLibExtensions.Contains(file.Extension) || file.NameWithoutExtension == "libalphaskiatest") + { + // found a library or test executable dir + FileSystemTasks.MoveDirectoryToDirectory(subDir, DistBasePath, DirectoryExistsPolicy.Merge, + FileExistsPolicy.OverwriteIfNewer); + moved = true; + Log.Information(" Treated as library dir"); + } + else if (file.Extension is ".jar" or ".pom") + { + // find maven base. + // /net/alphatab/net.alphatab.alphaskia//file + var netDir = file / ".." / ".." / ".." / ".."; + if (netDir.DirectoryExists() && netDir.Name == "net") + { + FileSystemTasks.MoveDirectoryToDirectory(netDir.Parent, DistBasePath, + DirectoryExistsPolicy.Merge, + FileExistsPolicy.OverwriteIfNewer); + moved = true; + } + } + else if (file.Extension is ".nupkg" or ".snupkg") + { + // flatten nugets + FileSystemTasks.MoveFileToDirectory(file, DistBasePath / "NuPkgs", + FileExistsPolicy.OverwriteIfNewer); + } + else if (file.Extension is ".tgz") + { + FileSystemTasks.MoveFileToDirectory(file, DistBasePath / "NodeTars", + FileExistsPolicy.OverwriteIfNewer); + } + } + + // check if we have remains + if (subDir.DirectoryExists() && subDir.GetFiles().Any() && !moved) + { + Log.Warning( + "Did not handle directory '{Unhandled}' could not identify contents by files {FileList}", + subDir, + string.Join(", ", subDir.GetFiles().Select(f => f.Name)) + ); + } } } }); - + bool CanUseCachedBinaries(string buildTarget, Variant variant) { if (!UseCache) @@ -101,7 +153,7 @@ bool CanUseCachedBinaries(string buildTarget, Variant variant) { return false; } - + return true; } } \ No newline at end of file From 84a74b7658bfae0b7090b7a797a4b0f65acaa07f Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 21 Oct 2023 15:45:50 +0200 Subject: [PATCH 25/38] Use single quotes --- .github/actions/build-alphaskia/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build-alphaskia/action.yml b/.github/actions/build-alphaskia/action.yml index bb57304..82fdcd7 100644 --- a/.github/actions/build-alphaskia/action.yml +++ b/.github/actions/build-alphaskia/action.yml @@ -19,7 +19,7 @@ runs: using: "composite" steps: - uses: ./.github/actions/setup-java-home - if: ${{ matrix.variant == "jni" }} + if: ${{ matrix.variant == 'jni' }} - uses: actions/download-artifact@v3 with: From e633e8911a7179d2673cd587b02f70174d8ad5ed Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 21 Oct 2023 15:50:49 +0200 Subject: [PATCH 26/38] Handle dynamic changes --- .github/actions/setup-java-home/action.yml | 4 ++-- build/Build.cs | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/actions/setup-java-home/action.yml b/.github/actions/setup-java-home/action.yml index 59eb790..2d6bb88 100644 --- a/.github/actions/setup-java-home/action.yml +++ b/.github/actions/setup-java-home/action.yml @@ -4,7 +4,7 @@ runs: using: "composite" steps: - run: - if [[ -z "${JAVA_HOME}" ]]; then - echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV + if [[ -z "$JAVA_HOME" ]]; then + echo "JAVA_HOME=$JAVA_HOME_17_X64" >> "$GITHUB_ENV" fi shell: bash \ No newline at end of file diff --git a/build/Build.cs b/build/Build.cs index e0cfe68..cab96cb 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -77,8 +77,16 @@ static void AppendToFlagList( return; } - foreach (var subDir in dist.GlobDirectories("**")) + var directories = dist.GlobDirectories("**").ToArray(); + + foreach (var subDir in directories) { + // could have been moved recusively + if (subDir.DirectoryExists()) + { + continue; + } + Log.Information("Flattening folder {Folder}", subDir); // known include path From 0d8973122cfd3ee082d50548e124bb52452cb665 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 21 Oct 2023 15:54:11 +0200 Subject: [PATCH 27/38] Alternative syntax --- .github/actions/setup-java-home/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-java-home/action.yml b/.github/actions/setup-java-home/action.yml index 2d6bb88..73aabd1 100644 --- a/.github/actions/setup-java-home/action.yml +++ b/.github/actions/setup-java-home/action.yml @@ -4,7 +4,7 @@ runs: using: "composite" steps: - run: - if [[ -z "$JAVA_HOME" ]]; then + if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME=$JAVA_HOME_17_X64" >> "$GITHUB_ENV" fi shell: bash \ No newline at end of file From 356f80c721925d46bea8af14e6dc2bb07fd47f4d Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 21 Oct 2023 15:55:24 +0200 Subject: [PATCH 28/38] More logs --- build/Build.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/build/Build.cs b/build/Build.cs index cab96cb..9c91119 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -78,23 +78,24 @@ static void AppendToFlagList( } var directories = dist.GlobDirectories("**").ToArray(); + Log.Debug("Starting GitHub Artifact Preparation"); foreach (var subDir in directories) { // could have been moved recusively - if (subDir.DirectoryExists()) + if (!subDir.DirectoryExists()) { continue; } - Log.Information("Flattening folder {Folder}", subDir); + Log.Debug("Flattening folder {Folder}", subDir); // known include path if (subDir.Name == "include") { FileSystemTasks.MoveDirectoryToDirectory(subDir, DistBasePath, DirectoryExistsPolicy.Merge, FileExistsPolicy.OverwriteIfNewer); - Log.Information(" Treated as header include dir"); + Log.Debug(" Treated as header include dir"); } else { @@ -108,7 +109,7 @@ static void AppendToFlagList( FileSystemTasks.MoveDirectoryToDirectory(subDir, DistBasePath, DirectoryExistsPolicy.Merge, FileExistsPolicy.OverwriteIfNewer); moved = true; - Log.Information(" Treated as library dir"); + Log.Debug(" Treated as library dir"); } else if (file.Extension is ".jar" or ".pom") { From 57e1861908370962b0ffac0bdf8d99923890b4c6 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 21 Oct 2023 16:04:17 +0200 Subject: [PATCH 29/38] More modification while iteration handling --- build/Build.cs | 136 +++++++++++++++++++++++++++---------------------- 1 file changed, 74 insertions(+), 62 deletions(-) diff --git a/build/Build.cs b/build/Build.cs index 9c91119..204210b 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -82,73 +82,85 @@ static void AppendToFlagList( foreach (var subDir in directories) { - // could have been moved recusively - if (!subDir.DirectoryExists()) - { - continue; - } - - Log.Debug("Flattening folder {Folder}", subDir); + FlattenFolder(subDir); + } + }); + + static void FlattenFolder(AbsolutePath subDir) + { + // could have been moved recusively + if (!subDir.DirectoryExists()) + { + return; + } + + Log.Debug("Flattening folder {Folder}", subDir); + + // known include path + if (subDir.Name == "include") + { + FileSystemTasks.MoveDirectoryToDirectory(subDir, DistBasePath, DirectoryExistsPolicy.Merge, + FileExistsPolicy.OverwriteIfNewer); + Log.Debug(" Treated as header include dir"); + return; + } + + var moved = false; + + var files = subDir.GetFiles().ToArray(); + + foreach (var file in files) + { + if (!file.FileExists()) + { + continue; + } + + if (AllLibExtensions.Contains(file.Extension) || file.NameWithoutExtension == "libalphaskiatest") + { + // found a library or test executable dir + FileSystemTasks.MoveDirectoryToDirectory(subDir, DistBasePath, DirectoryExistsPolicy.Merge, + FileExistsPolicy.OverwriteIfNewer); + Log.Debug(" Treated as library dir"); + return; + } - // known include path - if (subDir.Name == "include") + if (file.Extension is ".jar" or ".pom") + { + // find maven base. + // /net/alphatab/net.alphatab.alphaskia//file + var netDir = file / ".." / ".." / ".." / ".."; + if (netDir.DirectoryExists() && netDir.Name == "net") { - FileSystemTasks.MoveDirectoryToDirectory(subDir, DistBasePath, DirectoryExistsPolicy.Merge, + FileSystemTasks.MoveDirectoryToDirectory(netDir.Parent, DistBasePath, + DirectoryExistsPolicy.Merge, FileExistsPolicy.OverwriteIfNewer); - Log.Debug(" Treated as header include dir"); - } - else - { - var moved = false; - - foreach (var file in subDir.GetFiles()) - { - if (AllLibExtensions.Contains(file.Extension) || file.NameWithoutExtension == "libalphaskiatest") - { - // found a library or test executable dir - FileSystemTasks.MoveDirectoryToDirectory(subDir, DistBasePath, DirectoryExistsPolicy.Merge, - FileExistsPolicy.OverwriteIfNewer); - moved = true; - Log.Debug(" Treated as library dir"); - } - else if (file.Extension is ".jar" or ".pom") - { - // find maven base. - // /net/alphatab/net.alphatab.alphaskia//file - var netDir = file / ".." / ".." / ".." / ".."; - if (netDir.DirectoryExists() && netDir.Name == "net") - { - FileSystemTasks.MoveDirectoryToDirectory(netDir.Parent, DistBasePath, - DirectoryExistsPolicy.Merge, - FileExistsPolicy.OverwriteIfNewer); - moved = true; - } - } - else if (file.Extension is ".nupkg" or ".snupkg") - { - // flatten nugets - FileSystemTasks.MoveFileToDirectory(file, DistBasePath / "NuPkgs", - FileExistsPolicy.OverwriteIfNewer); - } - else if (file.Extension is ".tgz") - { - FileSystemTasks.MoveFileToDirectory(file, DistBasePath / "NodeTars", - FileExistsPolicy.OverwriteIfNewer); - } - } - - // check if we have remains - if (subDir.DirectoryExists() && subDir.GetFiles().Any() && !moved) - { - Log.Warning( - "Did not handle directory '{Unhandled}' could not identify contents by files {FileList}", - subDir, - string.Join(", ", subDir.GetFiles().Select(f => f.Name)) - ); - } + return; } } - }); + else if (file.Extension is ".nupkg" or ".snupkg") + { + // flatten nugets + FileSystemTasks.MoveFileToDirectory(file, DistBasePath / "NuPkgs", + FileExistsPolicy.OverwriteIfNewer); + } + else if (file.Extension is ".tgz") + { + FileSystemTasks.MoveFileToDirectory(file, DistBasePath / "NodeTars", + FileExistsPolicy.OverwriteIfNewer); + } + } + + // check if we have remains + if (subDir.DirectoryExists() && subDir.GetFiles().Any() && !moved) + { + Log.Warning( + "Did not handle directory '{Unhandled}' could not identify contents by files {FileList}", + subDir, + string.Join(", ", subDir.GetFiles().Select(f => f.Name)) + ); + } + } bool CanUseCachedBinaries(string buildTarget, Variant variant) { From 7519251154daa73c59b855f8528dea04d55e3d2a Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 21 Oct 2023 16:18:44 +0200 Subject: [PATCH 30/38] Missing pipe --- .github/actions/setup-java-home/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-java-home/action.yml b/.github/actions/setup-java-home/action.yml index 73aabd1..d3fed8e 100644 --- a/.github/actions/setup-java-home/action.yml +++ b/.github/actions/setup-java-home/action.yml @@ -3,7 +3,7 @@ description: 'Setup JAVA_HOME from the default runner image setup' runs: using: "composite" steps: - - run: + - run: | if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME=$JAVA_HOME_17_X64" >> "$GITHUB_ENV" fi From bda1ddb1b408e89c29289374ac349f49062d8c03 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 21 Oct 2023 17:30:01 +0200 Subject: [PATCH 31/38] Add targets --- .github/workflows/build.yml | 2 +- build/Build.DotNet.cs | 1 + build/Build.Java.cs | 1 + build/Build.Node.cs | 2 ++ 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1579c16..cf5099f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -237,8 +237,8 @@ jobs: runs-on: ubuntu-latest needs: [windows-alphaskia, linux-alphaskia, android-alphaskia, macos-alphaskia] steps: - - uses: ./.github/actions/setup-java-home - uses: actions/checkout@v3 + - uses: ./.github/actions/setup-java-home - uses: actions/download-artifact@v3 with: path: dist/.organize diff --git a/build/Build.DotNet.cs b/build/Build.DotNet.cs index b660ff1..fcb7427 100644 --- a/build/Build.DotNet.cs +++ b/build/Build.DotNet.cs @@ -96,6 +96,7 @@ void DotNetWriteVersionInfoProps() } public Target DotNetTest => _ => _ + .DependsOn(PrepareGitHubArtifacts) .Executes(() => { DotNetTasks.DotNetRun(_ => _ diff --git a/build/Build.Java.cs b/build/Build.Java.cs index 8e2cdcc..d7f37dd 100644 --- a/build/Build.Java.cs +++ b/build/Build.Java.cs @@ -56,6 +56,7 @@ partial class Build }); public Target JavaTest => _ => _ + .DependsOn(PrepareGitHubArtifacts) .Executes(() => { GradlewTool("run", diff --git a/build/Build.Node.cs b/build/Build.Node.cs index 6512aa5..daad8b1 100644 --- a/build/Build.Node.cs +++ b/build/Build.Node.cs @@ -78,6 +78,7 @@ void PrepareTgzForTest() public Target NodeBuild => _ => _ .Unlisted() + .DependsOn(PrepareGitHubArtifacts) .Executes(() => { NodeWritePackageJson(); @@ -156,6 +157,7 @@ void NodeWritePackageJson() } public Target NodeTest => _ => _ + .DependsOn(PrepareGitHubArtifacts) .Executes(() => { PrepareTgzForTest(); From c0043012e035c1d1060bc5834b29c46bec5c7a19 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 22 Oct 2023 17:59:53 +0200 Subject: [PATCH 32/38] More test fixe --- .github/workflows/build.yml | 2 +- build/Build.cs | 1 + test/dotnet/AlphaSkia.Test/Program.cs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf5099f..dc21aa0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -259,8 +259,8 @@ jobs: runs-on: ${{ matrix.runs-on }} needs: [java] steps: - - uses: ./.github/actions/setup-java-home - uses: actions/checkout@v3 + - uses: ./.github/actions/setup-java-home - uses: actions/download-artifact@v3 with: name: Maven diff --git a/build/Build.cs b/build/Build.cs index 204210b..9322519 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -60,6 +60,7 @@ static void AppendToFlagList( ".lib", ".so", ".dylib", + ".node", ".a", ".exe" }; diff --git a/test/dotnet/AlphaSkia.Test/Program.cs b/test/dotnet/AlphaSkia.Test/Program.cs index 00e722b..681f664 100644 --- a/test/dotnet/AlphaSkia.Test/Program.cs +++ b/test/dotnet/AlphaSkia.Test/Program.cs @@ -174,7 +174,7 @@ public static int Main() : OperatingSystem.IsLinux() ? "linux" : OperatingSystem.IsMacOS() - ? "maxos" + ? "macos" : OperatingSystem.IsAndroid() ? "android" : OperatingSystem.IsIOS() From 1dd237e510a852aae7f5c97592915c2e571e477e Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 22 Oct 2023 18:27:23 +0200 Subject: [PATCH 33/38] Further test adaptions --- .github/actions/build-alphaskia/action.yml | 3 --- .github/actions/setup-java-home/action.yml | 10 ---------- .github/workflows/build.yml | 4 ---- build/Build.Java.cs | 19 +++++++++++++++++-- build/Build.cs | 12 ------------ 5 files changed, 17 insertions(+), 31 deletions(-) delete mode 100644 .github/actions/setup-java-home/action.yml diff --git a/.github/actions/build-alphaskia/action.yml b/.github/actions/build-alphaskia/action.yml index 82fdcd7..1dcfc54 100644 --- a/.github/actions/build-alphaskia/action.yml +++ b/.github/actions/build-alphaskia/action.yml @@ -18,9 +18,6 @@ inputs: runs: using: "composite" steps: - - uses: ./.github/actions/setup-java-home - if: ${{ matrix.variant == 'jni' }} - - uses: actions/download-artifact@v3 with: path: dist/.organize/libskia-${{ inputs.target-os }}-${{ inputs.architecture }}-static diff --git a/.github/actions/setup-java-home/action.yml b/.github/actions/setup-java-home/action.yml deleted file mode 100644 index d3fed8e..0000000 --- a/.github/actions/setup-java-home/action.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: 'Setup JAVA_HOME' -description: 'Setup JAVA_HOME from the default runner image setup' -runs: - using: "composite" - steps: - - run: | - if [ -z "${JAVA_HOME}" ]; then - echo "JAVA_HOME=$JAVA_HOME_17_X64" >> "$GITHUB_ENV" - fi - shell: bash \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc21aa0..b5153a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -238,7 +238,6 @@ jobs: needs: [windows-alphaskia, linux-alphaskia, android-alphaskia, macos-alphaskia] steps: - uses: actions/checkout@v3 - - uses: ./.github/actions/setup-java-home - uses: actions/download-artifact@v3 with: path: dist/.organize @@ -260,7 +259,6 @@ jobs: needs: [java] steps: - uses: actions/checkout@v3 - - uses: ./.github/actions/setup-java-home - uses: actions/download-artifact@v3 with: name: Maven @@ -293,7 +291,6 @@ jobs: matrix: runs-on: [ubuntu-latest, windows-latest, macos-latest] node: [latest, "lts/*"] - architecture: [x64, x86] runs-on: ${{ matrix.runs-on }} needs: [node] steps: @@ -305,7 +302,6 @@ jobs: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - architecture: ${{ matrix.architecture }} - run: node -e "console.log(process)" - run: ./build.cmd NodeTest - uses: actions/upload-artifact@v3 diff --git a/build/Build.Java.cs b/build/Build.Java.cs index d7f37dd..a6e8b68 100644 --- a/build/Build.Java.cs +++ b/build/Build.Java.cs @@ -2,6 +2,7 @@ using Nuke.Common; using Nuke.Common.IO; using Nuke.Common.Tooling; +using Serilog; using static Nuke.Common.EnvironmentInfo; partial class Build @@ -10,7 +11,7 @@ partial class Build readonly AbsolutePath GradlewExe = GetVariable("GRADLEW_EXE") ?? RootDirectory / "lib" / "java" / $"gradlew{ScriptExtension}"; - [Parameter] readonly AbsolutePath JavaHome = GetVariable("JAVA_HOME"); + [Parameter] readonly AbsolutePath JavaHome = GetVariable("JAVA_HOME") ?? GetVariable("JAVA_HOME_17_X64"); Tool GradlewTool => ToolResolver.GetTool(GradlewExe); @@ -22,6 +23,10 @@ partial class Build .Executes(() => { GradlewTool("publishAllPublicationsToDistPathRepository", + environmentVariables: new Dictionary + { + ["JAVA_HOME"] = JavaHome + }, workingDirectory: RootDirectory / "lib" / "java"); if (IsLocalBuild) @@ -47,11 +52,19 @@ partial class Build if (Rebuild) { GradlewTool("clean", + environmentVariables: new Dictionary + { + ["JAVA_HOME"] = JavaHome + }, workingDirectory: RootDirectory / "lib" / "java"); (RootDirectory / "lib" / "java" / "dist").DeleteDirectory(); } GradlewTool("build", + environmentVariables: new Dictionary + { + ["JAVA_HOME"] = JavaHome + }, workingDirectory: RootDirectory / "lib" / "java"); }); @@ -59,10 +72,12 @@ partial class Build .DependsOn(PrepareGitHubArtifacts) .Executes(() => { + Log.Debug("Testing with Java at {JavaHome} and version {AlphaSkiaTestVersion}", JavaHome, JavaVersion); GradlewTool("run", environmentVariables: new Dictionary { - ["ALPHASKIA_TEST_VERSION"] = JavaVersion + ["ALPHASKIA_TEST_VERSION"] = JavaVersion, + ["JAVA_HOME"] = JavaHome }, workingDirectory: RootDirectory / "test" / "java"); }); diff --git a/build/Build.cs b/build/Build.cs index 9322519..eab2d8b 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -106,8 +106,6 @@ static void FlattenFolder(AbsolutePath subDir) return; } - var moved = false; - var files = subDir.GetFiles().ToArray(); foreach (var file in files) @@ -151,16 +149,6 @@ static void FlattenFolder(AbsolutePath subDir) FileExistsPolicy.OverwriteIfNewer); } } - - // check if we have remains - if (subDir.DirectoryExists() && subDir.GetFiles().Any() && !moved) - { - Log.Warning( - "Did not handle directory '{Unhandled}' could not identify contents by files {FileList}", - subDir, - string.Join(", ", subDir.GetFiles().Select(f => f.Name)) - ); - } } bool CanUseCachedBinaries(string buildTarget, Variant variant) From 5d30409babf5a7027beb43ed9e45e365891f9681 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sun, 22 Oct 2023 18:58:49 +0200 Subject: [PATCH 34/38] Set java compiler encoding --- lib/java/build.gradle.kts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/java/build.gradle.kts b/lib/java/build.gradle.kts index 8cead64..8a349cb 100644 --- a/lib/java/build.gradle.kts +++ b/lib/java/build.gradle.kts @@ -93,6 +93,22 @@ subprojects { withJavadocJar() } + tasks.withType { + options.encoding = "UTF-8" + } + + tasks.withType { + defaultCharacterEncoding = "UTF-8" + } + + tasks.withType { + defaultCharacterEncoding = "UTF-8" + } + + tasks.withType{ + options.encoding = "UTF-8" + } + configure { repositories { // maven { From 1c15e9c78d20bea3883e83de167ca3e2f776c751 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Mon, 23 Oct 2023 21:48:08 +0200 Subject: [PATCH 35/38] Correct Maven folder casing --- build/Build.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Build.cs b/build/Build.cs index eab2d8b..1d78be1 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -131,7 +131,7 @@ static void FlattenFolder(AbsolutePath subDir) var netDir = file / ".." / ".." / ".." / ".."; if (netDir.DirectoryExists() && netDir.Name == "net") { - FileSystemTasks.MoveDirectoryToDirectory(netDir.Parent, DistBasePath, + FileSystemTasks.MoveDirectoryToDirectory(netDir, DistBasePath / "Maven", DirectoryExistsPolicy.Merge, FileExistsPolicy.OverwriteIfNewer); return; From 83b074233848bc1253948945a39a35267bd7075b Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Mon, 23 Oct 2023 22:10:53 +0200 Subject: [PATCH 36/38] Avoid clearing of env-vars --- build/Build.Java.cs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/build/Build.Java.cs b/build/Build.Java.cs index a6e8b68..6b94c63 100644 --- a/build/Build.Java.cs +++ b/build/Build.Java.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; using Nuke.Common; using Nuke.Common.IO; using Nuke.Common.Tooling; +using Nuke.Common.Utilities.Collections; using Serilog; using static Nuke.Common.EnvironmentInfo; @@ -23,10 +23,10 @@ partial class Build .Executes(() => { GradlewTool("publishAllPublicationsToDistPathRepository", - environmentVariables: new Dictionary - { - ["JAVA_HOME"] = JavaHome - }, + environmentVariables: Variables + .ToDictionary(x => x.Key, x => x.Value) + .SetKeyValue("JAVA_HOME", JavaHome) + .AsReadOnly(), workingDirectory: RootDirectory / "lib" / "java"); if (IsLocalBuild) @@ -52,19 +52,19 @@ partial class Build if (Rebuild) { GradlewTool("clean", - environmentVariables: new Dictionary - { - ["JAVA_HOME"] = JavaHome - }, + environmentVariables: Variables + .ToDictionary(x => x.Key, x => x.Value) + .SetKeyValue("JAVA_HOME", JavaHome) + .AsReadOnly(), workingDirectory: RootDirectory / "lib" / "java"); (RootDirectory / "lib" / "java" / "dist").DeleteDirectory(); } GradlewTool("build", - environmentVariables: new Dictionary - { - ["JAVA_HOME"] = JavaHome - }, + environmentVariables: Variables + .ToDictionary(x => x.Key, x => x.Value) + .SetKeyValue("JAVA_HOME", JavaHome) + .AsReadOnly(), workingDirectory: RootDirectory / "lib" / "java"); }); @@ -74,11 +74,11 @@ partial class Build { Log.Debug("Testing with Java at {JavaHome} and version {AlphaSkiaTestVersion}", JavaHome, JavaVersion); GradlewTool("run", - environmentVariables: new Dictionary - { - ["ALPHASKIA_TEST_VERSION"] = JavaVersion, - ["JAVA_HOME"] = JavaHome - }, + environmentVariables: Variables + .ToDictionary(x => x.Key, x => x.Value) + .SetKeyValue("JAVA_HOME", JavaHome) + .SetKeyValue("ALPHASKIA_TEST_VERSION", JavaVersion) + .AsReadOnly(), workingDirectory: RootDirectory / "test" / "java"); }); From 3aab266d96bd63e05bd0749f939b766beba56438 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Mon, 23 Oct 2023 22:37:02 +0200 Subject: [PATCH 37/38] Use lowercase folders to avoid conflicts --- .github/workflows/build.yml | 12 ++++++------ build/.idea/.idea.build.dir/.idea/vcs.xml | 1 + build/Build.DotNet.cs | 4 ++-- build/Build.Java.cs | 4 ++-- build/Build.Node.cs | 8 ++++---- build/Build.cs | 8 ++++---- test/dotnet/NuGet.config | 2 +- test/java/build.gradle.kts | 2 +- test/node/package-lock.json | 16 ++++++++-------- test/node/package.json | 8 ++++---- 10 files changed, 33 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5153a5..c8d3d6f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -202,7 +202,7 @@ jobs: - run: ./build.cmd DotNet - uses: actions/upload-artifact@v3 with: - name: Nupkgs + name: nupkgs path: | lib/dotnet/**/*.nupkg dotnet-test: @@ -224,7 +224,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/download-artifact@v3 with: - name: Nupkgs + name: nupkgs path: dist/.organize/nupkgs - run: ./build.cmd DotNetTest --architecture ${{ matrix.architecture }} - uses: actions/upload-artifact@v3 @@ -244,7 +244,7 @@ jobs: - run: ./build.cmd Java - uses: actions/upload-artifact@v3 with: - name: Maven + name: maven path: | lib/java/dist/**/*.* java-test: @@ -261,7 +261,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/download-artifact@v3 with: - name: Maven + name: maven path: dist/.organize/maven - run: ./build.cmd JavaTest - uses: actions/upload-artifact@v3 @@ -282,7 +282,7 @@ jobs: - run: ./build.cmd Node - uses: actions/upload-artifact@v3 with: - name: NodeTars + name: nodetars path: | lib/node/*/*.tgz node-test: @@ -297,7 +297,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/download-artifact@v3 with: - name: NodeTars + name: nodetars path: dist/.organize/node - uses: actions/setup-node@v3 with: diff --git a/build/.idea/.idea.build.dir/.idea/vcs.xml b/build/.idea/.idea.build.dir/.idea/vcs.xml index a02cb15..1c72989 100644 --- a/build/.idea/.idea.build.dir/.idea/vcs.xml +++ b/build/.idea/.idea.build.dir/.idea/vcs.xml @@ -3,6 +3,7 @@ + \ No newline at end of file diff --git a/build/Build.DotNet.cs b/build/Build.DotNet.cs index fcb7427..d2b545d 100644 --- a/build/Build.DotNet.cs +++ b/build/Build.DotNet.cs @@ -32,13 +32,13 @@ partial class Build { if (Rebuild) { - (RootDirectory / "dist" / "NuPkgs").DeleteDirectory(); + (RootDirectory / "dist" / "nupkgs").DeleteDirectory(); } foreach (var nupkg in (RootDirectory / "lib" / "dotnet").GetFiles("*.nupkg", int.MaxValue)) { FileSystemTasks.CopyFile(nupkg, - RootDirectory / "dist" / "NuPkgs" / nupkg.Name, + RootDirectory / "dist" / "nupkgs" / nupkg.Name, FileExistsPolicy.OverwriteIfNewer); } } diff --git a/build/Build.Java.cs b/build/Build.Java.cs index 6b94c63..146ce6b 100644 --- a/build/Build.Java.cs +++ b/build/Build.Java.cs @@ -33,11 +33,11 @@ partial class Build { if (Rebuild) { - (RootDirectory / "dist" / "Maven").DeleteDirectory(); + (RootDirectory / "dist" / "maven").DeleteDirectory(); } FileSystemTasks.CopyDirectoryRecursively(RootDirectory / "lib" / "java" / "dist", - RootDirectory / "dist" / "Maven", + RootDirectory / "dist" / "maven", DirectoryExistsPolicy.Merge, FileExistsPolicy.OverwriteIfNewer); } }); diff --git a/build/Build.Node.cs b/build/Build.Node.cs index daad8b1..44524ff 100644 --- a/build/Build.Node.cs +++ b/build/Build.Node.cs @@ -43,13 +43,13 @@ partial class Build { if (Rebuild) { - (RootDirectory / "dist" / "NodeTars").DeleteDirectory(); + (RootDirectory / "dist" / "nodetars").DeleteDirectory(); } foreach (var tgz in (RootDirectory / "lib" / "node").GetFiles("*.tgz", int.MaxValue)) { FileSystemTasks.CopyFile(tgz, - RootDirectory / "dist" / "NodeTars" / tgz.Name, + RootDirectory / "dist" / "nodetars" / tgz.Name, FileExistsPolicy.OverwriteIfNewer); } @@ -61,14 +61,14 @@ void PrepareTgzForTest() { Log.Information("Preparing TGZ files for test by creating copy without version"); var files = new List(); - foreach (var tgz in (RootDirectory / "dist" / "NodeTars").GetFiles("*.tgz")) + foreach (var tgz in (RootDirectory / "dist" / "nodetars").GetFiles("*.tgz")) { // coderline-alphaskia-1.0.0-local.0.tgz var nameWithoutVersion = string.Join("-", tgz.NameWithoutExtension.Split('-').TakeWhile(p => !char.IsDigit(p[0]))) + tgz.Extension; FileSystemTasks.CopyFile(tgz, - RootDirectory / "dist" / "NodeTars" / nameWithoutVersion, + RootDirectory / "dist" / "nodetars" / nameWithoutVersion, FileExistsPolicy.OverwriteIfNewer); files.Add(nameWithoutVersion); } diff --git a/build/Build.cs b/build/Build.cs index 1d78be1..266afa7 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -127,11 +127,11 @@ static void FlattenFolder(AbsolutePath subDir) if (file.Extension is ".jar" or ".pom") { // find maven base. - // /net/alphatab/net.alphatab.alphaskia//file + // /net/alphatab/net.alphatab.alphaskia//file var netDir = file / ".." / ".." / ".." / ".."; if (netDir.DirectoryExists() && netDir.Name == "net") { - FileSystemTasks.MoveDirectoryToDirectory(netDir, DistBasePath / "Maven", + FileSystemTasks.MoveDirectoryToDirectory(netDir.Parent, DistBasePath, DirectoryExistsPolicy.Merge, FileExistsPolicy.OverwriteIfNewer); return; @@ -140,12 +140,12 @@ static void FlattenFolder(AbsolutePath subDir) else if (file.Extension is ".nupkg" or ".snupkg") { // flatten nugets - FileSystemTasks.MoveFileToDirectory(file, DistBasePath / "NuPkgs", + FileSystemTasks.MoveFileToDirectory(file, DistBasePath / "nupkgs", FileExistsPolicy.OverwriteIfNewer); } else if (file.Extension is ".tgz") { - FileSystemTasks.MoveFileToDirectory(file, DistBasePath / "NodeTars", + FileSystemTasks.MoveFileToDirectory(file, DistBasePath / "nodetars", FileExistsPolicy.OverwriteIfNewer); } } diff --git a/test/dotnet/NuGet.config b/test/dotnet/NuGet.config index 40d35cf..f34a00a 100644 --- a/test/dotnet/NuGet.config +++ b/test/dotnet/NuGet.config @@ -5,6 +5,6 @@ - + \ No newline at end of file diff --git a/test/java/build.gradle.kts b/test/java/build.gradle.kts index 85f4e43..77effbc 100644 --- a/test/java/build.gradle.kts +++ b/test/java/build.gradle.kts @@ -19,7 +19,7 @@ if (versionEnv.isPresent) { repositories { mavenCentral() maven { - url = projectDir.resolve("../../dist/Maven/").toURI() + url = projectDir.resolve("../../dist/maven/").toURI() } } diff --git a/test/node/package-lock.json b/test/node/package-lock.json index a0b1363..ad3cdb6 100644 --- a/test/node/package-lock.json +++ b/test/node/package-lock.json @@ -8,16 +8,16 @@ "name": "alphaskiatest", "version": "1.0.0", "dependencies": { - "@coderline/alphaskia": "file:../../dist/NodeTars/coderline-alphaskia.tgz", - "@coderline/alphaskia-linux": "file:../../dist/NodeTars/coderline-alphaskia-linux.tgz", - "@coderline/alphaskia-macos": "file:../../dist/NodeTars/coderline-alphaskia-macos.tgz", - "@coderline/alphaskia-windows": "file:../../dist/NodeTars/coderline-alphaskia-windows.tgz", + "@coderline/alphaskia": "file:../../dist/nodetars/coderline-alphaskia.tgz", + "@coderline/alphaskia-linux": "file:../../dist/nodetars/coderline-alphaskia-linux.tgz", + "@coderline/alphaskia-macos": "file:../../dist/nodetars/coderline-alphaskia-macos.tgz", + "@coderline/alphaskia-windows": "file:../../dist/nodetars/coderline-alphaskia-windows.tgz", "tsx": "^3.13.0" } }, "node_modules/@coderline/alphaskia": { "version": "1.0.0-local.0", - "resolved": "file:../../dist/NodeTars/coderline-alphaskia.tgz", + "resolved": "file:../../dist/nodetars/coderline-alphaskia.tgz", "integrity": "sha512-uWa+Nf1klIV0Hrwpy0herWbxVkkN8xli9V4SlTx2IMMw/w854zron/otogMHmwPEw1kvGHQw9zaZucd96lFLyg==", "license": "BSD-3-Clause", "engines": { @@ -31,7 +31,7 @@ }, "node_modules/@coderline/alphaskia-linux": { "version": "1.0.0-local.0", - "resolved": "file:../../dist/NodeTars/coderline-alphaskia-linux.tgz", + "resolved": "file:../../dist/nodetars/coderline-alphaskia-linux.tgz", "integrity": "sha512-fsJLwL0q+TK99H1kZPj4OHE+q9v0MNXAqD+4tSLbB01M2VHDKd9oDjenK+yaJOXSzflBC1lVMONTjulzz1FoLQ==", "license": "BSD-3-Clause", "engines": { @@ -40,7 +40,7 @@ }, "node_modules/@coderline/alphaskia-macos": { "version": "1.0.0-local.0", - "resolved": "file:../../dist/NodeTars/coderline-alphaskia-macos.tgz", + "resolved": "file:../../dist/nodetars/coderline-alphaskia-macos.tgz", "integrity": "sha512-HtlkmftKtG2YHPkkeaVPjjFK6ElgTNMC8TscIIhbNDzdLgA6d09/byCzyh1K8+/yFY8omv8zq/WGfeeclYRTjQ==", "license": "BSD-3-Clause", "engines": { @@ -49,7 +49,7 @@ }, "node_modules/@coderline/alphaskia-windows": { "version": "1.0.0-local.0", - "resolved": "file:../../dist/NodeTars/coderline-alphaskia-windows.tgz", + "resolved": "file:../../dist/nodetars/coderline-alphaskia-windows.tgz", "integrity": "sha512-4w5ZbWxacjEOoSjT8NJL5RuOepcTbIHljH0DAYY03b97hzbc8PT/XL6eXBnGhNL6U7tIEMBlUg+PfthANHChgg==", "license": "BSD-3-Clause", "engines": { diff --git a/test/node/package.json b/test/node/package.json index 0598dab..6b852cb 100644 --- a/test/node/package.json +++ b/test/node/package.json @@ -6,10 +6,10 @@ "start": "tsx index.ts" }, "dependencies": { - "@coderline/alphaskia": "file:../../dist/NodeTars/coderline-alphaskia.tgz", - "@coderline/alphaskia-linux": "file:../../dist/NodeTars/coderline-alphaskia-linux.tgz", - "@coderline/alphaskia-macos": "file:../../dist/NodeTars/coderline-alphaskia-macos.tgz", - "@coderline/alphaskia-windows": "file:../../dist/NodeTars/coderline-alphaskia-windows.tgz", + "@coderline/alphaskia": "file:../../dist/nodetars/coderline-alphaskia.tgz", + "@coderline/alphaskia-linux": "file:../../dist/nodetars/coderline-alphaskia-linux.tgz", + "@coderline/alphaskia-macos": "file:../../dist/nodetars/coderline-alphaskia-macos.tgz", + "@coderline/alphaskia-windows": "file:../../dist/nodetars/coderline-alphaskia-windows.tgz", "tsx": "^3.13.0" } } From 8e92c71aceb05bbe6244ed99b8d6c3948886da54 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Tue, 24 Oct 2023 22:02:34 +0200 Subject: [PATCH 38/38] Add java setup --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c8d3d6f..03804ee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -258,6 +258,10 @@ jobs: runs-on: ${{ matrix.runs-on }} needs: [java] steps: + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' - uses: actions/checkout@v3 - uses: actions/download-artifact@v3 with: