From d8eb2dff97ccbf5b04f724fede7760337227ba99 Mon Sep 17 00:00:00 2001 From: "marcin.sciesinski" Date: Tue, 8 Oct 2019 08:12:58 -0700 Subject: [PATCH] Image rendering - couple fixes --- .../main/web/cards/set40/set40-dwarven.json | 2 +- .../lotro/images/recipe/JSONImageRecipe.java | 110 ++-- .../images/recipe/RotateLayerRecipe.java | 31 + .../lotro/images/recipe/TextLayerRecipe.java | 3 + .../src/main/resources/cardRecipe.json | 605 +++++++++++++----- 5 files changed, 535 insertions(+), 216 deletions(-) create mode 100644 gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/RotateLayerRecipe.java diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set40/set40-dwarven.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set40/set40-dwarven.json index 11ea97544..4b4c94077 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set40/set40-dwarven.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set40/set40-dwarven.json @@ -1,6 +1,6 @@ { "40_3": { - "title": "*Balin's Lament", + "title": "Balin's Lament", "culture": "dwarven", "cost": 2, "type": "Condition", diff --git a/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/JSONImageRecipe.java b/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/JSONImageRecipe.java index e1db0d884..86b934c29 100644 --- a/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/JSONImageRecipe.java +++ b/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/JSONImageRecipe.java @@ -28,6 +28,7 @@ public class JSONImageRecipe implements ImageRecipe { private List layers = new LinkedList<>(); private Map imageCache = new HashMap<>(); private Map fontCache = new HashMap<>(); + private Map> functionMap; public JSONImageRecipe(File file) { JSONParser parser = new JSONParser(); @@ -36,27 +37,27 @@ public class JSONImageRecipe implements ImageRecipe { width = ((Number) recipe.get("width")).intValue(); height = ((Number) recipe.get("height")).intValue(); - Map> functionMap = new HashMap<>(); + functionMap = new HashMap<>(); for (Map.Entry function : (Set>) ((JSONObject) recipe.get("functions")).entrySet()) { final JSONObject functionObj = function.getValue(); final String type = (String) functionObj.get("type"); if (type.equalsIgnoreCase("boolean")) - functionMap.put(function.getKey(), createBooleanProvider(functionObj.get("function"), functionMap)); + functionMap.put(function.getKey(), createBooleanProvider(functionObj.get("function"))); } JSONArray jsonLayers = (JSONArray) recipe.get("layers"); for (JSONObject jsonLayer : (List) jsonLayers) { - layers.add(createLayerRecipe(jsonLayer, functionMap)); + layers.add(createLayerRecipe(jsonLayer)); } } catch (IOException | ParseException e) { throw new RecipeGenerationException("Unable to generate recipe", e); } } - private LayerRecipe createLayerRecipe(JSONObject jsonLayer, Map> functionMap) { + private LayerRecipe createLayerRecipe(JSONObject jsonLayer) { String type = (String) jsonLayer.get("type"); if (type.equalsIgnoreCase("image")) { - Function pathProvider = createPathProvider(jsonLayer.get("path"), functionMap); + Function pathProvider = createPathProvider(jsonLayer.get("path")); final Function x = createIntProvider(jsonLayer.get("x")); final Function y = createIntProvider(jsonLayer.get("y")); final Function width = createIntProvider(jsonLayer.get("width")); @@ -76,32 +77,38 @@ public class JSONImageRecipe implements ImageRecipe { }, x, y, width, height); } else if (type.equalsIgnoreCase("text")) { - Function font = createFontProvider(jsonLayer.get("font"), functionMap); + Function font = createFontProvider(jsonLayer.get("font")); Function paint = createPaintProvider(jsonLayer.get("paint"), "black"); - final Function text = createStringProvider(jsonLayer.get("text"), functionMap); - Function textBox = createTextBoxProvider(jsonLayer.get("box"), functionMap); + final Function text = createStringProvider(jsonLayer.get("text")); + Function textBox = createTextBoxProvider(jsonLayer.get("box")); return new TextLayerRecipe( font, text, paint, textBox); } else if (type.equalsIgnoreCase("textBox")) { - final Function text = createStringArrayProvider(jsonLayer.get("text"), functionMap); + final Function text = createStringArrayProvider(jsonLayer.get("text")); JSONObject object = (JSONObject) jsonLayer.get("font"); Map> map = new HashMap<>(); for (Map.Entry fontEntry : (Set>) object.entrySet()) { - map.put(fontEntry.getKey(), createFontProvider(fontEntry.getValue(), functionMap)); + map.put(fontEntry.getKey(), createFontProvider(fontEntry.getValue())); } Map glyphMap = (JSONObject) jsonLayer.get("glyphs"); Function> fontStyleProvider = map::get; - Function textBox = createTextBoxProvider(jsonLayer.get("box"), functionMap); + Function textBox = createTextBoxProvider(jsonLayer.get("box")); return new TextBoxLayerRecipe( fontStyleProvider, s -> glyphMap.get(s), text, textBox); + } else if (type.equalsIgnoreCase("rotate")) { + final LayerRecipe layer = createLayerRecipe((JSONObject) jsonLayer.get("layer")); + final Function angle = createIntProvider(jsonLayer.get("angle")); + final Function x = createIntProvider(jsonLayer.get("x")); + final Function y = createIntProvider(jsonLayer.get("y")); + return new RotateLayerRecipe(layer, angle, x, y); } else if (type.equalsIgnoreCase("conditional")) { - Function condition = createBooleanProvider(jsonLayer.get("condition"), functionMap); - final List values = getObjects(jsonLayer.get("values")).stream().map(value -> createLayerRecipe(value, functionMap)).collect(toList()); + Function condition = createBooleanProvider(jsonLayer.get("condition")); + final List values = getObjects(jsonLayer.get("values")).stream().map(value -> createLayerRecipe(value)).collect(toList()); return (renderContext, graphics) -> { if (condition.apply(renderContext)) { @@ -113,12 +120,12 @@ public class JSONImageRecipe implements ImageRecipe { throw new RecipeGenerationException("Unable to find layer recipe of type: " + type); } - private Function createStringArrayProvider(Object value, Map> functionMap) { + private Function createStringArrayProvider(Object value) { if (value instanceof JSONObject) { JSONObject valueObj = (JSONObject) value; final String type = (String) valueObj.get("type"); if (type.equalsIgnoreCase("cardProperty")) { - final Function name = createStringProvider(valueObj.get("name"), functionMap); + final Function name = createStringProvider(valueObj.get("name")); return renderContext -> { final String propertyName = name.apply(renderContext); @@ -152,7 +159,7 @@ public class JSONImageRecipe implements ImageRecipe { throw new RecipeGenerationException("Unable to find paint recipe of type: " + paintVal); } - private Function createTextBoxProvider(Object box, Map> functionMap) { + private Function createTextBoxProvider(Object box) { if (box instanceof JSONObject) { JSONObject boxObj = (JSONObject) box; final String type = (String) boxObj.get("type"); @@ -161,7 +168,7 @@ public class JSONImageRecipe implements ImageRecipe { final Function y = createIntProvider(boxObj.get("y")); final Function width = createIntProvider(boxObj.get("width")); final Function height = createIntProvider(boxObj.get("height")); - final Function horizontalAlignment = createStringProvider(boxObj.get("horAlign"), functionMap); + final Function horizontalAlignment = createStringProvider(boxObj.get("horAlign")); return renderContext -> new TextBox() { @Override @@ -194,12 +201,12 @@ public class JSONImageRecipe implements ImageRecipe { throw new RecipeGenerationException("Unable to find text box recipe of type: " + box); } - private Function createFontProvider(Object font, Map> functionMap) { + private Function createFontProvider(Object font) { if (font instanceof JSONObject) { JSONObject fontObj = (JSONObject) font; final String type = (String) fontObj.get("type"); if (type.equalsIgnoreCase("ttf")) { - Function pathProvider = createPathProvider(fontObj.get("path"), functionMap); + Function pathProvider = createPathProvider(fontObj.get("path")); Function sizeProvider = createFloatProvider(fontObj.get("size")); Function styleProvider = createIntProvider(fontObj.get("style")); @@ -223,18 +230,18 @@ public class JSONImageRecipe implements ImageRecipe { throw new RecipeGenerationException("Unable to find font recipe: " + font); } - private Function createPathProvider(Object path, Map> functionMap) { + private Function createPathProvider(Object path) { if (path instanceof String) { return renderContext -> Paths.get((String) path); } else if (path instanceof JSONObject) { JSONObject pathObj = (JSONObject) path; final String type = (String) pathObj.get("type"); if (type.equalsIgnoreCase("string")) { - final Function value = createStringProvider(pathObj.get("value"), functionMap); + final Function value = createStringProvider(pathObj.get("value")); return renderContext -> Paths.get(value.apply(renderContext)); } else if (type.equalsIgnoreCase("resolve")) { - final Function parent = createPathProvider(pathObj.get("parent"), functionMap); - final Function child = createStringProvider(pathObj.get("child"), functionMap); + final Function parent = createPathProvider(pathObj.get("parent")); + final Function child = createStringProvider(pathObj.get("child")); return renderContext -> { final Path path1 = parent.apply(renderContext); final String childStr = child.apply(renderContext); @@ -245,11 +252,11 @@ public class JSONImageRecipe implements ImageRecipe { throw new RecipeGenerationException("Unable to recognize path type: " + path); } - private Function createStringProvider(Object string, Map> functionMap) { - return createStringProvider(string, functionMap, null); + private Function createStringProvider(Object string) { + return createStringProvider(string, null); } - private Function createStringProvider(Object string, Map> functionMap, String defaultValue) { + private Function createStringProvider(Object string, String defaultValue) { if (string == null) return renderContext -> defaultValue; if (string instanceof String) { @@ -258,22 +265,22 @@ public class JSONImageRecipe implements ImageRecipe { JSONObject stringObj = (JSONObject) string; final String type = (String) stringObj.get("type"); if (type.equalsIgnoreCase("property")) { - Function propertyName = createStringProvider(stringObj.get("name"), functionMap); + Function propertyName = createStringProvider(stringObj.get("name")); return renderContext -> renderContext.getProperties().getProperty( propertyName.apply(renderContext)); } else if (type.equalsIgnoreCase("map")) { - final Function key = createStringProvider(stringObj.get("key"), functionMap); + final Function key = createStringProvider(stringObj.get("key")); Map map = (Map) stringObj.get("map"); return renderContext -> map.get(key.apply(renderContext)); } else if (type.equalsIgnoreCase("replace")) { - final Function source = createStringProvider(stringObj.get("source"), functionMap); - final Function match = createStringProvider(stringObj.get("match"), functionMap); - final Function with = createStringProvider(stringObj.get("with"), functionMap); + final Function source = createStringProvider(stringObj.get("source")); + final Function match = createStringProvider(stringObj.get("match")); + final Function with = createStringProvider(stringObj.get("with")); return renderContext -> source.apply(renderContext).replace(match.apply(renderContext), with.apply(renderContext)); } else if (type.equalsIgnoreCase("capitalize")) { - final Function source = createStringProvider(stringObj.get("value"), functionMap); + final Function source = createStringProvider(stringObj.get("value")); return renderContext -> { final String text = source.apply(renderContext); @@ -284,7 +291,7 @@ public class JSONImageRecipe implements ImageRecipe { return String.join(" ", textSplit); }; } else if (type.equalsIgnoreCase("cardProperty")) { - final Function name = createStringProvider(stringObj.get("name"), functionMap); + final Function name = createStringProvider(stringObj.get("name")); return renderContext -> { final String propertyName = name.apply(renderContext); @@ -298,9 +305,9 @@ public class JSONImageRecipe implements ImageRecipe { throw new ImageGenerationException("Unable to get card property: " + propertyName + ", unknown type: " + value); }; } else if (type.equalsIgnoreCase("cardPropertyValueIn")) { - final Function name = createStringProvider(stringObj.get("name"), functionMap); + final Function name = createStringProvider(stringObj.get("name")); final JSONArray values = (JSONArray) stringObj.get("values"); - final List> list = (List>) values.stream().map(value -> createStringProvider(value, functionMap)).collect(toList()); + final List> list = (List>) values.stream().map(value -> createStringProvider(value)).collect(toList()); return renderContext -> { final String propertyName = name.apply(renderContext); @@ -316,7 +323,7 @@ public class JSONImageRecipe implements ImageRecipe { }; } else if (type.equalsIgnoreCase("append")) { final JSONArray values = (JSONArray) stringObj.get("values"); - final List> list = (List>) values.stream().map(value -> createStringProvider(value, functionMap)).collect(toList()); + final List> list = (List>) values.stream().map(value -> createStringProvider(value)).collect(toList()); return renderContext -> { StringBuilder sb = new StringBuilder(); @@ -328,8 +335,8 @@ public class JSONImageRecipe implements ImageRecipe { return sb.toString(); }; } else if (type.equalsIgnoreCase("optional")) { - Function condition = createBooleanProvider(stringObj.get("condition"), functionMap); - final Function value = createStringProvider(stringObj.get("value"), functionMap); + Function condition = createBooleanProvider(stringObj.get("condition")); + final Function value = createStringProvider(stringObj.get("value")); return renderContext -> { if (condition.apply(renderContext)) @@ -341,18 +348,18 @@ public class JSONImageRecipe implements ImageRecipe { throw new RecipeGenerationException("Unable to recognize String: " + string); } - private Function createBooleanProvider(Object condition, Map> functionMap) { + private Function createBooleanProvider(Object condition) { if (condition instanceof JSONObject) { JSONObject conditionObj = (JSONObject) condition; final String type = (String) conditionObj.get("type"); if (type.equalsIgnoreCase("cardHasProperty")) { - final Function name = createStringProvider(conditionObj.get("name"), functionMap); + final Function name = createStringProvider(conditionObj.get("name")); return renderContext -> renderContext.getCardInfo().get(name.apply(renderContext)) != null; } else if (type.equalsIgnoreCase("cardHasPropertyValueIn")) { - final Function name = createStringProvider(conditionObj.get("name"), functionMap); + final Function name = createStringProvider(conditionObj.get("name")); final JSONArray values = (JSONArray) conditionObj.get("values"); - final List> list = (List>) values.stream().map(value -> createStringProvider(value, functionMap)).collect(toList()); + final List> list = (List>) values.stream().map(value -> createStringProvider(value)).collect(toList()); return renderContext -> { String[] propertyValues = getStringArray(renderContext.getCardInfo().get(name.apply(renderContext))); @@ -366,16 +373,16 @@ public class JSONImageRecipe implements ImageRecipe { return false; }; } else if (type.equalsIgnoreCase("cardPropertyGreaterThanZero")) { - final Function name = createStringProvider(conditionObj.get("name"), functionMap); + final Function name = createStringProvider(conditionObj.get("name")); return renderContext -> ((Number) renderContext.getCardInfo().get(name.apply(renderContext))).floatValue() > 0; } else if (type.equalsIgnoreCase("not")) { - final Function opposite = createBooleanProvider(conditionObj.get("condition"), functionMap); + final Function opposite = createBooleanProvider(conditionObj.get("condition")); return renderContext -> !opposite.apply(renderContext); } else if (type.equalsIgnoreCase("or")) { final JSONArray values = (JSONArray) conditionObj.get("conditions"); - final List> list = (List>) values.stream().map(value -> createBooleanProvider(value, functionMap)).collect(Collectors.toList()); + final List> list = (List>) values.stream().map(value -> createBooleanProvider(value)).collect(Collectors.toList()); return renderContext -> { for (Function oneCondition : list) { @@ -385,7 +392,7 @@ public class JSONImageRecipe implements ImageRecipe { return false; }; } else if (type.equalsIgnoreCase("function")) { - final Function name = createStringProvider(conditionObj.get("name"), functionMap); + final Function name = createStringProvider(conditionObj.get("name")); return renderContext -> (Boolean) functionMap.get(name.apply(renderContext)).apply(renderContext); } } @@ -412,7 +419,18 @@ public class JSONImageRecipe implements ImageRecipe { private Function createIntProvider(Object value) { if (value instanceof Number) { return renderContext -> ((Number) value).intValue(); + } else if (value instanceof JSONObject) { + final JSONObject valueObj = (JSONObject) value; + final String type = (String) valueObj.get("type"); + if (type.equalsIgnoreCase("conditional")) { + final Function condition = createBooleanProvider(valueObj.get("condition")); + final Function aTrue = createIntProvider(valueObj.get("true")); + final Function aFalse = createIntProvider(valueObj.get("false")); + + return renderContext -> condition.apply(renderContext) ? aTrue.apply(renderContext) : aFalse.apply(renderContext); + } } + throw new RecipeGenerationException("Unable to recognize int: " + value); } diff --git a/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/RotateLayerRecipe.java b/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/RotateLayerRecipe.java new file mode 100644 index 000000000..66d5f681e --- /dev/null +++ b/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/RotateLayerRecipe.java @@ -0,0 +1,31 @@ +package com.gempukku.lotro.images.recipe; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.util.function.Function; + +public class RotateLayerRecipe implements LayerRecipe { + private LayerRecipe target; + private Function angle; + private Function x; + private Function y; + + public RotateLayerRecipe(LayerRecipe target, Function angle, Function x, Function y) { + this.target = target; + this.angle = angle; + this.x = x; + this.y = y; + } + + @Override + public void renderLayer(RenderContext renderContext, Graphics2D graphics) { + final AffineTransform originalTransform = graphics.getTransform(); + + final AffineTransform rotateInstance = AffineTransform.getRotateInstance( + Math.toRadians(angle.apply(renderContext)), x.apply(renderContext), y.apply(renderContext)); + graphics.setTransform(rotateInstance); + target.renderLayer(renderContext, graphics); + + graphics.setTransform(originalTransform); + } +} diff --git a/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/TextLayerRecipe.java b/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/TextLayerRecipe.java index 10381d20f..b042de56d 100644 --- a/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/TextLayerRecipe.java +++ b/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/TextLayerRecipe.java @@ -37,6 +37,9 @@ public class TextLayerRecipe implements LayerRecipe { } else if (horizontalAlignment.equals("center")) { final int width = fontMetrics.stringWidth(text); graphics.drawString(text, textBox.getX() + (textBox.getWidth() - width) / 2, textBox.getY() + fontMetrics.getAscent()); + } else if (horizontalAlignment.equals("right")) { + final int width = fontMetrics.stringWidth(text); + graphics.drawString(text, textBox.getX() + (textBox.getWidth() - width), textBox.getY() + fontMetrics.getAscent()); } else { throw new ImageGenerationException("Unable to recognize horizontal alignment: " + horizontalAlignment); } diff --git a/gemp-lotr/gemp-lotr-images/src/main/resources/cardRecipe.json b/gemp-lotr/gemp-lotr-images/src/main/resources/cardRecipe.json index 86eb8f353..d32e22a84 100644 --- a/gemp-lotr/gemp-lotr-images/src/main/resources/cardRecipe.json +++ b/gemp-lotr/gemp-lotr-images/src/main/resources/cardRecipe.json @@ -5,24 +5,29 @@ "isNoun": { "type": "boolean", "function": { - "type": "or", - "conditions": [ - { - "type": "cardHasPropertyValueIn", - "name": "type", - "values": [ - "companion", - "ally", - "follower", - "minion" - ] - }, - { - "type": "cardHasProperty", - "name": "target" - } + "type": "cardHasPropertyValueIn", + "name": "type", + "values": [ + "companion", + "ally", + "minion" ] } + }, + "isVerb": { + "type": "boolean", + "function": { + "type": "not", + "condition": { + "type": "cardHasPropertyValueIn", + "name": "type", + "values": [ + "companion", + "ally", + "minion" + ] + } + } } }, "layers": [ @@ -58,11 +63,8 @@ { "type": "optional", "condition": { - "type": "not", - "condition": { - "type": "function", - "name": "isNoun" - } + "type": "function", + "name": "isVerb" }, "value": "_verb" } @@ -80,70 +82,195 @@ "height": 335 }, { - "comment": "Title", - "type": "text", - "font": { - "type": "ttf", - "path": { - "type": "resolve", - "parent": { - "type": "string", - "value": { - "type": "property", - "name": "lotro.resources.folder" - } + "comment": "Title noun", + "type": "conditional", + "condition": { + "type": "function", + "name": "isNoun" + }, + "values": { + "type": "text", + "font": { + "type": "ttf", + "path": { + "type": "resolve", + "parent": { + "type": "string", + "value": { + "type": "property", + "name": "lotro.resources.folder" + } + }, + "child": "fonts/o_wap_matbolsmallcap.ttf" }, - "child": "fonts/o_wap_matbolsmallcap.ttf" + "size": 13, + "style": 0 }, - "size": 15, - "style": 0 - }, - "text": { - "type": "replace", - "source": { - "type": "cardProperty", - "name": "title" + "text": { + "type": "replace", + "source": { + "type": "cardProperty", + "name": "title" + }, + "match": "*", + "with": "•" }, - "match": "*", - "with": "•" - }, - "box": { - "type": "box", - "x": 48, - "y": 15, - "width": 192, - "height": 24 + "box": { + "type": "box", + "x": 48, + "y": 16, + "width": 192, + "height": 24 + } } }, { - "comment": "Subtitle", - "type": "text", - "font": { - "type": "ttf", - "path": { - "type": "resolve", - "parent": { - "type": "string", - "value": { - "type": "property", - "name": "lotro.resources.folder" - } + "comment": "Title verb", + "type": "conditional", + "condition": { + "type": "function", + "name": "isVerb" + }, + "values": { + "type": "rotate", + "angle": -90, + "x": { + "type": "conditional", + "condition": { + "type": "cardHasProperty", + "name": "subtitle" }, - "child": "fonts/o_wap_matbolsmallcap.ttf" + "true": 16, + "false": 21 }, - "size": 12, - "style": 0 + "y": 180, + "layer": { + "type": "text", + "font": { + "type": "ttf", + "path": { + "type": "resolve", + "parent": { + "type": "string", + "value": { + "type": "property", + "name": "lotro.resources.folder" + } + }, + "child": "fonts/o_wap_matbolsmallcap.ttf" + }, + "size": 13, + "style": 0 + }, + "text": { + "type": "replace", + "source": { + "type": "cardProperty", + "name": "title" + }, + "match": "*", + "with": "•" + }, + "box": { + "type": "box", + "x": { + "type": "conditional", + "condition": { + "type": "cardHasProperty", + "name": "subtitle" + }, + "true": 16, + "false": 21 + }, + "y": 180, + "width": 127, + "height": 24, + "horAlign": "right" + } + } + } + }, + { + "comment": "Subtitle noun", + "type": "conditional", + "condition": { + "type": "function", + "name": "isNoun" }, - "text": { - "type": "cardProperty", - "name": "subtitle" + "values": { + "type": "text", + "font": { + "type": "ttf", + "path": { + "type": "resolve", + "parent": { + "type": "string", + "value": { + "type": "property", + "name": "lotro.resources.folder" + } + }, + "child": "fonts/o_wap_matbolsmallcap.ttf" + }, + "size": 12, + "style": 0 + }, + "text": { + "type": "cardProperty", + "name": "subtitle" + }, + "box": { + "type": "box", + "x": 48, + "y": 30, + "width": 192, + "height": 24 + } + } + }, + { + "comment": "Subtitle verb", + "type": "conditional", + "condition": { + "type": "function", + "name": "isVerb" }, - "box": { - "type": "box", - "x": 48, - "y": 30, - "width": 192, - "height": 24 + "values": { + "type": "rotate", + "angle": -90, + "x": 26, + "y": 180, + "layer": { + "type": "text", + "font": { + "type": "ttf", + "path": { + "type": "resolve", + "parent": { + "type": "string", + "value": { + "type": "property", + "name": "lotro.resources.folder" + } + }, + "child": "fonts/o_wap_matbolsmallcap.ttf" + }, + "size": 12, + "style": 0 + }, + "text": { + "type": "cardProperty", + "name": "subtitle" + }, + "box": { + "type": "box", + "x": 26, + "y": 180, + "width": 127, + "height": 24, + "horAlign": "right" + } + } } }, { @@ -180,108 +307,248 @@ } }, { - "comment": "Type line", - "type": "text", - "font": { - "type": "ttf", - "path": { - "type": "resolve", - "parent": { - "type": "string", - "value": { - "type": "property", - "name": "lotro.resources.folder" - } + "comment": "Type line noun", + "type": "conditional", + "condition": { + "type": "function", + "name": "isNoun" + }, + "values": { + "type": "text", + "font": { + "type": "ttf", + "path": { + "type": "resolve", + "parent": { + "type": "string", + "value": { + "type": "property", + "name": "lotro.resources.folder" + } + }, + "child": "fonts/o_wap_matbolsmallcap.ttf" }, - "child": "fonts/o_wap_matbolsmallcap.ttf" + "size": 13, + "style": 0 }, - "size": 14, - "style": 0 - }, - "text": { - "type": "append", - "values": [ - { - "comment": "Card type", - "type": "capitalize", - "value": { - "type": "cardProperty", - "name": "type" - } - }, - { - "comment": "Race", - "type": "optional", - "condition": { - "type": "cardHasProperty", - "name": "race" + "text": { + "type": "append", + "values": [ + { + "comment": "Card type", + "type": "capitalize", + "value": { + "type": "cardProperty", + "name": "type" + } }, - "value": { - "type": "append", - "values": [ - " • ", - { - "type": "capitalize", - "value": { - "type": "cardProperty", - "name": "race" + { + "comment": "Race", + "type": "optional", + "condition": { + "type": "cardHasProperty", + "name": "race" + }, + "value": { + "type": "append", + "values": [ + " • ", + { + "type": "capitalize", + "value": { + "type": "cardProperty", + "name": "race" + } } - } - ] - } - }, - { - "comment": "Type keyword", - "type": "optional", - "condition": { - "type": "cardHasPropertyValueIn", - "name": "keyword", - "values": [ - "fellowship", - "shadow", - "maneuver", - "archery", - "assignment", - "skirmish", - "regroup", - "response", - "support area" - ] + ] + } }, - "value": { - "type": "append", - "values": [ - " • ", - { - "type": "capitalize", - "value": { - "type": "cardPropertyValueIn", - "name": "keyword", - "values": [ - "fellowship", - "shadow", - "maneuver", - "archery", - "assignment", - "skirmish", - "regroup", - "response", - "support area" - ] + { + "comment": "Type keyword", + "type": "optional", + "condition": { + "type": "cardHasPropertyValueIn", + "name": "keyword", + "values": [ + "fellowship", + "shadow", + "maneuver", + "archery", + "assignment", + "skirmish", + "regroup", + "response", + "support area" + ] + }, + "value": { + "type": "append", + "values": [ + " • ", + { + "type": "capitalize", + "value": { + "type": "cardPropertyValueIn", + "name": "keyword", + "values": [ + "fellowship", + "shadow", + "maneuver", + "archery", + "assignment", + "skirmish", + "regroup", + "response", + "support area" + ] + } } - } - ] + ] + } } - } - ] + ] + }, + "box": { + "type": "box", + "x": 0, + "y": 197, + "width": 240, + "height": 30, + "horAlign": "center" + } + } + }, + { + "comment": "Type line verb", + "type": "conditional", + "condition": { + "type": "function", + "name": "isVerb" }, - "box": { - "type": "box", - "x": 0, - "y": 197, - "width": 240, - "height": 30, - "horAlign": "center" + "values": { + "type": "text", + "font": { + "type": "ttf", + "path": { + "type": "resolve", + "parent": { + "type": "string", + "value": { + "type": "property", + "name": "lotro.resources.folder" + } + }, + "child": "fonts/o_wap_matbolsmallcap.ttf" + }, + "size": 13, + "style": 0 + }, + "text": { + "type": "append", + "values": [ + { + "comment": "Card type", + "type": "capitalize", + "value": { + "type": "cardProperty", + "name": "type" + } + }, + { + "comment": "Race", + "type": "optional", + "condition": { + "type": "cardHasProperty", + "name": "race" + }, + "value": { + "type": "append", + "values": [ + " • ", + { + "type": "capitalize", + "value": { + "type": "cardProperty", + "name": "race" + } + } + ] + } + }, + { + "comment": "Possession class", + "type": "optional", + "condition": { + "type": "cardHasProperty", + "name": "possession" + }, + "value": { + "type": "append", + "values": [ + " • ", + { + "type": "capitalize", + "value": { + "type": "cardProperty", + "name": "possession" + } + } + ] + } + }, + { + "comment": "Type keyword", + "type": "optional", + "condition": { + "type": "cardHasPropertyValueIn", + "name": "keyword", + "values": [ + "fellowship", + "shadow", + "maneuver", + "archery", + "assignment", + "skirmish", + "regroup", + "response", + "support area" + ] + }, + "value": { + "type": "append", + "values": [ + " • ", + { + "type": "capitalize", + "value": { + "type": "cardPropertyValueIn", + "name": "keyword", + "values": [ + "fellowship", + "shadow", + "maneuver", + "archery", + "assignment", + "skirmish", + "regroup", + "response", + "support area" + ] + } + } + ] + } + } + ] + }, + "box": { + "type": "box", + "x": 18, + "y": 197, + "width": 240, + "height": 30, + "horAlign": "center" + } } }, {