From 82f664fafa80e9f8249300a68e1d07355485f0da Mon Sep 17 00:00:00 2001 From: Denis Denisov Date: Sun, 16 Nov 2025 17:43:23 +0300 Subject: [PATCH] Added card backs to pdf. Fixed determination of player cards from DB --- ArkhamDBApi.cs | 7 +++ ArkhamPnP.csproj | 19 ++++++- Data/Card.cs | 5 +- PDFComposer.cs | 77 ++++++++++++++++++++++++- Program.cs | 2 +- Resources.Designer.cs | 83 +++++++++++++++++++++++++++ Resources.resx | 127 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 Resources.Designer.cs create mode 100644 Resources.resx diff --git a/ArkhamDBApi.cs b/ArkhamDBApi.cs index 425bb09..38f01e3 100644 --- a/ArkhamDBApi.cs +++ b/ArkhamDBApi.cs @@ -10,6 +10,7 @@ namespace ArkhamPnP { internal class ArkhamDBApi { + const string BASE_IMAGE_URL = "https://ru.arkhamdb.com"; const string BASE_URL = "https://ru.arkhamdb.com/api/"; HttpClient http = new HttpClient(); @@ -38,5 +39,11 @@ namespace ArkhamPnP } return cards; } + + public async Task GetImage(string path) + { + var result = await http.GetAsync(BASE_IMAGE_URL + path); + return await result.Content.ReadAsStreamAsync(); + } } } diff --git a/ArkhamPnP.csproj b/ArkhamPnP.csproj index 4da1d92..e9339e3 100644 --- a/ArkhamPnP.csproj +++ b/ArkhamPnP.csproj @@ -13,8 +13,8 @@ - - + + @@ -23,4 +23,19 @@ + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + diff --git a/Data/Card.cs b/Data/Card.cs index c4d1bc6..650dc7d 100644 --- a/Data/Card.cs +++ b/Data/Card.cs @@ -17,6 +17,7 @@ namespace ArkhamPnP.Data public string image_src; public string back_image_src; public string type_code; + public string encounter_code; public Card() { @@ -32,6 +33,7 @@ namespace ArkhamPnP.Data image_src = element.GetProperty("imagesrc").GetString(); back_image_src = Get(element, "backimagesrc"); type_code = element.GetProperty("type_code").GetString(); + encounter_code = Get(element, "encounter_code"); } private string Get(JsonElement element, string v) @@ -47,7 +49,8 @@ namespace ArkhamPnP.Data { get { - return type_code == "asset" || type_code == "event" || type_code == "skill"; + return encounter_code == "" && + (type_code == "asset" || type_code == "event" || type_code == "skill"); } } } diff --git a/PDFComposer.cs b/PDFComposer.cs index e639bf4..7d1b214 100644 --- a/PDFComposer.cs +++ b/PDFComposer.cs @@ -1,9 +1,14 @@ using ArkhamPnP.Data; +using iText.IO.Image; using iText.Kernel.Pdf; using iText.Layout; +using iText.Layout.Element; using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Reflection; +using System.Resources; using System.Security.Cryptography.Xml; using System.Text; using System.Threading.Tasks; @@ -20,7 +25,14 @@ namespace ArkhamPnP const float MARGIN_SIDE = 5; // mm const float MARGIN_TOP_BOTTOM = 5; // mm - public void PrintCards(List cards, string destinationPath) + private ArkhamDBApi api; + + public PDFComposer() + { + api = new ArkhamDBApi(); + } + + public async Task PrintCards(List cards, string destinationPath) { var pdfDoc = new PdfDocument(new PdfWriter(destinationPath)); var doc = new Document(pdfDoc, new iText.Kernel.Geom.PageSize(PAGE_WIDTH, PAGE_HEIGHT)); @@ -32,6 +44,69 @@ namespace ArkhamPnP while (CARD_HEIGHT * (grid_height + 1) + FRONT_PADDING * (grid_height + 1 - 1) + 2 * MARGIN_TOP_BOTTOM <= PAGE_HEIGHT) grid_height++; Console.WriteLine("Will produce grid of cards " + grid_height + "x" + grid_width); + // compute position of left-top card in page + float left = (PAGE_WIDTH - grid_width * CARD_WIDTH - (grid_width - 1) * FRONT_PADDING) / 2; + float top = (PAGE_HEIGHT - grid_height * CARD_HEIGHT - (grid_height - 1) * FRONT_PADDING) / 2; + // + ResourceManager rm = new ResourceManager("ArkhamPnP.Resources", Assembly.GetExecutingAssembly()); + byte[] playerBytes = rm.GetObject("player") as byte[]; + // + int pos = 0; + int current_page = 1; + while (pos < cards.Count) + { + while (pdfDoc.GetNumberOfPages() < current_page + 1) + { + pdfDoc.AddNewPage(new iText.Kernel.Geom.PageSize(PAGE_WIDTH, PAGE_HEIGHT)); + } + float front_left = left; + float front_top = top; + float rear_left = left - FRONT_PADDING / 2; + float rear_top = top - FRONT_PADDING / 2; + for (int i = 0; i < grid_height; i++) + { + for (int j = 0; j < grid_width; j++) + { + if (pos >= cards.Count) break; + Console.WriteLine("Adding card " + (pos + 1) + " of " + cards.Count); + var current_card = cards[pos]; + var front_image = await LoadImage(current_card); + Image image = new Image(ImageDataFactory.Create(front_image)); + image.SetFixedPosition(current_page, front_left, front_top, CARD_WIDTH); + pos++; + doc.Add(image); + front_left += CARD_WIDTH + FRONT_PADDING; + // + Image backImage = new Image(ImageDataFactory.Create(playerBytes)); + backImage.SetFixedPosition(current_page + 1, PAGE_WIDTH - rear_left - (CARD_WIDTH + FRONT_PADDING), rear_top, CARD_WIDTH + FRONT_PADDING); + doc.Add(backImage); + rear_left += CARD_WIDTH + FRONT_PADDING; + } + front_left = left; + front_top += CARD_HEIGHT + FRONT_PADDING; + rear_left = left - FRONT_PADDING / 2; + rear_top += CARD_HEIGHT + FRONT_PADDING; + if (pos >= cards.Count) break; + } + current_page += 2; + } + doc.Close(); + } + + private async Task LoadImage(Card card) + { + string assembly_folder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var parts = card.image_src.Split('/'); + var destination_path = assembly_folder; + for (int i = 0; i < parts.Length; i++) destination_path = Path.Combine(destination_path, parts[i]); + var directory = Path.GetDirectoryName(destination_path); + if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); + if (File.Exists(destination_path)) return destination_path; + var stream = await api.GetImage(card.image_src); + var file_stream = File.Create(destination_path); + await stream.CopyToAsync(file_stream); + file_stream.Close(); + return destination_path; } } } diff --git a/Program.cs b/Program.cs index 1d25eea..7e6c5f9 100644 --- a/Program.cs +++ b/Program.cs @@ -15,7 +15,7 @@ foreach (var card in cards) Console.WriteLine($"ID: {card.id} front: {card.image_src} back: {card.back_image_src}"); } var composer = new PDFComposer(); -composer.PrintCards(cards, args[1]); +await composer.PrintCards(cards, args[1]); return 0; \ No newline at end of file diff --git a/Resources.Designer.cs b/Resources.Designer.cs new file mode 100644 index 0000000..928b302 --- /dev/null +++ b/Resources.Designer.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// +// Этот код создан программой. +// Исполняемая версия:4.0.30319.42000 +// +// Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае +// повторной генерации кода. +// +//------------------------------------------------------------------------------ + +namespace ArkhamPnP { + using System; + + + /// + /// Класс ресурса со строгой типизацией для поиска локализованных строк и т.д. + /// + // Этот класс создан автоматически классом StronglyTypedResourceBuilder + // с помощью такого средства, как ResGen или Visual Studio. + // Чтобы добавить или удалить член, измените файл .ResX и снова запустите ResGen + // с параметром /str или перестройте свой проект VS. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Возвращает кэшированный экземпляр ResourceManager, использованный этим классом. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ArkhamPnP.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Перезаписывает свойство CurrentUICulture текущего потока для всех + /// обращений к ресурсу с помощью этого класса ресурса со строгой типизацией. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Поиск локализованного ресурса типа System.Byte[]. + /// + internal static byte[] encounter { + get { + object obj = ResourceManager.GetObject("encounter", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Поиск локализованного ресурса типа System.Byte[]. + /// + internal static byte[] player { + get { + object obj = ResourceManager.GetObject("player", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/Resources.resx b/Resources.resx new file mode 100644 index 0000000..8096397 --- /dev/null +++ b/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Images\encounter.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Images\player.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file