/* * @(#)VisualFontDesigner.java 1.0 11/28/08 */ package darrylbu.sample; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsEnvironment; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Image; import java.awt.Insets; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import static java.awt.font.TextAttribute.*; import java.awt.font.TextAttribute; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.DefaultListCellRenderer; import javax.swing.JButton; import javax.swing.JColorChooser; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.JToggleButton; import javax.swing.SwingUtilities; import javax.swing.UIManager; /** * A visual font designer and code generator * * @version 1.0 11/25/08 * @author Darryl */ @SuppressWarnings(value = "unchecked") public class VisualFontDesigner { private Map attributes; private JComboBox fontCombo; Integer fontSize; private Font comboFont; private JComboBox sizeCombo; private JComboBox widthCombo; private JComboBox trackingCombo; private JComboBox superscriptCombo; final Insets buttonInsets = new Insets(0, 5, 0, 5); private Font buttonFont; private JToggleButton boldButton; private JToggleButton italicButton; private JToggleButton underlineButton; private JToggleButton strikethroughButton; private JToggleButton swapColorsButton; private JToggleButton kerningButton; private JButton foregroundButton; private JButton backgroundButton; private JButton copyButton; private JTextArea sampleTextArea; private JTextArea codeTextArea; private JFrame frame; ; /** * Entry point to the program * * @param args not used */ public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new VisualFontDesigner().makeUI(); } }); } /** * This method can be invoked on a new instance of * VisualFontDesigner to create and launch the GUI. *

* Invoked from the main method when run as a standalone application. */ public void makeUI() { if (frame != null) { throw new IllegalStateException( "VisualFontDesigner is already running."); } JFrame.setDefaultLookAndFeelDecorated(true); frame = new JFrame(" Visual Font Designer"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(640, 450); frame.setLocationRelativeTo(null); comboFont = UIManager.getFont("ComboBox.font"); buttonFont = UIManager.getFont("Button.font"); createComponents(); layoutComponents(); frame.setVisible(true); } /** * Invoked from makeUI to construct the components */ private void createComponents() { Font font = UIManager.getFont("TextArea.font"); fontSize = font.getSize(); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); String[] families = ge.getAvailableFontFamilyNames(); Font[] fonts = new Font[families.length]; for (int i = 0; i < fonts.length; i++) { fonts[i] = new Font(families[i], Font.PLAIN, fontSize); } fontCombo = new AttributeCombo(fonts); fontCombo.setToolTipText("Font family"); fontCombo.setRenderer(new FontListCellRenderer()); fontCombo.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Font font = (Font) fontCombo.getSelectedItem(); fontCombo.setFont(canDisplayOwnName(font) ? font.deriveFont((float) comboFont.getSize()) : comboFont); // put FONT resets many other attributes attributes.putAll(font.getAttributes()); attributes.put(WEIGHT, boldButton.isSelected() ? WEIGHT_BOLD : WEIGHT_REGULAR); attributes.put(POSTURE, italicButton.isSelected() ? POSTURE_OBLIQUE : POSTURE_REGULAR); attributes.put(UNDERLINE, underlineButton.isSelected() ? UNDERLINE_ON : -1); attributes.put(STRIKETHROUGH, strikethroughButton.isSelected() ? STRIKETHROUGH_ON : false); attributes.put(KERNING, kerningButton.isSelected() ? KERNING_ON : false); attributes.put(SWAP_COLORS, swapColorsButton.isSelected() ? SWAP_COLORS_ON : false); attributes.put(SIZE, sizeCombo.getSelectedItem()); attributes.put(TRACKING, trackingCombo.getSelectedItem()); attributes.put(WIDTH, widthCombo.getSelectedItem()); attributes.put(SUPERSCRIPT, superscriptCombo.getSelectedItem()); refreshFont(); } }); Object[] sizes = { 8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 26, 28, 36, 48, 72 }; sizeCombo = new JComboBox(sizes); sizeCombo.setToolTipText("Font size"); sizeCombo.setPrototypeDisplayValue(888); sizeCombo.setPreferredSize(sizeCombo.getPreferredSize()); sizeCombo.setEditable(true); sizeCombo.getEditor().getEditorComponent(). addFocusListener(new FocusAdapter() { @Override public void focusLost(FocusEvent e) { validateFontSize(); } }); sizeCombo.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { validateFontSize(); } }); trackingCombo = getComboBox(TRACKING, "Tracking"); widthCombo = getComboBox(WIDTH, "Width"); superscriptCombo = getComboBox(SUPERSCRIPT, "Superscript"); boldButton = getToggleButton(WEIGHT, "Bold"); italicButton = getToggleButton(POSTURE, "Italic"); underlineButton = getToggleButton(UNDERLINE, "Underline", true); strikethroughButton = getToggleButton(STRIKETHROUGH, "Strikethrough"); kerningButton = getToggleButton(KERNING, "Kerning"); foregroundButton = getColorButton(FOREGROUND, "Foreground color"); foregroundButton.setText("A"); backgroundButton = getColorButton(BACKGROUND, "Background color"); backgroundButton.setText("ab"); swapColorsButton = getToggleButton(SWAP_COLORS, "Swap colors"); sampleTextArea = new JTextArea(); sampleTextArea.setLineWrap(true); sampleTextArea.setWrapStyleWord(true); sampleTextArea.setText("Type here."); codeTextArea = new JTextArea(); codeTextArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, fontSize)); codeTextArea.setEditable(false); copyButton = new JButton("Copy Code"); copyButton.setToolTipText("Copy code to clipboard"); copyButton.setMargin(buttonInsets); copyButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String text = codeTextArea.getText(); StringSelection selection = new StringSelection(text); Toolkit toolkit = Toolkit.getDefaultToolkit(); Clipboard clipboard = toolkit.getSystemClipboard(); clipboard.setContents(selection, null); } }); setDefaultState(font); } /** * Invoked from createComponents to set the default initial states * of the components according to the default font for a JComboBox * * @param font the default font for a JComboBox in the current L&F */ private void setDefaultState(Font font) { attributes = (Map) font.getAttributes(); boldButton.setSelected(font.isBold()); italicButton.setSelected(font.isItalic()); underlineButton.setSelected(attributes.get(UNDERLINE) == UNDERLINE_ON); fontCombo.setSelectedItem(font); sizeCombo.setSelectedItem(fontSize); Object selectedItem; if ((selectedItem = attributes.get(WIDTH)) != null) { widthCombo.setSelectedItem(selectedItem); } if ((selectedItem = attributes.get(TRACKING)) != null) { trackingCombo.setSelectedItem(selectedItem); } if ((selectedItem = attributes.get(SUPERSCRIPT)) != null) { superscriptCombo.setSelectedItem(selectedItem); } } /** * Lays out the GUI in the JFrame. Two panels with GridBagLayout are * used for the two toolbar-like rows. Each set of buttons is placed * in a GridLayout to keep them the same size. *

* A JSplitPane houses the sample and code JTextAreas, each in its own * JScrollPane. */ private void layoutComponents() { GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0.0; gbc.weighty = 0.0; gbc.insets = new Insets(2, 1, 1, 1); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.anchor = GridBagConstraints.NORTH; JPanel toolBarPanelA = new JPanel(new GridBagLayout()); gbc.gridy = 0; gbc.weighty = 0.0; gbc.fill = GridBagConstraints.VERTICAL; toolBarPanelA.add(fontCombo, gbc); gbc.gridx++; toolBarPanelA.add(sizeCombo, gbc); gbc.gridx++; toolBarPanelA.add(new JSeparator(JSeparator.VERTICAL), gbc); JPanel buttonPanel = new JPanel(new GridLayout(1, 0)); buttonPanel.add(boldButton); buttonPanel.add(italicButton); buttonPanel.add(underlineButton); gbc.gridx++; toolBarPanelA.add(buttonPanel, gbc); gbc.gridx++; toolBarPanelA.add(new JSeparator(JSeparator.VERTICAL), gbc); buttonPanel = new JPanel(new GridLayout(1, 0)); buttonPanel.add(strikethroughButton); buttonPanel.add(kerningButton); gbc.gridx++; toolBarPanelA.add(buttonPanel, gbc); gbc.gridx++; toolBarPanelA.add(new JSeparator(JSeparator.VERTICAL), gbc); buttonPanel = new JPanel(new GridLayout(1, 0)); buttonPanel.add(foregroundButton); buttonPanel.add(backgroundButton); buttonPanel.add(swapColorsButton); gbc.gridx++; toolBarPanelA.add(buttonPanel, gbc); gbc.gridx++; gbc.anchor = GridBagConstraints.EAST; gbc.weightx = 1.0; toolBarPanelA.add(copyButton, gbc); JPanel toolBarPanelB = new JPanel(new GridBagLayout()); gbc.gridx = 0; gbc.anchor = GridBagConstraints.WEST; gbc.weightx = 0.0; toolBarPanelB.add(widthCombo, gbc); gbc.gridx++; toolBarPanelB.add(trackingCombo, gbc); gbc.gridx++; gbc.weightx = 1.0; toolBarPanelB.add(superscriptCombo, gbc); JPanel toolBarPanel = new JPanel(new GridLayout(0, 1)); toolBarPanel.add(toolBarPanelA); toolBarPanel.add(toolBarPanelB); frame.add(toolBarPanel, BorderLayout.NORTH); JScrollPane sampleScroll = new JScrollPane(sampleTextArea); sampleScroll.setPreferredSize(new Dimension(0, 150)); JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, sampleScroll, new JScrollPane(codeTextArea)); frame.add(splitPane, BorderLayout.CENTER); } /** * This method invokes getToggleButton(textAttribute, toolTipText, false) */ private JToggleButton getToggleButton(final TextAttribute textAttribute, String toolTipText) { return getToggleButton(textAttribute, toolTipText, false); } /** * Constructs and returns a JToggleButton mapped to a TextAttribute that * has only two valid values, typically ON and OFF (or default) * * @param textAttribute the TextAttribute to be mapped to the button * @param toolTipText the initial letter is used as the button's text * @param flip true for TextAttribute.UNDERLINE, false for others. * @return the toggle button */ private JToggleButton getToggleButton(final TextAttribute textAttribute, String toolTipText, final boolean flip) { final JToggleButton button = new JToggleButton(toolTipText.substring(0, 1)); button.setMargin(buttonInsets); button.setToolTipText(toolTipText); final Object[] keys = TextAttributeConstants.values(textAttribute); Map attribute = new HashMap(); attribute.put(textAttribute, keys[flip ? 0 : 1]); button.setFont(buttonFont.deriveFont(attribute)); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Map attribute = new HashMap(); attribute.put(textAttribute, keys[flip ^ button.isSelected() ? 0 : 1]); button.setFont(buttonFont.deriveFont(attribute)); attributes.put(textAttribute, keys[flip ^ button.isSelected() ? 1 : 0]); refreshFont(); } }); return button; } /** * This method constructs and returns a JButton that launches a color * chooser for selection of background or foreground color. *

* Clicking Cancel in the chooser will reset the color to its default. * * @param textAttribute FOREGROUND or BACKGROUND * @param toolTipText * @return the button */ private JButton getColorButton(final TextAttribute textAttribute, String toolTipText) { final boolean isForeground = textAttribute == TextAttribute.FOREGROUND; final JButton button = new JButton() { @Override public void paintComponent(Graphics g) { super.paintComponent(g); Color color = (Color) attributes.get(textAttribute); if (color != null) { g.setColor(color); g.fillRect(5, getHeight() - 6, getWidth() - 10, 3); } } }; button.setMargin(buttonInsets); button.setToolTipText(toolTipText); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Color color = (Color) attributes.get(textAttribute); if (color == null) { color = isForeground ? sampleTextArea.getForeground() : sampleTextArea.getBackground(); } color = JColorChooser.showDialog(frame, isForeground ? "SelectForeground" : "Select Background", color); attributes.put(textAttribute, color); refreshFont(); } }); return button; } /** * This method constructs and returns a JComboBox that lists the constants * associated with a TextAttribute, apropriately rendered. * * @param textAttribute the TextAttribute to be mapped to the combo box * @param toolTipText * @return the combo box */ private JComboBox getComboBox( final TextAttribute textAttribute, String toolTipText) { final JComboBox comboBox = new AttributeCombo(TextAttributeConstants.values(textAttribute)); comboBox.setToolTipText(toolTipText); comboBox.setSelectedIndex(0); comboBox.setRenderer(new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Map attribute = new HashMap(); attribute.put(textAttribute, value); Font font = getFont().deriveFont(attribute); setFont(font); setText(TextAttributeConstants.lookup(textAttribute, value)); if (isSelected) { setForeground(list.getSelectionForeground()); setBackground(list.getSelectionBackground()); } else { setForeground(list.getForeground()); setBackground(list.getBackground()); } return this; } }); comboBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Map attribute = new HashMap(); attribute.put(textAttribute, comboBox.getSelectedItem()); comboBox.setFont( comboBox.getFont().deriveFont(attribute)); attributes.put(textAttribute, comboBox.getSelectedItem()); refreshFont(); } }); return comboBox; } /** * Ensures integer selection of font size. If an illegal value is typed, * reverts to the last integer value */ private void validateFontSize() { try { fontSize = (Integer) sizeCombo.getSelectedItem(); attributes.put(SIZE, fontSize); refreshFont(); } catch (ClassCastException cce) { // illegal value entered, revert sizeCombo.setSelectedItem(fontSize); } } /* * Refreshes the font of the sample area and frame icon and invokes * generateCode() to update the code area. */ private void refreshFont() { Font font = Font.getFont(attributes); font = font.deriveFont(attributes); sampleTextArea.setFont(font); frame.setIconImage(getImage()); generateCode(); } /** * Code generator that translates the font's attributes to code that * will recreate the font. *

* This code can be copied to the clipboard by the "Copy Code" button. */ private void generateCode() { Map attribs = (Map) sampleTextArea.getFont().getAttributes(); final StringBuilder sb = new StringBuilder("Map" + " attributes\n = new HashMap();\n\n"); for (TextAttribute key : TextAttributeConstants.list) { Object value = attribs.get(key); if (value != null) { String keyName = String.valueOf(key); keyName = keyName.replaceAll("^[^(]*\\(([^)]*)\\).*$", "$1"). toUpperCase(); sb.append("attributes.put(TextAttribute." + keyName + ", "); if (value instanceof String) { sb.append("\""); } String valueName = TextAttributeConstants.lookup(key, value); if (valueName == null) { if (value instanceof Color) { Color color = (Color) value; sb.append("new Color(" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ")"); } else { sb.append(String.valueOf(value)); } } else { sb.append("TextAttribute." + valueName); } if (value instanceof String) { sb.append("\""); } sb.append(");\n"); } } sb.append("\nFont font = Font.getFont(attributes);\n"); sb.append("font = font.deriveFont(attributes);\n"); codeTextArea.setText(sb.toString()); } /* * Generates the image for the frame's dunamic icon */ private Image getImage() { BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB); Graphics2D g2 = image.createGraphics(); Font font = sampleTextArea.getFont().deriveFont(12.0f); Boolean isInverse = (Boolean) attributes.get(SWAP_COLORS); Color background = isInverse ? (Color) attributes.get(FOREGROUND) : (Color) attributes.get(BACKGROUND); if (background == null) { background = isInverse ? sampleTextArea.getForeground() : sampleTextArea.getBackground(); } g2.setColor(background); g2.fillRect(0, 0, 16, 16); g2.setFont(font); g2.setColor(Color.BLACK); g2.drawString("F", 1, 10); g2.drawString("D", 6, 15); return image; } /* * Tests whether the font has glyphs to display its own name. * */ private boolean canDisplayOwnName(Font font) { char[] chars = font.getName().toCharArray(); for (char c : chars) { if (!font.canDisplay(c)) { return false; } } return true; } /** * The font and/or the attributes of the combo boxes changes on selection. * The default behavior of JComboBox under these conditions is to change * the preferred size to accommodate the new font. *

* This behavior is not desirable, so the relevant methods are overridden * to keep the size constant. */ class AttributeCombo extends JComboBox { AttributeCombo(Object[] items) { super(items); } @Override public Dimension getPreferredSize() { return new Dimension(208, 25); } @Override public Dimension getMinimumSize() { return getPreferredSize(); } } /** * Renderer to display each Font name in its own font if possible. *

* A font that lacks the glyphs for this is displayed in red, in the * default font. */ class FontListCellRenderer extends DefaultListCellRenderer { Color foreground = super.getForeground(); Font defaultFont = super.getFont(); @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Font font = (Font) value; setText(font.getName()); if (isSelected) { setForeground(list.getSelectionForeground()); setBackground(list.getSelectionBackground()); } else { setForeground(list.getForeground()); setBackground(list.getBackground()); } if (canDisplayOwnName(font)) { setFont(font); } else { setForeground(Color.RED); setFont(defaultFont); } return this; } } /** * A hardcoded mapping of the TextAttribute class constants to their * variable names, for use by the code generator. *

* Also holds a list to provide a suitable sequence of atributes. */ static class TextAttributeConstants extends HashMap> { static Map> outer; static List list; static { outer = new HashMap>(); Map inner; // Bold inner = new HashMap(); inner.put(WEIGHT_REGULAR, "WEIGHT_REGULAR"); inner.put(WEIGHT_BOLD, "WEIGHT_BOLD"); outer.put(WEIGHT, inner); // Italic inner = new HashMap(); inner.put(POSTURE_REGULAR, "POSTURE_REGULAR"); inner.put(POSTURE_OBLIQUE, "POSTURE_OBLIQUE"); outer.put(POSTURE, inner); // Underline inner = new HashMap(); inner.put(UNDERLINE_ON, "UNDERLINE_ON"); inner.put(-1, "No Underline"); outer.put(UNDERLINE, inner); // Strikethrough inner = new HashMap(); inner.put(STRIKETHROUGH_ON, "STRIKETHROUGH_ON"); inner.put(false, "No Strikethrough"); outer.put(STRIKETHROUGH, inner); // Kerning inner = new HashMap(); inner.put(KERNING_ON, "KERNING_ON"); inner.put(0, "No Kerning"); outer.put(KERNING, inner); // Swap colors inner = new HashMap(); inner.put(SWAP_COLORS_ON, "SWAP_COLORS_ON"); inner.put(false, "No Color Swap"); outer.put(SWAP_COLORS, inner); // Tracking inner = new HashMap(); inner.put(0, "No Tracking"); inner.put(TRACKING_TIGHT, "TRACKING_TIGHT"); inner.put(TRACKING_LOOSE, "TRACKING_LOOSE"); outer.put(TRACKING, inner); // Width inner = new HashMap(); inner.put(WIDTH_CONDENSED, "WIDTH_CONDENSED"); inner.put(WIDTH_SEMI_CONDENSED, "WIDTH_SEMI_CONDENSED"); inner.put(WIDTH_REGULAR, "WIDTH_REGULAR"); inner.put(WIDTH_SEMI_EXTENDED, "WIDTH_SEMI_EXTENDED"); inner.put(WIDTH_EXTENDED, "WIDTH_EXTENDED"); outer.put(WIDTH, inner); // Superscript / subscript inner = new HashMap(); inner.put(SUPERSCRIPT_SUPER, "SUPERSCRIPT_SUPER"); inner.put(0, "Not Super / Subscript"); inner.put(SUPERSCRIPT_SUB, "SUPERSCRIPT_SUB"); outer.put(SUPERSCRIPT, inner); // For sequencing the generated code list = new ArrayList(); list.add(FAMILY); list.add(SIZE); list.add(WEIGHT); list.add(POSTURE); list.add(UNDERLINE); list.add(STRIKETHROUGH); list.add(KERNING); list.add(FOREGROUND); list.add(BACKGROUND); list.add(SWAP_COLORS); list.add(WIDTH); list.add(TRACKING); list.add(SUPERSCRIPT); } /** * returns the variable name of the constant of the TextAttribute * value corresponding to the key. * Used for the combo boxes' rendering and for code generation. * * @param key the TextAttribute whose value is to be looked up * @param value the value of the TextAttribute * @return the name of the static variable of the TextAttribute class, * or null if the key is not found */ static String lookup(TextAttribute key, Object value) { if (outer.containsKey(key)) { return outer.get(key).get(value); } return null; } /** * Returns an array of legitimate values for a TextAttribute. * Used for populating the combo boxes * * @param key the TextAttribute whose legal values are required * @return an array of legal values, or null if the key is not found */ static Object[] values(TextAttribute key) { if (outer.containsKey(key)) { return outer.get(key).keySet().toArray(); } return null; } } }