package magic.cardBuilder.renderers;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.font.FontRenderContext;
import java.awt.font.GraphicAttribute;
import java.awt.font.ImageGraphicAttribute;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import magic.awt.MagicFont;
import magic.cardBuilder.ResourceManager;
import magic.cardBuilder.CardResource;
import magic.model.IRenderableCard;
import magic.model.MagicColor;
import magic.model.MagicType;
import magic.ui.MagicImages;
import magic.ui.helpers.ImageHelper;
public class OracleText {
public static final char NEWLINE = '\n';
private static final int maxDistance = 260100;
private static final double transparencyTolerance = 0.001;
private static final int padding = 1;
private static final Font cardTextFont = MagicFont.MPlantin.get().deriveFont(Font.PLAIN, 18);//scale down when long string
private static final int topPadding = 7;
private static final int leftPadding = 3;
static void drawOracleText(BufferedImage cardImage, IRenderableCard cardDef) {
Rectangle textBoxBounds = getTextBoxSize(cardDef);
if (cardDef.hasText()) {
//29,327 = top left textbox position.
int xPos = 30;
int yPos = getYPosition(cardDef);
drawTextToCard(
cardImage,
xPos,
yPos,
cardDef.getText(),
textBoxBounds
);
} else if (cardDef.hasType(MagicType.Land)) {
//Currently ignoring colorless basic lands with no type - no watermark for colorless yet
Set<MagicColor> landColors = Frame.getLandColors(cardDef);
if (landColors.equals(Frame.getBasicLandColors(cardDef))) {
BufferedImage landImage = null;
if (landColors.size() == 1) {
landImage = getLandImage(landColors.iterator().next());
} else if (landColors.size() == 2) {
landImage = getHybridLandImage(landColors);
}
if (landImage != null) {
Graphics2D g2d = cardImage.createGraphics();
int heightPadding = (int)((textBoxBounds.getHeight() - landImage.getHeight()) / 2);
int widthPadding = (int)((textBoxBounds.getWidth() - landImage.getWidth()) / 2);
g2d.drawImage(landImage, 30 + widthPadding, 327 + heightPadding, null);
g2d.dispose();
}
}
}
}
static void drawPlaneswalkerOracleText(BufferedImage cardImage, IRenderableCard cardDef) {
int lines = getPlaneswalkerAbilityCount(cardDef);
int yPosOffset = cardDef.hasText() && lines <= 3 ? 330 : 289;
String[] abilityActivation = getPlaneswalkerActivationText(cardDef);
Rectangle textBoxBounds = new Rectangle(0, 0, 282, 49);
for (int i = 0; i < lines; i++) {
int xPos = 63;
int yPos = (int)(yPosOffset + i * textBoxBounds.getHeight());
drawTextToCard(
cardImage,
xPos,
yPos,
abilityActivation[i],
textBoxBounds
);
}
}
static void drawLevellerOracleText(BufferedImage cardImage, IRenderableCard cardDef) {
String[] abilities = getLevellerText(cardDef);
Rectangle textBoxBounds = new Rectangle(0, 0, 185, 49);
for (int i = 0; i < 3; i++) {
String oracleText = abilities[i];
if (!oracleText.isEmpty()) { //Not all levels have text
int xPos = i == 0 ? 30 : 104; // xpos 104 for level arrows, normal for first line
int yPos = (int)(330 + i * textBoxBounds.getHeight());
drawTextToCard(
cardImage,
xPos,
yPos,
oracleText,
textBoxBounds
);
}
}
}
private static int getYPosition(IRenderableCard cardDef) {
if (cardDef.isToken()) {
return 388;
}
if (cardDef.isPlaneswalker()) {
return 330;
}
return 327;
}
private static Rectangle getTextBoxSize(IRenderableCard cardDef) {
if (cardDef.isToken()) {
return new Rectangle(0, 0, 314, 94);
}
if (cardDef.isPlaneswalker()) {
return new Rectangle(0, 0, 282, 148);
}
return new Rectangle(0, 0, 314, 154);
}
private static BufferedImage getLandImage(MagicColor color) {
switch (color) {
case Black:
return ResourceManager.getImage(CardResource.blackLandImage);
case Blue:
return ResourceManager.getImage(CardResource.blueLandImage);
case Green:
return ResourceManager.getImage(CardResource.greenLandImage);
case Red:
return ResourceManager.getImage(CardResource.redLandImage);
case White:
return ResourceManager.getImage(CardResource.whiteLandImage);
}
return null;
}
private static BufferedImage getHybridLandImage(Collection<MagicColor> colors) {
if (colors.contains(MagicColor.Black) && colors.contains(MagicColor.Green)) {
return ResourceManager.getImage(CardResource.bgLandImage);
}
if (colors.contains(MagicColor.Black) && colors.contains(MagicColor.Red)) {
return ResourceManager.getImage(CardResource.brLandImage);
}
if (colors.contains(MagicColor.Green) && colors.contains(MagicColor.Blue)) {
return ResourceManager.getImage(CardResource.guLandImage);
}
if (colors.contains(MagicColor.Green) && colors.contains(MagicColor.White)) {
return ResourceManager.getImage(CardResource.gwLandImage);
}
if (colors.contains(MagicColor.Red) && colors.contains(MagicColor.Green)) {
return ResourceManager.getImage(CardResource.rgLandImage);
}
if (colors.contains(MagicColor.Red) && colors.contains(MagicColor.White)) {
return ResourceManager.getImage(CardResource.rwLandImage);
}
if (colors.contains(MagicColor.Blue) && colors.contains(MagicColor.Black)) {
return ResourceManager.getImage(CardResource.ubLandImage);
}
if (colors.contains(MagicColor.Blue) && colors.contains(MagicColor.Red)) {
return ResourceManager.getImage(CardResource.urLandImage);
}
if (colors.contains(MagicColor.White) && colors.contains(MagicColor.Black)) {
return ResourceManager.getImage(CardResource.wbLandImage);
}
if (colors.contains(MagicColor.White) && colors.contains(MagicColor.Blue)) {
return ResourceManager.getImage(CardResource.wuLandImage);
}
return null;
}
public static String[] getOracleAsLines(IRenderableCard cardDef) {
String text = cardDef.getText();
return text.split("\n");
}
static int getPlaneswalkerAbilityCount(IRenderableCard cardDef) {
String[] abilities = getOracleAsLines(cardDef);
return abilities.length;
}
static String[] getPlaneswalkerActivationText(IRenderableCard cardDef) {
String[] abilities = getOracleAsLines(cardDef);
String[] text = new String[abilities.length];
for (int i = 0; i < abilities.length; i++) {
String[] fulltext = abilities[i].split(": ", 2);
text[i] = fulltext[fulltext.length - 1];
}
return text;
}
static String[] getLevellerText(IRenderableCard cardDef) {
//Some levels contain /n as well
String[] abilities = getOracleAsLines(cardDef);
ArrayList<String> text = new ArrayList<>(3);
for (int i = 0; i < abilities.length; i++) {
if (i == 0) {
text.add(abilities[0]);
} else {
if (abilities[i].contains("LEVEL") && abilities[i + 2].contains("LEVEL")) { //Catch empty level text
text.add("");
} else {
if (!abilities[i].contains("LEVEL") && !abilities[i].matches("\\d+/\\d+")) {
if (i + 1 <= abilities.length - 1 && !abilities[i + 1].contains("LEVEL") && !abilities[i + 1].matches("\\d+/\\d+")) { //Catch multi-line level text
text.add(abilities[i] + NEWLINE + abilities[i + 1]);
} else {
text.add(abilities[i]);
}
}
}
}
}
return text.toArray(new String[3]);
}
static void drawTextToCard(
BufferedImage cardImage,
int xPos,
int yPos,
String oracleText,
Rectangle textBoxBounds) {
AttributedString realOracle = textIconReplace(oracleText);
BufferedImage textBoxText = drawTextToBox(
realOracle,
cardTextFont,
textBoxBounds,
leftPadding,
topPadding
);
Graphics2D g2d = cardImage.createGraphics();
BufferedImage trimmedTextBox = trimTransparency(textBoxText);
int heightPadding = (int)((textBoxBounds.getHeight() - trimmedTextBox.getHeight()) / 2);
int widthPadding = (int)Math.min((textBoxBounds.getWidth() - trimmedTextBox.getWidth()) / 2, 3);
g2d.drawImage(trimmedTextBox, xPos + widthPadding, yPos + heightPadding, null);
g2d.dispose();
}
private static SortedMap<Float, TextLayout> tryTextLayout(
AttributedString attrString,
FontRenderContext frc,
Rectangle box,
int leftPadding,
int topPadding
) {
final SortedMap<Float, TextLayout> lines = new TreeMap<>();
AttributedCharacterIterator text = attrString.getIterator();
int paragraphStart = text.getBeginIndex();
int paragraphEnd = text.getEndIndex();
LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(text, frc);
float boxWidth = (float)box.getWidth();
float boxHeight = (float)box.getHeight();
float posY = topPadding;
lineMeasurer.setPosition(paragraphStart);
//Measure length of string to fit in box
final AttributedCharacterIterator iter = attrString.getIterator();
while (lineMeasurer.getPosition() < paragraphEnd) {
//Check for ptPanel overlap
int next = lineMeasurer.nextOffset(posY >= 123 ? boxWidth - (leftPadding << 1) - 100 : boxWidth - (leftPadding << 1));
int limit = next;
//Check for newlines
for (int i = lineMeasurer.getPosition(); i < next; ++i) {
char c = iter.setIndex(i);
if (c == NEWLINE && i > lineMeasurer.getPosition()) {
limit = i;
break;
}
}
//get+draw measured length
TextLayout layout = lineMeasurer.nextLayout(boxWidth, limit, false);
posY += layout.getAscent();
lines.put(posY, layout);
//add extra space between paragraphs
if (limit < next) {
posY += layout.getLeading() + layout.getDescent();
}
//move to next line
posY += layout.getDescent();
//check if out of room
if (posY > boxHeight) {
lines.clear();
break;
}
}
return lines;
}
private static SortedMap<Float, TextLayout> fitTextLayout(
AttributedString attrString,
Font font,
FontRenderContext frc,
Rectangle box,
int leftPadding,
int topPadding
) {
// decrease font by 0.5 points each time until lines can fit
SortedMap<Float, TextLayout> lines = new TreeMap<>();
Font f = font;
while (lines.isEmpty()) {
attrString.addAttribute(TextAttribute.FONT, f);
lines = tryTextLayout(attrString, frc, box, leftPadding, topPadding);
f = f.deriveFont(f.getSize2D() - 0.5f);
}
return lines;
}
private static BufferedImage drawTextToBox(
AttributedString attrString,
Font font,
Rectangle box,
int leftPadding,
int topPadding
) {
//setUp baseImage and Graphics2D
BufferedImage baseImage = ImageHelper.getCompatibleBufferedImage(
(int)box.getWidth(),
(int)box.getHeight(),
Transparency.TRANSLUCENT);
Graphics2D g2d = baseImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setColor(Color.BLACK);
FontRenderContext frc = g2d.getFontRenderContext();
// find largest font that can fit text to box
final SortedMap<Float, TextLayout> lines = fitTextLayout(attrString, font, frc, box, leftPadding, topPadding);
// draw the text
for (final Entry<Float, TextLayout> entry : lines.entrySet()) {
final TextLayout layout = entry.getValue();
final float posY = entry.getKey();
layout.draw(g2d, leftPadding, posY);
}
// cleanup + return
g2d.dispose();
return baseImage;
}
private static BufferedImage trimTransparency(BufferedImage image) {
WritableRaster raster = image.getAlphaRaster();
int width = raster.getWidth();
int height = raster.getHeight();
int left = 0;
int top = 0;
int right = width - 1;
int bottom = height - 1;
int minRight = width - 1;
int minBottom = height - 1;
top:
for (;top < bottom; top++){
for (int x = 0; x < width; x++){
if (raster.getSample(x, top, 0) != 0){
minRight = x;
minBottom = top;
break top;
}
}
}
left:
for (;left < minRight; left++){
for (int y = height - 1; y > top; y--){
if (raster.getSample(left, y, 0) != 0){
minBottom = y;
break left;
}
}
}
bottom:
for (;bottom > minBottom; bottom--){
for (int x = width - 1; x >= left; x--){
if (raster.getSample(x, bottom, 0) != 0){
minRight = x;
break bottom;
}
}
}
right:
for (;right > minRight; right--){
for (int y = bottom; y >= top; y--){
if (raster.getSample(right, y, 0) != 0){
break right;
}
}
}
return image.getSubimage(left, top, right - left + 1, bottom - top + 1);
}
public static AttributedString textIconReplace(final String text) {
final String compacted = text.replaceAll("\\{[^}]+}", "M");
final AttributedString attrString = new AttributedString(compacted);
for (int i = 0, j = 0; i < text.length(); i++, j++) {
char c = text.charAt(i);
if (c == '{') {
final int endSymbol = text.indexOf('}', i);
// get mana-string, substring returns at -1 value
String iconString = text.substring(i, endSymbol + 1);
// get related Icon as Image
Image iconImage = MagicImages.getIcon(iconString).getImage();
// define replacement icon
ImageGraphicAttribute icon = new ImageGraphicAttribute(iconImage, GraphicAttribute.BOTTOM_ALIGNMENT);
// replace M with icon
attrString.addAttribute(
TextAttribute.CHAR_REPLACEMENT,
icon,
j,
j + 1
);
// advance i to the end of symbol
i = endSymbol;
}
}
return attrString;
}
}