summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--DejaVuSansCondensed.ttfbin0 -> 680264 bytes
-rw-r--r--LICENSE210
-rw-r--r--README.md15
-rw-r--r--b50
-rw-r--r--src/CSVReader.java35
-rw-r--r--src/ComparisonView.java107
-rw-r--r--src/FontFamily.java93
-rw-r--r--src/JSONReader.java96
-rw-r--r--src/LineGraph.java197
-rw-r--r--src/Reader.java2
-rw-r--r--week5.pngbin0 -> 10253 bytes
-rw-r--r--week6.pngbin0 -> 59354 bytes
12 files changed, 717 insertions, 88 deletions
diff --git a/DejaVuSansCondensed.ttf b/DejaVuSansCondensed.ttf
new file mode 100644
index 0000000..3259bc2
--- /dev/null
+++ b/DejaVuSansCondensed.ttf
Binary files differ
diff --git a/LICENSE b/LICENSE
index 7ae8865..12d7354 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,10 +1,200 @@
-Permission to use, copy, modify, and/or distribute this software for
-any purpose with or without fee is hereby granted.
-
-THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
-WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
-OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
-FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
-DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
-AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+The original software in this directory falls under the following license:
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+DejaVuSansCondensed.ttf falls under the following licenses:
+
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+Bitstream Vera Fonts Copyright
+------------------------------
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+documentation files (the "Font Software"), to reproduce and distribute the
+Font Software, including without limitation the rights to use, copy, merge,
+publish, distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright and trademark notices and this permission notice shall
+be included in all copies of one or more of the Font Software typefaces.
+
+The Font Software may be modified, altered, or added to, and in particular
+the designs of glyphs or characters in the Fonts may be modified and
+additional glyphs or characters may be added to the Fonts, only if the fonts
+are renamed to names not containing either the words "Bitstream" or the word
+"Vera".
+
+This License becomes null and void to the extent applicable to Fonts or Font
+Software that has been modified and is distributed under the "Bitstream
+Vera" names.
+
+The Font Software may be sold as part of a larger software package but no
+copy of one or more of the Font Software typefaces may be sold by itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
+FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
+ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
+FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font Software
+without prior written authorization from the Gnome Foundation or Bitstream
+Inc., respectively. For further information, contact: fonts at gnome dot
+org.
+
+Arev Fonts Copyright
+------------------------------
+
+Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and
+associated documentation files (the "Font Software"), to reproduce
+and distribute the modifications to the Bitstream Vera Font Software,
+including without limitation the rights to use, copy, merge, publish,
+distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be included in all copies of one or more of the Font Software
+typefaces.
+
+The Font Software may be modified, altered, or added to, and in
+particular the designs of glyphs or characters in the Fonts may be
+modified and additional glyphs or characters may be added to the
+Fonts, only if the fonts are renamed to names not containing either
+the words "Tavmjong Bah" or the word "Arev".
+
+This License becomes null and void to the extent applicable to Fonts
+or Font Software that has been modified and is distributed under the
+"Tavmjong Bah Arev" names.
+
+The Font Software may be sold as part of a larger software package but
+no copy of one or more of the Font Software typefaces may be sold by
+itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
+TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the name of Tavmjong Bah shall not
+be used in advertising or otherwise to promote the sale, use or other
+dealings in this Font Software without prior written authorization
+from Tavmjong Bah. For further information, contact: tavmjong @ free
+. fr.
+
+TeX Gyre DJV Math
+-----------------
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+
+Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski
+(on behalf of TeX users groups) are in public domain.
+
+Letters imported from Euler Fraktur from AMSfonts are (c) American
+Mathematical Society (see below).
+Bitstream Vera Fonts Copyright
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera
+is a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+documentation
+files (the "Font Software"), to reproduce and distribute the Font Software,
+including without limitation the rights to use, copy, merge, publish,
+distribute,
+and/or sell copies of the Font Software, and to permit persons to whom
+the Font Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be
+included in all copies of one or more of the Font Software typefaces.
+
+The Font Software may be modified, altered, or added to, and in particular
+the designs of glyphs or characters in the Fonts may be modified and
+additional
+glyphs or characters may be added to the Fonts, only if the fonts are
+renamed
+to names not containing either the words "Bitstream" or the word "Vera".
+
+This License becomes null and void to the extent applicable to Fonts or
+Font Software
+that has been modified and is distributed under the "Bitstream Vera"
+names.
+
+The Font Software may be sold as part of a larger software package but
+no copy
+of one or more of the Font Software typefaces may be sold by itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
+FOUNDATION
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL,
+SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN
+ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
+INABILITY TO USE
+THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
+Except as contained in this notice, the names of GNOME, the GNOME
+Foundation,
+and Bitstream Inc., shall not be used in advertising or otherwise to promote
+the sale, use or other dealings in this Font Software without prior written
+authorization from the GNOME Foundation or Bitstream Inc., respectively.
+For further information, contact: fonts at gnome dot org.
+
+AMSFonts (v. 2.2) copyright
+
+The PostScript Type 1 implementation of the AMSFonts produced by and
+previously distributed by Blue Sky Research and Y&Y, Inc. are now freely
+available for general use. This has been accomplished through the
+cooperation
+of a consortium of scientific publishers with Blue Sky Research and Y&Y.
+Members of this consortium include:
+
+Elsevier Science IBM Corporation Society for Industrial and Applied
+Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS)
+
+In order to assure the authenticity of these fonts, copyright will be
+held by
+the American Mathematical Society. This is not meant to restrict in any way
+the legitimate use of the fonts, such as (but not limited to) electronic
+distribution of documents containing these fonts, inclusion of these fonts
+into other public domain or commercial font collections or computer
+applications, use of the outline data to create derivative fonts and/or
+faces, etc. However, the AMS does require that the AMS copyright notice be
+removed from any derivative versions of the fonts which have been altered in
+any way. In addition, to ensure the fidelity of TeX documents using Computer
+Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces,
+has requested that any alterations which yield different font metrics be
+given a different name.
diff --git a/README.md b/README.md
index 0087f37..96644cc 100644
--- a/README.md
+++ b/README.md
@@ -162,3 +162,18 @@ sets of data describing the Unicode subsets implemented by a font. Along with JS
this fulfulls most of the same purpose. No disk space calculation, unfortunately.
There are 9 days left.
![week4](week4.png)
+
+## Weeks 5 and 6: Updates
+JSON parsing as it is needed is wholly implemented.
+Each FontFamily is populated with all fields.
+The popularity metrics are implemented as a HashMap.
+The LineGraph has been implemented as a JPanel with a custom paintCompontent method.
+Included with it is a calculation of the top 10 most popular typefaces.
+This isn't quite done yet, because also being able to view graphs of the top typeface styles
+would be preferrable. Further, there is not yet any option to expose the metadata of each font.
+Also, graphing by different time metrics would be preferable.
+The project is at the stage where there are pretty colors, however the utility is not yet fulfilled.
+The project current fulfills all LOs except LO6. I'll be working on that next.
+I have until the 25th. It's the 20th. 5 days left.
+![week5](week5.png)
+![week5](week5.png)
diff --git a/b b/b
new file mode 100644
index 0000000..51cd503
--- /dev/null
+++ b/b
@@ -0,0 +1,50 @@
+Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because the return value of "java.util.HashMap.get(Object)" is null
+ at LineGraph$1.compare(LineGraph.java:60)
+ at LineGraph$1.compare(LineGraph.java:58)
+ at java.base/java.util.TimSort.binarySort(TimSort.java:296)
+ at java.base/java.util.TimSort.sort(TimSort.java:239)
+ at java.base/java.util.Arrays.sort(Arrays.java:1308)
+ at java.base/java.util.ArrayList.sort(ArrayList.java:1804)
+ at java.base/java.util.Collections.sort(Collections.java:178)
+ at LineGraph.sortYearly(LineGraph.java:58)
+ at LineGraph.monthly(LineGraph.java:76)
+ at LineGraph.paintComponent(LineGraph.java:54)
+ at java.desktop/javax.swing.JComponent.paint(JComponent.java:1128)
+ at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:961)
+ at java.desktop/javax.swing.JComponent.paint(JComponent.java:1137)
+ at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:961)
+ at java.desktop/javax.swing.JComponent.paint(JComponent.java:1137)
+ at java.desktop/javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
+ at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:961)
+ at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5325)
+ at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedImpl(RepaintManager.java:1656)
+ at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1631)
+ at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1569)
+ at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1336)
+ at java.desktop/javax.swing.JComponent.paint(JComponent.java:1114)
+ at java.desktop/java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:39)
+ at java.desktop/sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:75)
+ at java.desktop/sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:112)
+ at java.desktop/java.awt.Container.paint(Container.java:2005)
+ at java.desktop/java.awt.Window.paint(Window.java:3959)
+ at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:889)
+ at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:861)
+ at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
+ at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
+ at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:861)
+ at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:834)
+ at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:784)
+ at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1897)
+ at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
+ at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
+ at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
+ at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
+ at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
+ at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
+ at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
+ at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
+ at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
+ at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
+ at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
+ at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
+ at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
diff --git a/src/CSVReader.java b/src/CSVReader.java
index fa588ef..3666531 100644
--- a/src/CSVReader.java
+++ b/src/CSVReader.java
@@ -17,14 +17,15 @@ import java.util.Arrays;
*/
public class CSVReader extends Reader {
- String[] header; // A CSV file has-a header. One line, multiple fields.
- /* Array of Hash map from header to value for each
- line past the header. */
- ArrayList<HashMap<String,String>> body; // A CSV file has-a body. Multiple lines, multiple fields.
+ private String[] header; // A CSV file has-a header. One line, multiple fields.
+ // Array of HashMaps. Headers are keys, values are on each line of the CSV following the header.
+ private ArrayList<HashMap<String,String>> body; // A CSV file has-a body. Multiple lines, multiple fields.
public CSVReader(File file) {
super(file); // Call parent constructor
+ /* Construct the body to be filled. */
body = new ArrayList<HashMap<String,String>>();
+ /* Parse the contents of the file, store in body. */
parse();
}
@@ -32,26 +33,46 @@ public class CSVReader extends Reader {
* Parse CSV file.
*/
public void parse() {
+ /* Split the input on newlines. */
String[] lines = this.getContents().split("\n");
+ /* Split the first line by commas, assign to header array. */
this.header = lines[0].split(",");
- /* Iterate over all lines of the body. */
+ /* Iterate over all lines of the body.
+ First line is header, count from 1 instead of 0. */
for (int i = 1; i < lines.length; i++) {
+ /* Some lines have commas within parentheses.
+ Angkor,"/South East Asian (Thai, Khmer, Lao)/Looped",100
+ Remove everything within parentheses. We need to escape
+ not on a language level, but on a function call level.
+ Double-backslash for a literal backslash, which is then
+ used to escape the parentheses in the regex library. */
+ lines[i] = lines[i].replaceAll("\\(.*\\)", "");
+
+ /* CSV, Comma-Separated Values. */
String[] fields = lines[i].split(",");
+ /* Allocate a new HashMap for the given line of the CSV. */
HashMap<String,String> map = new HashMap<String,String>();
- /* For each of the columns, add the associated
- value in this row. */
+
+ /* Associate each of the line's fields with its header. */
for (int j = 0; j < header.length; j++) {
map.put(header[j], fields[j]);
}
+ /* Add the HashMap to the list. */
body.add(map);
}
}
+ /**
+ * @return the length of the body.
+ */
public int getLength() {
return body.size();
}
+ /**
+ * @return the value associated with the given key on the given line.
+ */
public String get(int i, String key) {
return body.get(i).get(key);
}
diff --git a/src/ComparisonView.java b/src/ComparisonView.java
index 8e20cf3..78897e9 100644
--- a/src/ComparisonView.java
+++ b/src/ComparisonView.java
@@ -10,6 +10,10 @@ import java.awt.*;
* References:
* https://zetcode.com/java/listdirectory/
* https://stackoverflow.com/questions/4871051/how-to-get-the-current-working-directory-in-java
+ * https://stackoverflow.com/questions/2501861/how-can-i-remove-a-jpanel-from-a-jframe
+ * https://stackoverflow.com/questions/5652344/how-can-i-use-a-custom-font-in-java
+ * https://www.javaprogramto.com/2019/03/java-uimanager.html
+ *
*
* Date:
* 2024-05-08
@@ -18,25 +22,29 @@ import java.awt.*;
* Provide a view for comparing font families.
*/
+// ComparisonView is a JFrame
public class ComparisonView extends JFrame {
- private final int WINDOW_MIN_WIDTH = 960;
- private final int WINDOW_MIN_HEIGHT = 540;
- private ArrayList<FontFamily> fonts;
+ private final int WINDOW_MIN_WIDTH = 960; // A ComparisonView has a minimum height
+ private final int WINDOW_MIN_HEIGHT = 540; // A ComparisonView has a minimum width
+ private ArrayList<FontFamily> fonts; // A ComparisonView has a list of fonts
+ private Font textFont; // A ComparisonView has a preferred text font
+ private final int TEXT_SIZE = 20; // A ComparisonView has a constant text size.
/**
* Walk the file tree.
*/
static void walk(File dir, ArrayList<File> list) {
- File[] dirContents = dir.listFiles();
+ File[] dirContents = dir.listFiles(); // List the files in the directory.
+ // For each of the items in the directory:
for (int i = 0; i < dirContents.length; i++) {
- if (dirContents[i].isFile()) {
- if (dirContents[i] != null) {
- list.add(dirContents[i]);
- }
+ // If it's a file, and its name ends with '.pb', add it to the list.
+ if (dirContents[i].isFile() && dirContents[i].getName().endsWith(".pb")) {
+ list.add(dirContents[i]);
+ // If it's a directory, recurse through it.
} else if (dirContents[i].isDirectory()) {
walk(dirContents[i], list);
}
- /* Otherwise, an irregular file. Ignore it. */
+ // Otherwise, it's an irregular file. Ignore it.
}
}
@@ -44,66 +52,89 @@ public class ComparisonView extends JFrame {
* Initialize GUI and comparison.
*/
public ComparisonView() {
+ /* Set text font. Register the included ttf file as a font
+ in the current environment. This is akin to adding it to the map
+ of font family names and file names. */
+ try {
+ GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
+ environment.registerFont(Font.createFont(Font.TRUETYPE_FONT,
+ new File("./DejaVuSansCondensed.ttf")));
+ /* If either IOException or FontFormatException are thrown, error out. */
+ } catch (IOException|FontFormatException e) {
+ System.out.println(e.getMessage());
+ System.exit(-1);
+ }
+
+ /* Set default text font to included DejaVu Sans Condensed. */
+ textFont = new Font("DejaVu Sans Condensed", Font.PLAIN, TEXT_SIZE);
+ getContentPane().setFont(textFont);
+ UIManager.put("Label.font", textFont);
+ UIManager.put("Panel.font", textFont);
+
+
setTitle("Google Fonts Style vs. Popularity"); // Window title
- setMinimumSize(new Dimension(WINDOW_MIN_WIDTH,
- WINDOW_MIN_HEIGHT)); // Minimum window size
+ setMinimumSize(new Dimension(WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT)); // Minimum window size
/* On window close, kill the program. */
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack(); // Pack the GUI
setVisible(true); // Make the window visible
- /* In $(find . | grep METADATA.pb), each
- family is described in JSON. Human name is provided on first line.
- For each METADATA.pb, instantiate a FontFamily object, keep
- in an ArrayList. */
- //System.out.println(System.getProperty("user.dir"));
- ArrayList<File> fileList = new ArrayList<File>();
+ /* In metadata/*.pb, each font is described.
+ Add each of the files in that directory to a list. */
+ ArrayList<File> metadataList = new ArrayList<File>();
try {
- walk(new File("."), fileList);
+ walk(new File("."), metadataList);
} catch (Exception e) {
System.out.println(e.getMessage()); // Print error message
- System.exit(1); // Exit with error
- }
-
- ArrayList<File> metadataList = new ArrayList<File>();
- /* Enhanced for loop. */
- for (File file : fileList) {
- if (file.getPath().endsWith(".pb")) {
- metadataList.add(file);
- }
+ System.exit(-1); // Exit with error
}
+ /* Open families.csv and popularity.json. If either are missing,
+ the program cannot run. */
File styleFile = new File("families.csv");
if (!styleFile.exists()) {
+ System.out.println("families.csv doesn't exist.");
System.exit(-1);
}
-
File popularityFile = new File("popularity.json");
if (!popularityFile.exists()) {
System.exit(-1);
}
+ /* Panel to show while waiting for FontFamily data structures to populate. */
+ JPanel waiting = new JPanel();
+ JLabel waitingLabel = new JLabel("Memory structures take 10-15 seconds to set up. My apologies.");
+ waiting.add(waitingLabel);
+ this.add(waiting, BorderLayout.CENTER);
+ setVisible(true);
+ /* Construct Readers from each of the metadata files. */
CSVReader style = new CSVReader(styleFile);
JSONReader popularity = new JSONReader(popularityFile);
- /* Create FontFamily objects from each METADATA.pb file. */
+ // Create FontFamily objects from each METADATA.pb file.
fonts = new ArrayList<FontFamily>();
+ /* For each metadata file, parse a FontFamily. */
for (File file : metadataList) {
JSONReader metadata = new JSONReader(file);
- metadata.setPb();
+ metadata.setMetadata();
fonts.add(new FontFamily(metadata, popularity, style));
}
-
- for (FontFamily font : fonts) {
- System.out.println(font.getFamilyName());
- for (String subset : font.getSubsets()) {
- System.out.println(subset);
- }
- }
+
+ waiting.remove(waitingLabel); // Remove waiting message.
+ JPanel author = new JPanel();
+ JLabel authorLabel = new JLabel("Written by Kian Agheli");
+ author.add(authorLabel);
+ add(author, BorderLayout.SOUTH); // Add author panel.
+
+ LineGraph graph = new LineGraph(fonts); // Draw a line graph of the font views metadata.
+ add(graph, BorderLayout.NORTH); // Add the line graph.
+
+ pack(); // Pack the window.
+ setVisible(true); // Re-draw the window.
}
public static void main(String[] argv) {
- new ComparisonView();
+ new ComparisonView(); // Call the constructor.
}
}
diff --git a/src/FontFamily.java b/src/FontFamily.java
index cff4f87..59a458d 100644
--- a/src/FontFamily.java
+++ b/src/FontFamily.java
@@ -28,9 +28,10 @@ public class FontFamily {
private String designer;
/* total views, 7day views, 30day views, 90day views, year views.
Taken from popularity.json. */
+ final String[] popularityMetrics = {"7day", "30day", "90day", "year"};
private HashMap<String,Long> views;
- private String license;
- private String category;
+ private String license; // Distribution license.
+ private String category; // Vague category.
/* popularity.json and families.csv are shared among all families.
Have their associated objects passed from outside. */
@@ -38,16 +39,21 @@ public class FontFamily {
* Constructor.
*/
public FontFamily(JSONReader metadata, JSONReader popularity, CSVReader styles) {
- this.metadata = metadata;
- parseMetadata(metadata);
- parsePopularity(popularity);
- parseStyles(styles);
+ this.metadata = metadata; // Set the instance's metadata to passed value. */
+ parseMetadata(metadata); // Parse the metadata.
+
+ views = new HashMap<String,Long>(); // Instantiate views hash map.
+ parsePopularity(popularity); // Parse the popularity data
+
+ this.styles = new HashMap<String,Integer>(); // Instantiate styles hash map
+ parseStyles(styles); // Parse the styles data
}
/**
* Parse metadata.
*/
private void parseMetadata(JSONReader metadata) {
+ /* Parse the given fields. See their values described near the top of the class definition. */
familyName = metadata.get("name");
dateAdded = metadata.get("date_added");
subsets = metadata.getAll("subsets");
@@ -60,18 +66,91 @@ public class FontFamily {
* Parse popularity.
*/
private void parsePopularity(JSONReader popularity) {
+ String popularityJson = popularity.getFamily(familyName); // Block of JSON for this family
+ if (popularityJson == null) {
+ /* Not all families have popularity metadata, just most. */
+ return;
+ }
+
+ /* For each timeframe provided in popularity.json, parse as long and add to hash map. */
+ for (String timeframe : popularityMetrics) {
+ String timeframeKey = "\"" + timeframe + "\":"; // JSON key
+ int timeframeIndex = popularityJson.indexOf(timeframeKey); // Index of JSON key
+
+ String viewsKey = "\"views\": "; // JSON key for views following timeframe key
+ int viewsIndex = popularityJson.indexOf(viewsKey, timeframeIndex);
+
+ int start = viewsIndex + viewsKey.length(); // Start of data after key
+ int end = popularityJson.indexOf(",", start); // End of data before next comma
+
+ long value = 0;
+ /* Parse views as long. */
+ try {
+ value = Long.parseLong(popularityJson.substring(start, end));
+ /* If fail to parse as long, something is wrong with popularity.json.
+ 2^63 is a big number to overflow. */
+ } catch (NumberFormatException e) {
+ System.out.println("Failed to parse " + e.getMessage());
+ System.exit(-1);
+ }
+
+ views.put(timeframe, value); // Add the parsed long to the hash map
+ }
+
+ /* Parse total views for given family. */
+ try {
+ views.put("total", Long.parseLong(JSONReader.get(popularityJson, "totalViews")));
+ /* If fail to parse as long, something is wrong with popularity.json.
+ 2^63 is a big number to overflow. */
+ } catch (NumberFormatException e) {
+ System.out.println("Failed to parse " + e.getMessage());
+ System.exit(-1);
+ }
+
}
/**
* Parse styles.
*/
- private void parseStyles(CSVReader styles) {
+ private void parseStyles(CSVReader styleCsv) {
+ int len = styleCsv.getLength(); // Lines of body in CSV
+
+ /* Styles CSV has three header fields: Family, Group/Tag, and Weight. */
+ for (int i = 0; i < len; i++) {
+ /* If the current row contains data for the given family, add that data. */
+ if (familyName.equals(styleCsv.get(i, "Family"))) {
+ try {
+ /* Add the Group/Tag and the associated weight. */
+ styles.put(styleCsv.get(i, "Group/Tag"),
+ Integer.parseInt(styleCsv.get(i, "Weight")));
+ /* If unable to parse as integer, something is wrong with families.csv. */
+ } catch (NumberFormatException e) {
+ System.out.println("Unable to parse " + e.getMessage());
+ System.exit(-1);
+ }
+ }
+
+ }
}
+ /**
+ * @return family name
+ */
public String getFamilyName() {
return familyName;
}
+
+ /**
+ * @return unicode subsets.
+ */
public String[] getSubsets() {
return subsets;
}
+
+ /**
+ * @return views hash map
+ */
+ public HashMap<String,Long> getViews() {
+ return views;
+ }
}
diff --git a/src/JSONReader.java b/src/JSONReader.java
index 45f6bd5..44831c9 100644
--- a/src/JSONReader.java
+++ b/src/JSONReader.java
@@ -1,5 +1,6 @@
import java.util.HashMap;
import java.util.ArrayList;
+
import java.io.*;
/*
@@ -9,6 +10,8 @@ import java.io.*;
* References:
* https://googlefonts.github.io/gf-guide/metadata.html
* https://stackoverflow.com/questions/3880274/how-to-convert-the-object-to-string-in-java
+ * https://stackoverflow.com/questions/8938498/get-the-index-of-a-pattern-in-a-string-using-regex
+ * https://howtodoinjava.com/java/regex/start-end-of-string/
*
* Date:
* 2024-05-08
@@ -21,15 +24,23 @@ public class JSONReader extends Reader {
/* metadata.pb is not exactly JSON, but so close to it
that it's practically a subset of JSON with fewer quotation
marks. If set, this is a pb file. */
- private boolean pb;
+ private boolean isMetadata;
public JSONReader(File file) {
- super(file);
- pb = false;
+ super(file); // Call constructor.
+ isMetadata = false; // By default, not metadata.
}
private String composeKey(String key) {
- if (!pb) {
+ /* Call static version. This has the benefit of reducing code size.
+ Rather than write two separate static and instantiated versions, the
+ instantiated version may call the static version. */
+ return composeKey(key, this.isMetadata);
+ }
+
+ private static String composeKey(String key, boolean isMetadata) {
+ /* JSON usually has quotes surrounding keys. Google's metadata doesn't. */
+ if (!isMetadata) {
key = "\"" + key + "\"";
}
key += ": ";
@@ -42,20 +53,32 @@ public class JSONReader extends Reader {
* match.
*/
public String get(String key) {
- key = composeKey(key);
-
- int start = this.getContents().indexOf(key);
+ /* Call static version. */
+ return JSONReader.get(this.getContents(), key, isMetadata);
+ }
+
+ public static String get(String json, String key) {
+ /* No isMetadata boolean passed, presumably irrelevant. */
+ return JSONReader.get(json, key, false);
+ }
+
+ /**
+ * Get a value given a key and a block of JSON to search.
+ */
+ public static String get(String json, String key, boolean isMetadata) {
+ key = composeKey(key, isMetadata); // Compose a key
+ int start = json.indexOf(key); // Index of key
if (start == -1) {
/* No match. */
return null;
}
- start += key.length();
+ start += key.length(); // Start of data past key
/* This JSON is pretty-printed. */
- int nl = this.getContents().indexOf("\n", start);
+ int nl = json.indexOf("\n", start);
/* Extract substring. Remove any quotation marks and commas. */
- String substring = this.getContents().substring(start, nl);
+ String substring = json.substring(start, nl);
return substring.replaceAll("[\",]", "");
}
@@ -63,12 +86,13 @@ public class JSONReader extends Reader {
* Search for and return all matches.
*/
public String[] getAll(String key) {
- key = composeKey(key);
+ key = composeKey(key); // Compose key
- ArrayList<String> list = new ArrayList<String>();
- int lastMatch = 0;
+ ArrayList<String> list = new ArrayList<String>(); // ArrayList to compose output array
+ int lastMatch = 0; // Previous match's index. Necessary for iteration through file contents.
+ /* While matches exist, continue. */
while ((lastMatch = this.getContents().indexOf(key, lastMatch)) != -1) {
- lastMatch += key.length();
+ lastMatch += key.length(); // Data starts after key.
int start = lastMatch;
/* This JSON is pretty-printed. */
int nl = this.getContents().indexOf("\n", start);
@@ -90,16 +114,48 @@ public class JSONReader extends Reader {
}
/**
- * Search for and return a table of fields.
+ * @return a block of JSON associated with a font family.
+ * Heavily reliant on the format of popularity.json. Not
+ * suitable for generic JSON parsing.
*/
- public String getTable(HashMap<String,String> name) {
- return null;
+ public String getFamily(String family) {
+ /* Taking advantage of the specific structure
+ of popularity.json. Find the index of the family
+ name declaration. Then, all items until the next line
+ starting with ' }' are included. */
+ String contents = this.getContents();
+ int start = contents.indexOf(" \"family\": \"" + family + "\",");
+ if (start == -1) {
+ /* No match. */
+ return null;
+ }
+
+ /* Restring input starting from family name declaration. */
+ contents = contents.substring(start);
+
+ String[] lines = contents.split("\n");
+ int i = 0;
+ /* In this format, an end block is guaranteed.
+ Iterate through lines until the desired level of end
+ block is found. */
+ while (!lines[i].startsWith(" }")) {
+ i++;
+ }
+
+ /* For each of the lines in the block preceding the end block,
+ add to output. */
+ String result = "";
+ for (int j = 0; j < i; j++) {
+ result += lines[j] + "\n";
+ }
+
+ return result;
}
/**
- * Specify that this is the special PB format.
+ * Specify that this is the special pb metadata format. Almost JSON.
* See https://googlefonts.github.io/gf-guide/metadata.html. */
- public void setPb() {
- this.pb = true;
+ public void setMetadata() {
+ this.isMetadata = true;
}
}
diff --git a/src/LineGraph.java b/src/LineGraph.java
index 129bb1c..3097be4 100644
--- a/src/LineGraph.java
+++ b/src/LineGraph.java
@@ -1,10 +1,24 @@
import javax.swing.*;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Calendar;
+import java.util.Date;
/*
* @author
* Kian Agheli
*
* References:
+ * https://stackoverflow.com/questions/8693342/drawing-a-simple-line-graph-in-java
+ * https://www.geeksforgeeks.org/sorting-a-hashmap-according-to-values/
+ * https://stackoverflow.com/questions/16252269/how-to-sort-a-list-arraylist
+ * https://stackoverflow.com/questions/2839508/java2d-increase-the-line-width
+ * https://stackoverflow.com/questions/4285464/java2d-graphics-anti-aliased
+ * https://stackoverflow.com/questions/7182996/java-get-month-integer-from-date
+ * https://stackoverflow.com/questions/5799140/java-get-month-string-from-integer
*
* Date:
* 2024-05-08
@@ -13,20 +27,193 @@ import javax.swing.*;
* Draw a line graph.
*/
-public class LineGraph {
- private int[][] coordinates; // A line graph has-a set of coordinates
+// A LineGraph is-a JPanel
+public class LineGraph extends JPanel {
+ private ArrayList<FontFamily> fonts; // A line graph has-a set of fonts
+ private final int WIDTH = 640; // A line graph has a preferred width
+ private final int HEIGHT = 480; // A line graph has a preferred height
+ private final int LINES = 0x10; // A line graph has a maximum lines graphed at a time
+ private final int LINE_WIDTH = 3; // A line graph has lines with a given pixel width
+ private final int TICK_HEIGHT = 10; // A line graph has ticks with a given pixel height
+ /* A line graph has an array of abbreviated month names. */
+ private final String[] ABBREVIATED_MONTH = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
+ "Aug", "Sep", "Oct", "Nov", "Dec"};
+ /* A line graph has an array of unique color values to use for each of the lines. */
+ private final int[] COLORS = {0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff,
+ 0xff8040, 0xff4080, 0x80ff40, 0x40ff80, 0x4080ff, 0x8040ff, 0xff8080, 0x80ff80, 0x8080ff,
+ 0xff4040, 0x40ff40, 0x4040ff};
/**
* Constructor.
*/
- public LineGraph() {
- coordinates = null;
+ public LineGraph(ArrayList<FontFamily> fonts) {
+ this.fonts = fonts; // Set instance's list of fonts to the list passed.
+ this.setPreferredSize(new Dimension(WIDTH, HEIGHT)); // Set the preferred size of the panel.
}
-
/**
* Set a point at a pair of coordinates.
*/
void setCoordinates(int y, int x) {
}
+
+ /* Automatically called at construction time. */
+ @Override
+ public void paintComponent(Graphics g) {
+ /* Cast to Graphics2D. Permits use of Graphics2D methods. */
+ Graphics2D graphics = (Graphics2D) g;
+ /* White background. */
+ graphics.setBackground(Color.WHITE);
+ /* Fill entire space allocated. No margins or padding. */
+ graphics.clearRect(0, 0, getWidth(), getHeight());
+ /* Enable anti-aliasing. */
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ /* Set line width. */
+ graphics.setStroke(new BasicStroke(LINE_WIDTH));
+ /* Draw in black. */
+ graphics.setColor(Color.BLACK);
+ /* Store font metrics so as to calculate width and height of each tick's text. */
+ FontMetrics metrics = graphics.getFontMetrics();
+ /* Store enough space on the left for 12 digits. Assuming tabular figures. */
+ int xOffset = metrics.stringWidth("012345678901");
+ /* Keep enough space at bottom for text with descenders and ticks. */
+ int yMax = getHeight() - (metrics.getHeight() << 1) - TICK_HEIGHT;
+ /* Keep enough space at right for the right half of the text "MMM". */
+ int xMax = getWidth() - (metrics.stringWidth("MMM") >> 1) - TICK_HEIGHT - xOffset;
+ /* Draw x axis. */
+ graphics.drawLine(TICK_HEIGHT + xOffset, yMax,
+ getWidth(), yMax);
+
+ /* Store calendar, so as to get month. */
+ Date date = new Date();
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date); // Set calendar to current date
+
+ /* For each month in the year, draw a tick on the x axis. */
+ int x = xMax;
+ int xTicks = 12;
+ double xSpace = (float) x / xTicks;
+
+ for (int i = 0; i <= xTicks; i++) {
+ graphics.drawLine(x + TICK_HEIGHT + xOffset, yMax,
+ x + TICK_HEIGHT + xOffset, getHeight() - metrics.getHeight());
+ String month = ABBREVIATED_MONTH[calendar.get(Calendar.MONTH)];
+ /* Half the width for centering offset. */
+ int textOffset = metrics.stringWidth(month) >> 1;
+ graphics.drawString(month, x + TICK_HEIGHT + xOffset - textOffset,
+ getHeight() - metrics.getHeight() / 5 * 2);
+ x = (int) (x - xSpace); // Decrement x offset.
+ calendar.roll(Calendar.MONTH, false); // Roll back by one month
+ }
+
+ /* Draw y axis. Subtract one third of stroke width to account for width of first tick. */
+ graphics.drawLine(TICK_HEIGHT + xOffset - LINE_WIDTH / 3, 0, TICK_HEIGHT + xOffset -
+ LINE_WIDTH / 3, yMax);
+
+ sortFonts("30day"); // Sort the list of fonts by their 30day metrics.
+
+ int y = 0 + metrics.getHeight();
+ /* Cast to double within parentheses for floating-point
+ division, cast back to int to remove fractional part.
+ 5/4 spacing for legibility. */
+ int yTicks = (int) (yMax / ((double) metrics.getHeight() * 5 / 4));
+ /* Sorted values by monthly views in descending order. The greatest value is at the top. */
+ long maxViews = fonts.get(0).getViews().get("30day");
+ float ySpace = (float) yMax / yTicks;
+ for (int i = yTicks; i >= 0; i--) {
+ String text = String.format("%d", maxViews * i / yTicks);
+ /* Difference between maximum string width and actual string width is offset. */
+ int textOff = xOffset - metrics.stringWidth(text);
+ graphics.drawString(text, textOff, y);
+ y += ySpace;
+ }
+
+ /* Draw lines. */
+ for (int i = 0; i < LINES; i++) {
+ FontFamily font = fonts.get(i); // Keep the current font handy.
+ HashMap<String,Long> views = font.getViews(); // Keep the views hashmap handy.
+
+ /* Map of coordinates to draw. */
+ HashMap<Integer,Long> coordinates = new HashMap<Integer,Long>();
+
+ /* Use the given metrics to guesstimate the values of each month.
+ The last month's views are known. */
+ coordinates.put(12, views.get("30day"));
+
+ /* Using 90day views, we may subtract 30day to get a closer approximation
+ of 60day views. */
+ coordinates.put(11, (views.get("90day") - views.get("30day")) / 2);
+
+ /* Account for changes in views by deriving views 3 months ago from 90day
+ views / 3. */
+ coordinates.put(10, (views.get("90day") / 3));
+
+
+ /* Guesstimate the rest of the months by yearly views / 12. */
+ for (int j = 1; j <= 9; j++) {
+ coordinates.put(j, (views.get("year") / 12));
+ }
+
+ graphics.setColor(new Color(COLORS[i])); // Set the drawing color.
+
+ /* Previous x and y values. Necessary for drawing lines between points. */
+ int xPrevious = 0;
+ int yPrevious = 0;
+ for (int xCoordinate = 1; xCoordinate <= 12; xCoordinate++) {
+ /* x is ratio of month value / 12.
+ Because y starts at the top, invert with subtraction from maximum pixel value.
+ Ratio of given views over maximum views, multiplied by maximum pixel value.
+ Circle with radius of LINE_WIDTH * 2. */
+ int xCurrent = xOffset + TICK_HEIGHT + xMax * xCoordinate / 12;
+ int yCurrent = yMax - (int) ((float) coordinates.get(xCoordinate) /
+ maxViews * yMax);
+ graphics.fillOval(xCurrent, yCurrent, LINE_WIDTH << 1, LINE_WIDTH << 1);
+
+ /* Line to previous point, if set. */
+ if (xPrevious == 0) {
+ /* If no previous point, use current y value. */
+ graphics.drawLine(xOffset + TICK_HEIGHT + xMax * (xCoordinate - 1) / 12,
+ yCurrent, xCurrent, yCurrent);
+ } else {
+ /* Line from previous x and y to current x and y. */
+ graphics.drawLine(xPrevious, yPrevious, xCurrent, yCurrent);
+ }
+
+ /* Set previous values for next iteration. */
+ yPrevious = yCurrent;
+ xPrevious = xCurrent;
+ }
+ }
+ }
+
+ /* Sort by views, in descending order. */
+ private void sortFonts(String metric) {
+ Collections.sort(fonts, new Comparator<FontFamily>() {
+ /* If a is lesser, return 1. If b is lesser, return -1.
+ Otherwise, they are equal, return 0. */
+ public int compare (FontFamily a, FontFamily b) {
+ /* If equal, return 0. */
+ if (a.getViews().get(metric) == b.getViews().get(metric)) {
+ return 0;
+ }
+
+ /* If either is null, it is lesser. */
+ if (a.getViews().get(metric) == null) {
+ return 1;
+ }
+ if (b.getViews().get(metric) == null) {
+ return -1;
+ }
+
+ /* Neither are null, compare. */
+ if (a.getViews().get(metric) > b.getViews().get(metric)) {
+ return -1;
+ }
+
+ /* aViews must be lesser. */
+ return 1;
+ }
+ });
+ }
}
diff --git a/src/Reader.java b/src/Reader.java
index cdad0cc..05dd03e 100644
--- a/src/Reader.java
+++ b/src/Reader.java
@@ -33,7 +33,7 @@ class Reader {
/* On exception, exit. */
} catch (Exception e) {
System.out.println(e.getMessage());
- System.exit(1);
+ System.exit(-1);
}
}
diff --git a/week5.png b/week5.png
new file mode 100644
index 0000000..70c779e
--- /dev/null
+++ b/week5.png
Binary files differ
diff --git a/week6.png b/week6.png
new file mode 100644
index 0000000..b730c9d
--- /dev/null
+++ b/week6.png
Binary files differ