will add comments in draft pull request
This commit is contained in:
Amelia 2024-06-26 18:32:01 +02:00
parent 2fb72ab0d4
commit d77090beff
No known key found for this signature in database
GPG key ID: CB9DC866DE32D863
23 changed files with 3570 additions and 67 deletions

View file

@ -190,6 +190,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="config\fonts\" />
<Folder Include="config\themes" /> <Folder Include="config\themes" />
<Content Include="EmailTemplates\**"> <Content Include="EmailTemplates\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>

View file

@ -0,0 +1,72 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using API.Data;
using API.DTOs.Font;
using API.Services;
using API.Services.Tasks;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers;
public class FontController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly IFontService _fontService;
private readonly ITaskScheduler _taskScheduler;
public FontController(IUnitOfWork unitOfWork, IFontService fontService, ITaskScheduler taskScheduler)
{
_unitOfWork = unitOfWork;
_fontService = fontService;
_taskScheduler = taskScheduler;
}
[ResponseCache(CacheProfileName = "10Minute")]
[AllowAnonymous]
[HttpGet("GetFonts")]
public async Task<ActionResult<IEnumerable<EpubFontDto>>> GetFonts()
{
return Ok(await _unitOfWork.EpubFontRepository.GetFontDtos());
}
[AllowAnonymous]
[HttpGet("download-font")]
public async Task<IActionResult> GetFont(int fontId)
{
try
{
var font = await _unitOfWork.EpubFontRepository.GetFont(fontId);
if (font == null) return NotFound();
var contentType = GetContentType(font.FileName);
return File(await _fontService.GetContent(fontId), contentType, font.FileName);
}
catch (KavitaException ex)
{
return BadRequest(ex.Message);
}
}
[AllowAnonymous]
[HttpPost("scan")]
public IActionResult Scan()
{
_taskScheduler.ScanEpubFonts();
return Ok();
}
private string GetContentType(string fileName)
{
var extension = Path.GetExtension(fileName).ToLowerInvariant();
return extension switch
{
".ttf" => "application/font-tff",
".otf" => "application/font-otf",
".woff" => "application/font-woff",
".woff2" => "application/font-woff2",
_ => "application/octet-stream",
};
}
}

View file

@ -0,0 +1,13 @@
using System;
using API.Entities.Enums.Font;
namespace API.DTOs.Font;
public class EpubFontDto
{
public int Id { get; set; }
public string Name { get; set; }
public FontProvider Provider { get; set; }
public DateTime Created { get; set; }
public DateTime LastModified { get; set; }
}

View file

@ -66,6 +66,7 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
public DbSet<ManualMigrationHistory> ManualMigrationHistory { get; set; } = null!; public DbSet<ManualMigrationHistory> ManualMigrationHistory { get; set; } = null!;
public DbSet<SeriesBlacklist> SeriesBlacklist { get; set; } = null!; public DbSet<SeriesBlacklist> SeriesBlacklist { get; set; } = null!;
public DbSet<AppUserCollection> AppUserCollection { get; set; } = null!; public DbSet<AppUserCollection> AppUserCollection { get; set; } = null!;
public DbSet<EpubFont> EpubFont { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder builder) protected override void OnModelCreating(ModelBuilder builder)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Data.Migrations
{
/// <inheritdoc />
public partial class EpubFontInitial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EpubFont",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", nullable: true),
NormalizedName = table.Column<string>(type: "TEXT", nullable: true),
FileName = table.Column<string>(type: "TEXT", nullable: true),
Provider = table.Column<int>(type: "INTEGER", nullable: false),
Created = table.Column<DateTime>(type: "TEXT", nullable: false),
CreatedUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
LastModified = table.Column<DateTime>(type: "TEXT", nullable: false),
LastModifiedUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EpubFont", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EpubFont");
}
}
}

View file

