Initial commit

This commit is contained in:
2026-01-02 12:04:16 +09:00
commit 14444f1f47
27 changed files with 1521 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
using Avalonia.Controls;
using ReceiptPDFBuilder.Helpers;
using ReceiptPDFBuilder.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace ReceiptPDFBuilder.ViewModels
{
class BaseViewModel : ChangeNotifier
{
IChangeViewModel _viewModelChanger;
ITopLevelGrabber? _topLevelGrabber;
public BaseViewModel(IChangeViewModel viewModelChanger)
{
_viewModelChanger = viewModelChanger;
_topLevelGrabber = null;
}
public ITopLevelGrabber? TopLevelGrabber
{
get => _topLevelGrabber;
set { _topLevelGrabber = value; }
}
public IChangeViewModel ViewModelChanger
{
get { return _viewModelChanger; }
set { _viewModelChanger = value; }
}
#region IChangeViewModel
public void PopViewModel()
{
_viewModelChanger?.PopViewModel();
}
public void PushViewModel(BaseViewModel model)
{
_viewModelChanger?.PushViewModel(model);
}
#endregion
}
}
+173
View File
@@ -0,0 +1,173 @@
using System;
using System.IO;
using Avalonia.Platform.Storage;
using HarfBuzzSharp;
using ImageMagick;
using MigraDoc.DocumentObjectModel;
using MigraDoc.Rendering;
using PdfSharp.Drawing;
using PdfSharp.Fonts;
using PdfSharp.Pdf;
using PdfSharp.Pdf.IO;
using PdfSharp.Snippets.Font;
using ReceiptPDFBuilder.Interfaces;
namespace ReceiptPDFBuilder.ViewModels;
class MainViewModel : BaseViewModel, IFontResolver
{
public MainViewModel(IChangeViewModel viewModelChanger) : base(viewModelChanger)
{
}
public async void ChooseFolder()
{
var topLevel = TopLevelGrabber?.GetTopLevel();
if (topLevel is not null)
{
var folders = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions()
{
Title = "Pick a folder of files...",
AllowMultiple = false,
});
if (folders.Count == 1)
{
var folder = folders[0];
Console.WriteLine("Chosen folder: " + folder.Path.LocalPath);
if (Directory.Exists(folder.Path.LocalPath))
{
CreatePDF(folder.Path.LocalPath);
}
}
}
}
public byte[]? GetFont(string faceName)
{
Console.WriteLine("Getting font {0}", faceName);
if (faceName == "Noto Sans JP")
{
return File.ReadAllBytes("Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf");
}
if (faceName == "Noto Sans JP Bold")
{
return File.ReadAllBytes("Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf");
}
return null;
}
public FontResolverInfo? ResolveTypeface(string familyName, bool bold, bool italic)
{
Console.WriteLine("Resolving familyname {0}", familyName);
if (familyName == "Noto Sans JP")
{
if (bold)
{
return new FontResolverInfo(familyName + " Bold");
}
return new FontResolverInfo(familyName);
}
return null;
}
// https://forum.pdfsharp.net/viewtopic.php?f=2&t=1025
private void CreatePDF(string folderName)
{
var pdfDoc = new Document();
var outputFileName = "MyReceipts.pdf";
var section = pdfDoc.AddSection();
section.PageSetup.PageFormat = PageFormat.Letter;
section.PageSetup.PageWidth = "8.5in";
section.PageSetup.PageHeight = "11in";
section.PageSetup.TopMargin = "0.5in";
section.PageSetup.RightMargin = "0.5in";
section.PageSetup.BottomMargin = "0.5in";
section.PageSetup.LeftMargin = "0.5in";
// setup footer for page number
var footerPar = new Paragraph();
footerPar.Format.Alignment = ParagraphAlignment.Center;
footerPar.AddText("--Page ");
footerPar.AddPageField();
footerPar.AddText(" of ");
footerPar.AddNumPagesField();
footerPar.AddText("--");
section.Footers.Primary.Add(footerPar);
//
var files = Directory.GetFiles(folderName);
files.Sort();
GlobalFontSettings.FontResolver = this;
GlobalFontSettings.FallbackFontResolver = new FailsafeFontResolver();
for (var i = 0; i < files.Length; i++)
{
var file = files[i];
var fileName = Path.GetFileName(file);
if (fileName == ".DS_Store" || fileName == outputFileName)
{
continue;
}
// TODO: calculate needed width for images based on page width and margins and all that?
var imageTitlePar = section.AddParagraph();
imageTitlePar.Format.Alignment = ParagraphAlignment.Center;
imageTitlePar.Format.Font.Size = 12;
imageTitlePar.Format.Font.Bold = true;
imageTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
imageTitlePar.AddText(fileName);
section.AddParagraph(); // add empty line for spacing
// now add the image
var isPDF = fileName.EndsWith(".pdf");
var isHEIC = fileName.EndsWith(".HEIC") || fileName.EndsWith(".heic");
if (isHEIC)
{
var convertedDir = Path.Combine(folderName, "converted");
if (!Directory.Exists(convertedDir))
{
Directory.CreateDirectory(convertedDir);
}
var info = new FileInfo(file);
using var mImage = new MagickImage(info.FullName);
// Save frame as jpg
var outputPath = Path.Combine(convertedDir, info.Name + ".jpg");
mImage.Quality = 80;
mImage.Write(outputPath);
fileName = Path.Combine("Converted", info.Name + ".jpg");
Console.WriteLine("HEIC image fileName is now {0}", fileName);
}
var paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
var image = paragraph.AddImage(fileName);
image.LockAspectRatio = true;
image.Width = 400; // can't be too wide now...not sure why...maybe due to margins...
Console.WriteLine("Added image: {0}", fileName);
if (isPDF)
{
// add other PDF pages
// see: https://stackoverflow.com/a/65091204/3938401
var pdfFileToAdd = PdfReader.Open(file);
imageTitlePar.AddText(string.Format(" (PDF with {0} page{1}) ",
pdfFileToAdd.PageCount,
pdfFileToAdd.PageCount == 1 ? "" : "s"));
for (var j = 2; j <= pdfFileToAdd.PageCount; j++)
{
section.AddPageBreak();
paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
image = paragraph.AddImage(file + "#" + j);
image.LockAspectRatio = true;
image.Width = 400;
}
}
if (i < files.Length - 1)
{
section.AddPageBreak();
}
}
var pdfRenderer = new PdfDocumentRenderer();
pdfRenderer.Document = pdfDoc;
pdfRenderer.WorkingDirectory = folderName;
pdfRenderer.RenderDocument();
string filename = Path.Join(folderName, outputFileName);
pdfRenderer.PdfDocument.Save(filename);
Console.WriteLine(filename);
}
}
+50
View File
@@ -0,0 +1,50 @@
using ReceiptPDFBuilder.Helpers;
using ReceiptPDFBuilder.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace ReceiptPDFBuilder.ViewModels
{
class MainWindowViewModel : ChangeNotifier, IChangeViewModel
{
BaseViewModel _currentViewModel;
Stack<BaseViewModel> _viewModels;
public MainWindowViewModel(ITopLevelGrabber topLevelGrabber)
{
_viewModels = new Stack<BaseViewModel>();
var initialViewModel = new MainViewModel(this)
{
TopLevelGrabber = topLevelGrabber
};
_viewModels.Push(initialViewModel);
_currentViewModel = initialViewModel;
}
public BaseViewModel CurrentViewModel
{
get { return _currentViewModel; }
set { _currentViewModel = value; NotifyPropertyChanged(); }
}
#region IChangeViewModel
public void PushViewModel(BaseViewModel model)
{
_viewModels.Push(model);
CurrentViewModel = model;
}
public void PopViewModel()
{
if (_viewModels.Count > 1)
{
_viewModels.Pop();
CurrentViewModel = _viewModels.Peek();
}
}
#endregion
}
}