All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dua3.utility.text.Font Maven / Gradle / Ivy

There is a newer version: 15.1.1
Show newest version
// Copyright (c) 2019 Axel Howind
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

package com.dua3.utility.text;

import com.dua3.cabe.annotations.Nullable;
import com.dua3.utility.data.Color;
import com.dua3.utility.lang.LangUtil;

import java.util.Locale;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Generic font class.
 */
@SuppressWarnings("MagicCharacter")
public class Font {

    private final Color color;
    private final float size;
    private final String family;
    private final boolean bold;
    private final boolean italic;
    private final boolean underline;
    private final boolean strikeThrough;

    // lazily populated fields
    private String fontspec;
    private int hash;
    private FontDef fd;
    private double spaceWidth = -1;

    /**
     * Construct a new {@code Font}.
     */
    public Font() {
        this("Helvetica", 10.0f, Color.BLACK, false, false, false, false);
    }

    /**
     * Construct a new {@code Font} from a fontspec string.
     *
     * @param fontspec the fontspec
     */
    public Font(String fontspec) {
        this(new Font(), FontDef.parseFontspec(fontspec));
    }

    /**
     * Construct a new {@code Font}.
     *
     * @param family        the font family
     * @param size          the font size in points
     * @param color         the color to use for text
     * @param bold          if text should be displayed in bold letters
     * @param italic        if text should be displayed in italics
     * @param underline     if text should be displayed underlined
     * @param strikeThrough if text should be displayed strike-through
     */
    public Font(String family, float size, Color color, boolean bold, boolean italic, boolean underline,
                boolean strikeThrough) {
        this.size = size;
        this.bold = bold;
        this.italic = italic;
        this.underline = underline;
        this.strikeThrough = strikeThrough;
        this.family = family;
        this.color = color;
    }

    /**
     * Construct a new Font instance from another Font instance.
     * @param baseFont the base font
     * @param fd the font definition that contains the attributes to change when creating the new font.
     */
    protected Font(Font baseFont, FontDef fd) {
        this(
                LangUtil.orElse(fd.getFamily(), baseFont.getFamily()),
                LangUtil.orElse(fd.getSize(), baseFont.getSizeInPoints()),
                LangUtil.orElse(fd.getColor(), baseFont.getColor()),
                LangUtil.orElse(fd.getBold(), baseFont.isBold()),
                LangUtil.orElse(fd.getItalic(), baseFont.isItalic()),
                LangUtil.orElse(fd.getUnderline(), baseFont.isUnderline()),
                LangUtil.orElse(fd.getStrikeThrough(), baseFont.isStrikeThrough())
        );
    }

    /**
     * Test if two fonts are similar. This method is provided to be used when inheriting fonts because equals
     * also tests both instances to be of the exact same class.
     *
     * @param a the first font
     * @param b the second font
     * @return true, if a and b have the same attributes
     */
    public static boolean similar(Font a, Font b) {
        return b.size == a.size
                && b.bold == a.bold
                && b.italic == a.italic
                && b.underline == a.underline
                && b.strikeThrough == a.strikeThrough
                && b.family.equals(a.family)
                && b.color.equals(a.color);
    }

    /**
     * Compare values and store changes.
     *
     * @param o1     first object
     * @param o2     second object
     * @param getter attribute getter
     * @param setter attribute setter
     * @param     object type
     * @param     attribute type
     */
    private static  void deltaHelper(T o1, T o2, Function getter, Consumer setter) {
        U v1 = getter.apply(o1);
        U v2 = getter.apply(o2);
        if (!Objects.equals(v1, v2)) {
            setter.accept(v2);
        }
    }

    /**
     * Determine differences between two fonts.
     *
     * @param f1 the first font
     * @param f2 the second font
     * @return a {@link FontDef} instance that defines the changed values
     */
    public static FontDef delta(Font f1, Font f2) {
        FontDef fd = new FontDef();

        deltaHelper(f1, f2, Font::getFamily, fd::setFamily);
        deltaHelper(f1, f2, Font::getSizeInPoints, fd::setSize);
        deltaHelper(f1, f2, Font::isBold, fd::setBold);
        deltaHelper(f1, f2, Font::isItalic, fd::setItalic);
        deltaHelper(f1, f2, Font::isUnderline, fd::setUnderline);
        deltaHelper(f1, f2, Font::isStrikeThrough, fd::setStrikeThrough);
        deltaHelper(f1, f2, Font::getColor, fd::setColor);

        return fd;
    }

