com.google.collide.client.util.dom.FontDimensionsCalculator.java Source code

Java tutorial

Introduction

Here is the source code for com.google.collide.client.util.dom.FontDimensionsCalculator.java

Source

// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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 com.google.collide.client.util.dom;

import com.google.collide.client.util.CssUtils;
import com.google.collide.client.util.Elements;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.shared.util.JsonCollections;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;

import elemental.css.CSSStyleDeclaration;
import elemental.html.Element;

/**
 * A class that computes the height and width of a character.
 */
public class FontDimensionsCalculator {

    public interface FontDimensions {
        float getBaseWidth();

        float getBaseHeight();

        float getWidthZoomFactor();

        float getHeightZoomFactor();

        float getCharacterWidth();

        float getCharacterHeight();
    }

    private class FontDimensionsImpl implements FontDimensions {

        private float baseWidth;
        private float baseHeight;
        private float widthFactor = 1;
        private float heightFactor = 1;

        /**
         * Updates the width and height internally. If this has never been set
         * before this will set the baseWidth and baseHeight. Otherwise it will
         * adjust the zoom factor and the current character width and height.
         *
         * @return false if no update was required.
         */
        private boolean update(float width, float height) {
            if (baseWidth == 0 && baseHeight == 0) {
                baseWidth = width;
                baseHeight = height;
                return true;
            } else if (baseWidth * widthFactor != width || baseHeight * heightFactor != height) {
                widthFactor = width / baseWidth;
                heightFactor = height / baseHeight;
                return true;
            }

            return false;
        }

        @Override
        public float getBaseWidth() {
            return baseWidth;
        }

        @Override
        public float getBaseHeight() {
            return baseHeight;
        }

        @Override
        public float getWidthZoomFactor() {
            return widthFactor;
        }

        @Override
        public float getHeightZoomFactor() {
            return heightFactor;
        }

        @Override
        public float getCharacterWidth() {
            return widthFactor * baseWidth;
        }

        @Override
        public float getCharacterHeight() {
            return heightFactor * baseHeight;
        }
    }

    private static final int POLLING_CUTOFF = 10000;

    private static final String SAMPLE_TEXT = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
    private static final int SAMPLE_ROWS = 30;

    public interface Callback {
        void onFontDimensionsChanged(FontDimensions fontDimensions);
    }

    private static FontDimensionsCalculator INSTANCE;

    public static FontDimensionsCalculator get(String fontClassName) {
        if (INSTANCE == null) {
            INSTANCE = new FontDimensionsCalculator(fontClassName);
        }

        return INSTANCE;
    }

    private final Element dummyElement;

    /*
     * This timer is called during the first 12s. When using a web font it will be
     * loaded asynchronously and our initial measurements will be incorrect. By
     * polling we remeasure after this font has been loaded and updated our size.
     */
    private final Timer repeater = new Timer() {
        @Override
        public void run() {
            if (pollingDelay >= POLLING_CUTOFF) {
                // Terminate polling rescheduling once we cross the cutoff.
                return;
            }

            measureAndDispatch();

            schedule(pollingDelay);
            pollingDelay *= 2;
        }
    };

    private int pollingDelay = 500;

    private JsonArray<Callback> callbacks = JsonCollections.createArray();

    private final FontDimensionsImpl fontDimensions;
    private final String fontClassName;

    private FontDimensionsCalculator(String fontClassName) {
        this.fontClassName = fontClassName;
        // This handler will be called when the browser window zooms
        Window.addResizeHandler(new ResizeHandler() {
            @Override
            public void onResize(ResizeEvent arg0) {
                measureAndDispatch();
            }
        });

        // Build a multirow text block so we can measure it
        StringBuilder htmlContent = new StringBuilder(SAMPLE_TEXT);
        for (int i = 1; i < SAMPLE_ROWS; i++) {
            htmlContent.append("<br/>");
            htmlContent.append(SAMPLE_TEXT);
        }

        dummyElement = Elements.createSpanElement(fontClassName);
        dummyElement.setInnerHTML(htmlContent.toString());
        dummyElement.getStyle().setVisibility(CSSStyleDeclaration.Visibility.HIDDEN);
        dummyElement.getStyle().setPosition(CSSStyleDeclaration.Position.ABSOLUTE);
        Elements.getBody().appendChild(dummyElement);

        fontDimensions = new FontDimensionsImpl();

        repeater.schedule(pollingDelay);

        /*
         * Force an initial measure (the dispatch won't happen since no one is
         * attached)
         */
        measureAndDispatch();
    }

    public void addCallback(final Callback callback) {
        callbacks.add(callback);
    }

    public void removeCallback(final Callback callback) {
        callbacks.remove(callback);
    }

    /**
     * Returns a font dimensions that will be updated as the font dimensions
     * change.
     */
    public FontDimensions getFontDimensions() {
        return fontDimensions;
    }

    public String getFontClassName() {
        return fontClassName;
    }

    public String getFont() {
        return CssUtils.getComputedStyle(dummyElement).getPropertyValue("font");
    }

    private void measureAndDispatch() {
        float curWidth = dummyElement.getOffsetWidth() / ((float) SAMPLE_TEXT.length());
        float curHeight = dummyElement.getOffsetHeight() / ((float) SAMPLE_ROWS);

        if (fontDimensions.update(curWidth, curHeight)) {
            dispatchToCallbacks();
        }
    }

    private void dispatchToCallbacks() {
        for (int i = 0, n = callbacks.size(); i < n; i++) {
            callbacks.get(i).onFontDimensionsChanged(fontDimensions);
        }
    }
}