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

org.fernice.reflare.internal.impl.SunFontAccessorImpl Maven / Gradle / Ivy

There is a newer version: 1.3.0
Show newest version
/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package org.fernice.reflare.internal.impl;

import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import org.fernice.reflare.internal.SunFontHelper.SunFontAccessor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import sun.font.Font2D;
import sun.font.FontAccess;

public class SunFontAccessorImpl implements SunFontAccessor {

    private static final String[] WEIGHT_NAMES = { //
            "thin", "light", "ultralight", "ultra light", "ultra-light", //
            "extralight", "extlt", "extra light", "extra-light", //
            "demilight", "demi light", "demi-light", //
            "normal", "regular", "medium", "med", //
            "bold", "heavy", "black", "blk", //
            "semibold", "sembd", "semi bold", "semi-bold", //
            "demibold", "demi", "demi bold", "demi-bold", //
            "extrabold", "extbd", "extra bold", "extra-bold", //
            "ultrabold", "ultra bold", "ultra-bold", //
    };

    private static final int MERGING_STRATEGY_COMBINE = 0;
    private static final int MERGING_STRATEGY_KEEP = 1;
    private static final int MERGING_STRATEGY_OVERRIDE = 2;

    private static final AtomicBoolean initialized = new AtomicBoolean();
    private static final CountDownLatch initializationLatch = new CountDownLatch(1);

    private static volatile Map> fontFamilies = new HashMap<>();

    private static final Set fontExtensions = new HashSet<>();
    private static final Set fontExtensionOverrides = new HashSet<>();

    @Nullable
    @Override
    public Font findFont(@NotNull String family, int weight, boolean italic) {
        if (!ensureInitialized(family)) {
            return null;
        }

        List fontFamily = fontFamilies.get(family.toLowerCase());

        if (fontFamily != null && !fontFamily.isEmpty()) {
            int weightDistance = Integer.MAX_VALUE;
            Font matchingFont = null;

            for (FontPeer font : fontFamily) {
                boolean fontItalic = font.font2D.getStyle() == 2 || font.font2D.getStyle() == 3;

                if (fontItalic != italic) {
                    continue;
                }

                int distance = Math.abs(weight - font.font2D.getWeight());

                if (distance < weightDistance) {
                    weightDistance = distance;
                    matchingFont = font.font;
                }
            }

            return matchingFont;
        } else {
            return null;
        }
    }

    private boolean ensureInitialized(@NotNull String family) {
        // Force the full initialization of the FontManager
        if (!initialized.getAndSet(true)) {
            new Font(family, Font.BOLD | Font.ITALIC, 12);

            refresh();

            initializationLatch.countDown();
        } else {
            try {
                initializationLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean registerFontExtension(@NotNull Font font, boolean override) {
        if (override) {
            fontExtensions.remove(font);
            if (!fontExtensionOverrides.add(font)) {
                return false;
            }
        } else {
            fontExtensionOverrides.remove(font);
            if (!fontExtensions.add(font)) {
                return false;
            }
        }
        return recomputeFontFamilies(font);
    }

    @Override
    public boolean unregisterFontExtension(@NotNull Font font) {
        boolean result = fontExtensions.remove(font);
        result |= fontExtensionOverrides.remove(font);
        return result;
    }

    @Override
    public void refresh() {
        recomputeFontFamilies(null);
    }

    private boolean recomputeFontFamilies(@Nullable Font target) {
        boolean targetResult;

        Map> fontFamilies = new HashMap<>();

        Font[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
        targetResult = mergeFontFamilies(fontFamilies, fonts, MERGING_STRATEGY_COMBINE, target);

        Font[] fontExtensions = SunFontAccessorImpl.fontExtensions.toArray(new Font[0]);
        targetResult |= mergeFontFamilies(fontFamilies, fontExtensions, MERGING_STRATEGY_KEEP, target);

        Font[] fontExtensionOverrides = SunFontAccessorImpl.fontExtensionOverrides.toArray(new Font[0]);
        targetResult |= mergeFontFamilies(fontFamilies, fontExtensionOverrides, MERGING_STRATEGY_OVERRIDE, target);

        SunFontAccessorImpl.fontFamilies = fontFamilies;

        return targetResult;
    }

    private boolean mergeFontFamilies(@NotNull Map> fontFamilies, @NotNull Font[] fonts, int mergingStrategy, @Nullable Font target) {
        boolean targetResult = false;

        for (Font font : fonts) {
            String familyName = font.getFamily().toLowerCase();

            List fontFamily = fontFamilies.computeIfAbsent(familyName, (s) -> new ArrayList<>());
            targetResult |= mergeFontFamily(fontFamily, font, mergingStrategy) && font == target;

            for (String weightName : WEIGHT_NAMES) {
                int index = familyName.indexOf(weightName);

                if (index >= 0) {
                    String strippedFamilyName = familyName.substring(0, index).trim();

                    List additionalFontFamily = fontFamilies.computeIfAbsent(strippedFamilyName, (s) -> new ArrayList<>());
                    targetResult |= mergeFontFamily(additionalFontFamily, font, mergingStrategy) && font == target;
                }
            }
        }

        return targetResult;
    }

    private boolean mergeFontFamily(@NotNull List fonts, @NotNull Font font, int mergingStrategy) {
        FontAccess fontAccess = FontAccess.getFontAccess();
        Font2D font2D = fontAccess.getFont2D(font);

        int fontWeight = font2D.getWeight();
        boolean fontItalic = font2D.getStyle() == 2 || font2D.getStyle() == 3;

        while (true) {
            FontPeer matchingFont = null;
            for (FontPeer candidateFont : fonts) {
                Font2D candidateFont2D = candidateFont.font2D;

                int candidateFontWeight = candidateFont2D.getWeight();
                boolean candidateFontItalic = candidateFont2D.getStyle() == 2 || candidateFont2D.getStyle() == 3;

                if (candidateFontWeight == fontWeight && candidateFontItalic == fontItalic) {
                    matchingFont = candidateFont;
                    break;
                }
            }

            if (matchingFont != null) {
                if (mergingStrategy == MERGING_STRATEGY_KEEP) {
                    return false;
                }

                if (mergingStrategy == MERGING_STRATEGY_OVERRIDE) {
                    fonts.remove(matchingFont);
                    continue;
                }
            }
            break;
        }

        fonts.add(new FontPeer(font, font2D));

        return true;
    }

    private static final class FontPeer {

        @NotNull
        final Font font;
        @NotNull
        final Font2D font2D;

        private FontPeer(@NotNull Font font, @NotNull Font2D font2D) {
            this.font = font;
            this.font2D = font2D;
        }

        @NotNull
        @Override
        public String toString() {
            //            return "Font[" + font2D.toString() + " weight=" + font2D.getWeight() + "]";
            return "Font[family=" + font.getFamily() + " name=" + font.getName() + " style=" + font2D.getStyle() + " weight=" + font2D.getWeight() + "]";
        }
    }

    @Override
    public int getFontWeight(@NotNull Font font) {
        return FontAccess.getFontAccess().getFont2D(font).getWeight();
    }

    @Override
    public boolean isFontItalic(@NotNull Font font) {
        int style = FontAccess.getFontAccess().getFont2D(font).getStyle();
        return style == 2 || style == 3;
    }
}