@ -15,7 +15,7 @@ namespace API.Data.Migrations
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.4"); modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
modelBuilder.Entity("API.Entities.AppRole", b => modelBuilder.Entity("API.Entities.AppRole", b =>
{ {
@ -907,6 +907,41 @@ namespace API.Data.Migrations
b.ToTable("Device"); b.ToTable("Device");
}); });
modelBuilder.Entity("API.Entities.EpubFont", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedUtc")
.HasColumnType("TEXT");
b.Property<string>("FileName")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModifiedUtc")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("NormalizedName")
.HasColumnType("TEXT");
b.Property<int>("Provider")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("EpubFont");
});
modelBuilder.Entity("API.Entities.FolderPath", b => modelBuilder.Entity("API.Entities.FolderPath", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")

View file

@ -0,0 +1,77 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs.Font;
using API.Entities;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
namespace API.Data.Repositories;
public interface IEpubFontRepository
{
void Add(EpubFont font);
void Remove(EpubFont font);
void Update(EpubFont font);
Task<IEnumerable<EpubFontDto>> GetFontDtos();
Task<EpubFontDto?> GetFontDto(int fontId);
Task<IEnumerable<EpubFont>> GetFonts();
Task<EpubFont?> GetFont(int fontId);
}
public class EpubFontRepository: IEpubFontRepository
{
private readonly DataContext _context;
private readonly IMapper _mapper;
public EpubFontRepository(DataContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public void Add(EpubFont font)
{
_context.Add(font);
}
public void Remove(EpubFont font)
{
_context.Remove(font);
}
public void Update(EpubFont font)
{
_context.Entry(font).State = EntityState.Modified;
}
public async Task<IEnumerable<EpubFontDto>> GetFontDtos()
{
return await _context.EpubFont
.ProjectTo<EpubFontDto>(_mapper.ConfigurationProvider)
.ToListAsync();
}
public async Task<EpubFontDto?> GetFontDto(int fontId)
{
return await _context.EpubFont
.Where(f => f.Id == fontId)
.ProjectTo<EpubFontDto>(_mapper.ConfigurationProvider)
.FirstOrDefaultAsync();
}
public async Task<IEnumerable<EpubFont>> GetFonts()
{
return await _context.EpubFont
.ToListAsync();
}
public async Task<EpubFont?> GetFont(int fontId)
{
return await _context.EpubFont
.Where(f => f.Id == fontId)
.FirstOrDefaultAsync();
}
}

View file

@ -9,9 +9,11 @@ using API.Constants;
using API.Data.Repositories; using API.Data.Repositories;
using API.Entities; using API.Entities;
using API.Entities.Enums; using API.Entities.Enums;
using API.Entities.Enums.Font;
using API.Entities.Enums.Theme; using API.Entities.Enums.Theme;
using API.Extensions; using API.Extensions;
using API.Services; using API.Services;
using API.Services.Tasks.Scanner.Parser;
using Kavita.Common; using Kavita.Common;
using Kavita.Common.EnvironmentInfo; using Kavita.Common.EnvironmentInfo;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
@ -26,6 +28,20 @@ public static class Seed
/// </summary> /// </summary>
public static ImmutableArray<ServerSetting> DefaultSettings; public static ImmutableArray<ServerSetting> DefaultSettings;
public static readonly ImmutableArray<EpubFont> DefaultFonts =
[
..new List<EpubFont>
{
new ()
{
Name = "Merriweather",
NormalizedName = Parser.Normalize("Merriweather"),
Provider = FontProvider.System,
FileName = "Merriweather-Regular.woff2",
}
}
];
public static readonly ImmutableArray<SiteTheme> DefaultThemes = [ public static readonly ImmutableArray<SiteTheme> DefaultThemes = [
..new List<SiteTheme> ..new List<SiteTheme>
{ {
@ -153,6 +169,21 @@ public static class Seed
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
public static async Task SeedFonts(DataContext context)
{
await context.Database.EnsureCreatedAsync();
foreach (var font in DefaultFonts)
{
var existing = context.SiteTheme.FirstOrDefaultAsync(f => f.Name.Equals(font.Name));
if (existing == null)
{
await context.EpubFont.AddAsync(font);
}
}
await context.SaveChangesAsync();
}
public static async Task SeedDefaultStreams(IUnitOfWork unitOfWork) public static async Task SeedDefaultStreams(IUnitOfWork unitOfWork)
{ {
var allUsers = await unitOfWork.UserRepository.GetAllUsersAsync(AppUserIncludes.DashboardStreams); var allUsers = await unitOfWork.UserRepository.GetAllUsersAsync(AppUserIncludes.DashboardStreams);

View file

@ -31,6 +31,7 @@ public interface IUnitOfWork
IAppUserSmartFilterRepository AppUserSmartFilterRepository { get; } IAppUserSmartFilterRepository AppUserSmartFilterRepository { get; }
IAppUserExternalSourceRepository AppUserExternalSourceRepository { get; } IAppUserExternalSourceRepository AppUserExternalSourceRepository { get; }
IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository { get; } IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository { get; }
IEpubFontRepository EpubFontRepository { get; }
bool Commit(); bool Commit();
Task<bool> CommitAsync(); Task<bool> CommitAsync();
bool HasChanges(); bool HasChanges();
@ -74,6 +75,7 @@ public class UnitOfWork : IUnitOfWork
public IAppUserSmartFilterRepository AppUserSmartFilterRepository => new AppUserSmartFilterRepository(_context, _mapper); public IAppUserSmartFilterRepository AppUserSmartFilterRepository => new AppUserSmartFilterRepository(_context, _mapper);
public IAppUserExternalSourceRepository AppUserExternalSourceRepository => new AppUserExternalSourceRepository(_context, _mapper); public IAppUserExternalSourceRepository AppUserExternalSourceRepository => new AppUserExternalSourceRepository(_context, _mapper);
public IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository => new ExternalSeriesMetadataRepository(_context, _mapper); public IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository => new ExternalSeriesMetadataRepository(_context, _mapper);
public IEpubFontRepository EpubFontRepository => new EpubFontRepository(_context, _mapper);
/// <summary> /// <summary>
/// Commits changes to the DB. Completes the open transaction. /// Commits changes to the DB. Completes the open transaction.

View file

@ -0,0 +1,13 @@
namespace API.Entities.Enums.Font;
public enum FontProvider
{
/// <summary>
/// Font is provider by System, always avaible
/// </summary>
System = 1,
/// <summary>
/// Font provider by the User
/// </summary>
User = 2,
}

37
API/Entities/EpubFont.cs Normal file
View file

@ -0,0 +1,37 @@
using System;
using API.Entities.Enums.Font;
using API.Entities.Interfaces;
using API.Services;
namespace API.Entities;
/// <summary>
/// Represents a user provider font to be used in the epub reader
/// </summary>
public class EpubFont: IEntityDate
{
public int Id { get; set; }
/// <summary>
/// Name of the font
/// </summary>
public required string Name { get; set; }
/// <summary>
/// Normalized name for lookups
/// </summary>
public required string NormalizedName { get; set; }
/// <summary>
/// Filename of the font, stored under <see cref="DirectoryService.EpubFontDirectory"/>
/// </summary>
/// <remarks>System provided fonts use an alternative location as they are packaged with the app</remarks>
public required string FileName { get; set; }
/// <summary>
/// Where the font came from
/// </summary>
public FontProvider Provider { get; set; }
public DateTime Created { get; set; }
public DateTime CreatedUtc { get; set; }
public DateTime LastModified { get; set; }
public DateTime LastModifiedUtc { get; set; }
}

View file

@ -53,6 +53,7 @@ public static class ApplicationServiceExtensions
services.AddScoped<IMediaConversionService, MediaConversionService>(); services.AddScoped<IMediaConversionService, MediaConversionService>();
services.AddScoped<IRecommendationService, RecommendationService>(); services.AddScoped<IRecommendationService, RecommendationService>();
services.AddScoped<IStreamService, StreamService>(); services.AddScoped<IStreamService, StreamService>();
services.AddScoped<IFontService, FontService>();
services.AddScoped<IScannerService, ScannerService>(); services.AddScoped<IScannerService, ScannerService>();
services.AddScoped<IMetadataService, MetadataService>(); services.AddScoped<IMetadataService, MetadataService>();

View file

@ -10,6 +10,7 @@ using API.DTOs.Dashboard;
using API.DTOs.Device; using API.DTOs.Device;
using API.DTOs.Filtering; using API.DTOs.Filtering;
using API.DTOs.Filtering.v2; using API.DTOs.Filtering.v2;
using API.DTOs.Font;
using API.DTOs.MediaErrors; using API.DTOs.MediaErrors;
using API.DTOs.Metadata; using API.DTOs.Metadata;
using API.DTOs.Progress; using API.DTOs.Progress;
@ -257,6 +258,8 @@ public class AutoMapperProfiles : Profile
opt => opt =>
opt.MapFrom(src => src.BookReaderLayoutMode)); opt.MapFrom(src => src.BookReaderLayoutMode));
CreateMap<EpubFont, EpubFontDto>();
CreateMap<AppUserBookmark, BookmarkDto>(); CreateMap<AppUserBookmark, BookmarkDto>();

View file

@ -126,6 +126,7 @@ public class Program
await Seed.SeedRoles(services.GetRequiredService<RoleManager<AppRole>>()); await Seed.SeedRoles(services.GetRequiredService<RoleManager<AppRole>>());
await Seed.SeedSettings(context, directoryService); await Seed.SeedSettings(context, directoryService);
await Seed.SeedThemes(context); await Seed.SeedThemes(context);
await Seed.SeedFonts(context);
await Seed.SeedDefaultStreams(unitOfWork); await Seed.SeedDefaultStreams(unitOfWork);
await Seed.SeedDefaultSideNavStreams(unitOfWork); await Seed.SeedDefaultSideNavStreams(unitOfWork);
await Seed.SeedUserApiKeys(context); await Seed.SeedUserApiKeys(context);

View file

@ -33,6 +33,7 @@ public interface IDirectoryService
/// Original BookmarkDirectory. Only used for resetting directory. Use <see cref="ServerSettingKey.BackupDirectory"/> for actual path. /// Original BookmarkDirectory. Only used for resetting directory. Use <see cref="ServerSettingKey.BackupDirectory"/> for actual path.
/// </summary> /// </summary>
string BookmarkDirectory { get; } string BookmarkDirectory { get; }
string EpubFontDirectory { get; }
/// <summary> /// <summary>
/// Lists out top-level folders for a given directory. Filters out System and Hidden folders. /// Lists out top-level folders for a given directory. Filters out System and Hidden folders.
/// </summary> /// </summary>
@ -88,6 +89,8 @@ public class DirectoryService : IDirectoryService
public string LocalizationDirectory { get; } public string LocalizationDirectory { get; }
public string CustomizedTemplateDirectory { get; } public string CustomizedTemplateDirectory { get; }
public string TemplateDirectory { get; } public string TemplateDirectory { get; }
public string EpubFontDirectory { get; }
private readonly ILogger<DirectoryService> _logger; private readonly ILogger<DirectoryService> _logger;
private const RegexOptions MatchOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase; private const RegexOptions MatchOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase;
@ -125,6 +128,8 @@ public class DirectoryService : IDirectoryService
ExistOrCreate(CustomizedTemplateDirectory); ExistOrCreate(CustomizedTemplateDirectory);
TemplateDirectory = FileSystem.Path.Join(FileSystem.Directory.GetCurrentDirectory(), "EmailTemplates"); TemplateDirectory = FileSystem.Path.Join(FileSystem.Directory.GetCurrentDirectory(), "EmailTemplates");
ExistOrCreate(TemplateDirectory); ExistOrCreate(TemplateDirectory);
EpubFontDirectory = FileSystem.Path.Join(FileSystem.Directory.GetCurrentDirectory(), "config", "fonts");
ExistOrCreate(EpubFontDirectory);
} }
/// <summary> /// <summary>

View file

@ -35,6 +35,7 @@ public interface ITaskScheduler
void CovertAllCoversToEncoding(); void CovertAllCoversToEncoding();
Task CleanupDbEntries(); Task CleanupDbEntries();
Task CheckForUpdate(); Task CheckForUpdate();
void ScanEpubFonts();
} }
public class TaskScheduler : ITaskScheduler public class TaskScheduler : ITaskScheduler
@ -57,6 +58,7 @@ public class TaskScheduler : ITaskScheduler
private readonly ILicenseService _licenseService; private readonly ILicenseService _licenseService;
private readonly IExternalMetadataService _externalMetadataService; private readonly IExternalMetadataService _externalMetadataService;
private readonly ISmartCollectionSyncService _smartCollectionSyncService; private readonly ISmartCollectionSyncService _smartCollectionSyncService;
private readonly IFontService _fontService;
public static BackgroundJobServer Client => new (); public static BackgroundJobServer Client => new ();
public const string ScanQueue = "scan"; public const string ScanQueue = "scan";
@ -93,7 +95,8 @@ public class TaskScheduler : ITaskScheduler
ICleanupService cleanupService, IStatsService statsService, IVersionUpdaterService versionUpdaterService, ICleanupService cleanupService, IStatsService statsService, IVersionUpdaterService versionUpdaterService,
IThemeService themeService, IWordCountAnalyzerService wordCountAnalyzerService, IStatisticService statisticService, IThemeService themeService, IWordCountAnalyzerService wordCountAnalyzerService, IStatisticService statisticService,
IMediaConversionService mediaConversionService, IScrobblingService scrobblingService, ILicenseService licenseService, IMediaConversionService mediaConversionService, IScrobblingService scrobblingService, ILicenseService licenseService,
IExternalMetadataService externalMetadataService, ISmartCollectionSyncService smartCollectionSyncService) IExternalMetadataService externalMetadataService, ISmartCollectionSyncService smartCollectionSyncService,
IFontService fontService)
{ {
_cacheService = cacheService; _cacheService = cacheService;
_logger = logger; _logger = logger;
@ -112,6 +115,7 @@ public class TaskScheduler : ITaskScheduler
_licenseService = licenseService; _licenseService = licenseService;
_externalMetadataService = externalMetadataService; _externalMetadataService = externalMetadataService;
_smartCollectionSyncService = smartCollectionSyncService; _smartCollectionSyncService = smartCollectionSyncService;
_fontService = fontService;
} }
public async Task ScheduleTasks() public async Task ScheduleTasks()
@ -431,6 +435,13 @@ public class TaskScheduler : ITaskScheduler
await _versionUpdaterService.PushUpdate(update); await _versionUpdaterService.PushUpdate(update);
} }
// TODO: Make this auto scan from time to time?
public void ScanEpubFonts()
{
_logger.LogInformation("Starting Epub Font scam");
BackgroundJob.Enqueue(() => _fontService.Scan());
}
/// <summary> /// <summary>
/// If there is an enqueued or scheduled task for <see cref="ScannerService.ScanLibrary"/> method /// If there is an enqueued or scheduled task for <see cref="ScannerService.ScanLibrary"/> method
/// </summary> /// </summary>

