BitmapFont.java revision 59b2e6871c65f58fdad78cd7229c292f6a177578
1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.jme3.font;
34
35import com.jme3.export.*;
36import com.jme3.material.Material;
37import java.io.IOException;
38
39/**
40 * Represents a font within jME that is generated with the AngelCode Bitmap Font Generator
41 * @author dhdd
42 */
43public class BitmapFont implements Savable {
44
45    /**
46     * Specifies horizontal alignment for text.
47     *
48     * @see BitmapText#setAlignment(com.jme3.font.BitmapFont.Align)
49     */
50    public enum Align {
51
52        /**
53         * Align text on the left of the text block
54         */
55        Left,
56
57        /**
58         * Align text in the center of the text block
59         */
60        Center,
61
62        /**
63         * Align text on the right of the text block
64         */
65        Right
66    }
67
68    /**
69     * Specifies vertical alignment for text.
70     *
71     * @see BitmapText#setVerticalAlignment(com.jme3.font.BitmapFont.VAlign)
72     */
73    public enum VAlign {
74        /**
75         * Align text on the top of the text block
76         */
77        Top,
78
79        /**
80         * Align text in the center of the text block
81         */
82        Center,
83
84        /**
85         * Align text at the bottom of the text block
86         */
87        Bottom
88    }
89
90    private BitmapCharacterSet charSet;
91    private Material[] pages;
92
93    public BitmapFont() {
94    }
95
96    public BitmapText createLabel(String content){
97        BitmapText label = new BitmapText(this);
98        label.setSize(getCharSet().getRenderedSize());
99        label.setText(content);
100        return label;
101    }
102
103    public float getPreferredSize(){
104        return getCharSet().getRenderedSize();
105    }
106
107    public void setCharSet(BitmapCharacterSet charSet) {
108        this.charSet = charSet;
109    }
110
111    public void setPages(Material[] pages) {
112        this.pages = pages;
113        charSet.setPageSize(pages.length);
114    }
115
116    public Material getPage(int index) {
117        return pages[index];
118    }
119
120    public int getPageSize() {
121        return pages.length;
122    }
123
124    public BitmapCharacterSet getCharSet() {
125        return charSet;
126    }
127
128    /**
129     * Gets the line height of a StringBlock.
130     * @param sb
131     * @return
132     */
133    public float getLineHeight(StringBlock sb) {
134        return charSet.getLineHeight() * (sb.getSize() / charSet.getRenderedSize());
135    }
136
137    public float getCharacterAdvance(char curChar, char nextChar, float size){
138        BitmapCharacter c = charSet.getCharacter(curChar);
139        if (c == null)
140            return 0f;
141
142        float advance = size * c.getXAdvance();
143        advance += c.getKerning(nextChar) * size;
144        return advance;
145    }
146
147    private int findKerningAmount(int newLineLastChar, int nextChar) {
148        BitmapCharacter c = charSet.getCharacter(newLineLastChar);
149        if (c == null)
150            return 0;
151        return c.getKerning(nextChar);
152    }
153
154    @Override
155    public void write(JmeExporter ex) throws IOException {
156        OutputCapsule oc = ex.getCapsule(this);
157        oc.write(charSet, "charSet", null);
158        oc.write(pages, "pages", null);
159    }
160
161    @Override
162    public void read(JmeImporter im) throws IOException {
163        InputCapsule ic = im.getCapsule(this);
164        charSet = (BitmapCharacterSet) ic.readSavable("charSet", null);
165        Savable[] pagesSavable = ic.readSavableArray("pages", null);
166        pages = new Material[pagesSavable.length];
167        System.arraycopy(pagesSavable, 0, pages, 0, pages.length);
168    }
169
170    public float getLineWidth(CharSequence text){
171
172        // This method will probably always be a bit of a maintenance
173        // nightmare since it basis its calculation on a different
174        // routine than the Letters class.  The ideal situation would
175        // be to abstract out letter position and size into its own
176        // class that both BitmapFont and Letters could use for
177        // positioning.
178        // If getLineWidth() here ever again returns a different value
179        // than Letters does with the same text then it might be better
180        // just to create a Letters object for the sole purpose of
181        // getting a text size.  It's less efficient but at least it
182        // would be accurate.
183
184        // And here I am mucking around in here again...
185        //
186        // A font character has a few values that are pertinent to the
187        // line width:
188        //  xOffset
189        //  xAdvance
190        //  kerningAmount(nextChar)
191        //
192        // The way BitmapText ultimately works is that the first character
193        // starts with xOffset included (ie: it is rendered at -xOffset).
194        // Its xAdvance is wider to accomodate that initial offset.
195        // The cursor position is advanced by xAdvance each time.
196        //
197        // So, a width should be calculated in a similar way.  Start with
198        // -xOffset + xAdvance for the first character and then each subsequent
199        // character is just xAdvance more 'width'.
200        //
201        // The kerning amount from one character to the next affects the
202        // cursor position of that next character and thus the ultimate width
203        // and so must be factored in also.
204
205        float lineWidth = 0f;
206        float maxLineWidth = 0f;
207        char lastChar = 0;
208        boolean firstCharOfLine = true;
209//        float sizeScale = (float) block.getSize() / charSet.getRenderedSize();
210        float sizeScale = 1f;
211        for (int i = 0; i < text.length(); i++){
212            char theChar = text.charAt(i);
213            if (theChar == '\n'){
214                maxLineWidth = Math.max(maxLineWidth, lineWidth);
215                lineWidth = 0f;
216                firstCharOfLine = true;
217                continue;
218            }
219            BitmapCharacter c = charSet.getCharacter((int) theChar);
220            if (c != null){
221                if (theChar == '\\' && i<text.length()-1 && text.charAt(i+1)=='#'){
222                    if (i+5<text.length() && text.charAt(i+5)=='#'){
223                        i+=5;
224                        continue;
225                    }else if (i+8<text.length() && text.charAt(i+8)=='#'){
226                        i+=8;
227                        continue;
228                    }
229                }
230                if (!firstCharOfLine){
231                    lineWidth += findKerningAmount(lastChar, theChar) * sizeScale;
232                } else {
233                    // The first character needs to add in its xOffset but it
234                    // is the only one... and negative offsets = postive width
235                    // because we're trying to account for the part that hangs
236                    // over the left.  So we subtract.
237                    lineWidth -= c.getXOffset() * sizeScale;
238                    firstCharOfLine = false;
239                }
240                float xAdvance = c.getXAdvance() * sizeScale;
241
242                // If this is the last character, then we really should have
243                // only add its width.  The advance may include extra spacing
244                // that we don't care about.
245                if (i == text.length() - 1) {
246                    lineWidth += c.getWidth() * sizeScale;
247
248                    // Since theh width includes the xOffset then we need
249                    // to take it out again by adding it, ie: offset the width
250                    // we just added by the appropriate amount.
251                    lineWidth += c.getXOffset() * sizeScale;
252                } else {
253                    lineWidth += xAdvance;
254                }
255            }
256        }
257        return Math.max(maxLineWidth, lineWidth);
258    }
259
260
261    /**
262     * Merge two fonts.
263     * If two font have the same style, merge will fail.
264     * @param styleSet Style must be assigned to this.
265     * @author Yonghoon
266     */
267    public void merge(BitmapFont newFont) {
268        charSet.merge(newFont.charSet);
269        final int size1 = this.pages.length;
270        final int size2 = newFont.pages.length;
271
272        Material[] tmp = new Material[size1+size2];
273        System.arraycopy(this.pages, 0, tmp, 0, size1);
274        System.arraycopy(newFont.pages, 0, tmp, size1, size2);
275
276        this.pages = tmp;
277
278//        this.pages = Arrays.copyOf(this.pages, size1+size2);
279//        System.arraycopy(newFont.pages, 0, this.pages, size1, size2);
280    }
281
282    public void setStyle(int style) {
283        charSet.setStyle(style);
284    }
285
286}