/* * [Footprint.java] * * Summary: calculate various ways of measuring the size of the display. * * Copyright: (c) 2004-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 4.0 2009-04-12 shorter style names, improved highlighting. */ package com.mindprod.jdisplay; import com.mindprod.fastcat.FastCat; import com.mindprod.jtokens.NL; import com.mindprod.jtokens.Token; import java.awt.Component; import java.awt.Font; import java.awt.FontMetrics; import java.io.Serializable; import static java.lang.Math.max; import static java.lang.Math.min; /** * calculate various ways of measuring the size of the display. *

* You must execute the methods in order:

    *
  1. s1CalcHTMLLength, by JPrep.
  2. s2CalcPayloadFootprint: just the tokens. by JPrep.
  3. s3CalcFat: just * tokens if rendered in a browser. by JPrep.
  4. s4CalcScrollableFootprint: just the scrollable canvas
  5. *
  6. s5CalcIdealAppletFootPrint: includes menu bar, usually bigger than size allocated. Size we would use if we had an * infinitely big screen.
  7. s6CalcActualAppletFootPrint: includes menu bar the actual size will be rendered *
htmlacros/jdisplayaux uses this too, so don't delete code that JDisplay does not use. Don't make methods * private. * * @author Roedy Green, Canadian Mind Products * @version 4.0 2009-04-12 shorter style names, improved highlighting. * @see com.mindprod.jdisplayaux.WaysToRender * @since 2004 */ @SuppressWarnings( { "WeakerAccess", "UnusedDeclaration" } ) public final class Footprint implements Cloneable, Serializable { /** * Version of this class, also used as the embedded version numbers of the ser files. * Make match com.mindprod.jtokens.Token.serialVersionUID */ public static final long serialVersionUID = 29L; /** * HowToProcess render INLINE IFRAME or APPLET Needed by JDislayAux. */ public transient Rendering rendering; /** * description of ways to render, not actual WaysToRender enum. */ public transient String wayRendered; /** * True if this has a bar. Needed by JDisplayAux. */ public transient boolean hasBar; /** * True if the rendering will have a horizontal scroll bar on the bottom */ @SuppressWarnings( { "WeakerAccess" } ) public transient boolean hasHScroll; /** * True has line numbers */ @SuppressWarnings( { "WeakerAccess" } ) public transient boolean hasLineNumbers; /** * True if the rendering will have a vertical scroll bar on the right */ @SuppressWarnings( { "WeakerAccess" } ) public transient boolean hasVScroll; /** * actual height of the entire Applet in pixel Needed by JDislayAux. */ public transient int actualAPPLET_HEIGHT; /** * Actual width of the entire Applet, in pixels. Needed by JDisplayAux. */ public transient int actualAPPLET_WIDTH; /** * the size of this file in chars when rendered as html */ public int htmlLengthInChars; /** * height of the entire Applet if we had unlimited real estate, in pixels */ public transient int idealAppletHeight; /** * width of the entire Applet if we had unlimited real estate, in pixels */ public transient int idealAppletWidth; /** * width of the line number column not counting the margins */ public int lineNumberWidthInPixels; /** * Height of just token rendering area, not counting any margins, in pixels. Needed by JPrep. Does not include room * for descenders of the very last line. That is handled by Geometry.BOTTOM_PADDING_PX. */ public int payloadHeight; /** * Width of just token rendering area, not counting any margins, in pixels. Needed by Jprep. */ public int payloadWidth; /** * Height of virtual image that is scrolled over, includes margin, but not scrollbars, in pixels */ public transient int scrollableHeight; /** * Width of virtual image that is scrolled over, including linenumber is present, includes margin, but not * scrollbars, in pixels */ @SuppressWarnings( { "WeakerAccess" } ) public transient int scrollableWidth; /** * Width of virtual image that is scrolled over, with line numbers, includes margin, but not scrollbars, in pixels */ public transient int scrollableWidthWithLineNumbers; /** * Width of virtual image that is scrolled over, without line numbers, includes margin, but not scrollbars, in * pixels */ public transient int scrollableWidthWithoutLineNumbers; /** * total lines of text in the file. The last has no NL, but it still counts as a line, in lines of text. Computed in * s2CalcPayloadFootprint. */ public int totalLines; /** * height of just of visible part of the scrolling region, not counting scroll bars, the window through which we * peer at the virtual scrollable payload. It includes the margins and the line numbers. Needed by DisplayAux. */ @SuppressWarnings( { "WeakerAccess" } ) public int viewportHeight; /** * width of just of visible part of the scrolling region, not counting scroll bars, the window through which we peer * at the virtual scrollable payload. It includes the margins and the line numbers. Needed by DisplayAux */ public int viewportWidth; /** * how many pixels the payload would grow in height if rendered by a browser rather than by JDisplay which shrinks * vertical space. Just the variable effect. */ private int fatHeight; /** * how many pixels the payload would grow in width if rendered by a browser rather than by JDisplay. Just the * variable effect. */ private int fatWidth; /** * constructor. The main building is done later with set */ public Footprint() { } /** * Get the font metrics for this font. * * @param font font we are planning on rendering in * @param panel dummy component to define screen resolution * * @return FontMetrics object that knows character widths. * @noinspection WeakerAccess */ static FontMetrics getFontMetrics( Font font, Component panel ) { if ( font == null ) { throw new IllegalArgumentException( "null Font" ); } FontMetrics fm = panel.getFontMetrics( font ); if ( fm == null ) { throw new IllegalArgumentException( "no FontMetrics available for font " + font ); } return fm; } /** * calculate how many pixels wide the line numbers will be. * * @param panel The components where we are displaying the line numbers in the left margin. */ private void calcLineNumberWidthInPixels( Component panel ) { // find the widest digit int widestDigitInPixels = 0; FontMetrics fm = getFontMetrics( Token.getLineNumberFont(), panel ); for ( int i = 0; i < 10; i++ ) { int widthOfDigit = fm.stringWidth( String .valueOf( ( char ) ( '0' + i ) ) ); if ( widthOfDigit > widestDigitInPixels ) { widestDigitInPixels = widthOfDigit; } } // end for this.lineNumberWidthInPixels = Integer.toString( totalLines ).length() * widestDigitInPixels; } /** * presumes calcIdealPayloadFootPrint has already been done. Handles calling s4CalcScrollableFootprint, * calcIdealFootprint. Just a convenience method. Needed by JDisplayAux. * * @param rendering how snippets will be rendered INLINE,IFRAME, APPLET * @param hasBar true if want size with a menu bar * @param hasLineNumbers true if want size with line numbers included. * @param hasHScroll true if want size with horizontal scroll bars included. * @param hasVScroll true if want size with vertical scroll bars included. * @param minWidth minimum width in pixels of the total display including everything. This limit only applies * if we render in this style. * @param minHeight minimum height in pixels of the total display including everything. This limit only applies * if we render in this style. * @param maxWidth maximum width permitted for the entire Applet * @param maxHeight maximum height permitted for the entire Applet * @param safetyFactor extra room to allow for fact end user may have different fronts from us. 1.0f is no safety. * 1.01f is 1% safety. * * @noinspection UnusedDeclaration */ public void calcActualAppletFootPrint( Rendering rendering, boolean hasBar, boolean hasLineNumbers, boolean hasHScroll, boolean hasVScroll, int minWidth, int minHeight, int maxWidth, int maxHeight, float safetyFactor ) { s4CalcScrollableFootprint( rendering ); s5CalcIdealAppletFootPrint( rendering, hasBar, hasLineNumbers, hasHScroll, hasVScroll, safetyFactor ); s6CalcActualAppletFootPrint( minWidth, minHeight, maxWidth, maxHeight ); } /** * Clone this object * * @return a copy of this Footprint, JDK 1.5 lets us return Footprint instead of Object */ public Object clone() throws CloneNotSupportedException { try { return super.clone(); } catch ( CloneNotSupportedException e ) { return null; } } /** * calculate the length of the document if rendered in HTML. Stores result in htmlLengthInChars * * @param tokens array of tokens */ public void s1CalcHTMLLength( Token[] tokens ) { int htmlSize = 0; for ( Token t : tokens ) { htmlSize += t.getHTML().length(); } this.htmlLengthInChars = htmlSize; } /** * calculate the payload footprint, not the Applet footprint. Leaves results in internal fields, ready to be * serialized. Size would be in in-lined whole thing. * * @param tokens array of Tokens to render. * @param panel Panel upon which this will be drawn, defines the screen resolution and FontMetrics */ public void s2CalcPayloadFootprint( Token[] tokens, Component panel ) { int widest = 0; int height = 0; int totalLines = 0; // this logic is similar to that of PrettyCanvas.paint if ( tokens != null ) { // width of current line int x = 0; for ( Token t : tokens ) { if ( t instanceof NL ) { // render blank lines compressed. int lines = ( ( NL ) t ).getCount(); switch ( lines ) { case 0: break; case 1: // single space height += Geometry.LEADING_PX; break; case 2: // 1.5 spacing height += Geometry .LEADING_PX + Geometry .BLANK_LINE_HEIGHT_PX; break; case 3: default: // anything bigger, just double space. height += Geometry .LEADING_PX + Geometry.BLANK_LINE_HEIGHT_PX * 2; break; } totalLines += lines; x = 0; } else { // also handles spaces. final String text = t.getText(); final FontMetrics fm = Footprint.getFontMetrics( t.getFont(), panel ); x += fm.stringWidth( text ); if ( x > widest ) { widest = x; } } } // end for } // end if // there is no NL on the last line, so we must compensate totalLines++; height += Geometry.LEADING_PX; if ( totalLines == 0 ) { throw new IllegalArgumentException( "empty snippet" ); } this.totalLines = totalLines; this.payloadWidth = widest; this.payloadHeight = height; calcLineNumberWidthInPixels( panel ); } // calcPayLoadFootprint /** * calculate the fatWidth and fatHeight. * * @param tokens array of Tokens comprising the snippet. */ public void s3CalcFat( Token[] tokens ) { fatWidth = 0; fatHeight = 0; for ( Token t : tokens ) { if ( t instanceof NL ) { // render blank lines compressed. int lines = ( ( NL ) t ).getCount(); switch ( lines ) { case 1: // normal single spaces, no saving. break; case 2: // 1.5 spacing, we save ~.5 of a line // watch rounding! fatHeight += Geometry .LEADING_PX - Geometry .BLANK_LINE_HEIGHT_PX; break; case 3: default: // anything bigger, just double space. fatHeight += ( Geometry .LEADING_PX - Geometry .BLANK_LINE_HEIGHT_PX ) * 2; break; } // end switch } // end if } // end for } /** * presumes s2CalcPayloadFootprint has already been done. Calculate the size of the big region we scroll over. * * @param rendering how snippets will be rendered INLINE,IFRAME, APPLET * * @noinspection PointlessArithmeticExpression */ public void s4CalcScrollableFootprint( Rendering rendering ) { this.rendering = rendering; int xPadding = 0; int yPadding = 0; int lPadding = 0; xPadding += Geometry.LEFT_PADDING_PX + Geometry.RIGHT_PADDING_PX; yPadding += Geometry.TOP_PADDING_PX + Geometry.BOTTOM_PADDING_PX; switch ( rendering ) { case APPLET: lPadding = Geometry.LINE_NUMBER_PADDING_PX + lineNumberWidthInPixels; break; case INLINE: case IFRAME: // rendering in html will take more space than in Applet. // no line numbers with frame. // we don't deal with // SCROLLED_FAT_WIDTH_PX // UNSCROLLED_FAT_WIDTH_PX // SCROLLED_FAT_HEIGHT_PX // UNSCROLLED_FAT_HEIGHT_PX // until the last minute in RenderAsIframe xPadding += fatWidth; yPadding += fatHeight; break; } this.scrollableWidthWithoutLineNumbers = this.payloadWidth + xPadding; // interim until we find out if there are line numbers. this.scrollableWidth = this.scrollableWidthWithoutLineNumbers; this.scrollableWidthWithLineNumbers = this.payloadWidth + xPadding + lPadding; this.scrollableHeight = this.payloadHeight + yPadding; } /** * presumes s4CalcScrollableFootprint has already been done Calc Ideal footprint size if expanded as big as possible * and still drew bars and scrollbars. There is utterly no point in drawing bigger than this. * * @param rendering how snippets will be rendered INLINE,IFRAME, APPLET * @param hasBar true if want size with a menu bar * @param hasLineNumbers true if want size with line numbers included. * @param hasHScroll true if want size with horizontal scroll bars included. * @param hasVScroll true if want size with vertical scroll bars included. * @param safetyFactor ratio of extra horizontal space to leave to account for varying fonts widths.. * * @noinspection PointlessArithmeticExpression */ public void s5CalcIdealAppletFootPrint( Rendering rendering, boolean hasBar, boolean hasLineNumbers, boolean hasHScroll, boolean hasVScroll, float safetyFactor ) { this.rendering = rendering; this.hasBar = hasBar; this.hasLineNumbers = hasLineNumbers; this.hasHScroll = hasHScroll; this.hasVScroll = hasVScroll; int xPadding = 0; int yPadding = 0; switch ( rendering ) { case IFRAME: if ( hasVScroll ) { xPadding += Geometry.VERTICAL_SCROLLBAR_WIDTH_PX; } if ( hasHScroll ) { yPadding += Geometry.HORIZONTAL_SCROLLBAR_HEIGHT_PX; } break; case INLINE: break; case APPLET: xPadding += Geometry.FRAME_PADDING_PX * 2; yPadding += Geometry.FRAME_PADDING_PX * 2; if ( hasBar ) { yPadding += Geometry.BAR_HEIGHT_PX; } if ( hasVScroll ) { xPadding += Geometry.VERTICAL_SCROLLBAR_WIDTH_PX; } if ( hasHScroll ) { yPadding += Geometry.HORIZONTAL_SCROLLBAR_HEIGHT_PX; } break; } this.scrollableWidth = hasLineNumbers ? this.scrollableWidthWithLineNumbers : this.scrollableWidthWithoutLineNumbers; this.idealAppletWidth = ( int ) ( ( this.scrollableWidth + xPadding ) * safetyFactor ); this.idealAppletHeight = this.scrollableHeight + yPadding; } /** * Calculate final screen real estate will use, including bar and scroll bars. Might not be possible given * constraints. presumes s5CalcIdealAppletFootPrint has already been done * * @param minWidth minimum width permitted for the entire Applet * @param minHeight minimum height permitted for the entire Applet * @param maxWidth maximum width permitted for the entire Applet * @param maxHeight maximum height permitted for the entire Applet * * @noinspection WeakerAccess */ public void s6CalcActualAppletFootPrint( int minWidth, int minHeight, int maxWidth, int maxHeight ) { if ( hasHScroll ) { minWidth = max( minWidth, Geometry .MINIMUM_SCROLLED_VIEWPORT_WIDTH_PX + ( hasVScroll ? Geometry .VERTICAL_SCROLLBAR_WIDTH_PX : 0 ) ); if ( minWidth <= maxWidth ) { // actualAPPLET_WIDTH already has scrollbars included. this.actualAPPLET_WIDTH = min( max( minWidth, this.idealAppletWidth ), maxWidth ); } else { // won't fit this.actualAPPLET_WIDTH = 0; } } else {// no hScroll if ( this.idealAppletWidth <= maxWidth ) { this.actualAPPLET_WIDTH = max( minWidth, this.idealAppletWidth ); } else { // won't fit this.actualAPPLET_WIDTH = 0; } } if ( hasVScroll ) { minHeight = max( minHeight, Geometry .MINIMUM_SCROLLED_VIEWPORT_HEIGHT_PX + ( hasBar ? Geometry.BAR_HEIGHT_PX : 0 ) + ( hasHScroll ? Geometry.HORIZONTAL_SCROLLBAR_HEIGHT_PX : 0 ) ); if ( minHeight <= maxHeight ) { // actualAPPLET_HEIGHT already has scrollbars, bar included. this.actualAPPLET_HEIGHT = min( max( minHeight, this.idealAppletHeight ), maxHeight ); } else { // won't fit this.actualAPPLET_HEIGHT = 0; } } else { // no hScroll if ( this.idealAppletHeight <= maxHeight ) { this.actualAPPLET_HEIGHT = max( minHeight, this.idealAppletHeight ); } else { // won't fit this.actualAPPLET_HEIGHT = 0; } } if ( this.actualAPPLET_WIDTH <= 0 || this.actualAPPLET_HEIGHT <= 0 ) { this.actualAPPLET_WIDTH = 0; this.actualAPPLET_HEIGHT = 0; this.viewportWidth = 0; this.viewportHeight = 0; } else { // calc viewport back from Actual. this.viewportWidth = this.actualAPPLET_WIDTH; if ( this.hasVScroll ) { this.viewportWidth -= Geometry.VERTICAL_SCROLLBAR_WIDTH_PX; } this.viewportHeight = this.actualAPPLET_HEIGHT; if ( hasHScroll ) { this.viewportHeight -= Geometry.HORIZONTAL_SCROLLBAR_HEIGHT_PX; } if ( this.hasBar ) { this.viewportHeight -= Geometry.BAR_HEIGHT_PX; } if ( this.viewportWidth <= 0 || this.viewportHeight <= 0 ) { this.viewportWidth = 0; this.viewportHeight = 0; } } } /** * String representation of the Footprint * * @return debugging string dumping most of the fields the Footprint */ public String toString() { // actual:647x579 viewport:632x563 ideal:647x579 scrollable:620x563 fat:0x56 payload:608x499 return new FastCat( 7 * 4 + 6 * 2 + 1 ).append( "Footprint" ) .append( " scroll:", hasHScroll, "/", hasVScroll ) .append( " lines:", totalLines ) .append( " bar:", hasBar ) .append( " lineNumbers:", hasLineNumbers ) .append( " rendering:", rendering ) .append( " way:", wayRendered ) .append( " uid:", serialVersionUID ) .append( "\n payload:", payloadWidth, "x", payloadHeight ) .append( "\n scrollable:", scrollableWidth, "x", scrollableHeight ) .append( "\n viewport:", viewportWidth, "x", viewportHeight ) .append( "\n actual:", actualAPPLET_WIDTH, "x", actualAPPLET_HEIGHT ) .append( "\n ideal:", idealAppletWidth, "x", idealAppletHeight ) .append( "\n fat:", fatWidth, "x", fatHeight ) .toString(); } }