Fixed multiple issues with the layout

This commit is contained in:
marcin.sciesinski
2019-10-12 08:14:25 -07:00
parent 78c5e71d11
commit 7c7edbfb4c
6 changed files with 151 additions and 64 deletions

View File

@@ -81,9 +81,10 @@ public class JSONImageRecipe implements ImageRecipe {
Function<RenderContext, Paint> paint = createPaintProvider(jsonLayer.get("paint"), "black");
final Function<RenderContext, String> text = createStringProvider(jsonLayer.get("text"));
Function<RenderContext, TextBox> textBox = createTextBoxProvider(jsonLayer.get("box"));
Function<RenderContext, Boolean> 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<RenderContext, String[]> text = createStringArrayProvider(jsonLayer.get("text"));
JSONObject object = (JSONObject) jsonLayer.get("font");
@@ -95,11 +96,12 @@ public class JSONImageRecipe implements ImageRecipe {
Function<String, Function<RenderContext, Font>> fontStyleProvider = map::get;
Function<RenderContext, TextBox> textBox = createTextBoxProvider(jsonLayer.get("box"));
final Function<RenderContext, Float> 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<RenderContext, Integer> 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<Function<RenderContext, String>> list = (List<Function<RenderContext, String>>) values.stream().map(text -> createStringProvider(text)).collect(toList());
return renderContext -> {
StringBuilder sb = new StringBuilder();
for (Function<RenderContext, String> 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<Function<RenderContext, String[]>> providers = (List<Function<RenderContext, String[]>>) 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<RenderContext, Boolean> createBooleanProvider(Object condition) {
if (condition instanceof JSONObject) {
return createBooleanProvider(condition, null);
}
private Function<RenderContext, Boolean> 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")) {

View File

@@ -9,5 +9,9 @@ public interface TextBox {
int getHeight();
String getHorizontalAlignment();
HorizontalAlignment getHorizontalAlignment();
interface HorizontalAlignment {
float getXShift(float availableWidth, float usedWidth);
}
}

View File

@@ -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<String, String> glyphProvider;
private Function<RenderContext, String[]> text;
private Function<RenderContext, TextBox> textBox;
private Function<RenderContext, Float> minYStart;
public TextBoxLayerRecipe(
Function<String, Function<RenderContext, Font>> fontStyleProvider,
Function<String, String> glyphProvider,
Function<RenderContext, String[]> text,
Function<RenderContext, TextBox> textBox) {
Function<RenderContext, TextBox> textBox,
Function<RenderContext, Float> 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();
}

View File

@@ -8,12 +8,16 @@ public class TextLayerRecipe implements LayerRecipe {
private Function<RenderContext, String> textProvider;
private Function<RenderContext, Paint> paintProvider;
private Function<RenderContext, TextBox> textBoxProvider;
private Function<RenderContext, Boolean> dropShadow;
public TextLayerRecipe(Function<RenderContext, Font> fontProvider, Function<RenderContext, String> textProvider, Function<RenderContext, Paint> paintProvider, Function<RenderContext, TextBox> textBoxProvider) {
public TextLayerRecipe(Function<RenderContext, Font> fontProvider, Function<RenderContext, String> textProvider,
Function<RenderContext, Paint> paintProvider, Function<RenderContext, TextBox> textBoxProvider,
Function<RenderContext, Boolean> 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());
}
}

View File

@@ -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
}
}
]

View File

@@ -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