package tech.nevets.vcardgen; import com.google.gson.Gson; import com.google.gson.JsonObject; import javax.imageio.*; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.stream.ImageOutputStream; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import java.util.*; public class Card { public static final Map CARD_SESSIONS = new HashMap<>(); // ---------------- COLORS ---------------- // private static final Color WHITE = new Color(255, 255, 255); private static final Color GREEN = new Color(101, 142, 61); // ---------------- FONTS ---------------- // private static final Font ARIAL65 = new Font("Arial", Font.PLAIN, 65); private static final Font ARIAL55 = new Font("Arial", Font.PLAIN, 55); private static final Font ARIAL45 = new Font("Arial", Font.PLAIN, 45); private static final Font ARIAL45I = new Font("Arial", Font.ITALIC, 45); private static final Font ARIAL44I = new Font("Arial", Font.ITALIC, 44); private static final Font ARIAL40I = new Font("Arial", Font.ITALIC, 40); private static final Font ARIAL38I = new Font("Arial", Font.ITALIC, 38); // ---------------- LAYERS ---------------- // private BufferedImage background; private BufferedImage nameLayer = new BufferedImage(975, 90, BufferedImage.TYPE_INT_ARGB); private BufferedImage titleLayer = new BufferedImage(975, 55, BufferedImage.TYPE_INT_ARGB); private BufferedImage emailLayer = new BufferedImage(1000, 55, BufferedImage.TYPE_INT_ARGB); private BufferedImage locationLayer = new BufferedImage(700, 134, BufferedImage.TYPE_INT_ARGB); private BufferedImage phoneNumbersLayer = new BufferedImage(700, 90, BufferedImage.TYPE_INT_ARGB); // ---------------- Working Images ---------------- // private Graphics2D graphics; private BufferedImage rawImage = new BufferedImage(1080, 602, BufferedImage.TYPE_INT_ARGB); private BufferedImage workingImage = new BufferedImage(1080, 602, BufferedImage.TYPE_INT_ARGB); private IIOImage finalImage; // ---------------- DATA ---------------- // public String id; private String name; private String title; private String email; private Location location; private String extension; private String directNumber; private String cellNumber; private int size; private boolean hasLongAddress; private boolean hasDirectNumber; private boolean hasCellNumber; /** * Constructs an empty card.
* Calls:
{@link tech.nevets.vcardgen.Card#Card(String, String, String, String, String, String, String, int)} with empty strings and full size image */ public Card() { this("", "", "", "", "", "", "", 1); } /** * Constructor that fills out entire Card details.
* Calls:
{@link tech.nevets.vcardgen.Card#Card(String, String, String, String, String, String, String, int)} * @param json Json object from frontend */ public Card(JsonObject json) { this( json.get("name").getAsString(), //name json.get("title").getAsString(), //title json.get("email").getAsString(), //email json.get("locationId").getAsString(), //locationId json.get("extension").getAsString(), //extension json.get("directNumber").getAsString(), //directNumber json.get("cellNumber").getAsString(), //cellNumber json.get("size").getAsInt() //size ); } /** * Constructor that fills out entire Card details. * @param name User's Name * @param title User's Title * @param email User's Email * @param locationId User's Location ID * @param extension User's Extension (can be empty if {@link tech.nevets.vcardgen.Card#directNumber} is populated) * @param directNumber User's Direct Number (can be empty if {@link tech.nevets.vcardgen.Card#extension} is populated) * @param cellNumber User's Cell Phone Number (can be empty) * @param size Size to set the Card to (0 for Outlook 2016 size, 1 for full size) */ public Card(String name, String title, String email, String locationId, String extension, String directNumber, String cellNumber, int size) { this.id = getUniqueId(); this.name = name; this.title = title; this.email = email; Location location = Location.getLocation(locationId); background = location.getBackground(); this.extension = extension; this.directNumber = directNumber; this.cellNumber = cellNumber; this.hasDirectNumber = this.directNumber.length() > 0; this.hasCellNumber = this.cellNumber.length() > 0; this.size = size; CARD_SESSIONS.put(id, this); } public void setName(String name) { this.name = name; } public void setTitle(String title) { this.title = title; } public void setEmail(String email) { this.email = email; } public void setLocation(Location location) { this.location = location; } public void setExtension(String extension) { this.extension = extension; hasDirectNumber = false; this.directNumber = ""; } public void setDirectNumber(String directNumber) { this.directNumber = directNumber; hasDirectNumber = directNumber.length() > 0; this.extension = ""; } public void setCellNumber(String cellNumber) { this.cellNumber = cellNumber; hasCellNumber = cellNumber.length() > 0; } public BufferedImage getBackground() { return background; } private BufferedImage renderLayer(BufferedImage layer, String content, Color textColor, Font font) { return renderLayer(layer, content, textColor, font, 0, 0); } private BufferedImage renderLayer(BufferedImage layer, String content, Color textColor, Font font, int x, int y) { graphics = layer.createGraphics(); setAntiAlias(graphics); graphics.setColor(textColor); graphics.drawString(content, x, y); graphics.dispose(); return layer; } private BufferedImage renderResizableLayer(BufferedImage layer, String content, Color textColor, int maxLength, Font defaultFont, Font smallFont) { graphics = layer.createGraphics(); setAntiAlias(graphics); graphics.setColor(textColor); if (fitsDimensions(content, defaultFont, maxLength)) graphics.setFont(defaultFont); else graphics.setFont(smallFont); graphics.drawString(content, 0, 0); graphics.dispose(); return layer; } public BufferedImage renderNameLayer() { return renderResizableLayer(nameLayer, name, WHITE, 970, ARIAL65, ARIAL55); } public BufferedImage renderTitleLayer() { return renderResizableLayer(titleLayer, title, WHITE, 970, ARIAL45I, ARIAL40I); } public BufferedImage renderEmailLayer() { return renderLayer(emailLayer, email, WHITE, ARIAL45); } public BufferedImage renderLocationLayer() { StringBuilder sb = new StringBuilder(); String address = location.getAddress(); String[] splitAddr = address.split(","); if (fitsDimensions(address, ARIAL38I, 700)) { sb.append(address); } else { int lineOneLength = 0; int newLineIndex = 0; for (String addrPart : splitAddr) { if (fitsDimensions(addrPart, ARIAL38I, (700 - lineOneLength))) { sb.append(addrPart).append(","); lineOneLength += stringWidth(addrPart, ARIAL38I); newLineIndex += addrPart.length(); } else { sb.append(addrPart).append(","); } } sb.delete(newLineIndex - 2, newLineIndex); sb.insert(newLineIndex - 2, "\n"); sb.deleteCharAt(sb.length() - 1); } renderLayer(locationLayer, location.getName(), GREEN, ARIAL44I); return renderLayer(locationLayer, sb.toString(), GREEN, ARIAL38I, 0, 44); } public BufferedImage renderPhoneNumbersLayer() { return null; } private void renderImage() { graphics = rawImage.createGraphics(); setAntiAlias(graphics); graphics.drawImage(background, 0, 0, null); graphics.drawImage(renderNameLayer(), 85, 112, null); graphics.drawImage(renderTitleLayer(), 89, 176, null); int doubleNumOffset = hasDirectNumber ? 40 : 0; int longAddrOffset = fitsDimensions(location.getAddress(), ARIAL38I, 700) ? 0 : 40; graphics.drawImage(renderEmailLayer(), 62, (380 - doubleNumOffset - longAddrOffset), null); graphics.setColor(GREEN); graphics.setFont(ARIAL44I); graphics.drawString(location.getName(), 59, 447 - doubleNumOffset - longAddrOffset); if (!fitsDimensions(location.getAddress(), ARIAL38I, 694)) { StringBuilder addrLineOne = new StringBuilder(); StringBuilder addrLineTwo = new StringBuilder(); String[] splitAddr = location.getAddress().split(","); int i = 0; for (int width = 0; width < 694; i++) { int splitSize = graphics.getFontMetrics().stringWidth(splitAddr[i]); if ((width + splitSize) < 694) { width += splitSize; addrLineOne.append(splitAddr[i]); addrLineOne.append(","); } else { addrLineOne.deleteCharAt(addrLineOne.length() - 1); break; } } for (; i < splitAddr.length; i++) { addrLineTwo.append(splitAddr[i]); addrLineTwo.append(","); } addrLineTwo.deleteCharAt(addrLineTwo.length() - 1); graphics.setFont(ARIAL38I); graphics.drawString(addrLineOne.toString().trim(), 59, 491 - doubleNumOffset - longAddrOffset); graphics.drawString(addrLineTwo.toString().trim(), 59, 491 - doubleNumOffset); } else { graphics.drawString(location.getAddress(), 59, 491 - doubleNumOffset); } String number; int numY = hasDirectNumber ? 496 : 540; if (!hasDirectNumber) number = "W: " + location.getNumber() + " x" + extension; else number = "W: " + directNumber; graphics.drawString(number, 59, numY); if (hasCellNumber) graphics.drawString("C: " + cellNumber, 59, 540); graphics.dispose(); rawImage = workingImage; } public void resizeImage() { resizeImage(this.size); } public void resizeImage(int size) { BufferedImage resizedImage; switch (size) { case 0 -> resizedImage = resizeImage(300, 167, false); case 1 -> resizedImage = rawImage; default -> resizedImage = background; } workingImage = resizedImage; } public BufferedImage resizeImage(int width, int height, boolean antiAlias) { BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = resizedImage.createGraphics(); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); if (antiAlias) g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.drawImage(rawImage, 0, 0, width, height, null); g.dispose(); return resizedImage; } public void addMetadata() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(workingImage, "png", baos); ImageReader reader = ImageIO.getImageReadersByMIMEType("image/png").next(); reader.setInput(ImageIO.createImageInputStream(new ByteArrayInputStream(baos.toByteArray()))); finalImage = reader.readAll(0, null); IIOMetadataNode text = new IIOMetadataNode("tEXt"); // for (int i = 0; i < KEYS.length; i++) { // IIOMetadataNode textEntry = new IIOMetadataNode("tEXtEntry"); // textEntry.setAttribute("keyword", KEYS[i]); // textEntry.setAttribute("value", data[i]); // text.appendChild(textEntry); // } IIOMetadataNode root = new IIOMetadataNode("javax_imageio_png_1.0"); root.appendChild(text); finalImage.getMetadata().mergeTree("javax_imageio_png_1.0", root); } public byte[] toByteArray() throws IOException { ImageWriter writer = ImageIO.getImageWritersByMIMEType("image/png").next(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageOutputStream ios = ImageIO.createImageOutputStream(baos); writer.setOutput(ios); writer.write(finalImage); return baos.toByteArray(); } private void setAntiAlias(Graphics2D g) { g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } private static final Canvas C = new Canvas(); private static boolean fitsDimensions(String text, Font font, int maxLength) { return C.getFontMetrics(font).stringWidth(text) < maxLength; } private static int stringWidth(String text, Font font) { return C.getFontMetrics(font).stringWidth(text); } public static String getDataFromVCard(InputStream rawImage) throws IOException { ImageReader reader = ImageIO.getImageReadersByMIMEType("image/png").next(); reader.setInput(ImageIO.createImageInputStream(rawImage)); IIOMetadata metadata = reader.getImageMetadata(0); IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree("javax_imageio_png_1.0"); IIOMetadataNode text = (IIOMetadataNode) root.getElementsByTagName("tEXt").item(0); if (text == null) { return "$null"; } int numTextEntries = text.getLength(); Map keyValueMap = new HashMap<>(); for (int i = 0; i < numTextEntries; i++) { IIOMetadataNode textEntry = (IIOMetadataNode) text.item(i); String key = textEntry.getAttribute("keyword"); String value = textEntry.getAttribute("value"); if (key.equals("Address") || key.equals("SchoolNumber")) { continue; } if (key.equals("Location")) { value = Location.getLocationId(value); } keyValueMap.put(key, value); } return new Gson().toJson(keyValueMap); } private static String getUniqueId() { String uuid = UUID.randomUUID().toString().split("-")[4]; while (true) { String finalUuid = uuid; if (CARD_SESSIONS.entrySet().stream().noneMatch(card -> card.getKey().equals(finalUuid))) { return uuid; } uuid = UUID.randomUUID().toString().split("-")[4]; } } }