1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /* $Id: RasterFont.java 1761019 2016-09-16 10:43:45Z ssteiner $ */
19 
20 package org.apache.fop.afp.fonts;
21 
22 import java.awt.Rectangle;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.SortedMap;
26 import java.util.TreeMap;
27 
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 
31 /**
32  * A font where each character is stored as an array of pixels (a bitmap). Such
33  * fonts are not easily scalable, in contrast to vectored fonts. With this type
34  * of font, the font metrics information is held in character set files (one for
35  * each size and style).
36  */
37 public class RasterFont extends AFPFont {
38 
39     /** Static logging instance */
40     protected static final Log LOG = LogFactory.getLog("org.apache.fop.afp.fonts");
41 
42     private final SortedMap<Integer, CharacterSet> charSets = new TreeMap<Integer, CharacterSet>();
43     private Map<Integer, CharacterSet> substitutionCharSets;
44 
45     private CharacterSet charSet;
46 
47     /**
48      * Constructor for the raster font requires the name, weight and style
49      * attribute to be available as this forms the key to the font.
50      *
51      * @param name the name of the font
52      * @param embeddable {@code true} if the font is embeddable
53      */
RasterFont(String name, boolean embeddable)54     public RasterFont(String name, boolean embeddable) {
55         super(name, embeddable);
56     }
57 
58     /**
59      * Adds the character set for the given point size
60      * @param size point size (in mpt)
61      * @param characterSet character set
62      */
addCharacterSet(int size, CharacterSet characterSet)63     public void addCharacterSet(int size, CharacterSet characterSet) {
64         //TODO: replace with Integer.valueOf() once we switch to Java 5
65         this.charSets.put(size, characterSet);
66         this.charSet = characterSet;
67     }
68 
69     /**
70      * Get the character set metrics for the specified point size.
71      *
72      * @param sizeInMpt the point size (in mpt)
73      * @return the character set metrics
74      */
getCharacterSet(int sizeInMpt)75     public CharacterSet getCharacterSet(int sizeInMpt) {
76 
77         Integer requestedSize = sizeInMpt;
78         CharacterSet csm = charSets.get(requestedSize);
79         double sizeInPt = sizeInMpt / 1000.0;
80 
81         if (csm != null) {
82             return csm;
83         }
84 
85         if (substitutionCharSets != null) {
86             //Check first if a substitution has already been added
87             csm = substitutionCharSets.get(requestedSize);
88         }
89 
90         if (csm == null && !charSets.isEmpty()) {
91             // No match or substitution found, but there exist entries
92             // for other sizes
93             // Get char set with nearest, smallest font size
94             SortedMap<Integer, CharacterSet> smallerSizes = charSets.headMap(requestedSize);
95             SortedMap<Integer, CharacterSet> largerSizes = charSets.tailMap(requestedSize);
96             int smallerSize = smallerSizes.isEmpty() ? 0
97                     : smallerSizes.lastKey();
98             int largerSize = largerSizes.isEmpty() ? Integer.MAX_VALUE
99                     : largerSizes.firstKey();
100 
101             Integer fontSize;
102             if (!smallerSizes.isEmpty()
103                             && (sizeInMpt - smallerSize) <= (largerSize - sizeInMpt)) {
104                 fontSize = smallerSize;
105             } else {
106                 fontSize = largerSize;
107             }
108             csm = charSets.get(fontSize);
109 
110             if (csm != null) {
111                 // Add the substitute mapping, so subsequent calls will
112                 // find it immediately
113                 if (substitutionCharSets == null) {
114                     substitutionCharSets = new HashMap<Integer, CharacterSet>();
115                 }
116                 substitutionCharSets.put(requestedSize, csm);
117                 // do not output the warning if the font size is closer to an integer less than 0.1
118                 if (!(Math.abs(fontSize / 1000.0 - sizeInPt) < 0.1)) {
119                     String msg = "No " + sizeInPt + "pt font " + getFontName()
120                             + " found, substituted with " + fontSize / 1000f + "pt font";
121                     LOG.warn(msg);
122                 }
123             }
124         }
125 
126         if (csm == null) {
127             // Still no match -> error
128             String msg = "No font found for font " + getFontName() + " with point size " + sizeInPt;
129             LOG.error(msg);
130             throw new FontRuntimeException(msg);
131         }
132 
133         return csm;
134 
135     }
136 
metricsToAbsoluteSize(CharacterSet cs, int value, int givenSize)137     private int metricsToAbsoluteSize(CharacterSet cs, int value, int givenSize) {
138         int nominalVerticalSize = cs.getNominalVerticalSize();
139         if (nominalVerticalSize != 0) {
140             return value * nominalVerticalSize;
141         } else {
142             return value * givenSize;
143         }
144     }
145 
metricsToAbsoluteSize(CharacterSet cs, double value, int givenSize)146     private int metricsToAbsoluteSize(CharacterSet cs, double value, int givenSize) {
147         int nominalVerticalSize = cs.getNominalVerticalSize();
148         if (nominalVerticalSize != 0) {
149             return (int) (value * nominalVerticalSize);
150         } else {
151             return (int) (value * givenSize);
152         }
153     }
154 
155     /**
156      * The ascender is the part of a lowercase letter that extends above the
157      * "x-height" (the height of the letter "x"), such as "d", "t", or "h". Also
158      * used to denote the part of the letter extending above the x-height.
159      *
160      * @param size the font size (in mpt)
161      * @return the ascender for the given point size
162      */
getAscender(int size)163     public int getAscender(int size) {
164         CharacterSet cs = getCharacterSet(size);
165         return metricsToAbsoluteSize(cs, cs.getAscender(), size);
166     }
167 
168     /** {@inheritDoc} */
getUnderlinePosition(int size)169     public int getUnderlinePosition(int size) {
170         CharacterSet cs = getCharacterSet(size);
171         return metricsToAbsoluteSize(cs, cs.getUnderscorePosition(), size);
172     }
173 
174     @Override
getUnderlineThickness(int size)175     public int getUnderlineThickness(int size) {
176         CharacterSet cs = getCharacterSet(size);
177         int underscoreWidth = cs.getUnderscoreWidth();
178         return underscoreWidth == 0 ? super.getUnderlineThickness(size)
179                 : metricsToAbsoluteSize(cs, underscoreWidth, size);
180     }
181 
182     /**
183      * Obtains the height of capital letters for the specified point size.
184      *
185      * @param size the font size (in mpt)
186      * @return the cap height for the specified point size
187      */
getCapHeight(int size)188     public int getCapHeight(int size) {
189         CharacterSet cs = getCharacterSet(size);
190         return metricsToAbsoluteSize(cs, cs.getCapHeight(), size);
191     }
192 
193     /**
194      * The descender is the part of a lowercase letter that extends below the
195      * base line, such as "g", "j", or "p". Also used to denote the part of the
196      * letter extending below the base line.
197      *
198      * @param size the font size (in mpt)
199      * @return the descender for the specified point size
200      */
getDescender(int size)201     public int getDescender(int size) {
202         CharacterSet cs = getCharacterSet(size);
203         return metricsToAbsoluteSize(cs, cs.getDescender(), size);
204     }
205 
206     /**
207      * The "x-height" (the height of the letter "x").
208      *
209      * @param size the font size (in mpt)
210      * @return the x height for the given point size
211      */
getXHeight(int size)212     public int getXHeight(int size) {
213         CharacterSet cs = getCharacterSet(size);
214         return metricsToAbsoluteSize(cs, cs.getXHeight(), size);
215     }
216 
217     /**
218      * Obtain the width of the character for the specified point size.
219      * @param character the character
220      * @param size the font size (in mpt)
221      * @return the width for the given point size
222      */
getWidth(int character, int size)223     public int getWidth(int character, int size) {
224         CharacterSet cs = getCharacterSet(size);
225         return metricsToAbsoluteSize(cs, cs.getWidth(toUnicodeCodepoint(character), 1), size);
226     }
227 
228     /**
229      * TODO
230      */
getBoundingBox(int character, int size)231     public Rectangle getBoundingBox(int character, int size) {
232         CharacterSet cs = getCharacterSet(size);
233         Rectangle characterBox = cs.getCharacterBox(toUnicodeCodepoint(character), 1);
234         int x = metricsToAbsoluteSize(cs, characterBox.getX(), size);
235         int y = metricsToAbsoluteSize(cs, characterBox.getY(), size);
236         int w = metricsToAbsoluteSize(cs, characterBox.getWidth(), size);
237         int h = metricsToAbsoluteSize(cs, characterBox.getHeight(), size);
238         return new Rectangle(x, y, w, h);
239     }
240 
241     /** {@inheritDoc} */
hasChar(char c)242     public boolean hasChar(char c) {
243         return charSet.hasChar(c);
244     }
245 
246     /**
247      * Map a Unicode character to a code point in the font.
248      * @param c character to map
249      * @return the mapped character
250      */
mapChar(char c)251     public char mapChar(char c) {
252         return charSet.mapChar(c);
253     }
254 
255     /** {@inheritDoc} */
getEncodingName()256     public String getEncodingName() {
257         return charSet.getEncoding();
258     }
259 }
260