jetbrains.jetpad.projectional.domUtil.TextMetricsCalculator.java Source code

Java tutorial

Introduction

Here is the source code for jetbrains.jetpad.projectional.domUtil.TextMetricsCalculator.java

Source

/*
 * Copyright 2012-2016 JetBrains s.r.o
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.jetpad.projectional.domUtil;

import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.ImageData;
import com.google.gwt.user.client.Window;
import jetbrains.jetpad.geometry.DoubleVector;
import jetbrains.jetpad.values.Font;
import jetbrains.jetpad.values.FontFamily;

public class TextMetricsCalculator {
    private static final String METRICS_TEST_STRING = "x";

    public static String getFontName(FontFamily family) {
        if (family == FontFamily.MONOSPACED) {
            return "monospace";
        } else if (family == FontFamily.SERIF) {
            return "serif";
        }
        return family.toString();
    }

    public static TextMetrics calculateApprox(Font font) {
        return calculateApprox(font, METRICS_TEST_STRING);
    }

    public static TextMetrics calculate(Font font) {
        return calculate(font, METRICS_TEST_STRING);
    }

    public static TextMetrics calculateApprox(final Font font, String text) {
        final DoubleVector dimension = calculateDimension(font, text);
        return new TextMetrics() {
            @Override
            public DoubleVector dimension() {
                return dimension;
            }

            @Override
            public int baseLine() {
                return (2 * font.getSize()) / 3;
            }
        };
    }

    public static TextMetrics calculate(Font font, String text) {
        final DoubleVector dimension = calculateDimension(font, text);
        final int baseLine = fontBaseLine(font);
        return new TextMetrics() {
            @Override
            public DoubleVector dimension() {
                return dimension;
            }

            @Override
            public int baseLine() {
                return baseLine;
            }
        };
    }

    private static DoubleVector calculateDimension(Font font, String text) {
        return new DoubleVector(calculateWidth(font, text), adjustHeight(font.getSize()));
    }

    static double calculateWidth(Font font, String text) {
        Canvas canvas = canvas();
        Context2d ctx = canvas.getContext2d();
        ctx.setFont(getFontString(font));
        return ctx.measureText(normalize(text)).getWidth();
    }

    private static int adjustHeight(int height) {
        String agent = Window.Navigator.getUserAgent().toLowerCase();
        return agent.contains("firefox") ? height + 1 : height;
    }

    private static int fontBaseLine(Font font) {
        int allCharsHeight = measureHeight(font, allCharsString());
        int ascent = measureHeight(font, "A");
        int lineSpace = font.getSize() - allCharsHeight;
        return lineSpace / 2 + ascent;
    }

    static String normalize(String text) {
        //replace space with  
        return text.replaceAll(" ", "\u00a0");
    }

    private static String allCharsString() {
        StringBuilder allChars = new StringBuilder();
        for (char c = 'a'; c <= 'z'; c++) {
            allChars.append(c);
        }
        for (char c = 'A'; c <= 'Z'; c++) {
            allChars.append(c);
        }
        return allChars.toString();
    }

    private static int measureHeight(Font font, String text) {
        Canvas canvas = canvas();
        Context2d ctx = canvas.getContext2d();

        ctx.setFont(getFontString(font));
        ctx.setFillStyle("rgb(255, 0, 0)");

        int width = (int) ctx.measureText(text).getWidth();
        int canvasHeight = font.getSize() * 2;
        canvas.setHeight(canvasHeight + "px");
        canvas.setHeight(font.getSize() * 2 + "px");
        canvas.setWidth(width + "px");

        ctx.fillText(text, 0, font.getSize());
        ImageData data = ctx.getImageData(0, 0, width, canvasHeight);
        int firstY = canvasHeight - 1;
        int lastY = 0;
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < canvasHeight; y++) {
                int red = data.getRedAt(x, y);
                if (red != 0) {
                    if (firstY > y) {
                        firstY = y;
                    }
                    if (lastY < y) {
                        lastY = y;
                    }
                }
            }
        }
        return lastY - firstY;
    }

    private static String getFontString(Font font) {
        StringBuilder result = new StringBuilder();
        if (font.isBold()) {
            result.append(" bold");
        }
        if (font.isItalic()) {
            result.append(" italic");
        }
        result.append(' ').append(font.getSize()).append("px ").append(getFontName(font.getFamily()));
        return result.toString();
    }

    private static Canvas canvas() {
        Canvas canvas = Canvas.createIfSupported();
        if (canvas == null) {
            throw new IllegalStateException();
        }
        return canvas;
    }
}