View file

@ -0,0 +1,108 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using API.Data;
using API.Entities;
using API.Entities.Enums.Font;
using API.Services.Tasks.Scanner.Parser;
using API.SignalR;
using Kavita.Common;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
namespace API.Services.Tasks;
public interface IFontService
{
Task<byte[]> GetContent(int fontId);
Task Scan();
}
public class FontService: IFontService
{
private readonly IDirectoryService _directoryService;
private readonly IUnitOfWork _unitOfWork;
private readonly IHubContext<MessageHub> _messageHub;
private readonly ILogger<FontService> _logger;
public FontService(IDirectoryService directoryService, IUnitOfWork unitOfWork, IHubContext<MessageHub> messageHub,
ILogger<FontService> logger)
{
_directoryService = directoryService;
_unitOfWork = unitOfWork;
_messageHub = messageHub;
_logger = logger;
}
public async Task<byte[]> GetContent(int fontId)
{
// TODO: Differentiate between Provider.User & Provider.System
var font = await _unitOfWork.EpubFontRepository.GetFont(fontId);
if (font == null) throw new KavitaException("Font file missing or invalid");
var fontFile = _directoryService.FileSystem.Path.Join(_directoryService.EpubFontDirectory, font.FileName);
if (string.IsNullOrEmpty(fontFile) || !_directoryService.FileSystem.File.Exists(fontFile))
throw new KavitaException("Font file missing or invalid");
return await _directoryService.FileSystem.File.ReadAllBytesAsync(fontFile);
}
public async Task Scan()
{
_directoryService.Exists(_directoryService.EpubFontDirectory);
var reservedNames = Seed.DefaultFonts.Select(f => f.NormalizedName).ToList();
var fontFiles =
_directoryService.GetFilesWithExtension(Parser.NormalizePath(_directoryService.EpubFontDirectory), @"\.[woff2|tff|otf|woff]")
.Where(name => !reservedNames.Contains(Parser.Normalize(name))).ToList();
var allFonts = (await _unitOfWork.EpubFontRepository.GetFonts()).ToList();
var userFonts = allFonts.Where(f => f.Provider == FontProvider.User).ToList();
foreach (var userFont in userFonts)
{
var filePath = Parser.NormalizePath(
_directoryService.FileSystem.Path.Join(_directoryService.EpubFontDirectory, userFont.FileName));
if (_directoryService.FileSystem.File.Exists(filePath)) continue;
allFonts.Remove(userFont);
await RemoveFont(userFont);
// TODO: Send update to UI
_logger.LogInformation("Removed a font because it didn't exist on disk {FilePath}", filePath);
}
var allFontNames = allFonts.Select(f => f.NormalizedName).ToList();
foreach (var fontFile in fontFiles)
{
var nakedFileName = _directoryService.FileSystem.Path.GetFileNameWithoutExtension(fontFile);
// TODO: discuss this, using this to "prettyfy" the file name, to display in the UI
var fontName = Regex.Replace(nakedFileName, "[^a-zA-Z0-9]", " ");
var normalizedName = Parser.Normalize(nakedFileName);
if (allFontNames.Contains(normalizedName)) continue;
_unitOfWork.EpubFontRepository.Add(new EpubFont()
{
Name = fontName,
NormalizedName = normalizedName,
FileName = _directoryService.FileSystem.Path.GetFileName(fontFile),
Provider = FontProvider.User,
});
// TODO: Send update to UI
_logger.LogInformation("Added a new font from disk {FontFile}", fontFile);
}
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
}
// TODO: Send update to UI
_logger.LogInformation("Finished FontService#Scan");
}
public async Task RemoveFont(EpubFont font)
{
// TODO: Default font? Ask in kavita discord if needed, as we can always fallback to the browsers default font.
_unitOfWork.EpubFontRepository.Remove(font);
await _unitOfWork.CommitAsync();
}
}

