Path: blob/master/src/demo/share/jfc/Font2DTest/FontPanel.java
41149 views
/*1* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.2*3* Redistribution and use in source and binary forms, with or without4* modification, are permitted provided that the following conditions5* are met:6*7* - Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9*10* - Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* - Neither the name of Oracle nor the names of its15* contributors may be used to endorse or promote products derived16* from this software without specific prior written permission.17*18* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS19* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,20* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR21* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR22* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,23* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,24* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR25* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF26* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING27* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS28* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.29*/3031/*32* This source code is provided to illustrate the usage of a given feature33* or technique and has been deliberately simplified. Additional steps34* required for a production-quality application, such as security checks,35* input validation and proper error handling, might not be present in36* this sample code.37*/38394041import java.awt.BorderLayout;42import java.awt.Color;43import java.awt.Cursor;44import java.awt.Dimension;45import java.awt.Font;46import java.awt.FontMetrics;47import java.awt.Graphics;48import java.awt.Graphics2D;49import java.awt.GraphicsConfiguration;50import java.awt.GraphicsEnvironment;51import java.awt.Point;52import java.awt.Rectangle;53import java.awt.RenderingHints;54import java.awt.Toolkit;55import java.awt.event.AdjustmentEvent;56import java.awt.event.AdjustmentListener;57import java.awt.event.ComponentAdapter;58import java.awt.event.ComponentEvent;59import java.awt.event.MouseEvent;60import java.awt.event.MouseListener;61import java.awt.event.MouseMotionListener;62import java.awt.font.FontRenderContext;63import java.awt.font.GlyphVector;64import java.awt.font.LineBreakMeasurer;65import java.awt.font.TextLayout;66import java.awt.geom.AffineTransform;67import java.awt.geom.NoninvertibleTransformException;68import java.awt.geom.Rectangle2D;69import java.awt.image.BufferedImage;70import java.awt.print.PageFormat;71import java.awt.print.Printable;72import java.awt.print.PrinterJob;73import java.io.BufferedOutputStream;74import java.io.FileOutputStream;75import java.text.AttributedString;76import java.util.Vector;7778import javax.imageio.*;79import javax.swing.*;8081import static java.awt.RenderingHints.*;8283/**84* FontPanel.java85*86* @author Shinsuke Fukuda87* @author Ankit Patel [Conversion to Swing - 01/07/30]88*/8990/// This panel is combination of the text drawing area of Font2DTest91/// and the custom controlled scroll bar9293public final class FontPanel extends JPanel implements AdjustmentListener {9495/// Drawing Option Constants96private final String[] STYLES =97{ "plain", "bold", "italic", "bold italic" };9899private final int NONE = 0;100private final int SCALE = 1;101private final int SHEAR = 2;102private final int ROTATE = 3;103private final String[] TRANSFORMS =104{ "with no transforms", "with scaling", "with Shearing", "with rotation" };105106private final int DRAW_STRING = 0;107private final int DRAW_CHARS = 1;108private final int DRAW_BYTES = 2;109private final int DRAW_GLYPHV = 3;110private final int TL_DRAW = 4;111private final int GV_OUTLINE = 5;112private final int TL_OUTLINE = 6;113private final String[] METHODS = {114"drawString", "drawChars", "drawBytes", "drawGlyphVector",115"TextLayout.draw", "GlyphVector.getOutline", "TextLayout.getOutline" };116117public final int RANGE_TEXT = 0;118public final int ALL_GLYPHS = 1;119public final int USER_TEXT = 2;120public final int FILE_TEXT = 3;121private final String[] MS_OPENING =122{ " Unicode ", " Glyph Code ", " lines ", " lines " };123private final String[] MS_CLOSING =124{ "", "", " of User Text ", " of LineBreakMeasurer-reformatted Text " };125126/// General Graphics Variable127private final JScrollBar verticalBar;128private final FontCanvas fc;129private boolean updateFontMetrics = true;130private boolean updateFont = true;131private boolean force16Cols = false;132public boolean showingError = false;133private int g2Transform = NONE; /// ABP134135/// Printing constants and variables136public final int ONE_PAGE = 0;137public final int CUR_RANGE = 1;138public final int ALL_TEXT = 2;139private int printMode = ONE_PAGE;140private PageFormat page = null;141private PrinterJob printer = null;142143/// Text drawing variables144private String fontName = "Dialog";145private float fontSize = 12;146private int fontStyle = Font.PLAIN;147private int fontTransform = NONE;148private Font testFont = null;149private Object antiAliasType = VALUE_TEXT_ANTIALIAS_DEFAULT;150private Object fractionalMetricsType = VALUE_FRACTIONALMETRICS_DEFAULT;151private Object lcdContrast = getDefaultLCDContrast();152private int drawMethod = DRAW_STRING;153private int textToUse = RANGE_TEXT;154private String[] userText = null;155private String[] fileText = null;156private int[] drawRange = { 0x0000, 0x007f };157private String[] fontInfos = new String[2];158private boolean showGrid = true;159160/// Parent Font2DTest panel161private final Font2DTest f2dt;162private final JFrame parent;163164public FontPanel( Font2DTest demo, JFrame f ) {165f2dt = demo;166parent = f;167168verticalBar = new JScrollBar ( JScrollBar.VERTICAL );169fc = new FontCanvas();170171this.setLayout( new BorderLayout() );172this.add( "Center", fc );173this.add( "East", verticalBar );174175verticalBar.addAdjustmentListener( this );176this.addComponentListener( new ComponentAdapter() {177public void componentResized( ComponentEvent e ) {178updateFontMetrics = true;179}180});181182/// Initialize font and its infos183testFont = new Font(fontName, fontStyle, (int)fontSize);184if ((float)((int)fontSize) != fontSize) {185testFont = testFont.deriveFont(fontSize);186}187updateFontInfo();188}189190public Dimension getPreferredSize() {191return new Dimension(600, 200);192}193194/// Functions called by the main programs to set the various parameters195196public void setTransformG2( int transform ) {197g2Transform = transform;198updateFontMetrics = true;199fc.repaint();200}201202/// convenience fcn to create AffineTransform of appropriate type203private AffineTransform getAffineTransform( int transform ) {204/// ABP205AffineTransform at = new AffineTransform();206switch ( transform )207{208case SCALE:209at.setToScale( 1.5f, 1.5f ); break;210case ROTATE:211at.setToRotation( Math.PI / 6 ); break;212case SHEAR:213at.setToShear( 0.4f, 0 ); break;214case NONE:215break;216default:217//System.err.println( "Illegal G2 Transform Arg: " + transform);218break;219}220221return at;222}223224public void setFontParams(Object obj, float size,225int style, int transform) {226setFontParams( (String)obj, size, style, transform );227}228229public void setFontParams(String name, float size,230int style, int transform) {231boolean fontModified = false;232if ( !name.equals( fontName ) || style != fontStyle )233fontModified = true;234235fontName = name;236fontSize = size;237fontStyle = style;238fontTransform = transform;239240/// Recreate the font as specified241testFont = new Font(fontName, fontStyle, (int)fontSize);242if ((float)((int)fontSize) != fontSize) {243testFont = testFont.deriveFont(fontSize);244}245246if ( fontTransform != NONE ) {247AffineTransform at = getAffineTransform( fontTransform );248testFont = testFont.deriveFont( at );249}250updateFontMetrics = true;251fc.repaint();252if ( fontModified ) {253/// Tell main panel to update the font info254updateFontInfo();255f2dt.fireUpdateFontInfo();256}257}258259public void setRenderingHints( Object aa, Object fm, Object contrast) {260antiAliasType = ((AAValues)aa).getHint();261fractionalMetricsType = ((FMValues)fm).getHint();262lcdContrast = contrast;263updateFontMetrics = true;264fc.repaint();265}266267public void setDrawMethod( int i ) {268drawMethod = i;269fc.repaint();270}271272public void setTextToDraw( int i, int[] range,273String[] textSet, String[] fileData ) {274textToUse = i;275276if ( textToUse == RANGE_TEXT )277drawRange = range;278else if ( textToUse == ALL_GLYPHS )279drawMethod = DRAW_GLYPHV;280else if ( textToUse == USER_TEXT )281userText = textSet;282else if ( textToUse == FILE_TEXT ) {283fileText = fileData;284drawMethod = TL_DRAW;285}286287updateFontMetrics = true;288fc.repaint();289updateFontInfo();290}291292public void setGridDisplay( boolean b ) {293showGrid = b;294fc.repaint();295}296297public void setForce16Columns( boolean b ) {298force16Cols = b;299updateFontMetrics = true;300fc.repaint();301}302303/// Prints out the text display area304public void doPrint( int i ) {305if ( printer == null ) {306printer = PrinterJob.getPrinterJob();307page = printer.defaultPage();308}309printMode = i;310printer.setPrintable( fc, page );311312if ( printer.printDialog() ) {313try {314printer.print();315}316catch ( Exception e ) {317f2dt.fireChangeStatus( "ERROR: Printing Failed; See Stack Trace", true );318}319}320}321322/// Displays the page setup dialog and updates PageFormat info323public void doPageSetup() {324if ( printer == null ) {325printer = PrinterJob.getPrinterJob();326page = printer.defaultPage();327}328page = printer.pageDialog( page );329}330331/// Obtains the information about selected font332private void updateFontInfo() {333int numGlyphs = 0, numCharsInRange = drawRange[1] - drawRange[0] + 1;334fontInfos[0] = "Font Face Name: " + testFont.getFontName();335fontInfos[1] = "Glyphs in This Range: ";336337if ( textToUse == RANGE_TEXT ) {338for ( int i = drawRange[0]; i < drawRange[1]; i++ )339if ( testFont.canDisplay( i ))340numGlyphs++;341fontInfos[1] = fontInfos[1] + numGlyphs + " / " + numCharsInRange;342}343else344fontInfos[1] = null;345}346347/// Accessor for the font information348public String[] getFontInfo() {349return fontInfos;350}351352/// Collects the currectly set options and returns them as string353public String getCurrentOptions() {354/// Create a new String to store the options355/// The array will contain all 8 setting (font name, size...) and356/// character range or user text data used (no file text data)357int userTextSize = 0;358String options;359360options = ( fontName + "\n" + fontSize + "\n" + fontStyle + "\n" +361fontTransform + "\n" + g2Transform + "\n"+362textToUse + "\n" + drawMethod + "\n" +363AAValues.getHintVal(antiAliasType) + "\n" +364FMValues.getHintVal(fractionalMetricsType) + "\n" +365lcdContrast + "\n");366if ( textToUse == USER_TEXT )367for ( int i = 0; i < userText.length; i++ )368options += ( userText[i] + "\n" );369370return options;371}372373/// Reload all options and refreshes the canvas374public void loadOptions( boolean grid, boolean force16, int start, int end,375String name, float size, int style,376int transform, int g2transform,377int text, int method, int aa, int fm,378int contrast, String[] user ) {379int[] range = { start, end };380381/// Since repaint call has a low priority, these functions will finish382/// before the actual repainting is done383setGridDisplay( grid );384setForce16Columns( force16 );385// previous call to readTextFile has already set the text to draw386if (textToUse != FILE_TEXT) {387setTextToDraw( text, range, user, null );388}389setFontParams( name, size, style, transform );390setTransformG2( g2transform ); // ABP391setDrawMethod( method );392setRenderingHints(AAValues.getValue(aa), FMValues.getValue(fm),393Integer.valueOf(contrast));394}395396/// Writes the current screen to PNG file397public void doSavePNG( String fileName ) {398fc.writePNG( fileName );399}400401/// When scrolled using the scroll bar, update the backbuffer402public void adjustmentValueChanged( AdjustmentEvent e ) {403fc.repaint();404}405406public void paintComponent( Graphics g ) {407// Windows does not repaint correctly, after408// a zoom. Thus, we need to force the canvas409// to repaint, but only once. After the first repaint,410// everything stabilizes. [ABP]411fc.repaint();412}413414/// Inner class definition...415416/// Inner panel that holds the actual drawing area and its routines417private class FontCanvas extends JPanel implements MouseListener, MouseMotionListener, Printable {418419/// Number of characters that will fit across and down this canvas420private int numCharAcross, numCharDown;421422/// First and last character/line that will be drawn423/// Limit is the end of range/text where no more draw will be done424private int drawStart, drawEnd, drawLimit;425426/// FontMetrics variables427/// Here, gridWidth is equivalent to maxAdvance (slightly bigger though)428/// and gridHeight is equivalent to lineHeight429private int maxAscent, maxDescent, gridWidth = 0, gridHeight = 0;430431/// Offset from the top left edge of the canvas where the draw will start432private int canvasInset_X = 5, canvasInset_Y = 5;433434/// LineBreak'ed TextLayout vector435private Vector<TextLayout> lineBreakTLs = null;436437/// Whether the current draw command requested is for printing438private boolean isPrinting = false;439440/// Other printing infos441private int lastPage, printPageNumber, currentlyShownChar = 0;442private final int PR_OFFSET = 10;443private final int PR_TITLE_LINEHEIGHT = 30;444445/// Information about zooming (used with range text draw)446private final JWindow zoomWindow;447private BufferedImage zoomImage = null;448private int mouseOverCharX = -1, mouseOverCharY = -1;449private int currMouseOverChar = -1, prevZoomChar = -1;450private float ZOOM = 2.0f;451private boolean nowZooming = false;452private boolean firstTime = true;453// ABP454455/// Status bar message backup456private String backupStatusString = null;457458/// Error constants459private final String[] ERRORS = {460"ERROR: drawBytes cannot handle characters beyond 0x00FF. Select different range or draw methods.",461"ERROR: Cannot fit text with the current font size. Resize the window or use smaller font size.",462"ERROR: Cannot print with the current font size. Use smaller font size.",463};464465private final int DRAW_BYTES_ERROR = 0;466private final int CANT_FIT_DRAW = 1;467private final int CANT_FIT_PRINT = 2;468469/// Other variables470private final Cursor blankCursor;471472public FontCanvas() {473this.addMouseListener( this );474this.addMouseMotionListener( this );475this.setForeground( Color.black );476this.setBackground( Color.white );477478/// Creates an invisble pointer by giving it bogus image479/// Possibly find a workaround for this...480Toolkit tk = Toolkit.getDefaultToolkit();481byte[] bogus = { (byte) 0 };482blankCursor =483tk.createCustomCursor( tk.createImage( bogus ), new Point(0, 0), "" );484485zoomWindow = new JWindow( parent ) {486public void paint( Graphics g ) {487g.drawImage( zoomImage, 0, 0, zoomWindow );488}489};490zoomWindow.setCursor( blankCursor );491zoomWindow.pack();492}493494public boolean firstTime() { return firstTime; }495public void refresh() {496firstTime = false;497repaint();498}499500/// Sets the font, hints, according to the set parameters501private void setParams( Graphics2D g2 ) {502g2.setFont( testFont );503g2.setRenderingHint(KEY_TEXT_ANTIALIASING, antiAliasType);504g2.setRenderingHint(KEY_FRACTIONALMETRICS, fractionalMetricsType);505g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, lcdContrast);506/* I am preserving a somewhat dubious behaviour of this program.507* Outline text would be drawn anti-aliased by setting the508* graphics anti-aliasing hint if the text anti-aliasing hint509* was set. The dubious element here is that people simply510* using this program may think this is built-in behaviour511* but its not - at least not when the app explicitly draws512* outline text.513* This becomes more dubious in cases such as "GASP" where the514* size at which text is AA'ed is not something you can easily515* calculate, so mimicing that behaviour isn't going to be easy.516* So I precisely preserve the behaviour : this is done only517* if the AA value is "ON". Its not applied in the other cases.518*/519if (antiAliasType == VALUE_TEXT_ANTIALIAS_ON &&520(drawMethod == TL_OUTLINE || drawMethod == GV_OUTLINE)) {521g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);522} else {523g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF);524}525}526527/// Draws the grid (Used for unicode/glyph range drawing)528private void drawGrid( Graphics2D g2 ) {529int totalGridWidth = numCharAcross * gridWidth;530int totalGridHeight = numCharDown * gridHeight;531532g2.setColor( Color.black );533for ( int i = 0; i < numCharDown + 1; i++ )534g2.drawLine( canvasInset_X, i * gridHeight + canvasInset_Y,535canvasInset_X + totalGridWidth, i * gridHeight + canvasInset_Y );536for ( int i = 0; i < numCharAcross + 1; i++ )537g2.drawLine( i * gridWidth + canvasInset_X, canvasInset_Y,538i * gridWidth + canvasInset_X, canvasInset_Y + totalGridHeight );539}540541/// Draws one character at time onto the canvas according to542/// the method requested (Used for RANGE_TEXT and ALL_GLYPHS)543public void modeSpecificDrawChar( Graphics2D g2, int charCode,544int baseX, int baseY ) {545GlyphVector gv;546int[] oneGlyph = { charCode };547char[] charArray = Character.toChars( charCode );548549FontRenderContext frc = g2.getFontRenderContext();550AffineTransform oldTX = g2.getTransform();551552/// Create GlyphVector to measure the exact visual advance553/// Using that number, adjust the position of the character drawn554if ( textToUse == ALL_GLYPHS )555gv = testFont.createGlyphVector( frc, oneGlyph );556else557gv = testFont.createGlyphVector( frc, charArray );558Rectangle2D r2d2 = gv.getPixelBounds(frc, 0, 0);559int shiftedX = baseX;560// getPixelBounds returns a result in device space.561// we need to convert back to user space to be able to562// calculate the shift as baseX is in user space.563try {564double[] pt = new double[4];565pt[0] = r2d2.getX();566pt[1] = r2d2.getY();567pt[2] = r2d2.getX()+r2d2.getWidth();568pt[3] = r2d2.getY()+r2d2.getHeight();569oldTX.inverseTransform(pt,0,pt,0,2);570shiftedX = baseX - (int) ( pt[2] / 2 + pt[0] );571} catch (NoninvertibleTransformException e) {572}573574/// ABP - keep track of old tform, restore it later575576g2.translate( shiftedX, baseY );577g2.transform( getAffineTransform( g2Transform ) );578579if ( textToUse == ALL_GLYPHS )580g2.drawGlyphVector( gv, 0f, 0f );581else {582if ( testFont.canDisplay( charCode ))583g2.setColor( Color.black );584else {585g2.setColor( Color.lightGray );586}587588switch ( drawMethod ) {589case DRAW_STRING:590g2.drawString( new String( charArray ), 0, 0 );591break;592case DRAW_CHARS:593g2.drawChars( charArray, 0, 1, 0, 0 );594break;595case DRAW_BYTES:596if ( charCode > 0xff )597throw new CannotDrawException( DRAW_BYTES_ERROR );598byte[] oneByte = { (byte) charCode };599g2.drawBytes( oneByte, 0, 1, 0, 0 );600break;601case DRAW_GLYPHV:602g2.drawGlyphVector( gv, 0f, 0f );603break;604case TL_DRAW:605TextLayout tl = new TextLayout( new String( charArray ), testFont, frc );606tl.draw( g2, 0f, 0f );607break;608case GV_OUTLINE:609r2d2 = gv.getVisualBounds();610shiftedX = baseX - (int) ( r2d2.getWidth() / 2 + r2d2.getX() );611g2.draw( gv.getOutline( 0f, 0f ));612break;613case TL_OUTLINE:614r2d2 = gv.getVisualBounds();615shiftedX = baseX - (int) ( r2d2.getWidth() / 2 + r2d2.getX() );616TextLayout tlo =617new TextLayout( new String( charArray ), testFont,618g2.getFontRenderContext() );619g2.draw( tlo.getOutline( null ));620}621}622623/// ABP - restore old tform624g2.setTransform ( oldTX );625}626627/// Draws one line of text at given position628private void modeSpecificDrawLine( Graphics2D g2, String line,629int baseX, int baseY ) {630/// ABP - keep track of old tform, restore it later631AffineTransform oldTx = null;632oldTx = g2.getTransform();633g2.translate( baseX, baseY );634g2.transform( getAffineTransform( g2Transform ) );635636switch ( drawMethod ) {637case DRAW_STRING:638g2.drawString( line, 0, 0 );639break;640case DRAW_CHARS:641g2.drawChars( line.toCharArray(), 0, line.length(), 0, 0 );642break;643case DRAW_BYTES:644try {645byte[] lineBytes = line.getBytes( "ISO-8859-1" );646g2.drawBytes( lineBytes, 0, lineBytes.length, 0, 0 );647}648catch ( Exception e ) {649e.printStackTrace();650}651break;652case DRAW_GLYPHV:653GlyphVector gv =654testFont.createGlyphVector( g2.getFontRenderContext(), line );655g2.drawGlyphVector( gv, (float) 0, (float) 0 );656break;657case TL_DRAW:658TextLayout tl = new TextLayout( line, testFont,659g2.getFontRenderContext() );660tl.draw( g2, (float) 0, (float) 0 );661break;662case GV_OUTLINE:663GlyphVector gvo =664testFont.createGlyphVector( g2.getFontRenderContext(), line );665g2.draw( gvo.getOutline( (float) 0, (float) 0 ));666break;667case TL_OUTLINE:668TextLayout tlo =669new TextLayout( line, testFont,670g2.getFontRenderContext() );671AffineTransform at = new AffineTransform();672g2.draw( tlo.getOutline( at ));673}674675/// ABP - restore old tform676g2.setTransform ( oldTx );677678}679680/// Draws one line of text at given position681private void tlDrawLine( Graphics2D g2, TextLayout tl,682float baseX, float baseY ) {683/// ABP - keep track of old tform, restore it later684AffineTransform oldTx = null;685oldTx = g2.getTransform();686g2.translate( baseX, baseY );687g2.transform( getAffineTransform( g2Transform ) );688689tl.draw( g2, (float) 0, (float) 0 );690691/// ABP - restore old tform692g2.setTransform ( oldTx );693694}695696697/// If textToUse is set to range drawing, then convert698/// int to hex string and prepends 0s to make it length 4699/// Otherwise line number was fed; simply return number + 1 converted to String700/// (This is because first line is 1, not 0)701private String modeSpecificNumStr( int i ) {702if ( textToUse == USER_TEXT || textToUse == FILE_TEXT )703return String.valueOf( i + 1 );704705StringBuffer s = new StringBuffer( Integer.toHexString( i ));706while ( s.length() < 4 )707s.insert( 0, "0" );708return s.toString().toUpperCase();709}710711/// Resets the scrollbar to display correct range of text currently on screen712/// (This scrollbar is not part of a "ScrollPane". It merely simulates its effect by713/// indicating the necessary area to be drawn within the panel.714/// By doing this, it prevents creating gigantic panel when large text range,715/// i.e. CJK Ideographs, is requested)716private void resetScrollbar( int oldValue ) {717int totalNumRows = 1, numCharToDisplay;718if ( textToUse == RANGE_TEXT || textToUse == ALL_GLYPHS ) {719if ( textToUse == RANGE_TEXT )720numCharToDisplay = drawRange[1] - drawRange[0];721else /// textToUse == ALL_GLYPHS722numCharToDisplay = testFont.getNumGlyphs();723724totalNumRows = numCharToDisplay / numCharAcross;725if ( numCharToDisplay % numCharAcross != 0 )726totalNumRows++;727if ( oldValue / numCharAcross > totalNumRows )728oldValue = 0;729730verticalBar.setValues( oldValue / numCharAcross,731numCharDown, 0, totalNumRows );732}733else {734if ( textToUse == USER_TEXT )735totalNumRows = userText.length;736else /// textToUse == FILE_TEXT;737totalNumRows = lineBreakTLs.size();738verticalBar.setValues( oldValue, numCharDown, 0, totalNumRows );739}740if ( totalNumRows <= numCharDown && drawStart == 0) {741verticalBar.setEnabled( false );742}743else {744verticalBar.setEnabled( true );745}746}747748/// Calculates the font's metrics that will be used for draw749private void calcFontMetrics( Graphics2D g2d, int w, int h ) {750FontMetrics fm;751Graphics2D g2 = (Graphics2D)g2d.create();752753/// ABP754if ( g2Transform != NONE && textToUse != FILE_TEXT ) {755g2.setFont( g2.getFont().deriveFont( getAffineTransform( g2Transform )) );756fm = g2.getFontMetrics();757}758else {759fm = g2.getFontMetrics();760}761762maxAscent = fm.getMaxAscent();763maxDescent = fm.getMaxDescent();764if (maxAscent == 0) maxAscent = 10;765if (maxDescent == 0) maxDescent = 5;766if ( textToUse == RANGE_TEXT || textToUse == ALL_GLYPHS ) {767/// Give slight extra room for each character768maxAscent += 3;769maxDescent += 3;770gridWidth = fm.getMaxAdvance() + 6;771gridHeight = maxAscent + maxDescent;772if ( force16Cols )773numCharAcross = 16;774else775numCharAcross = ( w - 10 ) / gridWidth;776numCharDown = ( h - 10 ) / gridHeight;777778canvasInset_X = ( w - numCharAcross * gridWidth ) / 2;779canvasInset_Y = ( h - numCharDown * gridHeight ) / 2;780if ( numCharDown == 0 || numCharAcross == 0 )781throw new CannotDrawException( isPrinting ? CANT_FIT_PRINT : CANT_FIT_DRAW );782783if ( !isPrinting )784resetScrollbar( verticalBar.getValue() * numCharAcross );785}786else {787maxDescent += fm.getLeading();788canvasInset_X = 5;789canvasInset_Y = 5;790/// gridWidth and numCharAcross will not be used in this mode...791gridHeight = maxAscent + maxDescent;792numCharDown = ( h - canvasInset_Y * 2 ) / gridHeight;793794if ( numCharDown == 0 )795throw new CannotDrawException( isPrinting ? CANT_FIT_PRINT : CANT_FIT_DRAW );796/// If this is text loaded from file, prepares the LineBreak'ed797/// text layout at this point798if ( textToUse == FILE_TEXT ) {799if ( !isPrinting )800f2dt.fireChangeStatus( "LineBreaking Text... Please Wait", false );801lineBreakTLs = new Vector<>();802for ( int i = 0; i < fileText.length; i++ ) {803AttributedString as =804new AttributedString( fileText[i], g2.getFont().getAttributes() );805806LineBreakMeasurer lbm =807new LineBreakMeasurer( as.getIterator(), g2.getFontRenderContext() );808809while ( lbm.getPosition() < fileText[i].length() )810lineBreakTLs.add( lbm.nextLayout( (float) w ));811812}813}814if ( !isPrinting )815resetScrollbar( verticalBar.getValue() );816}817}818819/// Calculates the amount of text that will be displayed on screen820private void calcTextRange() {821String displaying = null;822823if ( textToUse == RANGE_TEXT || textToUse == ALL_GLYPHS ) {824if ( isPrinting )825if ( printMode == ONE_PAGE )826drawStart = currentlyShownChar;827else /// printMode == CUR_RANGE828drawStart = numCharAcross * numCharDown * printPageNumber;829else830drawStart = verticalBar.getValue() * numCharAcross;831if ( textToUse == RANGE_TEXT ) {832drawStart += drawRange[0];833drawLimit = drawRange[1];834}835else836drawLimit = testFont.getNumGlyphs();837drawEnd = drawStart + numCharAcross * numCharDown - 1;838839if ( drawEnd >= drawLimit )840drawEnd = drawLimit;841}842else {843if ( isPrinting )844if ( printMode == ONE_PAGE )845drawStart = currentlyShownChar;846else /// printMode == ALL_TEXT847drawStart = numCharDown * printPageNumber;848else {849drawStart = verticalBar.getValue();850}851852drawEnd = drawStart + numCharDown - 1;853854if ( textToUse == USER_TEXT )855drawLimit = userText.length - 1;856else857drawLimit = lineBreakTLs.size() - 1;858859if ( drawEnd >= drawLimit )860drawEnd = drawLimit;861}862863// ABP864if ( drawStart > drawEnd ) {865drawStart = 0;866verticalBar.setValue(drawStart);867}868869870/// Change the status bar if not printing...871if ( !isPrinting ) {872backupStatusString = ( "Displaying" + MS_OPENING[textToUse] +873modeSpecificNumStr( drawStart ) + " to " +874modeSpecificNumStr( drawEnd ) +875MS_CLOSING[textToUse] );876f2dt.fireChangeStatus( backupStatusString, false );877}878}879880/// Draws text according to the parameters set by Font2DTest GUI881private void drawText( Graphics g, int w, int h ) {882Graphics2D g2 = (Graphics2D) g;883g2.setColor(Color.white);884g2.fillRect(0, 0, w, h);885g2.setColor(Color.black);886887/// sets font, RenderingHints.888setParams( g2 );889890/// If flag is set, recalculate fontMetrics and reset the scrollbar891if ( updateFontMetrics || isPrinting ) {892/// NOTE: re-calculates in case G2 transform893/// is something other than NONE894calcFontMetrics( g2, w, h );895updateFontMetrics = false;896}897/// Calculate the amount of text that can be drawn...898calcTextRange();899900/// Draw according to the set "Text to Use" mode901if ( textToUse == RANGE_TEXT || textToUse == ALL_GLYPHS ) {902int charToDraw = drawStart;903if ( showGrid )904drawGrid( g2 );905906for ( int i = 0; i < numCharDown && charToDraw <= drawEnd; i++ ) {907for ( int j = 0; j < numCharAcross && charToDraw <= drawEnd; j++, charToDraw++ ) {908int gridLocX = j * gridWidth + canvasInset_X;909int gridLocY = i * gridHeight + canvasInset_Y;910911modeSpecificDrawChar( g2, charToDraw,912gridLocX + gridWidth / 2,913gridLocY + maxAscent );914915}916}917}918else if ( textToUse == USER_TEXT ) {919g2.drawRect( 0, 0, w - 1, h - 1 );920for ( int i = drawStart; i <= drawEnd; i++ ) {921int lineStartX = canvasInset_Y;922int lineStartY = ( i - drawStart ) * gridHeight + maxAscent;923modeSpecificDrawLine( g2, userText[i], lineStartX, lineStartY );924}925}926else {927float xPos, yPos = (float) canvasInset_Y;928g2.drawRect( 0, 0, w - 1, h - 1 );929for ( int i = drawStart; i <= drawEnd; i++ ) {930TextLayout oneLine = lineBreakTLs.elementAt( i );931xPos =932oneLine.isLeftToRight() ?933canvasInset_X : ( (float) w - oneLine.getAdvance() - canvasInset_X );934935float[] fmData = {0, oneLine.getAscent(), 0, oneLine.getDescent(), 0, oneLine.getLeading()};936if (g2Transform != NONE) {937AffineTransform at = getAffineTransform(g2Transform);938at.transform( fmData, 0, fmData, 0, 3);939}940//yPos += oneLine.getAscent();941yPos += fmData[1]; // ascent942//oneLine.draw( g2, xPos, yPos );943tlDrawLine( g2, oneLine, xPos, yPos );944//yPos += oneLine.getDescent() + oneLine.getLeading();945yPos += fmData[3] + fmData[5]; // descent + leading946}947}948g2.dispose();949}950951/// Component paintComponent function...952/// Draws/Refreshes canvas according to flag(s) set by other functions953public void paintComponent( Graphics g ) {954super.paintComponent(g);955956Dimension d = this.getSize();957isPrinting = false;958try {959drawText( g, d.width, d.height );960}961catch ( CannotDrawException e ) {962super.paintComponent(g);963f2dt.fireChangeStatus( ERRORS[ e.id ], true );964return;965}966967showingError = false;968}969970/// Printable interface function971/// Component print function...972public int print( Graphics g, PageFormat pf, int pageIndex ) {973if ( pageIndex == 0 ) {974/// Reset the last page index to max...975lastPage = Integer.MAX_VALUE;976currentlyShownChar = verticalBar.getValue() * numCharAcross;977}978979if ( printMode == ONE_PAGE ) {980if ( pageIndex > 0 )981return NO_SUCH_PAGE;982}983else {984if ( pageIndex > lastPage )985return NO_SUCH_PAGE;986}987988int pageWidth = (int) pf.getImageableWidth();989int pageHeight = (int) pf.getImageableHeight();990/// Back up metrics and other drawing info before printing modifies it991int backupDrawStart = drawStart, backupDrawEnd = drawEnd;992int backupNumCharAcross = numCharAcross, backupNumCharDown = numCharDown;993Vector<TextLayout> backupLineBreakTLs = null;994if ( textToUse == FILE_TEXT )995backupLineBreakTLs = new Vector<>(lineBreakTLs);996997printPageNumber = pageIndex;998isPrinting = true;999/// Push the actual draw area 60 down to allow info to be printed1000g.translate( (int) pf.getImageableX(), (int) pf.getImageableY() + 60 );1001try {1002drawText( g, pageWidth, pageHeight - 60 );1003}1004catch ( CannotDrawException e ) {1005f2dt.fireChangeStatus( ERRORS[ e.id ], true );1006return NO_SUCH_PAGE;1007}10081009/// Draw information about what is being printed1010String hints = ( " with antialias " + antiAliasType + "and" +1011" fractional metrics " + fractionalMetricsType +1012" and lcd contrast = " + lcdContrast);1013String infoLine1 = ( "Printing" + MS_OPENING[textToUse] +1014modeSpecificNumStr( drawStart ) + " to " +1015modeSpecificNumStr( drawEnd ) + MS_CLOSING[textToUse] );1016String infoLine2 = ( "With " + fontName + " " + STYLES[fontStyle] + " at " +1017fontSize + " point size " + TRANSFORMS[fontTransform] );1018String infoLine3 = "Using " + METHODS[drawMethod] + hints;1019String infoLine4 = "Page: " + ( pageIndex + 1 );1020g.setFont( new Font( "dialog", Font.PLAIN, 12 ));1021g.setColor( Color.black );1022g.translate( 0, -60 );1023g.drawString( infoLine1, 15, 10 );1024g.drawString( infoLine2, 15, 22 );1025g.drawString( infoLine3, 15, 34 );1026g.drawString( infoLine4, 15, 46 );10271028if ( drawEnd == drawLimit )1029/// This indicates that the draw will be completed with this page1030lastPage = pageIndex;10311032/// Restore the changed values back...1033/// This is important for JScrollBar settings and LineBreak'ed TLs1034drawStart = backupDrawStart;1035drawEnd = backupDrawEnd;1036numCharAcross = backupNumCharAcross;1037numCharDown = backupNumCharDown;1038if ( textToUse == FILE_TEXT )1039lineBreakTLs = backupLineBreakTLs;1040return PAGE_EXISTS;1041}10421043/// Ouputs the current canvas into a given PNG file1044public void writePNG( String fileName ) {1045try {1046int w = this.getSize().width;1047int h = this.getSize().height;1048BufferedImage buffer = (BufferedImage) this.createImage( w, h );1049Graphics2D g2 = buffer.createGraphics();1050g2.setColor(Color.white);1051g2.fillRect(0, 0, w, h);1052g2.setColor(Color.black);1053updateFontMetrics = true;1054drawText(g2, w, h);1055updateFontMetrics = true;1056ImageIO.write(buffer, "png", new java.io.File(fileName));1057}1058catch ( Exception e ) {1059f2dt.fireChangeStatus( "ERROR: Failed to Save PNG image; See stack trace", true );1060e.printStackTrace();1061}1062}10631064/// Figures out whether a character at the pointer location is valid1065/// And if so, updates mouse location informations, as well as1066/// the information on the status bar1067private boolean checkMouseLoc( MouseEvent e ) {1068if ( gridWidth != 0 && gridHeight != 0 )1069if ( textToUse == RANGE_TEXT || textToUse == ALL_GLYPHS ) {1070int charLocX = ( e.getX() - canvasInset_X ) / gridWidth;1071int charLocY = ( e.getY() - canvasInset_Y ) / gridHeight;10721073/// Check to make sure the mouse click location is within drawn area1074if ( charLocX >= 0 && charLocY >= 0 &&1075charLocX < numCharAcross && charLocY < numCharDown ) {1076int mouseOverChar =1077charLocX + ( verticalBar.getValue() + charLocY ) * numCharAcross;1078if ( textToUse == RANGE_TEXT )1079mouseOverChar += drawRange[0];1080if ( mouseOverChar > drawEnd )1081return false;10821083mouseOverCharX = charLocX;1084mouseOverCharY = charLocY;1085currMouseOverChar = mouseOverChar;1086/// Update status bar1087f2dt.fireChangeStatus( "Pointing to" + MS_OPENING[textToUse] +1088modeSpecificNumStr( mouseOverChar ), false );1089return true;1090}1091}1092return false;1093}10941095/// Shows (updates) the character zoom window1096public void showZoomed() {1097GlyphVector gv;1098Font backup = testFont;1099Point canvasLoc = this.getLocationOnScreen();11001101/// Calculate the zoom area's location and size...1102int dialogOffsetX = (int) ( gridWidth * ( ZOOM - 1 ) / 2 );1103int dialogOffsetY = (int) ( gridHeight * ( ZOOM - 1 ) / 2 );1104int zoomAreaX =1105mouseOverCharX * gridWidth + canvasInset_X - dialogOffsetX;1106int zoomAreaY =1107mouseOverCharY * gridHeight + canvasInset_Y - dialogOffsetY;1108int zoomAreaWidth = (int) ( gridWidth * ZOOM );1109int zoomAreaHeight = (int) ( gridHeight * ZOOM );11101111/// Position and set size of zoom window as needed1112zoomWindow.setLocation( canvasLoc.x + zoomAreaX, canvasLoc.y + zoomAreaY );1113if ( !nowZooming ) {1114if ( zoomWindow.getWarningString() != null )1115/// If this is not opened as a "secure" window,1116/// it has a banner below the zoom dialog which makes it look really BAD1117/// So enlarge it by a bit1118zoomWindow.setSize( zoomAreaWidth + 1, zoomAreaHeight + 20 );1119else1120zoomWindow.setSize( zoomAreaWidth + 1, zoomAreaHeight + 1 );1121}11221123/// Prepare zoomed image1124zoomImage =1125(BufferedImage) zoomWindow.createImage( zoomAreaWidth + 1,1126zoomAreaHeight + 1 );1127Graphics2D g2 = (Graphics2D) zoomImage.getGraphics();1128testFont = testFont.deriveFont( fontSize * ZOOM );1129setParams( g2 );1130g2.setColor( Color.white );1131g2.fillRect( 0, 0, zoomAreaWidth, zoomAreaHeight );1132g2.setColor( Color.black );1133g2.drawRect( 0, 0, zoomAreaWidth, zoomAreaHeight );1134modeSpecificDrawChar( g2, currMouseOverChar,1135zoomAreaWidth / 2, (int) ( maxAscent * ZOOM ));1136g2.dispose();1137if ( !nowZooming )1138zoomWindow.setVisible(true);1139/// This is sort of redundant... since there is a paint function1140/// inside zoomWindow definition that does the drawImage.1141/// (I should be able to call just repaint() here)1142/// However, for some reason, that paint function fails to respond1143/// from second time and on; So I have to force the paint here...1144zoomWindow.getGraphics().drawImage( zoomImage, 0, 0, this );11451146nowZooming = true;1147prevZoomChar = currMouseOverChar;1148testFont = backup;11491150// Windows does not repaint correctly, after1151// a zoom. Thus, we need to force the canvas1152// to repaint, but only once. After the first repaint,1153// everything stabilizes. [ABP]1154if ( firstTime() ) {1155refresh();1156}1157}11581159/// Listener Functions11601161/// MouseListener interface function1162/// Zooms a character when mouse is pressed above it1163public void mousePressed( MouseEvent e ) {1164if ( !showingError) {1165if ( checkMouseLoc( e )) {1166showZoomed();1167this.setCursor( blankCursor );1168}1169}1170}11711172/// MouseListener interface function1173/// Redraws the area that was drawn over by zoomed character1174public void mouseReleased( MouseEvent e ) {1175if ( textToUse == RANGE_TEXT || textToUse == ALL_GLYPHS ) {1176if ( nowZooming )1177zoomWindow.setVisible(false);1178nowZooming = false;1179}1180this.setCursor( Cursor.getDefaultCursor() );1181}11821183/// MouseListener interface function1184/// Resets the status bar to display range instead of a specific character1185public void mouseExited( MouseEvent e ) {1186if ( !showingError && !nowZooming )1187f2dt.fireChangeStatus( backupStatusString, false );1188}11891190/// MouseMotionListener interface function1191/// Adjusts the status bar message when mouse moves over a character1192public void mouseMoved( MouseEvent e ) {1193if ( !showingError ) {1194if ( !checkMouseLoc( e ))1195f2dt.fireChangeStatus( backupStatusString, false );1196}1197}11981199/// MouseMotionListener interface function1200/// Scrolls the zoomed character when mouse is dragged1201public void mouseDragged( MouseEvent e ) {1202if ( !showingError )1203if ( nowZooming ) {1204if ( checkMouseLoc( e ) && currMouseOverChar != prevZoomChar )1205showZoomed();1206}1207}12081209/// Empty function to comply with interface requirement1210public void mouseClicked( MouseEvent e ) {}1211public void mouseEntered( MouseEvent e ) {}1212}12131214private final class CannotDrawException extends RuntimeException {1215/// Error ID1216public final int id;12171218public CannotDrawException( int i ) {1219id = i;1220}1221}12221223enum FMValues {1224FMDEFAULT ("DEFAULT", VALUE_FRACTIONALMETRICS_DEFAULT),1225FMOFF ("OFF", VALUE_FRACTIONALMETRICS_OFF),1226FMON ("ON", VALUE_FRACTIONALMETRICS_ON);12271228private String name;1229private Object hint;12301231private static FMValues[] valArray;12321233FMValues(String s, Object o) {1234name = s;1235hint = o;1236}12371238public String toString() {1239return name;1240}12411242public Object getHint() {1243return hint;1244}1245public static Object getValue(int ordinal) {1246if (valArray == null) {1247valArray = FMValues.values();1248}1249for (int i=0;i<valArray.length;i++) {1250if (valArray[i].ordinal() == ordinal) {1251return valArray[i];1252}1253}1254return valArray[0];1255}1256private static FMValues[] getArray() {1257if (valArray == null) {1258valArray = FMValues.values();1259}1260return valArray;1261}12621263public static int getHintVal(Object hint) {1264getArray();1265for (int i=0;i<valArray.length;i++) {1266if (valArray[i].getHint() == hint) {1267return i;1268}1269}1270return 0;1271}1272}12731274enum AAValues {1275AADEFAULT ("DEFAULT", VALUE_TEXT_ANTIALIAS_DEFAULT),1276AAOFF ("OFF", VALUE_TEXT_ANTIALIAS_OFF),1277AAON ("ON", VALUE_TEXT_ANTIALIAS_ON),1278AAGASP ("GASP", VALUE_TEXT_ANTIALIAS_GASP),1279AALCDHRGB ("LCD_HRGB", VALUE_TEXT_ANTIALIAS_LCD_HRGB),1280AALCDHBGR ("LCD_HBGR", VALUE_TEXT_ANTIALIAS_LCD_HBGR),1281AALCDVRGB ("LCD_VRGB", VALUE_TEXT_ANTIALIAS_LCD_VRGB),1282AALCDVBGR ("LCD_VBGR", VALUE_TEXT_ANTIALIAS_LCD_VBGR);12831284private String name;1285private Object hint;12861287private static AAValues[] valArray;12881289AAValues(String s, Object o) {1290name = s;1291hint = o;1292}12931294public String toString() {1295return name;1296}12971298public Object getHint() {1299return hint;1300}13011302public static boolean isLCDMode(Object o) {1303return (o instanceof AAValues &&1304((AAValues)o).ordinal() >= AALCDHRGB.ordinal());1305}13061307public static Object getValue(int ordinal) {1308if (valArray == null) {1309valArray = AAValues.values();1310}1311for (int i=0;i<valArray.length;i++) {1312if (valArray[i].ordinal() == ordinal) {1313return valArray[i];1314}1315}1316return valArray[0];1317}13181319private static AAValues[] getArray() {1320if (valArray == null) {1321valArray = AAValues.values();1322}1323return valArray;1324}13251326public static int getHintVal(Object hint) {1327getArray();1328for (int i=0;i<valArray.length;i++) {1329if (valArray[i].getHint() == hint) {1330return i;1331}1332}1333return 0;1334}13351336}13371338private static Integer defaultContrast;1339static Integer getDefaultLCDContrast() {1340if (defaultContrast == null) {1341GraphicsConfiguration gc =1342GraphicsEnvironment.getLocalGraphicsEnvironment().1343getDefaultScreenDevice().getDefaultConfiguration();1344Graphics2D g2d =1345(Graphics2D)(gc.createCompatibleImage(1,1).getGraphics());1346defaultContrast = (Integer)1347g2d.getRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST);1348}1349return defaultContrast;1350}1351}135213531354