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 9d1e3a60a..255e3de39 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 @@ -81,9 +81,10 @@ public class JSONImageRecipe implements ImageRecipe { Function paint = createPaintProvider(jsonLayer.get("paint"), "black"); final Function text = createStringProvider(jsonLayer.get("text")); Function textBox = createTextBoxProvider(jsonLayer.get("box")); + Function dropShadow = createBooleanProvider(jsonLayer.get("dropShadow"), false); return new TextLayerRecipe( - font, text, paint, textBox); + font, text, paint, textBox, dropShadow); } else if (type.equalsIgnoreCase("textBox")) { final Function text = createStringArrayProvider(jsonLayer.get("text")); JSONObject object = (JSONObject) jsonLayer.get("font"); @@ -95,11 +96,12 @@ public class JSONImageRecipe implements ImageRecipe { Function> fontStyleProvider = map::get; Function textBox = createTextBoxProvider(jsonLayer.get("box")); + final Function minYStart = createFloatProvider(jsonLayer.get("minYStart")); return new TextBoxLayerRecipe( fontStyleProvider, s -> glyphMap.get(s), - text, textBox); + text, textBox, minYStart); } else if (type.equalsIgnoreCase("rotate")) { final LayerRecipe layer = createLayerRecipe((JSONObject) jsonLayer.get("layer")); final Function angle = createIntProvider(jsonLayer.get("angle")); @@ -124,7 +126,20 @@ public class JSONImageRecipe implements ImageRecipe { if (value instanceof JSONObject) { JSONObject valueObj = (JSONObject) value; final String type = (String) valueObj.get("type"); - if (type.equalsIgnoreCase("append")) { + if (type.equalsIgnoreCase("appendText")) { + final JSONArray values = (JSONArray) valueObj.get("values"); + final List> list = (List>) values.stream().map(text -> createStringProvider(text)).collect(toList()); + + return renderContext -> { + StringBuilder sb = new StringBuilder(); + for (Function valueProvider : list) { + final String textValue = valueProvider.apply(renderContext); + if (textValue != null) + sb.append(textValue); + } + return new String[]{sb.toString()}; + }; + } else if (type.equalsIgnoreCase("append")) { final JSONArray values = (JSONArray) valueObj.get("values"); List> providers = (List>) values.stream().map(appendValue -> createStringArrayProvider(appendValue)).collect(Collectors.toList()); @@ -211,8 +226,16 @@ public class JSONImageRecipe implements ImageRecipe { } @Override - public String getHorizontalAlignment() { - return horizontalAlignment.apply(renderContext); + public HorizontalAlignment getHorizontalAlignment() { + final String align = horizontalAlignment.apply(renderContext); + if (align == null || align.equalsIgnoreCase("left")) + return (availableWidth, usedWidth) -> 0; + else if (align.equalsIgnoreCase("center")) + return (availableWidth, usedWidth) -> (availableWidth - usedWidth) / 2; + else if (align.equalsIgnoreCase("right")) + return (availableWidth, usedWidth) -> (availableWidth - usedWidth); + + throw new ImageGenerationException("Unable to recognize horizontal alignment: " + align); } }; } @@ -376,7 +399,15 @@ public class JSONImageRecipe implements ImageRecipe { } private Function createBooleanProvider(Object condition) { - if (condition instanceof JSONObject) { + return createBooleanProvider(condition, null); + } + + private Function createBooleanProvider(Object condition, Boolean defaultValue) { + if (condition == null && defaultValue != null) + return renderContext -> defaultValue; + else if (condition instanceof Boolean) + return renderContext -> (Boolean) condition; + else if (condition instanceof JSONObject) { JSONObject conditionObj = (JSONObject) condition; final String type = (String) conditionObj.get("type"); if (type.equalsIgnoreCase("cardHasProperty")) { diff --git a/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/TextBox.java b/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/TextBox.java index 979033e60..f2dbceb77 100644 --- a/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/TextBox.java +++ b/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/TextBox.java @@ -9,5 +9,9 @@ public interface TextBox { int getHeight(); - String getHorizontalAlignment(); + HorizontalAlignment getHorizontalAlignment(); + + interface HorizontalAlignment { + float getXShift(float availableWidth, float usedWidth); + } } diff --git a/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/TextBoxLayerRecipe.java b/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/TextBoxLayerRecipe.java index cbb72c87f..d2b8279eb 100644 --- a/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/TextBoxLayerRecipe.java +++ b/gemp-lotr/gemp-lotr-images/src/main/java/com/gempukku/lotro/images/recipe/TextBoxLayerRecipe.java @@ -5,6 +5,7 @@ import java.awt.font.FontRenderContext; import java.awt.font.LineBreakMeasurer; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; +import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.text.AttributedCharacterIterator; import java.text.AttributedString; @@ -15,16 +16,19 @@ public class TextBoxLayerRecipe implements LayerRecipe { private Function glyphProvider; private Function text; private Function textBox; + private Function minYStart; public TextBoxLayerRecipe( Function> fontStyleProvider, Function glyphProvider, Function text, - Function textBox) { + Function textBox, + Function minYStart) { this.fontStyleProvider = fontStyleProvider; this.glyphProvider = glyphProvider; this.text = text; this.textBox = textBox; + this.minYStart = minYStart; } @Override @@ -46,20 +50,27 @@ public class TextBoxLayerRecipe implements LayerRecipe { // Dry run to check for height float textHeight = processText((layout, x, y) -> { - }, iteratorLines, 0, 0, box.getWidth(), frc); + }, iteratorLines, 0, 0, box.getWidth(), box.getHorizontalAlignment(), frc); - float textAscent = Math.min(0.125f, (1 - textHeight / box.getHeight()) / 2f) * box.getHeight(); - processText((layout, x, y) -> layout.draw(graphics, x, y), iteratorLines, box.getX(), box.getY() + textAscent, box.getWidth(), frc); + final float boxPercUsed = textHeight / box.getHeight(); + float textAscent = Math.min(minYStart.apply(renderContext), (1 - boxPercUsed) / 2f) * box.getHeight(); + processText((layout, x, y) -> layout.draw(graphics, x, y), iteratorLines, box.getX(), box.getY() + textAscent, box.getWidth(), box.getHorizontalAlignment(), frc); } } - private float processText(LineRenderingCallback callback, AttributedCharacterIterator[] iteratorLines, float x, float y, float width, FontRenderContext frc) { + private float processText(LineRenderingCallback callback, AttributedCharacterIterator[] iteratorLines, float x, float y, float width, TextBox.HorizontalAlignment horizontalAlignment, FontRenderContext frc) { Point2D.Float pen = new Point2D.Float(x, y); + boolean firstParagraph = true; for (AttributedCharacterIterator styledText : iteratorLines) { // let styledText be an AttributedCharacterIterator containing at least // one character + // Paragraph break + if (!firstParagraph) { + pen.y += 5; + } + LineBreakMeasurer measurer = new LineBreakMeasurer(styledText, frc); float wrappingWidth = width; @@ -67,14 +78,13 @@ public class TextBoxLayerRecipe implements LayerRecipe { TextLayout layout = measurer.nextLayout(wrappingWidth); pen.y += (layout.getAscent()); - float dx = layout.isLeftToRight() ? - 0 : (wrappingWidth - layout.getAdvance()); + float xShift = horizontalAlignment.getXShift(wrappingWidth, layout.getAdvance()); - callback.drawLine(layout, pen.x + dx, pen.y); + callback.drawLine(layout, pen.x + xShift, pen.y); pen.y += layout.getDescent() + layout.getLeading(); } - // Paragraph break - pen.y += 5; + + firstParagraph = false; } return pen.y; } @@ -86,11 +96,20 @@ public class TextBoxLayerRecipe implements LayerRecipe { AttributedString string = new AttributedString(resultText.toString()); parseText(text, new TextParsingCallback() { private int index = 0; + private boolean lowered = false; @Override public void appendText(String text, String style) { String resultStyle = (style != null) ? style : "default"; - string.addAttribute(TextAttribute.FONT, fontStyleProvider.apply(resultStyle).apply(renderContext), index, index + text.length()); + Font font = fontStyleProvider.apply(resultStyle).apply(renderContext); + if (resultStyle.equals("glyph") && !lowered) { + font = font.deriveFont(AffineTransform.getTranslateInstance(0, 3)); + lowered = true; + } else if (!resultStyle.equals("glyph") && lowered) { + font = font.deriveFont(AffineTransform.getTranslateInstance(0, -3)); + lowered = false; + } + string.addAttribute(TextAttribute.FONT, font, index, index + text.length()); string.addAttribute(TextAttribute.FOREGROUND, Color.BLACK, index, index + text.length()); index += text.length(); } 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 c151b4b1a..3cf8e3ac7 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 @@ -8,12 +8,16 @@ public class TextLayerRecipe implements LayerRecipe { private Function textProvider; private Function paintProvider; private Function textBoxProvider; + private Function dropShadow; - public TextLayerRecipe(Function fontProvider, Function textProvider, Function paintProvider, Function textBoxProvider) { + public TextLayerRecipe(Function fontProvider, Function textProvider, + Function paintProvider, Function textBoxProvider, + Function dropShadow) { this.fontProvider = fontProvider; this.textProvider = textProvider; this.paintProvider = paintProvider; this.textBoxProvider = textBoxProvider; + this.dropShadow = dropShadow; } @Override @@ -28,25 +32,29 @@ public class TextLayerRecipe implements LayerRecipe { final Font font = fontProvider.apply(renderContext); graphics.setFont(font); - graphics.setPaint(paintProvider.apply(renderContext)); final FontMetrics fontMetrics = graphics.getFontMetrics(); + int dropShadowDistance = 3; int yMargin = (textBox.getHeight() - fontMetrics.getHeight()) / 2; - final String horizontalAlignment = textBox.getHorizontalAlignment(); - if (horizontalAlignment == null) { - graphics.drawString(text, textBox.getX(), textBox.getY() + yMargin + fontMetrics.getAscent()); - } else if (horizontalAlignment.equals("center")) { - final int width = fontMetrics.stringWidth(text); - graphics.drawString(text, textBox.getX() + (textBox.getWidth() - width) / 2, textBox.getY() + yMargin + fontMetrics.getAscent()); - } else if (horizontalAlignment.equals("right")) { - final int width = fontMetrics.stringWidth(text); - graphics.drawString(text, textBox.getX() + (textBox.getWidth() - width), textBox.getY() + yMargin + fontMetrics.getAscent()); - } else { - throw new ImageGenerationException("Unable to recognize horizontal alignment: " + horizontalAlignment); + final TextBox.HorizontalAlignment horizontalAlignment = textBox.getHorizontalAlignment(); + + if (dropShadow.apply(renderContext)) { + graphics.setPaint(Color.BLACK); + drawText(graphics, text, textBox, fontMetrics, yMargin, horizontalAlignment, dropShadowDistance); } + graphics.setPaint(paintProvider.apply(renderContext)); + drawText(graphics, text, textBox, fontMetrics, yMargin, horizontalAlignment, 0); } } + + private void drawText(Graphics2D graphics, String text, TextBox textBox, FontMetrics fontMetrics, int yMargin, TextBox.HorizontalAlignment horizontalAlignment, + int distance) { + final int width = fontMetrics.stringWidth(text); + float xShift = horizontalAlignment.getXShift(textBox.getWidth(), width); + + graphics.drawString(text, xShift + distance + textBox.getX(), distance + textBox.getY() + yMargin + fontMetrics.getAscent()); + } } 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 af3d712d1..908fffcb3 100644 --- a/gemp-lotr/gemp-lotr-images/src/main/resources/cardRecipe.json +++ b/gemp-lotr/gemp-lotr-images/src/main/resources/cardRecipe.json @@ -334,6 +334,7 @@ "comment": "Cost", "type": "text", "paint": "white", + "dropShadow": true, "font": { "type": "ttf", "path": { @@ -374,28 +375,9 @@ "name": "isCharacter" }, "values": { - "type": "text", - "font": { - "type": "ttf", - "path": { - "type": "resolve", - "parent": { - "type": "property", - "name": "lotro.resources.folder" - }, - "child": { - "type": "property", - "name": "type.font" - } - }, - "size": { - "type": "property", - "name": "type.size" - }, - "style": 0 - }, + "type": "textBox", "text": { - "type": "append", + "type": "appendText", "values": [ { "comment": "Card type", @@ -415,7 +397,7 @@ "value": { "type": "append", "values": [ - " • ", + " {bullet} ", { "type": "capitalize", "value": { @@ -447,7 +429,7 @@ "value": { "type": "append", "values": [ - " • ", + " {bullet} ", { "type": "capitalize", "value": { @@ -471,12 +453,52 @@ } ] }, + "font": { + "default": { + "type": "ttf", + "path": { + "type": "resolve", + "parent": { + "type": "property", + "name": "lotro.resources.folder" + }, + "child": { + "type": "property", + "name": "type.font" + } + }, + "size": { + "type": "property", + "name": "type.size" + }, + "style": 0 + }, + "glyph": { + "type": "ttf", + "path": { + "type": "resolve", + "parent": { + "type": "property", + "name": "lotro.resources.folder" + }, + "child": "LOTRSymbols.ttf" + }, + "size": 40, + "style": 0 + } + }, + "glyphs": { + "shire": "#", + "dwarven": "@", + "bullet": "•" + }, + "minYStart": 1, "box": { "type": "box", "x": 0, - "y": 615, + "y": 611, "width": 751, - "height": 30, + "height": 40, "horAlign": "center" } } @@ -645,6 +667,7 @@ "comment": "Strength text", "type": "text", "paint": "white", + "dropShadow": true, "font": { "type": "ttf", "path": { @@ -727,6 +750,7 @@ "comment": "Vitality text", "type": "text", "paint": "white", + "dropShadow": true, "font": { "type": "ttf", "path": { @@ -979,12 +1003,13 @@ "shire": "#", "dwarven": "@" }, + "minYStart": 0.15, "box": { "type": "box", - "x": 168, - "y": 663, - "width": 500, - "height": 318 + "x": 160, + "y": 690, + "width": 540, + "height": 265 } } ] diff --git a/gemp-lotr/gemp-lotr-images/src/main/resources/config.properties b/gemp-lotr/gemp-lotr-images/src/main/resources/config.properties index 02c115864..7edc47a9e 100644 --- a/gemp-lotr/gemp-lotr-images/src/main/resources/config.properties +++ b/gemp-lotr/gemp-lotr-images/src/main/resources/config.properties @@ -1,7 +1,7 @@ recipe.file=/Users/marcin.sciesinski/private/gemp-lotr/gemp-lotr/gemp-lotr-images/src/main/resources/cardRecipe.json output.folder=/Users/marcin.sciesinski/private/gemp-lotr/gemp-lotr/gemp-lotr-images/target/images lotro.resources.folder=/Users/marcin.sciesinski/lotro-resources -"child":"fonts/o_wap_matbolsmallcap.ttf" + #title.font=Joe-resources/Matrix-Bold.ttf title.font=fonts/o_wap_matbolsmallcap.ttf title.size=40 @@ -15,11 +15,11 @@ numbers.font=Joe-resources/GaramondBE-Medium.otf numbers.size=60 #text.font=Joe-resources/LOTR.ttf text.font=fonts/o_lotrrg__.ttf -text.size=25 +text.size=27 #keyword.font=Joe-resources/LOTR Bold.ttf keyword.font=fonts/o_lotrb___.ttf -keyword.size=25 +keyword.size=27 #flavor.font=Joe-resources/CentaurMT-SwashCapitals.otf flavor.font=fonts/o_lotr-cmt.ttf -flavor.size=25 +flavor.size=27 glyph.size=22