Mass Refactoring

This commit is contained in:
Steven Tracey 2023-06-05 16:15:12 -04:00
parent 4bdc25964b
commit 26d4857221
6 changed files with 330 additions and 104 deletions

View File

@ -10,13 +10,16 @@ import javax.imageio.stream.ImageOutputStream;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.*; import java.io.*;
import java.util.HashMap; import java.util.*;
import java.util.Map;
public class Card { public class Card {
public static final Map<String, Card> CARD_SESSIONS = new HashMap<>();
// ---------------- COLORS ---------------- //
private static final Color WHITE = new Color(255, 255, 255); private static final Color WHITE = new Color(255, 255, 255);
private static final Color GREEN = new Color(101, 142, 61); 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 ARIAL65 = new Font("Arial", Font.PLAIN, 65);
private static final Font ARIAL55 = new Font("Arial", Font.PLAIN, 55); 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 ARIAL45 = new Font("Arial", Font.PLAIN, 45);
@ -25,106 +28,216 @@ public class Card {
private static final Font ARIAL40I = new Font("Arial", Font.ITALIC, 40); private static final Font ARIAL40I = new Font("Arial", Font.ITALIC, 40);
private static final Font ARIAL38I = new Font("Arial", Font.ITALIC, 38); private static final Font ARIAL38I = new Font("Arial", Font.ITALIC, 38);
private static final String[] KEYS = {"Name", "Title", "Email", "Location", "Address", "SchoolNumber", "Extension", "DirectNumber", "CellNumber"}; // ---------------- 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 rawImage = new BufferedImage(1080, 602, BufferedImage.TYPE_INT_ARGB);
private BufferedImage workingImage = new BufferedImage(1080, 602, BufferedImage.TYPE_INT_ARGB); private BufferedImage workingImage = new BufferedImage(1080, 602, BufferedImage.TYPE_INT_ARGB);
private IIOImage finalImage; private IIOImage finalImage;
private final String[] data = new String[9]; // ---------------- DATA ---------------- //
private final boolean[] flags = new boolean[3]; // 0 - Long Address, 1 - Has Direct Number, 2 - Has Cell Phone public String id;
private final BufferedImage background; private String name;
private final int size; 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.<br>
* Calls:<br>{@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.<br>
* Calls:<br>{@link tech.nevets.vcardgen.Card#Card(String, String, String, String, String, String, String, int)}
* @param json Json object from frontend
*/
public Card(JsonObject json) { public Card(JsonObject json) {
this.data[0] = json.get("name").getAsString(); //name this(
this.data[1] = json.get("title").getAsString(); //title json.get("name").getAsString(), //name
this.data[2] = json.get("email").getAsString(); //email json.get("title").getAsString(), //title
Location location = Location.getLocation(json.get("locationId").getAsString()); json.get("email").getAsString(), //email
this.data[3] = location.getName(); //locationName json.get("locationId").getAsString(), //locationId
this.data[4] = location.getAddress(); //address json.get("extension").getAsString(), //extension
this.data[5] = location.getNumber(); //schoolNumber json.get("directNumber").getAsString(), //directNumber
background = location.getBackground(); json.get("cellNumber").getAsString(), //cellNumber
this.data[6] = json.get("extension").getAsString(); //extension json.get("size").getAsInt() //size
this.data[7] = json.get("directNumber").getAsString(); //directNumber );
this.data[8] = json.get("cellNumber").getAsString(); //cellNumber
this.flags[1] = this.data[7].length() > 0; //hasDirectNumber
this.flags[2] = this.data[8].length() > 0; //hasCellNumber
size = json.get("size").getAsInt();
renderImage();
resizeImage();
try {
addMetadata();
} catch (IOException e) {
System.out.println("Error adding metadata");
e.printStackTrace();
}
} }
/**
* 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 <b>if</b> {@link tech.nevets.vcardgen.Card#directNumber} is populated)
* @param directNumber User's Direct Number (can be empty <b>if</b> {@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) { public Card(String name, String title, String email, String locationId, String extension, String directNumber, String cellNumber, int size) {
this.data[0] = name; //name this.id = getUniqueId();
this.data[1] = title; //title this.name = name;
this.data[2] = email; //email this.title = title;
this.email = email;
Location location = Location.getLocation(locationId); Location location = Location.getLocation(locationId);
this.data[3] = location.getName(); //locationName
this.data[4] = location.getAddress(); //address
this.data[5] = location.getName(); //schoolNumber
background = location.getBackground(); background = location.getBackground();
this.data[6] = extension; //extension this.extension = extension;
this.data[7] = directNumber; //directNumber this.directNumber = directNumber;
this.data[8] = cellNumber; //cellNumber this.cellNumber = cellNumber;
this.flags[1] = this.data[7].length() > 0; //hasDirectNumber this.hasDirectNumber = this.directNumber.length() > 0;
this.flags[2] = this.data[8].length() > 0; //hasCellNumber this.hasCellNumber = this.cellNumber.length() > 0;
this.size = size; this.size = size;
CARD_SESSIONS.put(id, this);
renderImage();
resizeImage();
try {
addMetadata();
} catch (IOException e) {
System.out.println("Error adding metadata");
e.printStackTrace();
} }
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() { private void renderImage() {
Graphics2D g = workingImage.createGraphics(); graphics = rawImage.createGraphics();
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); setAntiAlias(graphics);
g.drawImage(background, 0, 0, null); graphics.drawImage(background, 0, 0, null);
graphics.drawImage(renderNameLayer(), 85, 112, null);
graphics.drawImage(renderTitleLayer(), 89, 176, null);
g.setColor(WHITE); int doubleNumOffset = hasDirectNumber ? 40 : 0;
if (fitsDimensions(data[0], ARIAL65, 969)) g.setFont(ARIAL65); int longAddrOffset = fitsDimensions(location.getAddress(), ARIAL38I, 700) ? 0 : 40;
else g.setFont(ARIAL55);
g.drawString(data[0], 85, 112);
if (fitsDimensions(data[1], ARIAL45I, 970)) g.setFont(ARIAL45I); graphics.drawImage(renderEmailLayer(), 62, (380 - doubleNumOffset - longAddrOffset), null);
else g.setFont(ARIAL40I);
g.drawString(data[1], 89, 176);
int doubleNumOffset = 0; graphics.setColor(GREEN);
if (flags[2]) { graphics.setFont(ARIAL44I);
doubleNumOffset = 40; graphics.drawString(location.getName(), 59, 447 - doubleNumOffset - longAddrOffset);
}
int longAddrOffset = fitsDimensions(data[4], ARIAL38I, 694) ? 0 : 40; if (!fitsDimensions(location.getAddress(), ARIAL38I, 694)) {
g.setFont(ARIAL45);
g.drawString(data[2], 62, 380 - doubleNumOffset - longAddrOffset);
g.setColor(GREEN);
g.setFont(ARIAL44I);
g.drawString(data[3], 59, 447 - doubleNumOffset - longAddrOffset);
if (!fitsDimensions(data[4], ARIAL38I, 694)) {
StringBuilder addrLineOne = new StringBuilder(); StringBuilder addrLineOne = new StringBuilder();
StringBuilder addrLineTwo = new StringBuilder(); StringBuilder addrLineTwo = new StringBuilder();
String[] splitAddr = data[4].split(","); String[] splitAddr = location.getAddress().split(",");
int i = 0; int i = 0;
for (int width = 0; width < 694; i++) { for (int width = 0; width < 694; i++) {
int splitSize = g.getFontMetrics().stringWidth(splitAddr[i]); int splitSize = graphics.getFontMetrics().stringWidth(splitAddr[i]);
if ((width + splitSize) < 694) { if ((width + splitSize) < 694) {
width += splitSize; width += splitSize;
addrLineOne.append(splitAddr[i]); addrLineOne.append(splitAddr[i]);
@ -141,21 +254,21 @@ public class Card {
} }
addrLineTwo.deleteCharAt(addrLineTwo.length() - 1); addrLineTwo.deleteCharAt(addrLineTwo.length() - 1);
g.setFont(ARIAL38I); graphics.setFont(ARIAL38I);
g.drawString(addrLineOne.toString().trim(), 59, 491 - doubleNumOffset - longAddrOffset); graphics.drawString(addrLineOne.toString().trim(), 59, 491 - doubleNumOffset - longAddrOffset);
g.drawString(addrLineTwo.toString().trim(), 59, 491 - doubleNumOffset); graphics.drawString(addrLineTwo.toString().trim(), 59, 491 - doubleNumOffset);
} else { } else {
g.drawString(data[4], 59, 491 - doubleNumOffset); graphics.drawString(location.getAddress(), 59, 491 - doubleNumOffset);
} }
String number; String number;
int numY = flags[2] ? 496 : 540; int numY = hasDirectNumber ? 496 : 540;
if (!flags[1]) number = "W: " + data[5] + " x" + data[6]; if (!hasDirectNumber) number = "W: " + location.getNumber() + " x" + extension;
else number = "W: " + data[7]; else number = "W: " + directNumber;
g.drawString(number, 59, numY); graphics.drawString(number, 59, numY);
if (flags[2]) g.drawString("C: " + data[8], 59, 540); if (hasCellNumber) graphics.drawString("C: " + cellNumber, 59, 540);
g.dispose(); graphics.dispose();
rawImage = workingImage; rawImage = workingImage;
} }
@ -192,12 +305,12 @@ public class Card {
finalImage = reader.readAll(0, null); finalImage = reader.readAll(0, null);
IIOMetadataNode text = new IIOMetadataNode("tEXt"); IIOMetadataNode text = new IIOMetadataNode("tEXt");
for (int i = 0; i < KEYS.length; i++) { // for (int i = 0; i < KEYS.length; i++) {
IIOMetadataNode textEntry = new IIOMetadataNode("tEXtEntry"); // IIOMetadataNode textEntry = new IIOMetadataNode("tEXtEntry");
textEntry.setAttribute("keyword", KEYS[i]); // textEntry.setAttribute("keyword", KEYS[i]);
textEntry.setAttribute("value", data[i]); // textEntry.setAttribute("value", data[i]);
text.appendChild(textEntry); // text.appendChild(textEntry);
} // }
IIOMetadataNode root = new IIOMetadataNode("javax_imageio_png_1.0"); IIOMetadataNode root = new IIOMetadataNode("javax_imageio_png_1.0");
root.appendChild(text); root.appendChild(text);
@ -213,10 +326,17 @@ public class Card {
return baos.toByteArray(); 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 final Canvas C = new Canvas();
private static boolean fitsDimensions(String text, Font font, int maxLength) { private static boolean fitsDimensions(String text, Font font, int maxLength) {
return C.getFontMetrics(font).stringWidth(text) < 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 { public static String getDataFromVCard(InputStream rawImage) throws IOException {
ImageReader reader = ImageIO.getImageReadersByMIMEType("image/png").next(); ImageReader reader = ImageIO.getImageReadersByMIMEType("image/png").next();
@ -246,4 +366,15 @@ public class Card {
return new Gson().toJson(keyValueMap); 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];
}
}
} }

View File

@ -7,9 +7,10 @@ import spark.Request;
import spark.Response; import spark.Response;
import spark.Route; import spark.Route;
import java.io.IOException;
public class GenerateRoute implements Route { public class GenerateRoute implements Route {
private Card card; private Card card;
@Override @Override
public Object handle(Request req, Response res) { public Object handle(Request req, Response res) {
JsonObject data; JsonObject data;
@ -17,11 +18,18 @@ public class GenerateRoute implements Route {
data = new Gson().fromJson(req.body(), JsonObject.class); data = new Gson().fromJson(req.body(), JsonObject.class);
} catch (JsonSyntaxException e) { } catch (JsonSyntaxException e) {
res.status(422); res.status(422);
return "Not valid json: " + e.getMessage();
} }
res.type("image/png"); res.type("image/png");
res.header("Access-Control-Expose-Headers", "Id"); res.header("Access-Control-Expose-Headers", "Id");
card = new Card(data); card = new Card(data);
try {
res.status(200);
return new Card(data).toByteArray(); return new Card(data).toByteArray();
} catch (IOException e) {
res.status(500);
return "Error getting image stream: " + e.getMessage();
}
} }
} }

View File

@ -0,0 +1,85 @@
package tech.nevets.vcardgen;
import org.eclipse.jetty.websocket.api.*;
import org.eclipse.jetty.websocket.api.annotations.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@WebSocket
public class LiveGenWebSocket {
private Card card;
@OnWebSocketConnect
public void connected(Session session) {}
@OnWebSocketClose
public void closed(Session session, int statusCode, String reason) {}
@OnWebSocketMessage
public void message(Session session, String message) throws IOException {
String[] splitMsg = message.split(";");
String messageType = splitMsg[0];
String messageContent = splitMsg[1];
RemoteEndpoint client = session.getRemote();
switch (messageType) {
case "start" -> {
card = new Card();
client.sendString("success;Card successfully created.");
}
case "continue" -> {
card = Card.CARD_SESSIONS.get(messageContent);
if (card == null) {
card = new Card();
client.sendString("notExist;Card with that id does not exist, a new card has been created.");
} else {
client.sendString("success;Card loaded successfully.");
}
}
case "location" -> {
card.setLocation(Location.getLocation(messageContent));
client.sendString("background");
client.sendBytes(byteBufferFromImage(card.getBackground()));
client.sendString("location");
client.sendBytes(byteBufferFromImage(card.renderLocationLayer()));
}
case "name" -> {
}
case "title" -> {
}
case "email" -> {
}
case "extension" -> {
}
case "directNumber" -> {
}
case "cellNumber" -> {
}
default -> session.getRemote().sendString("Unknown Message Type: " + messageType);
}
}
private ByteBuffer byteBufferFromImage(BufferedImage image) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ImageIO.write(image, "png", baos);
} catch (IOException e) {
e.printStackTrace();
return ByteBuffer.wrap(("Error writing image: " + e.getMessage()).getBytes(StandardCharsets.UTF_8));
}
return ByteBuffer.wrap(baos.toByteArray());
}
}

View File

@ -55,6 +55,11 @@ public class Location {
background = ImageIO.read(backgroundURL); background = ImageIO.read(backgroundURL);
} }
@Override
public String toString() {
return "{ \"id\": " + id + ", \"name\": " + name + ", \"address\": " + address + ", \"number\": " + number + " }";
}
public static void loadLocations () { public static void loadLocations () {
try { try {
Gson gson = new Gson(); Gson gson = new Gson();
@ -87,9 +92,5 @@ public class Location {
return null; return null;
} }
@Override
public String toString() {
return "{ \"id\": " + id + ", \"name\": " + name + ", \"address\": " + address + ", \"number\": " + number + " }";
}
} }

View File

@ -1,8 +1,5 @@
package tech.nevets.vcardgen; package tech.nevets.vcardgen;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.*; import java.io.*;
import static spark.Spark.*; import static spark.Spark.*;
@ -12,6 +9,9 @@ public class Main {
Location.loadLocations(); Location.loadLocations();
port(8080); port(8080);
webSocket("/backend/generate/live", LiveGenWebSocket.class);
path("/backend", () -> { path("/backend", () -> {
before("/*", (request, response) -> response.header("Access-Control-Allow-Origin", "*")); before("/*", (request, response) -> response.header("Access-Control-Allow-Origin", "*"));
@ -25,7 +25,6 @@ public class Main {
return "OK"; return "OK";
}); });
get("/heartbeat", (req, res) -> { get("/heartbeat", (req, res) -> {
res.status(200); res.status(200);
res.type("application/json"); res.type("application/json");
@ -38,6 +37,7 @@ public class Main {
return new FileInputStream("locations.json"); return new FileInputStream("locations.json");
}); });
post("/generate", new GenerateRoute()); post("/generate", new GenerateRoute());
post("/edit", new EditRoute()); post("/edit", new EditRoute());

View File

@ -2,5 +2,6 @@ package tech.nevets.vcardgen;
public class TestMain { public class TestMain {
public static void main(String[] args) { public static void main(String[] args) {
} }
} }