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

net.kemitix.text.fit.BoxFitterImpl Maven / Gradle / Ivy

The newest version!
package net.kemitix.text.fit;

import lombok.RequiredArgsConstructor;

import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Fit the text to a box by finding the best font size and word wrapping to fill
 * the space using a binary search.
 */
@RequiredArgsConstructor
class BoxFitterImpl implements BoxFitter {

    private final WordWrapper wordWrapper;

    @Override
    public int fit(
            String text,
            Function fontFactory,
            Graphics2D graphics2D,
            Rectangle2D box
    ) {
        int fit = fitMinMax(0, (int) box.getHeight(),
                new FitEnvironment(text, fontFactory, graphics2D, box));
        if (fit <= 2) {
            throw new IllegalArgumentException("The text is too long to fit");
        }
        return fit;
    }

    private Integer fitMinMax(
            int min,
            int max,
            FitEnvironment e
    ) {
        int mid = (max + min) / 2;
        if (mid == min){
            return mid;
        }
        Font font = e.getFont(mid);
        List lines = wrapLines(font, e);
        List lineSizes =
                lineSizes(font, lines, e.fontRenderContext());
        if (sumLineHeights(lineSizes) > e.boxHeight() ||
                maxLineWidth(lineSizes) > e.boxWidth()) {
            return fitMinMax(min, mid, e);
        }
        return fitMinMax(mid, max, e);
    }

    private List wrapLines(
            Font font,
            FitEnvironment e
    ) {
        return wordWrapper.wrap(e.text, font, e.graphics2D, e.boxWidth());
    }

    private List lineSizes(
            Font font,
            List lines,
            FontRenderContext renderContext
    ) {
        return lines.stream()
                .map(line -> font.getStringBounds(line.strip(), renderContext))
                .collect(Collectors.toList());
    }

    private int sumLineHeights(List lineSizes) {
        return lineSizes.stream().map(Rectangle2D::getHeight)
                .mapToInt(Double::intValue).sum();
    }

    private int maxLineWidth(List lineSizes) {
        return lineSizes.stream().map(Rectangle2D::getWidth)
                .mapToInt(Double::intValue).max().orElse(0);
    }

    @RequiredArgsConstructor
    private static class FitEnvironment {
        private final String text;
        private final Function fontFactory;
        private final Graphics2D graphics2D;
        private final Rectangle2D box;

        public Font getFont(int size) {
            return fontFactory.apply(size);
        }
        public int boxWidth() {
            return (int) box.getWidth();
        }

        public int boxHeight() {
            return (int) box.getHeight();
        }

        public FontRenderContext fontRenderContext() {
            return graphics2D.getFontRenderContext();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy