/*
* [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:
* - s1CalcHTMLLength, by JPrep.
- s2CalcPayloadFootprint: just the tokens. by JPrep.
- s3CalcFat: just
* tokens if rendered in a browser. by JPrep.
- s4CalcScrollableFootprint: just the scrollable canvas
* - s5CalcIdealAppletFootPrint: includes menu bar, usually bigger than size allocated. Size we would use if we had an
* infinitely big screen.
- 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();
}
}