blob: 272019e968132a75ac3c720accead353cc0610e4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/* $Id$ */
package org.apache.fop.fonts;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.complexscripts.fonts.Positionable;
import org.apache.fop.complexscripts.fonts.Substitutable;
import org.apache.fop.render.java2d.CustomFontMetricsMapper;
import org.apache.fop.util.CharUtilities;
/**
* This class holds font state information and provides access to the font
* metrics.
*/
public class Font implements Substitutable, Positionable {
/** Extra Bold font weight */
public static final int WEIGHT_EXTRA_BOLD = 800;
/** Bold font weight */
public static final int WEIGHT_BOLD = 700;
/** Normal font weight */
public static final int WEIGHT_NORMAL = 400;
/** Light font weight */
public static final int WEIGHT_LIGHT = 200;
/** Normal font style */
public static final String STYLE_NORMAL = "normal";
/** Italic font style */
public static final String STYLE_ITALIC = "italic";
/** Oblique font style */
public static final String STYLE_OBLIQUE = "oblique";
/** Inclined font style */
public static final String STYLE_INCLINED = "inclined";
/** Default selection priority */
public static final int PRIORITY_DEFAULT = 0;
/** Default fallback key */
public static final FontTriplet DEFAULT_FONT = new FontTriplet(
"any", STYLE_NORMAL, WEIGHT_NORMAL, PRIORITY_DEFAULT);
/** logger */
private static Log log = LogFactory.getLog(Font.class);
private final String fontName;
private final FontTriplet triplet;
private final int fontSize;
/**
* normal or small-caps font
*/
//private int fontVariant;
private final FontMetrics metric;
/**
* Main constructor
* @param key key of the font
* @param triplet the font triplet that was used to lookup this font (may be null)
* @param met font metrics
* @param fontSize font size
*/
public Font(String key, FontTriplet triplet, FontMetrics met, int fontSize) {
this.fontName = key;
this.triplet = triplet;
this.metric = met;
this.fontSize = fontSize;
}
/**
* Returns the associated font metrics object.
* @return the font metrics
*/
public FontMetrics getFontMetrics() {
return this.metric;
}
/**
* Determines whether the font is a multibyte font.
* @return True if it is multibyte
*/
public boolean isMultiByte() {
return getFontMetrics().isMultiByte();
}
/**
* Returns the font's ascender.
* @return the ascender
*/
public int getAscender() {
return metric.getAscender(fontSize) / 1000;
}
/**
* Returns the font's CapHeight.
* @return the capital height
*/
public int getCapHeight() {
return metric.getCapHeight(fontSize) / 1000;
}
/**
* Returns the font's Descender.
* @return the descender
*/
public int getDescender() {
return metric.getDescender(fontSize) / 1000;
}
/**
* Returns the font's name.
* @return the font name
*/
public String getFontName() {
return fontName;
}
/** @return the font triplet that selected this font */
public FontTriplet getFontTriplet() {
return this.triplet;
}
/**
* Returns the font size
* @return the font size
*/
public int getFontSize() {
return fontSize;
}
/**
* Returns the XHeight
* @return the XHeight
*/
public int getXHeight() {
return metric.getXHeight(fontSize) / 1000;
}
/** @return true if the font has kerning info */
public boolean hasKerning() {
return metric.hasKerningInfo();
}
/** @return true if the font has feature (i.e., at least one lookup matches) */
public boolean hasFeature(int tableType, String script, String language, String feature) {
return metric.hasFeature(tableType, script, language, feature);
}
/**
* Returns the font's kerning table
* @return the kerning table
*/
public Map<Integer, Map<Integer, Integer>> getKerning() {
if (metric.hasKerningInfo()) {
return metric.getKerningInfo();
} else {
return Collections.emptyMap();
}
}
/**
* Returns the amount of kerning between two characters.
*
* The value returned measures in pt. So it is already adjusted for font size.
*
* @param ch1 first character
* @param ch2 second character
* @return the distance to adjust for kerning, 0 if there's no kerning
*/
public int getKernValue(int ch1, int ch2) {
// Isolate surrogate pair
if ((ch1 >= 0xD800) && (ch1 <= 0xE000)) {
return 0;
} else if ((ch2 >= 0xD800) && (ch2 <= 0xE000)) {
return 0;
}
Map<Integer, Integer> kernPair = getKerning().get(ch1);
if (kernPair != null) {
Integer width = kernPair.get(ch2);
if (width != null) {
return width * getFontSize() / 1000;
}
}
return 0;
}
/**
* Returns the width of a character
* @param charnum character to look up
* @return width of the character
*/
public int getWidth(int charnum) {
// returns width of given character number in millipoints
return (metric.getWidth(charnum, fontSize) / 1000);
}
/**
* Map a java character (unicode) to a font character.
* Default uses CodePointMapping.
* @param c character to map
* @return the mapped character
*/
public char mapChar(char c) {
if (metric instanceof org.apache.fop.fonts.Typeface) {
return ((org.apache.fop.fonts.Typeface)metric).mapChar(c);
}
// Use default CodePointMapping
char d = CodePointMapping.getMapping("WinAnsiEncoding").mapChar(c);
if (d != SingleByteEncoding.NOT_FOUND_CODE_POINT) {
c = d;
} else {
log.warn("Glyph " + (int) c + " not available in font " + fontName);
c = Typeface.NOT_FOUND;
}
return c;
}
/**
* Map a unicode code point to a font character.
* Default uses CodePointMapping.
* @param cp code point to map
* @return the mapped character
*/
public int mapCodePoint(int cp) {
FontMetrics fontMetrics = getRealFontMetrics();
if (fontMetrics instanceof CIDFont) {
return ((CIDFont) fontMetrics).mapCodePoint(cp);
}
if (CharUtilities.isBmpCodePoint(cp)) {
return mapChar((char) cp);
}
return Typeface.NOT_FOUND;
}
/**
* Determines whether this font contains a particular character/glyph.
* @param c character to check
* @return True if the character is supported, False otherwise
*/
public boolean hasChar(char c) {
if (metric instanceof org.apache.fop.fonts.Typeface) {
return ((org.apache.fop.fonts.Typeface)metric).hasChar(c);
} else {
// Use default CodePointMapping
return (CodePointMapping.getMapping("WinAnsiEncoding").mapChar(c) > 0);
}
}
/**
* Determines whether this font contains a particular code point/glyph.
* @param cp code point to check
* @return True if the code point is supported, False otherwise
*/
public boolean hasCodePoint(int cp) {
FontMetrics realFont = getRealFontMetrics();
if (realFont instanceof CIDFont) {
return ((CIDFont) realFont).hasCodePoint(cp);
}
if (CharUtilities.isBmpCodePoint(cp)) {
return hasChar((char) cp);
}
return false;
}
/**
* Get the real underlying font if it is wrapped inside some container such as a {@link LazyFont} or a
* {@link CustomFontMetricsMapper}.
*
* @return instance of the font
*/
private FontMetrics getRealFontMetrics() {
FontMetrics realFontMetrics = metric;
if (realFontMetrics instanceof CustomFontMetricsMapper) {
realFontMetrics = ((CustomFontMetricsMapper) realFontMetrics).getRealFont();
}
if (realFontMetrics instanceof LazyFont) {
return ((LazyFont) realFontMetrics).getRealFont();
}
return realFontMetrics;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuffer sbuf = new StringBuffer(super.toString());
sbuf.append('{');
/*
sbuf.append(fontFamily);
sbuf.append(',');*/
sbuf.append(fontName);
sbuf.append(',');
sbuf.append(fontSize);
/*
sbuf.append(',');
sbuf.append(fontStyle);
sbuf.append(',');
sbuf.append(fontWeight);*/
sbuf.append('}');
return sbuf.toString();
}
/**
* Helper method for getting the width of a unicode char
* from the current fontstate.
* This also performs some guessing on widths on various
* versions of space that might not exists in the font.
* @param c character to inspect
* @return the width of the character or -1 if no width available
*/
public int getCharWidth(char c) {
int width;
if ((c == '\n') || (c == '\r') || (c == '\t') || (c == '\u00A0')) {
width = getCharWidth(' ');
} else {
if (hasChar(c)) {
int mappedChar = mapChar(c);
width = getWidth(mappedChar);
} else {
width = -1;
}
if (width <= 0) {
// Estimate the width of spaces not represented in
// the font
int em = getFontSize(); //http://en.wikipedia.org/wiki/Em_(typography)
int en = em / 2; //http://en.wikipedia.org/wiki/En_(typography)
if (c == ' ') {
width = em;
} else if (c == '\u2000') {
width = en;
} else if (c == '\u2001') {
width = em;
} else if (c == '\u2002') {
width = em / 2;
} else if (c == '\u2003') {
width = getFontSize();
} else if (c == '\u2004') {
width = em / 3;
} else if (c == '\u2005') {
width = em / 4;
} else if (c == '\u2006') {
width = em / 6;
} else if (c == '\u2007') {
width = getCharWidth('0');
} else if (c == '\u2008') {
width = getCharWidth('.');
} else if (c == '\u2009') {
width = em / 5;
} else if (c == '\u200A') {
width = em / 10;
} else if (c == '\u200B') {
width = 0;
} else if (c == '\u202F') {
width = getCharWidth(' ') / 2;
} else if (c == '\u2060') {
width = 0;
} else if (c == '\u3000') {
width = getCharWidth(' ') * 2;
} else if (c == '\ufeff') {
width = 0;
} else {
//Will be internally replaced by "#" if not found
width = getWidth(mapChar(c));
}
}
}
return width;
}
/**
* Helper method for getting the width of a unicode char
* from the current fontstate.
* This also performs some guessing on widths on various
* versions of space that might not exists in the font.
* @param c character to inspect
* @return the width of the character or -1 if no width available
*/
public int getCharWidth(int c) {
if (c < 0x10000) {
return getCharWidth((char) c);
}
if (hasCodePoint(c)) {
int mappedChar = mapCodePoint(c);
return getWidth(mappedChar);
}
return -1;
}
/**
* Calculates the word width.
* @param word text to get width for
* @return the width of the text
*/
public int getWordWidth(String word) {
if (word == null) {
return 0;
}
int wordLength = word.length();
int width = 0;
char[] characters = new char[wordLength];
word.getChars(0, wordLength, characters, 0);
for (int i = 0; i < wordLength; i++) {
width += getCharWidth(characters[i]);
}
return width;
}
/** {@inheritDoc} */
public boolean performsSubstitution() {
if (metric instanceof Substitutable) {
Substitutable s = (Substitutable) metric;
return s.performsSubstitution();
} else {
return false;
}
}
/** {@inheritDoc} */
public CharSequence performSubstitution(CharSequence cs,
String script, String language, List associations, boolean retainControls) {
if (metric instanceof Substitutable) {
Substitutable s = (Substitutable) metric;
return s.performSubstitution(cs, script, language, associations, retainControls);
} else {
throw new UnsupportedOperationException();
}
}
/** {@inheritDoc} */
public CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa,
String script, String language, List associations) {
if (metric instanceof Substitutable) {
Substitutable s = (Substitutable) metric;
return s.reorderCombiningMarks(cs, gpa, script, language, associations);
} else {
throw new UnsupportedOperationException();
}
}
/** {@inheritDoc} */
public boolean performsPositioning() {
if (metric instanceof Positionable) {
Positionable p = (Positionable) metric;
return p.performsPositioning();
} else {
return false;
}
}
/** {@inheritDoc} */
public int[][] performPositioning(CharSequence cs, String script, String language, int fontSize) {
if (metric instanceof Positionable) {
Positionable p = (Positionable) metric;
return p.performPositioning(cs, script, language, fontSize);
} else {
throw new UnsupportedOperationException();
}
}
/** {@inheritDoc} */
public int[][] performPositioning(CharSequence cs, String script, String language) {
return performPositioning(cs, script, language, fontSize);
}
}