View file

@ -1,45 +1,3 @@
@font-face {
font-family: "Fira_Sans";
src: url(../../../../assets/fonts/Fira_Sans/FiraSans-Regular.woff2) format("woff2");
font-display: swap;
}
@font-face {
font-family: "Lato";
src: url(../../../../assets/fonts/Lato/Lato-Regular.woff2) format("woff2");
font-display: swap;
}
@font-face {
font-family: "Libre_Baskerville";
src: url(../../../../assets/fonts/Libre_Baskerville/LibreBaskerville-Regular.woff2) format("woff2");
font-display: swap;
}
@font-face {
font-family: "Merriweather";
src: url(../../../../assets/fonts/Merriweather/Merriweather-Regular.woff2) format("woff2");
font-display: swap;
}
@font-face {
font-family: "Nanum_Gothic";
src: url(../../../../assets/fonts/Nanum_Gothic/NanumGothic-Regular.woff2) format("woff2");
font-display: swap;
}
@font-face {
font-family: "RocknRoll_One";
src: url(../../../../assets/fonts/RocknRoll_One/RocknRollOne-Regular.woff2) format("woff2");
font-display: swap;
}
@font-face {
font-family: "OpenDyslexic2";
src: url(../../../../assets/fonts/OpenDyslexic2/OpenDyslexic-Regular.woff2) format("woff2");
font-display: swap;
}
:root { :root {
--br-actionbar-button-text-color: #6c757d; --br-actionbar-button-text-color: #6c757d;
--accordion-body-bg-color: black; --accordion-body-bg-color: black;

View file

@ -583,6 +583,15 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this.bookService.getEpubFonts().subscribe(fonts => {
fonts.forEach(font => {
const fontFace = new FontFace(font.name, `url(${this.bookService.baseUrl}Font/download-font?fontId=${font.id})`);
fontFace.load().then(loadedFace => {
(document as any).fonts.add(loadedFace);
});
})
})
const libraryId = this.route.snapshot.paramMap.get('libraryId'); const libraryId = this.route.snapshot.paramMap.get('libraryId');
const seriesId = this.route.snapshot.paramMap.get('seriesId'); const seriesId = this.route.snapshot.paramMap.get('seriesId');
const chapterId = this.route.snapshot.paramMap.get('chapterId'); const chapterId = this.route.snapshot.paramMap.get('chapterId');

View file

@ -19,7 +19,7 @@ import { ThemeProvider } from 'src/app/_models/preferences/site-theme';
import { User } from 'src/app/_models/user'; import { User } from 'src/app/_models/user';
import { AccountService } from 'src/app/_services/account.service'; import { AccountService } from 'src/app/_services/account.service';
import { ThemeService } from 'src/app/_services/theme.service'; import { ThemeService } from 'src/app/_services/theme.service';
import { FontFamily, BookService } from '../../_services/book.service'; import {BookService, EpubFont} from '../../_services/book.service';
import { BookBlackTheme } from '../../_models/book-black-theme'; import { BookBlackTheme } from '../../_models/book-black-theme';
import { BookDarkTheme } from '../../_models/book-dark-theme'; import { BookDarkTheme } from '../../_models/book-dark-theme';
import { BookWhiteTheme } from '../../_models/book-white-theme'; import { BookWhiteTheme } from '../../_models/book-white-theme';
@ -131,7 +131,7 @@ export class ReaderSettingsComponent implements OnInit {
* List of all font families user can select from * List of all font families user can select from
*/ */
fontOptions: Array<string> = []; fontOptions: Array<string> = [];
fontFamilies: Array<FontFamily> = []; fontFamilies: Array<EpubFont> = [];
/** /**
* Internal property used to capture all the different css properties to render on all elements * Internal property used to capture all the different css properties to render on all elements
*/ */
@ -174,10 +174,11 @@ export class ReaderSettingsComponent implements OnInit {
private readonly cdRef: ChangeDetectorRef) {} private readonly cdRef: ChangeDetectorRef) {}
ngOnInit(): void { ngOnInit(): void {
this.bookService.getEpubFonts().subscribe(fonts => {
this.fontFamilies = this.bookService.getFontFamilies(); this.fontFamilies = fonts;
this.fontOptions = this.fontFamilies.map(f => f.title); this.fontOptions = fonts.map(f => f.name);
this.cdRef.markForCheck(); this.cdRef.markForCheck();
})
this.accountService.currentUser$.pipe(take(1)).subscribe(user => { this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (user) { if (user) {
@ -208,11 +209,10 @@ export class ReaderSettingsComponent implements OnInit {
this.settingsForm.addControl('bookReaderFontFamily', new FormControl(this.user.preferences.bookReaderFontFamily, [])); this.settingsForm.addControl('bookReaderFontFamily', new FormControl(this.user.preferences.bookReaderFontFamily, []));
this.settingsForm.get('bookReaderFontFamily')!.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(fontName => { this.settingsForm.get('bookReaderFontFamily')!.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(fontName => {
const familyName = this.fontFamilies.filter(f => f.title === fontName)[0].family; if (fontName === 'default') {
if (familyName === 'default') {
this.pageStyles['font-family'] = 'inherit'; this.pageStyles['font-family'] = 'inherit';
} else { } else {
this.pageStyles['font-family'] = "'" + familyName + "'"; this.pageStyles['font-family'] = "'" + fontName + "'";
} }
this.styleUpdate.emit(this.pageStyles); this.styleUpdate.emit(this.pageStyles);

View file

@ -4,16 +4,19 @@ import { TextResonse } from 'src/app/_types/text-response';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { BookChapterItem } from '../_models/book-chapter-item'; import { BookChapterItem } from '../_models/book-chapter-item';
import { BookInfo } from '../_models/book-info'; import { BookInfo } from '../_models/book-info';
import {Observable} from "rxjs";
export interface FontFamily { export enum FontProvider {
/** System = 1,
* What the user should see User = 2,
*/ }
title: string;
/** export interface EpubFont {
* The actual font face id: number;
*/ name: string;
family: string; provider: FontProvider;
created: Date;
lastModified: Date;
} }
@Injectable({ @Injectable({
@ -25,10 +28,8 @@ export class BookService {
constructor(private http: HttpClient) { } constructor(private http: HttpClient) { }
getFontFamilies(): Array<FontFamily> { getEpubFonts(): Observable<EpubFont[]> {
return [{title: 'default', family: 'default'}, {title: 'EBGaramond', family: 'EBGaramond'}, {title: 'Fira Sans', family: 'Fira_Sans'}, return this.http.get<Array<EpubFont>>(this.baseUrl + 'Font/GetFonts')
{title: 'Lato', family: 'Lato'}, {title: 'Libre Baskerville', family: 'Libre_Baskerville'}, {title: 'Merriweather', family: 'Merriweather'},
{title: 'Nanum Gothic', family: 'Nanum_Gothic'}, {title: 'RocknRoll One', family: 'RocknRoll_One'}, {title: 'Open Dyslexic', family: 'OpenDyslexic2'}];
} }
getBookChapters(chapterId: number) { getBookChapters(chapterId: number) {

View file

@ -166,7 +166,6 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
constructor() { constructor() {
this.fontFamilies = this.bookService.getFontFamilies().map(f => f.title);
this.cdRef.markForCheck(); this.cdRef.markForCheck();
this.accountService.getOpdsUrl().subscribe(res => { this.accountService.getOpdsUrl().subscribe(res => {
@ -174,6 +173,11 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
this.cdRef.markForCheck(); this.cdRef.markForCheck();
}); });
this.bookService.getEpubFonts().subscribe(res => {
this.fontFamilies = res.map(f => f.name);
this.cdRef.markForCheck();
})
this.settingsService.getOpdsEnabled().subscribe(res => { this.settingsService.getOpdsEnabled().subscribe(res => {
this.opdsEnabled = res; this.opdsEnabled = res;
this.cdRef.markForCheck(); this.cdRef.markForCheck();