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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
Assets/BundledAssets/* linguist-vendored
Assets/dictionaries/* linguist-vendored

# Line endings
*.sh text eol=lf
*.sln text eol=crlf
#
# The above will handle all files NOT found below
#

*.cs text diff=csharp
*.cshtml text diff=html
*.csx text diff=csharp
*.sln text eol=crlf

# Content below from: https://github.com/gitattributes/gitattributes/blob/master/Common.gitattributes

Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,18 @@ jobs:

# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v4
# - name: Autobuild
# uses: github/codeql-action/autobuild@v4

# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun

# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.

# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- run: |
sh ./download-assets-pipeline.sh
dotnet build

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
branches: [ master ]

jobs:

build:

runs-on: ubuntu-latest
Expand All @@ -23,9 +24,15 @@ jobs:
dotnet-version: 10.0.x
dotnet-quality: 'ga'
#dotnet-quality: 'preview'

- name: get render dependencies
run: sh ./download-assets-pipeline.sh

- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --no-restore

- name: Test
run: dotnet test --no-build --verbosity normal
2 changes: 2 additions & 0 deletions Assets/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tools/*
dictionaries/*
1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/af_ZA.aff

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/af_ZA.dic

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/de_DE_frami.aff

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/de_DE_frami.dic

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/en_GB.aff

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/en_GB.dic

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/en_US.aff

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/en_US.dic

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/es_ES.aff

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/es_ES.dic

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/hr_HR.aff

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/hr_HR.dic

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/hu_HU.aff

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/hu_HU.dic

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/it_IT.aff

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/it_IT.dic

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/nl_NL.aff

This file was deleted.

1,485 changes: 0 additions & 1,485 deletions Assets/dictionaries/nl_NL.dic

This file was deleted.

24 changes: 0 additions & 24 deletions Assets/download-dictionaries.ps1

This file was deleted.

2 changes: 2 additions & 0 deletions BookGen.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<File Path="Changelog.md" />
<File Path="Directory.Packages.props" />
<File Path="Docs/decision_notes.md" />
<File Path="download-assets-pipeline.sh" />
<File Path="download-assets.ps1" />
<File Path="getting-started.md" />
</Folder>
<Folder Name="/Pipelines/">
Expand Down
18 changes: 18 additions & 0 deletions Source/BookGen.Contents/BookGen.Contents.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@
</None>
</ItemGroup>

<ItemGroup Condition="'$(OS)' != 'Windows_NT'">
<None Include="..\..\Assets\tools\mmdr" Link="mmdr">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\..\Assets\tools\ratex-svg" Link="ratex-svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
<None Include="..\..\Assets\tools\mmdr.exe" Link="mmdr.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\..\Assets\tools\ratex-svg.exe" Link="ratex-svg.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<Target Name="ZipAssetsWindows" AfterTargets="Build" Condition="'$(OS)' == 'Windows_NT'">
<PropertyGroup>
<SourcePath>$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..\..\Assets\BundledAssets'))</SourcePath>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

using System.Text;

namespace Bookgen.Lib.JsInterop;
namespace Bookgen.Lib;

public sealed class JavascriptBuilder
{
Expand Down
12 changes: 12 additions & 0 deletions Source/Bookgen.Lib/Markdown/RenderInterop/IRenderInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Bookgen.Lib.Domain.IO.Configuration;
using Bookgen.Lib.ImageService;

namespace Bookgen.Lib.Markdown.RenderInterop;

internal interface IRenderInterop : IDisposable
{
ImageResult RenderNomnoml(string nomnomlCode, ImageConfig imageConfig);
ImageResult RenderLatex(string latex, ImageConfig imageConfig);
ImageResult RenderQrCode(string url, ImageConfig imageConfig);
string PrismSyntaxHighlight(string code, string language);
}
54 changes: 54 additions & 0 deletions Source/Bookgen.Lib/Markdown/RenderInterop/JavascriptEngine.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Microsoft.ClearScript;
using Microsoft.ClearScript.JavaScript;
using Microsoft.ClearScript.V8;

namespace Bookgen.Lib.Markdown.RenderInterop;

internal sealed class JavascriptEngine : IDisposable
{
private readonly V8ScriptEngine _engine;
private bool _disposed;

public JavascriptEngine()
{
_engine = new V8ScriptEngine();
}

public void Dispose()
{
_engine.Dispose();
_disposed = true;
}

public dynamic Script => _engine.Script;

public void Execute(string code)
{
ObjectDisposedException.ThrowIf(_disposed, nameof(_engine));

_engine.Execute(code);
}

public object Evaluate(string code)
{
ObjectDisposedException.ThrowIf(_disposed, nameof(_engine));

return _engine.Evaluate(code);
}

public string ExecuteAndGetResult(string code)
{
ObjectDisposedException.ThrowIf(_disposed, nameof(_engine));

object? result = _engine.Evaluate(code);

if (result is ScriptObject) //if the result is a promise, we need to wait for it
{
var tsk = result.ToTask();
result = tsk.GetAwaiter().GetResult();
}

return result as string
?? throw new InvalidOperationException($"Expected result to be a string but was: {result.GetType()}");
}
}
54 changes: 54 additions & 0 deletions Source/Bookgen.Lib/Markdown/RenderInterop/ProcessInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Diagnostics;

namespace Bookgen.Lib.Markdown.RenderInterop;

internal static class ProcessInterop
{
private static string GetBinary(string name)
{
return OperatingSystem.IsWindows()
? Path.Combine(AppContext.BaseDirectory, $"{name}.exe")
: Path.Combine(AppContext.BaseDirectory, name);
}

public static string RunRatex(string input)
{
var binary = GetBinary("ratex-svg");

if (OperatingSystem.IsLinux()
|| OperatingSystem.IsMacOS())
{
UnixFileMode permissions = File.GetUnixFileMode(binary);
if (!permissions.HasFlag(UnixFileMode.UserExecute))
{
permissions |= UnixFileMode.UserExecute;
File.SetUnixFileMode(binary, permissions);
}
}

var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = binary,
Arguments = "--stdout",
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};

process.Start();
process.StandardInput.Write(input);
process.StandardInput.Close();
string outout = process.StandardOutput.ReadToEnd();

process.WaitForExit();

return process.ExitCode != 0
? throw new InvalidOperationException($"Ratex process exited with code {process.ExitCode}")
: outout;
}
}
120 changes: 120 additions & 0 deletions Source/Bookgen.Lib/Markdown/RenderInterop/RenderInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System.Diagnostics;

using Bookgen.Lib.Domain.IO.Configuration;
using Bookgen.Lib.ImageService;

using BookGen.Vfs;

using SkiaSharp;

namespace Bookgen.Lib.Markdown.RenderInterop;

internal sealed class RenderInterop : IRenderInterop
{
private readonly IAssetSource _assetSource;
private readonly JavascriptEngine _javascriptEngine;
private readonly HashSet<string> _loadedScripts;
private bool _disposed;

public RenderInterop(IAssetSource assetSource)
{
_assetSource = assetSource;
_javascriptEngine = new JavascriptEngine();
_loadedScripts = new HashSet<string>();
}

public void Dispose()
{
_javascriptEngine.Dispose();
_disposed = true;
}

private void LoadScriptIfNotLoaded(string scriptFile)
{
if (_loadedScripts.Contains(scriptFile))
{
return;
}

string script = _assetSource.GetAsset(scriptFile);
_javascriptEngine.Execute(script);
_loadedScripts.Add(scriptFile);
}

private static ImageType GetImateType(SvgRecodeOption recodeOption)
{
return recodeOption switch
{
SvgRecodeOption.AsPng => ImageType.Png,
SvgRecodeOption.AsWebp => ImageType.Webp,
SvgRecodeOption.Passtrough => ImageType.Svg,
_ => throw new UnreachableException(),
};
}

private static ImageResult EncodeSvg(string svgData, ImageConfig imageConfig)
{
if (imageConfig.SvgRecode == SvgRecodeOption.Passtrough)
{
return new ImageResult
{
Data = svgData,
ImageType = ImageType.Svg,
OriginalName = string.Empty,
};
}



using SKData rendered = ImageUtils.RenderSvg(svgData,
imageConfig.ResizeWith,
imageConfig.ResizeHeight,
imageConfig.SvgRecode);

return new ImageResult
{
Data = Convert.ToBase64String(rendered.AsSpan()),
ImageType = GetImateType(imageConfig.SvgRecode),
OriginalName = string.Empty
};
}


public string PrismSyntaxHighlight(string code, string language)
{
ObjectDisposedException.ThrowIf(_disposed, nameof(RenderInterop));
LoadScriptIfNotLoaded(BundledAssets.PrismJs);

_javascriptEngine.Script.code = code;
return _javascriptEngine.ExecuteAndGetResult($"Prism.highlight(code, Prism.languages.{language}, '{language}');");
}

public ImageResult RenderLatex(string latex, ImageConfig imageConfig)
{
ObjectDisposedException.ThrowIf(_disposed, nameof(RenderInterop));

return EncodeSvg(ProcessInterop.RunRatex(latex), imageConfig);

}

public ImageResult RenderNomnoml(string nomnomlCode, ImageConfig imageConfig)
{
ObjectDisposedException.ThrowIf(_disposed, nameof(RenderInterop));
LoadScriptIfNotLoaded(BundledAssets.GraphreJs);
LoadScriptIfNotLoaded(BundledAssets.NomnomlJs);

_javascriptEngine.Script.nomnomlCode = nomnomlCode;
string svg = _javascriptEngine.ExecuteAndGetResult("nomnoml.renderSvg(nomnomlCode)");
return EncodeSvg(svg, imageConfig);
}

public ImageResult RenderQrCode(string url, ImageConfig imageConfig)
{
ObjectDisposedException.ThrowIf(_disposed, nameof(RenderInterop));
LoadScriptIfNotLoaded(BundledAssets.QrCodeJs);

string cmd = $"new QRCode({{content: \"{url}\", padding: 2, color: \"#000000\"}}).svg();";
string svg = _javascriptEngine.ExecuteAndGetResult(cmd);
return EncodeSvg(svg, imageConfig);
}
}
Loading
Loading