001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.gwt.client.util;
029
030import java.util.HashMap;
031import java.util.Map;
032
033import com.google.gwt.dom.client.Element;
034import com.google.gwt.dom.client.Style;
035import com.google.gwt.user.client.DOM;
036import com.google.gwt.user.client.ui.RootPanel;
037
038/**
039 * Provides precise pixel measurements for blocks of text so that you can
040 * determine exactly how high and wide, in pixels, a given block of text will
041 * be.<p>
042 *
043 * Normal usage would be:
044 * <pre>
045 * for(Element e: elements) {
046 *   CmsTextMetrics tm = CmsTextMetrics.get(e, "TextMetricsKey");
047 *
048 *   // measure text
049 *   if (r.getWidth(text) > 500) {
050 *      // do something
051 *   }
052 *   // release
053 *   tm.release();
054 * }
055 * </pre>
056 *
057 * Based on <a href="http://code.google.com/p/my-gwt/source/browse/trunk/user/src/net/mygwt/ui/client/util/TextMetrics.java">my-gwt TextMetrics</a>.<p>
058 *
059 * @since 8.0.0
060 */
061public final class CmsTextMetrics {
062
063    /** Default attributes to bind. */
064    private static final CmsDomUtil.Style[] ATTRIBUTES = new CmsDomUtil.Style[] {
065        CmsDomUtil.Style.fontSize,
066        CmsDomUtil.Style.fontSizeAdjust,
067        CmsDomUtil.Style.fontFamily,
068        CmsDomUtil.Style.fontStretch,
069        CmsDomUtil.Style.fontStyle,
070        CmsDomUtil.Style.fontVariant,
071        CmsDomUtil.Style.fontWeight,
072        CmsDomUtil.Style.letterSpacing,
073        CmsDomUtil.Style.textAlign,
074        CmsDomUtil.Style.textDecoration,
075        CmsDomUtil.Style.textIndent,
076        CmsDomUtil.Style.textShadow,
077        CmsDomUtil.Style.textTransform,
078        CmsDomUtil.Style.lineHeight,
079        CmsDomUtil.Style.whiteSpace,
080        CmsDomUtil.Style.wordSpacing,
081        CmsDomUtil.Style.wordWrap,
082        CmsDomUtil.Style.padding};
083
084    /** The map containing the instances. */
085    private static Map<String, CmsTextMetrics> m_instances = new HashMap<String, CmsTextMetrics>();
086
087    /** The playground. */
088    private Element m_elem;
089
090    /** The text metrics key. */
091    private String m_key;
092
093    /**
094     * Internal constructor for creating a text metrics object with a given key.<p>
095     *
096     * @param key the key identifying the text metrics.
097     */
098    private CmsTextMetrics(String key) {
099
100        m_key = key;
101    }
102
103    /**
104     * Gets the text metrics object for a given DOM element and key.<p>
105     *
106     * If the key is null, or the method has been never called with the same key
107     * before, a new text metrics object will be created, with its style taken from
108     * the element parameter. Otherwise, the text metrics object for the given key will
109     * be returned, and the element parameter will be ignored.
110     *
111     * @param element the element from which to take the style
112     * @param key the text metrics key
113     *
114     * @return a text metrics object
115     */
116    public static CmsTextMetrics get(Element element, String key) {
117
118        CmsTextMetrics instance = null;
119        if (key == null) {
120            instance = new CmsTextMetrics(key);
121            instance.bind(element);
122        } else {
123            instance = m_instances.get(key);
124            if (instance == null) {
125                instance = new CmsTextMetrics(key);
126                instance.bind(element);
127                m_instances.put(key, instance);
128            }
129        }
130        return instance;
131    }
132
133    /**
134     * Returns the measured height of the specified text. For multiline text, be
135     * sure to call {@link #setFixedWidth} if necessary.<p>
136     *
137     * @param text the text to be measured
138     * @return the height in pixels
139     */
140    public int getHeight(String text) {
141
142        m_elem.setInnerText(text);
143        return CmsDomUtil.getCurrentStyleInt(m_elem, CmsDomUtil.Style.height);
144    }
145
146    /**
147     * Returns the measured width of the specified text.<p>
148     *
149     * @param text the text to measure
150     * @return the width in pixels
151     */
152    public int getWidth(String text) {
153
154        m_elem.setInnerText(text);
155        return CmsDomUtil.getCurrentStyleInt(m_elem, CmsDomUtil.Style.width);
156    }
157
158    /**
159     * Should be called, when finished measuring, to release the playground.<p>
160     */
161    public void release() {
162
163        if (m_key == null) {
164            m_elem.removeFromParent();
165            m_elem = null;
166        }
167        // if we have a key, we do nothing so that the instance can be reused later
168
169    }
170
171    /**
172     * Sets a fixed width on the internal measurement element. If the text will be
173     * multiline, you have to set a fixed width in order to accurately measure the
174     * text height.<p>
175     *
176     * @param width the width to set on the element
177     */
178    public void setFixedWidth(int width) {
179
180        m_elem.getStyle().setWidth(width, Style.Unit.PX);
181    }
182
183    /**
184     * Binds this text metrics instance to an element from which to copy existing
185     * CSS styles that can affect the size of the rendered text.<p>
186     *
187     * @param element the element
188     */
189    protected void bind(Element element) {
190
191        bind(element, ATTRIBUTES);
192    }
193
194    /**
195     * Binds this text metrics instance to an element from which to copy existing
196     * CSS styles that can affect the size of the rendered text.<p>
197     *
198     * @param element the element
199     * @param attributes the attributes to bind
200     */
201    protected void bind(Element element, CmsDomUtil.Style... attributes) {
202
203        if (m_elem == null) {
204            // create playground
205            m_elem = DOM.createDiv();
206            Style style = m_elem.getStyle();
207            style.setVisibility(Style.Visibility.HIDDEN);
208            style.setPosition(Style.Position.ABSOLUTE);
209            style.setLeft(-5000, Style.Unit.PX);
210            style.setTop(-5000, Style.Unit.PX);
211        }
212        // copy all relevant CSS properties
213        Style style = m_elem.getStyle();
214        for (CmsDomUtil.Style attr : attributes) {
215            String attrName = attr.toString();
216            style.setProperty(attrName, CmsDomUtil.getCurrentStyle(element, attr));
217        }
218        // append playground
219        RootPanel.getBodyElement().appendChild(m_elem);
220    }
221}