diff options
| author | ArghKevin <kagheli@student.sdccd.edu> | 2024-05-20 05:42:37 -0700 | 
|---|---|---|
| committer | ArghKevin <kagheli@student.sdccd.edu> | 2024-05-20 05:42:37 -0700 | 
| commit | 583ecf2df556c0bb3ae5468a63d9231b3ef56f58 (patch) | |
| tree | 32aadcfbd21d79e34eb7d228f8b6e76dce9c0158 | |
| parent | ae99f9afc615bfd6f4dd0b6d6f7847928956a318 (diff) | |
Weeks 5 and 6.
| -rw-r--r-- | DejaVuSansCondensed.ttf | bin | 0 -> 680264 bytes | |||
| -rw-r--r-- | LICENSE | 210 | ||||
| -rw-r--r-- | README.md | 15 | ||||
| -rw-r--r-- | b | 50 | ||||
| -rw-r--r-- | src/CSVReader.java | 35 | ||||
| -rw-r--r-- | src/ComparisonView.java | 107 | ||||
| -rw-r--r-- | src/FontFamily.java | 93 | ||||
| -rw-r--r-- | src/JSONReader.java | 96 | ||||
| -rw-r--r-- | src/LineGraph.java | 197 | ||||
| -rw-r--r-- | src/Reader.java | 2 | ||||
| -rw-r--r-- | week5.png | bin | 0 -> 10253 bytes | |||
| -rw-r--r-- | week6.png | bin | 0 -> 59354 bytes | 
12 files changed, 717 insertions, 88 deletions
diff --git a/DejaVuSansCondensed.ttf b/DejaVuSansCondensed.ttf Binary files differnew file mode 100644 index 0000000..3259bc2 --- /dev/null +++ b/DejaVuSansCondensed.ttf @@ -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. @@ -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.   + +## 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. + + @@ -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 Binary files differnew file mode 100644 index 0000000..70c779e --- /dev/null +++ b/week5.png diff --git a/week6.png b/week6.png Binary files differnew file mode 100644 index 0000000..b730c9d --- /dev/null +++ b/week6.png  | 