    /**
     * Derive font.
     * 

* A new font based on this font is returned. The attributes defined * {@code fd} are applied to the new font. If an attribute in {@code fd} is * not set, the attribute is copied from this font. *

* * @param fd the {@link FontDef} describing the attributes to set * @return new Font instance */ public Font deriveFont(FontDef fd) { return new Font(this, fd); } /** * Get text color. * * @return the text color. */ public Color getColor() { return color; } /** * Get font family. * * @return the font family as {@code String}. */ public String getFamily() { return family; } /** * Get font size. * * @return the font size in points. */ public float getSizeInPoints() { return size; } /** * Get bold property. * * @return true if font is bold. */ public boolean isBold() { return bold; } /** * Get italic property. * * @return true if font is italic. */ public boolean isItalic() { return italic; } /** * Get strike-through property. * * @return true if font is strike-through. */ public boolean isStrikeThrough() { return strikeThrough; } /** * Get underline property. * * @return true if font is underline. */ public boolean isUnderline() { return underline; } /** * Get a description of the font. * * @return font description */ public String fontspec() { if (fontspec == null) { StringBuilder sb = new StringBuilder(32); sb.append(getFamily()); if (isBold()) { sb.append('-').append("bold"); } if (isItalic()) { sb.append('-').append("italic"); } if (isUnderline()) { sb.append('-').append("underline"); } if (isStrikeThrough()) { sb.append('-').append("strikethrough"); } sb.append('-'); sb.append(getSizeInPoints()); sb.append('-'); sb.append(getColor().toCss()); fontspec = sb.toString(); } return fontspec; } @Override public String toString() { return fontspec(); } @SuppressWarnings({"boxing", "NonFinalFieldReferencedInHashCode"}) @Override public int hashCode() { int h = hash; if (h == 0) { hash = h = Objects.hash(family, size, bold, italic, underline, strikeThrough, color); } return h; } @Override public boolean equals(@Nullable Object obj) { if (obj == null || obj.getClass() != getClass() || obj.hashCode() != hashCode()) { return false; } return similar(this, (Font) obj); } /** * Get CSS compatible fontstyle definition. * * @return fontstyle definition */ public String getCssStyle() { return String.format(Locale.ROOT, "color: %s; font-size: %spt; font-family: %s; font-weight: %s; font-style: %s;%s", color, size, /* use "%s" for size to avoid unnecessary zeros after decimal point */ family, bold ? "bold" : "normal", italic ? "italic" : "normal", strikeThrough || underline ? "text-decoration:" + (underline ? " underline" : "") + (strikeThrough ? " line-through" : "") + ";" : "" ); } /** * Get a {@link FontDef} instance with all fields set according to this font. * * @return FontDef instance describing this font */ public FontDef toFontDef() { if (fd == null) { fd = new FontDef(); fd.setFamily(getFamily()); fd.setSize(getSizeInPoints()); fd.setBold(isBold()); fd.setItalic(isItalic()); fd.setUnderline(isUnderline()); fd.setStrikeThrough(isStrikeThrough()); fd.setColor(getColor()); } return fd; } /** * Get width of a space character in this font. * * @return width of a single space character in this font */ public double getSpaceWidth() { if (spaceWidth < 0) { spaceWidth = TextUtil.getTextWidth(" ", this); } return spaceWidth; } /** * Return a font derived from this font by applying the given size. * * @param size the new font size * @return a copy of this font with the requested size, or this font, if the size matches */ public Font withSize(float size) { return size == this.size ? this : deriveFont(FontDef.size(size)); } /** * Scales the font by a given factor. * * @param s the scaling factor * @return a new Font instance that is scaled by the given factor, or this font if the scaling factor is 1 */ public Font scaled(float s) { return s == 1 ? this : deriveFont(FontDef.size(s * size)); } /** * Return a font derived from this font by applying the given value for the bold attribute. * * @param flag the value to use * @return a copy of this font with the bold attribute set to the requested value, or this font if values match */ public Font withBold(boolean flag) { return flag == bold ? this : deriveFont(FontDef.bold(flag)); } /** * Return a font derived from this font by applying the given value for the italic attribute. * * @param flag the value to use * @return a copy of this font with the italic attribute set to the requested value, or this font if values match */ public Font withItalic(boolean flag) { return flag == italic ? this : deriveFont(FontDef.italic(flag)); } /** * Return a font derived from this font by applying the given value for the underline attribute. * * @param flag the value to use * @return a copy of this font with the underline attribute set to the requested value, or this font if values match */ public Font withUnderline(boolean flag) { return flag == underline ? this : deriveFont(FontDef.underline(flag)); } /** * Return a font derived from this font by applying the given value for the strike-through attribute. * * @param flag the value to use * @return a copy of this font with the strike-through attribute set to the requested value, or this font if values match */ public Font withStrikeThrough(boolean flag) { return flag == strikeThrough ? this : deriveFont(FontDef.strikeThrough(flag)); } /** * Return a font derived from this font by replacing the family with the given value. * * @param family the value to use * @return a copy of this font with the family set to the requested value, or this font if values match */ public Font withFamily(String family) { return family.equals(this.family) ? this : deriveFont(FontDef.family(family)); } /** * Return a font derived from this font by replacing the color with the given value. * * @param color the value to use * @return a copy of this font with the color set to the requested value, or this font if values match */ public Font withColor(Color color) { return color.equals(this.color) ? this : deriveFont(FontDef.color(color)); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy