FontConf.java
package swingtree.style;
import com.google.errorprone.annotations.Immutable;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import swingtree.UI;
import swingtree.api.Configurator;
import javax.swing.*;
import java.awt.*;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* An immutable, wither-like method based config API for font styles
* that is part of the full {@link StyleConf} configuration object.
* <p>
* The following properties with their respective purpose are available:
* <br>
* <ol>
* <li><b>Name</b>
* <p>
* The name of the font, which is essentially the font family.
* This will ultimately translate to {@link Font#getFamily()}.<br>
* You may specify the font family name through the {@link #family(String)} method.
* </p>
* </li>
* <li><b>Size</b>
* <p>
* The size of the font in points,
* which will ultimately translate to {@link Font#getSize()}.
* Use the {@link #size(int)} method to specify the size of the font.
* </p>
* </li>
* <li><b>Posture</b>
* <p>
* The posture of the font, which is a value between 0 and 1.
* <br>
* A value of 0 means that the font is not italic,
* while a value of 1 means that the font is "fully" italic.
* <br>
* You can use the {@link #posture(float)} method to specify the posture of the font.
* </p>
* </li>
* <li><b>Weight</b>
* <p>
* The weight of the font (boldness, see {@link Font#BOLD}),
* which is a value between 0 and 2.
* </p>
* <p>
* The weight of the font can be specified using the {@link #weight(double)} method.
* </p>
* </li>
* <li><b>Spacing (Tracking)</b>
* <p>
* This property controls the tracking which is a floating point number
* with the default value of
* {@code 0}, meaning no additional tracking is added to the font.
*
* <p>Useful constant values are the predefined {@link TextAttribute#TRACKING_TIGHT} and {@link
* TextAttribute#TRACKING_LOOSE} values, which represent values of {@code -0.04} and {@code 0.04},
*
* <p>The tracking value is multiplied by the font point size and
* passed through the font transform to determine an additional
* amount to add to the advance of each glyph cluster. Positive
* tracking values will inhibit formation of optional ligatures.
* Tracking values are typically between {@code -0.1} and
* {@code 0.3}; values outside this range are generally not
* desirable.
* </p>
* <p>
* You can use the {@link #spacing(float)} method to specify the tracking of the font.
* </p>
* </li>
* <li><b>Color</b>
* <p>
* The color of the font, which translates to the text property
* {@link TextAttribute#FOREGROUND}.
* </p>
* <p>
* You can use the {@link #color(Color)} or {@link #color(String)} methods to specify the color of the font.
* </p>
* </li>
* <li><b>Background Color</b>
* <p>
* The background color of the font
* which translates to the text property {@link TextAttribute#BACKGROUND}.
* </p>
* </li>
* <li><b>Selection Color</b>
* <p>
* The selection color of the font, which translates to
* {@link javax.swing.text.JTextComponent#setSelectionColor(Color)}.
* <br>
* Note that this property is only relevant for text components,
* most components do not support text selection.
* </p>
* </li>
* <li><b>Underlined</b>
* <p>
* Whether or not the font is underlined.
* This will ultimately translate to {@link TextAttribute#UNDERLINE}.
* </p>
* </li>
* <li><b>Strike</b>
* <p>
* Whether or not the font is strike through.
* This will ultimately translate to {@link TextAttribute#STRIKETHROUGH}.
* </p>
* </li>
* <li><b>Transform</b>
* <p>
* The transform of the font, which is an {@link AffineTransform} instance.
* </p>
* </li>
* <li><b>Paint</b>
* <p>
* The paint of the font, which is a {@link Paint} instance.
* Note that specifying a custom paint will override the effects of the color property
* as the color property is in essence merely a convenience property for a
* paint painting across the entire font area homogeneously using the specified color.
* </p>
* </li>
* <li><b>Background Paint</b>
* <p>
* The background paint of the font, which is a {@link Paint} instance
* that is used to paint the background of the font.
* </p>
* </li>
* <li><b>Horizontal Alignment</b>
* <p>
* The horizontal alignment of the font.
* <br>
* Note that this property is not relevant for all components,
* It will usually only be relevant for {@link JLabel}, {@link AbstractButton} and {@link JTextField}
* types or maybe some custom components.
* Not all components support horizontal alignment.
* </p>
* </li>
* <li><b>Vertical Alignment</b>
* <p>
* The vertical alignment of the font.
* <br>
* Note that this property is not relevant for all components,
* It will usually only be relevant for {@link JLabel}, {@link AbstractButton} and {@link JTextField}
* types or maybe some custom components.
* Not all components support vertical alignment.
* </p>
* </li>
* </ol>
* <p>
* You can use the {@link #none()} method to specify that no font should be used,
* as the instance returned by that method is a font style with an empty string as its name,
* and other properties set to their default values,
* effectively making it a representation of the absence of a font style.
* <p>
* Also note that this class is immutable, which means that wither-like methods
* will always return new instances of this class, leaving the original instance untouched.
* <br>
* This means that you can not modify a font style instance directly, but you can
* easily create a modified copy of it by calling one of the wither-like methods.
*
* @author Daniel Nepp
*/
@Immutable
@SuppressWarnings("Immutable")
public final class FontConf
{
private static final Logger log = org.slf4j.LoggerFactory.getLogger(FontConf.class);
private static final FontConf _NONE = new FontConf(
"", // Font name (family)
0, // size
0, // posture
0, // weight
0, // spacing
null, // selection color
null, // is underlined
null, // is strike through
null, // transform
FontPaintConf.none(), // paint
FontPaintConf.none(), // background paint
UI.HorizontalAlignment.UNDEFINED, // horizontal alignment
UI.VerticalAlignment.UNDEFINED // vertical alignment
);
public static FontConf none() { return _NONE; }
static FontConf of(
String name,
int fontSize,
float posture,
float weight,
float spacing,
@Nullable Color selectionColor,
@Nullable Boolean isUnderline,
@Nullable Boolean isStrike,
@Nullable AffineTransform transform,
FontPaintConf paint,
FontPaintConf backgroundPaint,
UI.HorizontalAlignment horizontalAlignment,
UI.VerticalAlignment verticalAlignment
) {
if (
name.isEmpty() &&
fontSize == 0 &&
posture == 0 &&
weight == 0 &&
spacing == 0 &&
selectionColor == null &&
isUnderline == null &&
isStrike == null &&
transform == null &&
paint.equals(FontPaintConf.none()) &&
backgroundPaint.equals(FontPaintConf.none()) &&
horizontalAlignment == _NONE._horizontalAlignment &&
verticalAlignment == _NONE._verticalAlignment
)
return _NONE;
else
return new FontConf(
name,
fontSize,
posture,
weight,
spacing,
selectionColor,
isUnderline,
isStrike,
transform,
paint,
backgroundPaint,
horizontalAlignment,
verticalAlignment
);
}
private final String _familyName;
private final int _size;
private final float _posture;
private final float _weight;
private final float _spacing;
private final @Nullable Color _selectionColor; // Only relevant for text components with selection support.
private final @Nullable Boolean _isUnderlined;
private final @Nullable Boolean _isStrike;
private final @Nullable AffineTransform _transform;
private final FontPaintConf _paint;
private final FontPaintConf _backgroundPaint;
private final UI.HorizontalAlignment _horizontalAlignment;
private final UI.VerticalAlignment _verticalAlignment;
private FontConf(
String name,
int fontSize,
float posture,
float weight,
float spacing,
@Nullable Color selectionColor,
@Nullable Boolean isUnderline,
@Nullable Boolean isStrike,
@Nullable AffineTransform transform,
FontPaintConf paint,
FontPaintConf backgroundPaint,
UI.HorizontalAlignment horizontalAlignment,
UI.VerticalAlignment verticalAlignment
) {
_familyName = Objects.requireNonNull(name);
_size = fontSize;
_posture = posture;
_weight = weight;
_spacing = spacing;
_selectionColor = selectionColor;
_isUnderlined = isUnderline;
_isStrike = isStrike;
_transform = transform;
_paint = paint;
_backgroundPaint = backgroundPaint;
_horizontalAlignment = horizontalAlignment;
_verticalAlignment = verticalAlignment;
}
String family() { return _familyName; }
int size() { return _size; }
float posture() { return _posture; }
float weight() { return _weight; }
float spacing() { return _spacing; }
Optional<Color> selectionColor() { return Optional.ofNullable(_selectionColor); }
boolean isUnderlined() { return _isUnderlined != null ? _isUnderlined : false; }
Optional<AffineTransform> transform() { return Optional.ofNullable(_transform); }
Optional<Paint> paint() {
if ( FontPaintConf.none().equals(_paint) )
return Optional.empty();
return Optional.ofNullable(_paint.getFor(BoxModelConf.none()));
}
Optional<Paint> backgroundPaint() {
if ( FontPaintConf.none().equals(_backgroundPaint) )
return Optional.empty();
return Optional.ofNullable(_backgroundPaint.getFor(BoxModelConf.none()));
}
UI.HorizontalAlignment horizontalAlignment() { return _horizontalAlignment; }
UI.VerticalAlignment verticalAlignment() { return _verticalAlignment; }
/**
* Returns an updated font config with the specified font family name.
*
* @param fontFamily The font family name to use for the {@link Font#getFamily()} property.
* @return A new font style with the specified font family name.
*/
public FontConf family( String fontFamily ) {
return FontConf.of(fontFamily, _size, _posture, _weight, _spacing, _selectionColor, _isUnderlined, _isStrike, _transform, _paint, _backgroundPaint, _horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified font size,
* which will translate to a {@link Font} instance with the specified size
* (see {@link Font#getSize()}).
*
* @param fontSize The font size to use for the {@link Font#getSize()} property.
* @return A new font style with the specified font size.
*/
public FontConf size( int fontSize ) {
return FontConf.of(_familyName, fontSize, _posture, _weight, _spacing, _selectionColor, _isUnderlined, _isStrike, _transform, _paint, _backgroundPaint, _horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified posture, defining the tilt of the font.
* A {@link Font} with a higher posture value will be more italic.
* (see {@link Font#isItalic()}).
*
* @param posture The posture to use for the {@link Font#isItalic()} property.
* @return A new font style with the specified posture.
*/
public FontConf posture( float posture ) {
return FontConf.of(_familyName, _size, posture, _weight, _spacing, _selectionColor, _isUnderlined, _isStrike, _transform, _paint, _backgroundPaint, _horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified weight, defining the boldness of the font.
* A {@link Font} with a higher weight value will be bolder.
* (see {@link Font#isBold()}).
*
* @param fontWeight The weight to use for the {@link Font#isBold()} property.
* @return A new font style with the specified weight.
*/
public FontConf weight( double fontWeight ) {
return FontConf.of(_familyName, _size, _posture, (float) fontWeight, _spacing, _selectionColor, _isUnderlined, _isStrike, _transform, _paint, _backgroundPaint, _horizontalAlignment, _verticalAlignment);
}
/**
* Determines if the font should be plain, bold, italic or bold and italic
* based on the provided {@link UI.FontStyle} parameter,
* which may be {@link UI.FontStyle#PLAIN}, {@link UI.FontStyle#BOLD},
* {@link UI.FontStyle#ITALIC} or {@link UI.FontStyle#BOLD_ITALIC}.<br>
* <b>
* Note that this will override any previous bold or italic settings.
* </b>
* @param fontStyle The font style to use for the font in the {@link UI.FontStyle} enum.
* @return An updated font config with the specified font style.
*/
public FontConf style( UI.FontStyle fontStyle ) {
switch (fontStyle) {
case PLAIN:
return weight(0).posture(0);
case BOLD:
return weight(2).posture(0);
case ITALIC:
return weight(0).posture(0.2f);
case BOLD_ITALIC:
return weight(2).posture(0.2f);
default:
return this;
}
}
/**
* Returns an updated font config with the specified spacing, defining the tracking of the font.
* The tracking value is multiplied by the font point size and
* passed through the font transform to determine an additional
* amount to add to the advance of each glyph cluster. Positive
* tracking values will inhibit formation of optional ligatures.
* Tracking values are typically between {@code -0.1} and
* {@code 0.3}; values outside this range are generally not
* desirable.
*
* @param spacing The spacing to use for the {@link TextAttribute#TRACKING} property.
* @return A new font style with the specified spacing.
*/
public FontConf spacing( float spacing ) {
return FontConf.of(_familyName, _size, _posture, _weight, spacing, _selectionColor, _isUnderlined, _isStrike, _transform, _paint, _backgroundPaint, _horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified color,
* which will be used for the {@link TextAttribute#FOREGROUND} property
* of the resulting {@link Font} instance.
*
* @param color The color to use for the {@link TextAttribute#FOREGROUND} property.
* @return A new font style with the specified color.
*/
public FontConf color( Color color ) {
Objects.requireNonNull(color);
if ( StyleUtil.isUndefinedColor(color) )
color = null;
if ( _paint.representsColor(color) )
return this;
FontPaintConf paintConf = FontPaintConf.of(color, null, null, null);
return FontConf.of(_familyName, _size, _posture, _weight, _spacing, _selectionColor, _isUnderlined, _isStrike, _transform, paintConf, _backgroundPaint, _horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified color string used to define the font color.
* The color will be used for the {@link TextAttribute#FOREGROUND} property
* of the resulting {@link Font} instance.
*
* @param colorString The color string to use for the {@link TextAttribute#FOREGROUND} property.
* @return A new font style with the specified color.
*/
public FontConf color( String colorString ) {
Objects.requireNonNull(colorString);
Color newColor;
try {
if ( colorString.isEmpty() )
newColor = UI.Color.UNDEFINED;
else
newColor = UI.color(colorString);
} catch ( Exception e ) {
log.error("Failed to parse color string: '"+colorString+"'", e);
return this;
}
return color(newColor);
}
/**
* Returns an updated font config with the specified background color.
* The color value will be used for the {@link TextAttribute#BACKGROUND} property
* of the resulting {@link Font} instance.
*
* @param backgroundColor The background color to use for the {@link TextAttribute#BACKGROUND} property.
* @return A new font style with the specified background color.
*/
public FontConf backgroundColor( Color backgroundColor ) {
Objects.requireNonNull(backgroundColor);
if ( StyleUtil.isUndefinedColor(backgroundColor) )
backgroundColor = null;
if ( _backgroundPaint.representsColor(backgroundColor) )
return this;
FontPaintConf backgroundPaintConf = FontPaintConf.of(backgroundColor, null, null, null);
return FontConf.of(_familyName, _size, _posture, _weight, _spacing, _selectionColor, _isUnderlined, _isStrike, _transform, _paint, backgroundPaintConf, _horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified background color string used to define the background color.
* The background color will be used for the {@link TextAttribute#BACKGROUND} property
* of the resulting {@link Font} instance.
*
* @param colorString The color string to use for the {@link TextAttribute#BACKGROUND} property.
* @return A new font style with the specified background color.
*/
public FontConf backgroundColor( String colorString ) {
Objects.requireNonNull(colorString);
Color newColor;
try {
if ( colorString.isEmpty() )
newColor = UI.Color.UNDEFINED;
else
newColor = UI.color(colorString);
} catch ( Exception e ) {
log.error("Failed to parse color string: '{}'", colorString, e);
return this;
}
return backgroundColor(newColor);
}
/**
* Returns an updated font config with the specified selection color.
* The selection color will be used for the selection color of the font.
* Note that not all components support text selection, so this property may not
* have an effect on all components.
*
* @param selectionColor The selection color to use for the selection color of the font.
* @return A new font style with the specified selection color.
*/
public FontConf selectionColor( Color selectionColor ) {
Objects.requireNonNull(selectionColor);
if ( StyleUtil.isUndefinedColor(selectionColor) )
selectionColor = null;
if ( Objects.equals(selectionColor, _selectionColor) )
return this;
return FontConf.of(_familyName, _size, _posture, _weight, _spacing, selectionColor, _isUnderlined, _isStrike, _transform, _paint, _backgroundPaint, _horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified selection color string used to define the selection color.
* The selection color will be used for the selection color of the font.
* Note that not all components support text selection, so this property may not
* have an effect on all components.
*
* @param colorString The color string to use for the selection color of the font.
* @return A new font style with the specified selection color.
*/
public FontConf selectionColor( String colorString ) {
Objects.requireNonNull(colorString);
Color newColor;
try {
if ( colorString.isEmpty() )
newColor = UI.Color.UNDEFINED;
else
newColor = UI.color(colorString);
} catch ( Exception e ) {
log.error("Failed to parse color string: '"+colorString+"'", e);
return this;
}
return selectionColor(newColor);
}
/**
* Returns an updated font config with the specified underlined property.
* This boolean will translate to the {@link TextAttribute#UNDERLINE} property
* of the resulting {@link Font} instance.
*
* @param underlined Whether the font should be underlined.
* @return A new font style with the specified underlined property.
*/
public FontConf underlined( boolean underlined ) {
return FontConf.of(_familyName, _size, _posture, _weight, _spacing, _selectionColor, underlined, _isStrike, _transform, _paint, _backgroundPaint, _horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified strike through property.
* This boolean will translate to the {@link TextAttribute#STRIKETHROUGH} property
* of the resulting {@link Font} instance.
*
* @param strike Whether the font should be strike through.
* @return A new font style with the specified strike through property.
*/
public FontConf strikeThrough( boolean strike ) {
return FontConf.of(_familyName, _size, _posture, _weight, _spacing, _selectionColor, _isUnderlined, strike, _transform, _paint, _backgroundPaint, _horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified transform.
* This transform will be used for the {@link TextAttribute#TRANSFORM} property
* of the resulting {@link Font} instance.
*
* @param transform The transform to use for the {@link TextAttribute#TRANSFORM} property.
* @return A new font style with the specified transform.
*/
public FontConf transform( @Nullable AffineTransform transform ) {
return FontConf.of(_familyName, _size, _posture, _weight, _spacing, _selectionColor, _isUnderlined, _isStrike, transform, _paint, _backgroundPaint, _horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified paint.
* This paint will be used for the {@link TextAttribute#FOREGROUND} property
* of the resulting {@link Font} instance.
* Note that specifying a custom paint will override the effects of the color property
* as the color property is in essence merely a convenience property for a
* paint painting across the entire font area homogeneously using the specified color.
* <br>
* Note that this will override the effects of the {@link #color(Color)}, {@link #color(String)},
* {@link #noise(Configurator)} or {@link #gradient(Configurator)} methods
* as a font can only have one paint.
*
* @param paint The paint to use for the {@link TextAttribute#FOREGROUND} property.
* @return A new font style with the specified paint.
*/
public FontConf paint( @Nullable Paint paint ) {
FontPaintConf paintConf = FontPaintConf.of(null, paint, null, null);
return FontConf.of(_familyName, _size, _posture, _weight, _spacing, _selectionColor, _isUnderlined, _isStrike, _transform, paintConf, _backgroundPaint, _horizontalAlignment, _verticalAlignment);
}
/**
* Configures a noise function based {@link Paint} for the font appearance,
* using a configurator function that takes a {@link NoiseConf} instance
* and returns an updated {@link NoiseConf} instance with the desired properties.
* <br>
* Keep in mind that this will override the effects of the {@link #color(Color)},
* {@link #color(String)}, {@link #paint(Paint)} or {@link #gradient(Configurator)}
* methods as a font can only have one paint.
*
* @param configurator The configurator function that takes a {@link NoiseConf} instance
* and returns an updated {@link NoiseConf} instance with the desired properties.
* @return A new font style with the specified noise paint.
*/
public FontConf noise( Configurator<NoiseConf> configurator ) {
Objects.requireNonNull(configurator);
FontPaintConf paintConf = _paint.noise(configurator);
return _withPaintConf(paintConf);
}
/**
* Configures a gradient function based {@link Paint} for the font appearance,
* using a configurator function that takes a {@link GradientConf} instance
* and returns an updated {@link GradientConf} instance with the desired properties.
* <br>
* Keep in mind that this will override the effects of the {@link #color(Color)},
* {@link #color(String)}, {@link #paint(Paint)} or {@link #noise(Configurator)}
* methods as a font can only have one paint.
*
* @param configurator The configurator function that takes a {@link GradientConf} instance
* and returns an updated {@link GradientConf} instance with the desired properties.
* @return A new font style with the specified gradient paint.
*/
public FontConf gradient( Configurator<GradientConf> configurator ) {
Objects.requireNonNull(configurator);
FontPaintConf paintConf = _paint.gradient(configurator);
return _withPaintConf(paintConf);
}
private FontConf _withPaintConf( FontPaintConf paintConf ) {
return FontConf.of(_familyName, _size, _posture, _weight, _spacing, _selectionColor, _isUnderlined, _isStrike, _transform, paintConf, _backgroundPaint, _horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified background paint.
* This paint will be used for the {@link TextAttribute#BACKGROUND} property
* of the resulting {@link Font} instance.
*
* @param backgroundPaint The background paint to use for the {@link TextAttribute#BACKGROUND} property.
* @return A new font style with the specified background paint.
*/
public FontConf backgroundPaint( @Nullable Paint backgroundPaint ) {
FontPaintConf backgroundPaintConf = FontPaintConf.of(null, backgroundPaint, null, null);
return FontConf.of(_familyName, _size, _posture, _weight, _spacing, _selectionColor, _isUnderlined, _isStrike, _transform, _paint, backgroundPaintConf, _horizontalAlignment, _verticalAlignment);
}
/**
* Configures a noise function based {@link Paint} for the background of the font appearance,
* using a configurator function that takes a {@link NoiseConf} instance
* and returns an updated {@link NoiseConf} instance with the desired properties.
* <br>
* Note that the background can only have one paint, so specifying a noise based paint
* will override the effects of the {@link #backgroundPaint(Paint)}, {@link #backgroundGradient(Configurator)},
* and {@link #backgroundColor(String)} methods.
*
* @param configurator The configurator function that takes a {@link NoiseConf} instance
* and returns an updated {@link NoiseConf} instance with the desired properties.
* @return A new font style with the specified noise background paint.
*/
public FontConf backgroundNoise( Configurator<NoiseConf> configurator ) {
Objects.requireNonNull(configurator);
FontPaintConf backgroundPaintConf = _backgroundPaint.noise(configurator);
return _withBackgroundPaintConf(backgroundPaintConf);
}
/**
* Configures a gradient function based {@link Paint} for the background of the font appearance,
* using a configurator function that takes a {@link GradientConf} instance
* and returns an updated {@link GradientConf} instance with the desired properties.
* <br>
* The background of a font can only have one paint, so specifying a gradient based paint
* will override the effects of the {@link #backgroundPaint(Paint)}, {@link #backgroundNoise(Configurator)},
* and {@link #backgroundColor(String)} methods.
*
* @param configurator The configurator function that takes a {@link GradientConf} instance
* and returns an updated {@link GradientConf} instance with the desired properties.
* @return A new font style with the specified gradient background paint.
*/
public FontConf backgroundGradient( Configurator<GradientConf> configurator ) {
Objects.requireNonNull(configurator);
FontPaintConf backgroundPaintConf = _backgroundPaint.gradient(configurator);
return _withBackgroundPaintConf(backgroundPaintConf);
}
private FontConf _withBackgroundPaintConf( FontPaintConf backgroundPaintConf ) {
return FontConf.of(_familyName, _size, _posture, _weight, _spacing, _selectionColor, _isUnderlined, _isStrike, _transform, _paint, backgroundPaintConf, _horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified horizontal alignment.
* This property is not relevant for all components,
* It will usually only be relevant for {@link JLabel}, {@link AbstractButton} and {@link JTextField}
* types or maybe some custom components.
* Not all components support horizontal alignment.
* This will also not have an effect for font configs which
* are part of the {@link TextConf}.
* <b>
* This method is deliberately package-private as it is not relevant for the
* {@link ComponentStyleDelegate#componentFont(Configurator)} or
* {@link TextConf#font(Configurator)} methods. <br>
* <br>
* It only makes sense to specify this property
* through the {@link ComponentStyleDelegate#fontAlignment(UI.HorizontalAlignment)}
* method!!
* </b>
*
* @param horizontalAlignment The horizontal alignment to use for the font.
* @return A new font style with the specified horizontal alignment.
*/
FontConf horizontalAlignment( UI.HorizontalAlignment horizontalAlignment ) {
return FontConf.of(_familyName, _size, _posture, _weight, _spacing, _selectionColor, _isUnderlined, _isStrike, _transform, _paint, _backgroundPaint, horizontalAlignment, _verticalAlignment);
}
/**
* Returns an updated font config with the specified vertical alignment.
* This property is not relevant for all components,
* It will usually only be relevant for {@link JLabel}, {@link AbstractButton} and {@link JTextField}
* types or maybe some custom components.
* Not all components support vertical alignment.
* This will also not have an effect for font configs which
* are part of the {@link TextConf}.
* <b>
* This method is deliberately package-private as it is not relevant for the
* {@link ComponentStyleDelegate#componentFont(Configurator)} or
* {@link TextConf#font(Configurator)} methods. <br>
* <br>
* It only makes sense to specify this property
* through the {@link ComponentStyleDelegate#fontAlignment(UI.VerticalAlignment)}
* method!!
* </b>
*
* @param verticalAlignment The vertical alignment to use for the font.
* @return A new font style with the specified vertical alignment.
*/
FontConf verticalAlignment( UI.VerticalAlignment verticalAlignment ) {
return FontConf.of(_familyName, _size, _posture, _weight, _spacing, _selectionColor, _isUnderlined, _isStrike, _transform, _paint, _backgroundPaint, _horizontalAlignment, verticalAlignment);
}
FontConf withPropertiesFromFont( Font font )
{
if ( StyleUtil.isUndefinedFont(font) )
return this;
Map<TextAttribute, ?> attributeMap = font.getAttributes();
String family = font.getFamily();
int size = font.getSize();
float posture = font.isItalic() ? 0.2f : 0f;
try {
if (attributeMap.containsKey(TextAttribute.POSTURE))
posture = ((Number) attributeMap.get(TextAttribute.POSTURE)).floatValue();
} catch (Exception e) {
log.debug("Failed to fetch TextAttribute.POSTURE in font attributes '" + attributeMap + "' of font '" + font + "'", e);
}
float weight = font.isBold() ? 2f : 0f;
try {
if (attributeMap.containsKey(TextAttribute.WEIGHT))
weight = ((Number) attributeMap.get(TextAttribute.WEIGHT)).floatValue();
} catch (Exception e) {
log.debug("Failed to fetch TextAttribute.WEIGHT in font attributes '" + attributeMap + "' of font '" + font + "'", e);
}
float spacing = _spacing;
try {
if (attributeMap.containsKey(TextAttribute.TRACKING))
spacing = ((Number) attributeMap.get(TextAttribute.TRACKING)).floatValue();
} catch (Exception e) {
log.debug("Failed to fetch TextAttribute.TRACKING in font attributes '" + attributeMap + "' of font '" + font + "'", e);
}
Color selectionColor = _selectionColor;
// The selection color is not a text attribute, but a component property, like for text areas.
boolean isUnderline = ( _isUnderlined != null ? _isUnderlined : false );
try {
if (attributeMap.containsKey(TextAttribute.UNDERLINE))
isUnderline = Objects.equals(attributeMap.get(TextAttribute.UNDERLINE), TextAttribute.UNDERLINE_ON);
} catch (Exception e) {
log.debug("Failed to fetch TextAttribute.UNDERLINE in font attributes '" + attributeMap + "' of font '" + font + "'", e);
}
boolean isStriked = ( _isStrike != null ? _isStrike : false );
try {
if (attributeMap.containsKey(TextAttribute.STRIKETHROUGH))
isStriked = Objects.equals(attributeMap.get(TextAttribute.STRIKETHROUGH), TextAttribute.STRIKETHROUGH_ON);
} catch (Exception e) {
log.debug("Failed to fetch TextAttribute.STRIKETHROUGH in font attributes '" + attributeMap + "' of font '" + font + "'", e);
}
AffineTransform transform = _transform;
try {
if (attributeMap.containsKey(TextAttribute.TRANSFORM))
transform = (AffineTransform) attributeMap.get(TextAttribute.TRANSFORM);
} catch (Exception e) {
log.debug("Failed to fetch TextAttribute.TRANSFORM in font attributes '" + attributeMap + "' of font '" + font + "'", e);
}
FontPaintConf paint = _paint;
try {
Paint found = null;
if (attributeMap.containsKey(TextAttribute.FOREGROUND))
found = (Paint) attributeMap.get(TextAttribute.FOREGROUND);
if (found != null)
paint = FontPaintConf.of(null, found, null, null);
} catch (Exception e) {
log.warn("Failed to extract font attributes from font: " + font, e);
}
FontPaintConf backgroundPaint = _backgroundPaint;
try {
Paint found = null;
if (attributeMap.containsKey(TextAttribute.BACKGROUND))
found = (Paint) attributeMap.get(TextAttribute.BACKGROUND);
if (found != null)
backgroundPaint = FontPaintConf.of(null, found, null, null);
} catch (Exception e) {
log.warn("Failed to extract font attributes from font: " + font, e);
}
Objects.requireNonNull(font);
return FontConf.of(
family,
size,
posture,
weight,
spacing,
selectionColor,
isUnderline,
isStriked,
transform,
paint,
backgroundPaint,
_horizontalAlignment,
_verticalAlignment
);
}
Optional<Font> createDerivedFrom( Font existingFont, JComponent component ) {
return _createDerivedFrom(existingFont, component);
}
Optional<Font> createDerivedFrom( Font existingFont, BoxModelConf boxModel ) {
return _createDerivedFrom(existingFont, boxModel);
}
private Optional<Font> _createDerivedFrom( Font existingFont, Object boxModelOrComponent )
{
if ( this.equals(_NONE) )
return Optional.empty();
boolean isChange = false;
if ( existingFont == null )
existingFont = new JLabel().getFont();
Map<TextAttribute, Object> currentAttributes = (Map<TextAttribute, Object>) existingFont.getAttributes();
Map<TextAttribute, Object> attributes = new HashMap<>();
if ( _size > 0 ) {
isChange = isChange || !Integer.valueOf(_size).equals(currentAttributes.get(TextAttribute.SIZE));
attributes.put(TextAttribute.SIZE, _size);
}
if ( _posture > 0 ) {
isChange = isChange || !Float.valueOf(_posture).equals(currentAttributes.get(TextAttribute.POSTURE));
attributes.put(TextAttribute.POSTURE, _posture);
}
if ( _weight > 0 ) {
isChange = isChange || !Float.valueOf(_weight).equals(currentAttributes.get(TextAttribute.WEIGHT));
attributes.put(TextAttribute.WEIGHT, _weight);
}
if ( _spacing != 0 ) {
isChange = isChange || !Float.valueOf(_spacing).equals(currentAttributes.get(TextAttribute.TRACKING));
attributes.put(TextAttribute.TRACKING, _spacing);
}
if ( _isUnderlined != null ) {
isChange = isChange || !Objects.equals(_isUnderlined, currentAttributes.get(TextAttribute.UNDERLINE));
attributes.put(TextAttribute.UNDERLINE, _isUnderlined);
}
if ( _isStrike != null ) {
isChange = isChange || !Objects.equals(_isStrike, currentAttributes.get(TextAttribute.STRIKETHROUGH));
attributes.put(TextAttribute.STRIKETHROUGH, _isStrike);
}
if ( _transform != null ) {
isChange = isChange || !Objects.equals(_transform, currentAttributes.get(TextAttribute.TRANSFORM));
attributes.put(TextAttribute.TRANSFORM, _transform);
}
if ( !_familyName.isEmpty() ) {
isChange = isChange || !Objects.equals(_familyName, currentAttributes.get(TextAttribute.FAMILY));
attributes.put(TextAttribute.FAMILY, _familyName);
}
if ( !_paint.equals(FontPaintConf.none()) ) {
try {
Paint paint = null;
if ( boxModelOrComponent instanceof BoxModelConf )
paint = _paint.getFor((BoxModelConf) boxModelOrComponent);
else if ( boxModelOrComponent instanceof JComponent )
paint = _paint.getFor((JComponent) boxModelOrComponent);
isChange = isChange || !Objects.equals(paint, currentAttributes.get(TextAttribute.FOREGROUND));
attributes.put(TextAttribute.FOREGROUND, paint);
} catch ( Exception e ) {
log.error("Failed to create paint from paint config: "+_paint, e);
}
}
if ( !_backgroundPaint.equals(FontPaintConf.none()) ) {
try {
Paint backgroundPaint = null;
if ( boxModelOrComponent instanceof BoxModelConf )
backgroundPaint = _backgroundPaint.getFor((BoxModelConf) boxModelOrComponent);
else if ( boxModelOrComponent instanceof JComponent )
backgroundPaint = _backgroundPaint.getFor((JComponent) boxModelOrComponent);
isChange = isChange || !Objects.equals(backgroundPaint, currentAttributes.get(TextAttribute.BACKGROUND));
attributes.put(TextAttribute.BACKGROUND, backgroundPaint);
} catch ( Exception e ) {
log.error("Failed to create paint from paint config: "+_backgroundPaint, e);
}
}
if ( isChange )
return Optional.of(existingFont.deriveFont(attributes));
else
return Optional.empty();
}
FontConf _scale(double scale ) {
if ( scale == 1.0 )
return this;
else if ( this.equals(_NONE) )
return this;
else
return FontConf.of(
_familyName,
(int) Math.round(_size * scale),
_posture,
_weight,
_spacing,
_selectionColor,
_isUnderlined,
_isStrike,
_transform,
_paint,
_backgroundPaint,
_horizontalAlignment,
_verticalAlignment
);
}
@Override
public int hashCode()
{
int hash = 7;
hash = 97 * hash + Objects.hashCode(_familyName);
hash = 97 * hash + _size;
hash = 97 * hash + Float.hashCode(_posture);
hash = 97 * hash + Float.hashCode(_weight);
hash = 97 * hash + Float.hashCode(_spacing);
hash = 97 * hash + Objects.hashCode(_selectionColor);
hash = 97 * hash + Objects.hashCode(_isUnderlined);
hash = 97 * hash + Objects.hashCode(_transform);
hash = 97 * hash + Objects.hashCode(_paint);
hash = 97 * hash + Objects.hashCode(_backgroundPaint);
hash = 97 * hash + Objects.hashCode(_horizontalAlignment);
hash = 97 * hash + Objects.hashCode(_verticalAlignment);
return hash;
}
@Override
public boolean equals( Object obj )
{
if ( obj == null )
return false;
if ( getClass() != obj.getClass() )
return false;
final FontConf other = (FontConf)obj;
if ( !Objects.equals(_familyName, other._familyName) )
return false;
if ( _size != other._size )
return false;
if ( _posture != other._posture)
return false;
if ( _weight != other._weight )
return false;
if ( _spacing != other._spacing )
return false;
if ( !Objects.equals(_selectionColor, other._selectionColor) )
return false;
if ( !Objects.equals(_isUnderlined, other._isUnderlined) )
return false;
if ( !Objects.equals(_transform, other._transform) )
return false;
if ( !Objects.equals(_paint, other._paint) )
return false;
if ( !Objects.equals(_backgroundPaint, other._backgroundPaint) )
return false;
if ( _horizontalAlignment != other._horizontalAlignment )
return false;
if ( _verticalAlignment != other._verticalAlignment )
return false;
return true;
}
@Override
public String toString()
{
if ( this.equals(_NONE) )
return this.getClass().getSimpleName() + "[NONE]";
String underline = ( _isUnderlined == null ? "?" : String.valueOf(_isUnderlined) );
String strike = ( _isStrike == null ? "?" : String.valueOf(_isStrike) );
String transform = ( _transform == null ? "?" : _transform.toString() );
String horizontalAlign = ( _horizontalAlignment == UI.HorizontalAlignment.UNDEFINED ? "?" : _horizontalAlignment.toString() );
String verticalAlign = ( _verticalAlignment == UI.VerticalAlignment.UNDEFINED ? "?" : _verticalAlignment.toString() );
return this.getClass().getSimpleName() + "[" +
"family=" + _familyName + ", " +
"size=" + _size + ", " +
"posture=" + _posture + ", " +
"weight=" + _weight + ", " +
"spacing=" + _spacing + ", " +
"underlined=" + underline + ", " +
"strikeThrough=" + strike + ", " +
"selectionColor=" + StyleUtil.toString(_selectionColor) + ", " +
"transform=" + transform + ", " +
"paint=" + _paint + ", " +
"backgroundPaint=" + _backgroundPaint + ", " +
"horizontalAlignment=" + horizontalAlign + ", " +
"verticalAlignment=" + verticalAlign +
"]";
}
}