Added card backs to pdf. Fixed determination of player cards from DB

This commit is contained in:
ftc
2025-11-16 17:43:23 +03:00
parent 347b116929
commit 82f664fafa
7 changed files with 315 additions and 5 deletions
+7
View File
@@ -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<Stream> GetImage(string path)
{
var result = await http.GetAsync(BASE_IMAGE_URL + path);
return await result.Content.ReadAsStreamAsync();
}
}
}
+17 -2
View File
@@ -13,8 +13,8 @@
</ItemGroup>
<ItemGroup>
<Resource Include="Images\encounter.png" />
<Resource Include="Images\player.png" />
<EmbeddedResource Include="Images\encounter.png" />
<EmbeddedResource Include="Images\player.png" />
</ItemGroup>
<ItemGroup>
@@ -23,4 +23,19 @@
<PackageReference Include="System.Text.Json" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>
+4 -1
View File
@@ -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");
}
}
}
+76 -1
View File
@@ -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<Card> cards, string destinationPath)
private ArkhamDBApi api;
public PDFComposer()
{
api = new ArkhamDBApi();
}
public async Task PrintCards(List<Card> 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<string> 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;
}
}
}
+1 -1
View File
@@ -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;
+83
View File
@@ -0,0 +1,83 @@
//------------------------------------------------------------------------------
// <auto-generated>
// Этот код создан программой.
// Исполняемая версия:4.0.30319.42000
//
// Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае
// повторной генерации кода.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ArkhamPnP {
using System;
/// <summary>
/// Класс ресурса со строгой типизацией для поиска локализованных строк и т.д.
/// </summary>
// Этот класс создан автоматически классом 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() {
}
/// <summary>
/// Возвращает кэшированный экземпляр ResourceManager, использованный этим классом.
/// </summary>
[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;
}
}
/// <summary>
/// Перезаписывает свойство CurrentUICulture текущего потока для всех
/// обращений к ресурсу с помощью этого класса ресурса со строгой типизацией.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Поиск локализованного ресурса типа System.Byte[].
/// </summary>
internal static byte[] encounter {
get {
object obj = ResourceManager.GetObject("encounter", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Поиск локализованного ресурса типа System.Byte[].
/// </summary>
internal static byte[] player {
get {
object obj = ResourceManager.GetObject("player", resourceCulture);
return ((byte[])(obj));
}
}
}
}
+127
View File
@@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="encounter" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Images\encounter.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="player" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Images\player.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>