com.adobe.fontengine.font.FontData Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*
*
* File: FontData.java
*
*
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2004-2006 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains the property of
* Adobe Systems Incorporated and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe Systems
* Incorporated and its suppliers and may be covered by U.S. and Foreign
* Patents, patents in process, and are protected by trade secret or
* copyright law. Dissemination of this information or reproduction of this
* material is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
*
*/
/*
* Adobe Patent and/or Adobe Patent Pending invention included within this file:
*
* Adobe patent application tracking # P376,
* entitled 'Method for calculating CJK emboxes in fonts',
* invented by Nathaniel McCully
* Issued US Patent 7,071,941 on July 4, 2006.
*
* Adobe patent application tracking # P376,
* entitled 'A LINE COMPOSITION CONTROLLABLE DTP SYSTEM, A LINE
* COMPOSITION CONTROLLING METHOD, A LINE COMPOSITION CONTROL
* PROGRAM AND A RECORDING MEDIUM STORING THE SAME',
* invented by Nathaniel McCully
* Issued Japanese Patent 3708828 on August 12, 2005.
*
* Adobe patent application tracking # P377,
* entitled 'LINE PREEMPT CONTROLLABLE DTP SYSTEM, A LINE
* PREEMPT CONTROL METHOD, A LINE PREEMPT CONTROL PROGRAM
* AND A RECORDING MEDIUM STORING THE SAME'
* invented by Nathaniel McCully
* Issued Japanese Patent 3598070 on September 17, 2004.
*/
package com.adobe.fontengine.font;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Set;
import com.adobe.agl.util.ULocale;
import com.adobe.fontengine.fontmanagement.CacheSupportInfo;
import com.adobe.fontengine.fontmanagement.Platform;
import com.adobe.fontengine.fontmanagement.fxg.FXGFontDescription;
import com.adobe.fontengine.fontmanagement.platform.PlatformFontDescription;
import com.adobe.fontengine.fontmanagement.postscript.PostscriptFontDescription;
import com.adobe.fontengine.inlineformatting.css20.CSS20FontDescription;
import com.adobe.fontengine.inlineformatting.css20.CSS20Attribute.CSSStretchValue;
import com.adobe.fontengine.inlineformatting.css20.CSS20Attribute.CSSStyleValue;
import com.adobe.fontengine.inlineformatting.css20.CSS20Attribute.CSSVariantValue;
/**
* The base class for all FontData objects.
*
* This class provides the basic functionality common to all Fonts.
*
* Synchronization
*
* These objects are immutable.
*/
abstract public class FontData {
//---------------------------------------------------------------- members ---
private final byte[] containerFingerprint;
//----------------------------------------------------------- constructors ---
public FontData (byte[] digest) {
// store a clone of the digest, so that malicious
// code can not modify our copy
containerFingerprint = (digest == null ? null : (byte[]) digest.clone ());
}
//--------------------------------------------------------- general access ---
public byte[] getContainerFingerprint () {
// return a clone of our bytes, so that malicious
// code can not modify them
if (containerFingerprint == null)
return new byte[0];
return containerFingerprint.clone ();
}
/** Return the number of glyphs in this font. */
abstract public int getNumGlyphs ()
throws InvalidFontException, UnsupportedFontException;
/** Tell whether this font is symbolic.
* @throws UnsupportedFontException
* @throws InvalidFontException
*/
public boolean isSymbolic ()
throws UnsupportedFontException, InvalidFontException {
// most font formats do not have a notion of symbolic fonts.
return false;
}
//------------------------------------------------------------- unitsPerEm ---
abstract public double getUnitsPerEmX ()
throws UnsupportedFontException, InvalidFontException;
abstract public double getUnitsPerEmY ()
throws UnsupportedFontException, InvalidFontException;
public double getCoolTypeUnitsPerEm ()
throws UnsupportedFontException, InvalidFontException {
// most formats use that value (and ignore the font matrix);
// only OpenType fonts do something else
return 1000.0d;
}
//----------------------------------------------------------------- bboxes ---
abstract public Rect getFontBBox ()
throws InvalidFontException, UnsupportedFontException;
// CoolType does not use the same bbox as returned by getFontBBox
// First, in OpenType font, the bbox recorded in the CFF table (if present)
// is preferred over the bbox recorded in the head table.
// Second, "strange" bboxes are "corrected".
//
// getCoolTypeRawFontBBox takes care of the first part, and depends on
// the font type, while the second difference is accounted for in
// getCoolTypeFontBBox.
abstract protected Rect getCoolTypeRawFontBBox ()
throws InvalidFontException, UnsupportedFontException;
public Rect getCoolTypeFontBBox ()
throws InvalidFontException, UnsupportedFontException {
Rect r = getCoolTypeRawFontBBox ();
if (r.xmin == r.xmax || r.ymin == r.ymax) {
double unitsPerEmX = getUnitsPerEmX ();
double unitsPerEmY = getUnitsPerEmY ();
return new Rect (-0.5d * unitsPerEmX, -0.5d * unitsPerEmY,
1.5d * unitsPerEmX, 1.0d * unitsPerEmY); }
else {
return r; }
}
//----------------------------------------------------------------- script ---
abstract public CoolTypeScript getCoolTypeScript ()
throws UnsupportedFontException, InvalidFontException;
protected boolean useCoolTypeCJKHeuristics ()
throws UnsupportedFontException, InvalidFontException {
CoolTypeScript ctScript = getCoolTypeScript ();
return ( ctScript == CoolTypeScript.JAPANESE
|| ctScript == CoolTypeScript.SIMPLIFIED_CHINESE
|| ctScript == CoolTypeScript.TRADITIONAL_CHINESE
|| ctScript == CoolTypeScript.KOREAN);
}
//-------------------------------------------------------------- capHeight ---
/** Returns the CoolTypeCapHeight of this font from typical glyphs.
* @return Double.NaN if the value cannot be determined
*/
protected double getCoolTypeCapHeightFromGlyphs ()
throws UnsupportedFontException, InvalidFontException {
int gidO = getCoolTypeGlyphForChar ('O');
int gidH = getCoolTypeGlyphForChar ('H');
if (gidO != 0 && gidH != 0) {
Rect bboxO = getGlyphBBox (gidO);
Rect bboxH = getGlyphBBox (gidH);
if (! bboxO.equals (Rect.emptyRect) && ! bboxH.equals (Rect.emptyRect)) {
return Math.min (bboxO.ymax, bboxH.ymax); }}
return Double.NaN;
}
protected double getCoolTypeXHeightFromGlyphs()
throws UnsupportedFontException, InvalidFontException
{
int gidx = getCoolTypeGlyphForChar ('x');
if (gidx != 0) {
Rect bboxX = getGlyphBBox (gidx);
if (! bboxX.equals (Rect.emptyRect)) {
return bboxX.ymax;
}
}
return Double.NaN;
}
/** Returns the CoolTypeCapHeight of this font.
* @return Double.NaN if the value cannot be determined
*/
public double getCoolTypeCapHeight ()
throws UnsupportedFontException, InvalidFontException {
return getCoolTypeCapHeightFromGlyphs ();
}
public double getCoolTypeXHeight()
throws UnsupportedFontException, InvalidFontException {
return getCoolTypeXHeightFromGlyphs();
}
//-------------------------------------------------------------- ideoEmBox ---
protected Rect snapToKnownIdeoEmBox (double ymin, double ymax)
throws UnsupportedFontException, InvalidFontException {
double unitsPerEmX = getUnitsPerEmX ();
double unitsPerEmY = getUnitsPerEmY ();
if (unitsPerEmY == 1000
&& Math.abs (ymin - (-120)) <= .004 * unitsPerEmY
&& Math.abs (ymax - 880) <= 0.004 * unitsPerEmY) {
return new Rect (0, -0.120 * unitsPerEmY, unitsPerEmX, 0.880 * unitsPerEmY); }
if (unitsPerEmY == 256
&& Math.abs (ymin - (-36)) <= .0045 * unitsPerEmY
&& Math.abs (ymax - 220) <= 0.0045 * unitsPerEmY) {
return new Rect (0, -0.140625 * unitsPerEmY, unitsPerEmX, 0.859375 * unitsPerEmY); }
return null;
}
// When the font data is absent or deemed unreliable, we
// use the bounding boxes of a number of typical glyphs
protected static final int[] typicalCharactersForIdeoEmBoxComputation
= {'\u6c38', // U+6C38 CJK UNIFIED IDEOGRAPH-6C38
'\u9b31', // U+9B31 CJK UNIFIED IDEOGRAPH-9B31
'\uad2c', // U+AD2C HANGUL SYLLABLE GWAESS
'\u30fb', // U+30FB KATAKANA MIDDLE DOT
};
protected Rect getCoolTypeIdeoEmBoxFromFullBoxCharacter ()
throws InvalidFontException, UnsupportedFontException {
int gid = getCoolTypeGlyphForChar ('\u253C'); // U+253C ? BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
if (gid != 0) {
Rect bbox = getGlyphBBox (gid);
double unitsPerEmY = getUnitsPerEmY ();
if (! bbox.equals (Rect.emptyRect)) {
if (bbox.ymax - bbox.ymin != unitsPerEmY) {
return null; }
return snapToKnownIdeoEmBox (bbox.ymin, bbox.ymax); }}
return null;
}
protected Rect getCoolTypeIdeoEmBoxFromTypicalCharacter ()
throws InvalidFontException, UnsupportedFontException {
double unitsPerEmX = getUnitsPerEmX ();
double unitsPerEmY = getUnitsPerEmY ();
for (int i = 0; i < typicalCharactersForIdeoEmBoxComputation.length; i++) {
int gid = getCoolTypeGlyphForChar (typicalCharactersForIdeoEmBoxComputation [i]);
if (gid != 0) {
Rect bbox = getGlyphBBox (gid);
if (! bbox.equals (Rect.emptyRect)) {
double yMax = bbox.ymax + (unitsPerEmY - (bbox.ymax - bbox.ymin)) / 2.0;
Rect r = snapToKnownIdeoEmBox (yMax - unitsPerEmY, yMax);
if (r != null) {
return r; }
else {
return new Rect (0, yMax - unitsPerEmY, unitsPerEmX, yMax); }}}}
return null;
}
abstract public Rect getCoolTypeIdeoEmBox ()
throws UnsupportedFontException, InvalidFontException;
//----------------------------------------------------------------- icfBox ---
protected static final int[] typicalCharactersForIcfBoxComputation
= {'\u6c38', // U+6C38 CJK UNIFIED IDEOGRAPH-6C38
'\u9b31', // U+9B31 CJK UNIFIED IDEOGRAPH-9B31
'\uad2c', // U+AD2C HANGUL SYLLABLE GWAESS
'\u30db', // U+30DB KATAKANA LETTER HO
};
/** Compute the IcfBox from typical characters.
* In this heuristic, the IcfBox is slightly smaller than the IdeoEmBox,
* and centered into it. The margin between the two boxes is constant
* all around, and is determined from the position of typical characters
* in the IdeoEmBox.
*/
protected Rect getCoolTypeIcfBoxFromTypicalCharacter (Rect ideoEmBox)
throws InvalidFontException, UnsupportedFontException {
for (int i = 0; i < typicalCharactersForIcfBoxComputation.length; i++) {
int gid = getCoolTypeGlyphForChar (typicalCharactersForIcfBoxComputation [i]);
if (gid != 0) {
Rect bbox = getGlyphBBox (gid);
if (! bbox.equals (Rect.emptyRect)) {
double margin = ((ideoEmBox.ymax - bbox.ymax) +
(bbox.ymin - ideoEmBox.ymin)) / 2.0d;
if (margin < 0 && useCoolTypeCJKHeuristics ()) {
return null; }
else {
return new Rect (ideoEmBox.xmin + margin, ideoEmBox.ymin + margin,
ideoEmBox.xmax - margin, ideoEmBox.ymax - margin); }}}}
return null;
}
/**
* Compute the IcfBox from IdeoEmBox. In this heuristic, the IcfBox is
* slightly smaller than the IdeoEmBox, and centered into it. The margin
* between the two boxes is constant all around and is equal to the margin
* obtained by centering a square of area .9 square em inside a square of area
* 1 square em.
*/
protected Rect getCoolTypeIcfBoxFromIdeoEmBox (Rect ideoEmBox)
throws InvalidFontException, UnsupportedFontException {
double marginInEm = (1.0d - Math.sqrt (0.9d)) / 2.0d;
double marginXInMetricUnits = marginInEm * getUnitsPerEmX ();
double marginYInMetricUnits = marginInEm * getUnitsPerEmY ();
return new Rect (ideoEmBox.xmin + marginXInMetricUnits,
ideoEmBox.ymin + marginYInMetricUnits,
ideoEmBox.xmax - marginXInMetricUnits,
ideoEmBox.ymax - marginYInMetricUnits);
}
abstract public Rect getCoolTypeIcfBox ()
throws UnsupportedFontException, InvalidFontException;
//----------------------------------------------------------- line metrics ---
/** A number of heuristics eventually compute the line metrics
* from the font bbox. */
protected LineMetrics getCoolTypeLineMetricsFromFontBbox ()
throws UnsupportedFontException, InvalidFontException {
Rect bbox = getCoolTypeFontBBox ();
if (bbox == null) {
return null; }
double ascender;
double descender;
// The algorithm used in CoolType performs different computations
// based on the vertical extent of the font bbox. There is one computation
// for bboxes smaller than 1em, another computation for bboxes between
// 1em and 1.2em, and another computation for bboxes larger than 1.2em.
// These computations are discontinous, in the following sense: for fonts
// with a bbox height between 1em and 1.2em, the descender is aligned
// with the bottom of the bbox, and for fonts above 1.2em, the descender
// is significantly not aligned with the bottom of the bbox. So two
// fonts which have very similar bboxes can end up with fairly different
// ascender and descender.
// This discontinuity is probably problematic on its own, but we don't
// really care about that: we promise to do the same thing as CoolType,
// whatever that may be.
// But it makes our task a bit more difficult: let's say that CoolType
// determines the vertical extent to be just above the 1.2em threshold,
// and that AFE determines it to be just below that threshold: we
// end up with significantly different results. So our emulation has
// to be fairly precise, up to the point where AFE selects the same
// computation as CoolType.
// How difficult is that? CoolType internally represents metrics using
// 16.16 fixed numbers, expressed in em. For example, the quantity
// 1/4 em is represented as 0x00004000.
// Let's consider a font at 1000 units per em, with a vertical extent
// of (-250, 950), i.e. exactly 1.2em. These numbers, as seen by CoolType,
// are 0xfffc000 and 0xf333, for a vertical extent of 0x13333.
// That triggers the [1.0em, 1.2em[ computation.
// Let's consider another font also at 1000 units per em, with a
// vertical extent of (-238, 962), also exactly 1.2em high; these numbers,
// as seen by CoolType, are 0xfffc312 and 0xf646, for a vertical extent
// of 0x1334. and that triggers the >1.2em computation!
// What is happening here is that with 1000 units per em, not all
// integral metrics can be represented exactly in a 16.16 fixed number,
// so some rounding happens; and the combined rounding of ymin and ymax
// leads to a very slightly different extent (a 65,636th of an em!),
// but that is enough to select different computations.
// By the way, those two fonts are not hypothetical. An example of the
// first is 0e01a3f7f82952d5c7b24cb30f02ae1f4e1d1db1, and example of the
// second is 003383b2ad7501a9b63db36cbc10860c75bff14e.
// Because AFE works essentially with numbers expressed in design units,
// the two fonts are not distinguishable, and the same computation would
// normally be applied to those two fonts. And because of the discontinuity,
// one of the two fonts would have line metrics significantly different
// from CoolType.
// To ensure that AFE selects the same computation as CoolType, we need
// to compute the vertical extent with the same precision (or lack of) as
// CoolType. We achieve that by rounding to the same precision:
double unitsPerEmY = getUnitsPerEmY ();
long yMinCT = Math.round (65536.0d * bbox.ymin / unitsPerEmY);
long yMaxCT = Math.round (65536.0d * bbox.ymax / unitsPerEmY);
long extentCT = yMaxCT - yMinCT;
if (extentCT > 0x13333) {
ascender = bbox.ymax * unitsPerEmY / (bbox.ymax - bbox.ymin);
descender = ascender - unitsPerEmY; }
else if (extentCT >= 0x10000) {
descender = bbox.ymin;
ascender = descender + unitsPerEmY; }
else {
ascender = bbox.ymax;
descender = ascender - unitsPerEmY; }
return new LineMetrics (ascender, descender, unitsPerEmY / 5);
}
public Rect getCoolTypeGlyphBBox(int glyphID)
throws UnsupportedFontException, InvalidFontException
{
// for most font types, cooltype does the same thing as our getGlyphBBox.
return getGlyphBBox(glyphID);
}
/** Emulates the CoolType API CTFontDict:GetHorizontalMetrics CoolType API.
*
* See also the {@link #getLineMetrics()} method.
*
* @throws UnsupportedFontException
* @throws InvalidFontException
*/
abstract public LineMetrics getCoolTypeLineMetrics ()
throws UnsupportedFontException, InvalidFontException;
/** Return the line metrics for this font.
*
*
Some font formats do not support the notion of line metrics,
* and in those cases, this method returns null.
*
*
See also the {@link #getCoolTypeLineMetrics()} method.
*
* @throws UnsupportedFontException
* @throws InvalidFontException
*/
public LineMetrics getLineMetrics ()
throws UnsupportedFontException, InvalidFontException {
// most font formats doe not have a notion of line metrics,
// subclasses for the appropriate formats to override.
return null;
}
//------------------------------------------------------ underline metrics ---
abstract public UnderlineMetrics getCoolTypeUnderlineMetrics ()
throws UnsupportedFontException, InvalidFontException;
//----------------------------------------------------------- proportional ---
protected static final int[][] typicalCharactersForProportionalRomanDecision
= {
{'\u0020', /* space */
'\u002e', /* period */
'\u004d', /* latin capital M */
},
{'\u0020',
'\u002e',
'\u039c', /* Greek Mu...for symbol fonts */
},
{'\u0020',
'\u002e',
'\ufb03', /* ffi for expert fonts */
}
};
/** Determines whether a font has proportional roman, as defined by CoolType.
*
* @return whether the font is proportional
* @throws InvalidFontException
* @throws UnsupportedFontException
*/
public boolean hasCoolTypeProportionalRoman()
throws InvalidFontException, UnsupportedFontException {
final int notdef = 0;
int i;
double firstWidth = 0;
boolean gotFirstWidth = false;
int[] gids = new int [typicalCharactersForProportionalRomanDecision[0].length];
for (i = 0; i < typicalCharactersForProportionalRomanDecision.length; i++) {
int glyphsFound = 0;
for (int j = 0; j < typicalCharactersForProportionalRomanDecision[i].length; j++) {
gids[j] = this.getGlyphForChar(typicalCharactersForProportionalRomanDecision[i][j]);
if (gids[j] != notdef) {
glyphsFound++; }}
if (glyphsFound == typicalCharactersForProportionalRomanDecision[i].length) {
break; }}
// only roman glyphs are considered by cooltype for cjk fonts.
if (i > 0 && useCoolTypeCJKHeuristics()) {
return false; }
if (i < typicalCharactersForProportionalRomanDecision.length) {
double thisWidth;
for (int j = 0; j < typicalCharactersForProportionalRomanDecision[i].length; j++) {
thisWidth = getHorizontalAdvance (gids[j]);
if (!gotFirstWidth) {
if (Math.round (thisWidth) == 0) {
continue; }
gotFirstWidth = true;
firstWidth = thisWidth; }
else if (Math.abs (firstWidth - thisWidth) > 1) {
return true; }}
return false; }
return getCoolTypeProportionalRomanFromFontProperties ();
}
abstract public boolean getCoolTypeProportionalRomanFromFontProperties ()
throws InvalidFontException;
//------------------------------------------------------------------- cmap ---
/** Return the glyph to use to display a character.
*
* Depending on the layout technology of the font, the returned gid
* may be further processed.
*
* @param unicodeScalarValue the Unicode scalar value of the character;
* (by definition, surrogate code points are not Unicode scalar values).
* @return the gid of the glyph to use
*/
abstract public int getGlyphForChar (int unicodeScalarValue)
throws InvalidFontException, UnsupportedFontException;
/** Return the glyph used by CoolType for a character.
*
* This is slightly different from getGlyphForChar, and intend to be closer
* to the behavior of CoolType. The main use of this version is for other
* functions that emulate CoolType, such as the methods that compute
* metrics based on the charateristics of some glyphs.
*
* In most font types, this is the same as getGlyphForChar, so we
* provide a default implementation here.
*/
public int getCoolTypeGlyphForChar (int unicodeScalarValue)
throws InvalidFontException, UnsupportedFontException {
return getGlyphForChar (unicodeScalarValue);
}
//-------------------------------------------------- individual glyph data ---
/** Get the horizontal advance of a glyph.
* The returned value is in metric space.
*/
abstract public double getHorizontalAdvance (int gid)
throws InvalidGlyphException, UnsupportedFontException, InvalidFontException;
/** Send a glyph's outline to an OutlineConsumer. */
abstract public void getGlyphOutline (int gid, OutlineConsumer consumer)
throws InvalidFontException, UnsupportedFontException;
/** Get the bounding box of a glyph.
* The returned value is in metric space.
*/
abstract public Rect getGlyphBBox (int gid)
throws UnsupportedFontException, InvalidFontException;
//----------------------------------------------------------------------------
/** Get a scaler for this font.
* This scaler uses to the most appropriate scan converter for the font. */
public Scaler getScaler ()
throws InvalidFontException, UnsupportedFontException {
return getScaler (null);
}
/** Get a scaler for this font, using a specific scan converter. */
abstract public Scaler getScaler (ScanConverter c)
throws InvalidFontException, UnsupportedFontException;
//--------------------------------------------------------- swf generation ---
public abstract SWFFontDescription getSWFFontDescription (boolean wasEmbedded)
throws UnsupportedFontException, InvalidFontException;
public abstract SWFFont4Description getSWFFont4Description (boolean wasEmbedded)
throws UnsupportedFontException, InvalidFontException;
//--------------------------------------------------------- pdf generation ---
public abstract Permission getEmbeddingPermission
(boolean wasEmbedded)
throws InvalidFontException, UnsupportedFontException;
public abstract PDFFontDescription getPDFFontDescription (Font font)
throws UnsupportedFontException, InvalidFontException;
public abstract XDCFontDescription getXDCFontDescription (Font font)
throws UnsupportedFontException, InvalidFontException;
/** Create a subset for this font. */
abstract public Subset createSubset ()
throws InvalidFontException, UnsupportedFontException;
/** Subset and stream this font for PDF use.
* The stream is either a TrueType stream or a CID-keyed CFF stream.
* @param out the OutputStream to which the bytes are streamed
* @param preserveROS tells whether to preserve the cid -> gid mapping
*/
abstract public void subsetAndStream (Subset subset, OutputStream out,
boolean preserveROS)
throws InvalidFontException, UnsupportedFontException, IOException;
/**
* Evaluate the font to determine if it is a small cap font. This method assumes the font is NOT an allcap font.
*
* This uses heuristics determined for CoolType.
*
* @param gid1 The glyphID for a glyph that would contain an ascender in a typical font...such as 'h'.
* @param xHeight The height of glyphs that don't contain ascenders. This must be in metric space.
* @return true iff the font appears to have small caps.
*/
protected boolean isSmallCapFont(int gid1, double xHeight)
throws UnsupportedFontException, InvalidFontException
{
if (gid1 == 0)
return false;
Rect bbox1 = getGlyphBBox(gid1);
double ppemY = getUnitsPerEmY();
// if the height of the bboxes are within 20/1000's, call them close enough to be the same
if (bbox1.ymax > 0 && Math.abs(bbox1.ymax - xHeight) / ppemY < 20/1000.0 )
return true;
return false;
}
protected boolean isAllCapFont(int gid1, int gid2)
throws InvalidFontException, UnsupportedFontException {
if (gid1 == 0 || gid2 == 0)
return false;
IteratingOutlineConsumer consumer1 = new IteratingOutlineConsumer();
this.getGlyphOutline(gid1, consumer1);
ComparingOutlineConsumer consumer2 = new ComparingOutlineConsumer(consumer1);
this.getGlyphOutline(gid2, consumer2);
return consumer2.hasOutlines && consumer2.compares;
}
/**
* Evaluate the bitmap associated with up to 2 glyphs to heuristically determine if a font is a serif font.
*
* This uses heuristics developed for cooltype.
*
* @param gidForl The glyphID for the primary candidate for serif evaluation...such as 'l'.
* @param gidForI The glyphID for the secondary candidate for serif evalues...such as 'I'
* @param italicAngle The italic angle for the font.
* @return true iff the glyphs appear to have serifs.
*/
protected boolean isSerifFont(int gidForl, int gidForI, double italicAngle)
throws InvalidFontException, UnsupportedFontException
{
if (gidForl == 0 && gidForI == 0)
return false;
final int scale = 250;
boolean serifFound = true;
SerifEvaluator evaluator = new SerifEvaluator(scale, italicAngle);
Scaler rasterizer = getScaler ();
rasterizer.setScale (scale, scale, scale, 0, 0);
try {
if (gidForl != 0) {
evaluator.startBitmap(gidForl);
rasterizer.getBitmap (gidForl, evaluator);
serifFound = evaluator.hasSerif();
evaluator.endBitmap();
}
} catch (InvalidGlyphException e) {}
if (serifFound && gidForI != 0) {
evaluator.startBitmap(gidForI);
rasterizer.getBitmap (gidForI, evaluator);
serifFound = evaluator.hasSerif();
evaluator.endBitmap();
}
return serifFound;
}
/**
* Evaluates the runs associated with a glyph to heuristically determine if the glyph has serifs.
*
* The heuristics assume we are looking at a glyph that has 1 primary vertical stem. Compares
* the width of the middle of the stem with the width along the rest of the stem. If it is wide enough and
* left enough, it is a serif...
*
* Instances of this class are not threadsafe. They can be used sequentially for multiple glyphs in a given font
* provided startBitmap is always called between glyphs.
*/
static class SerifEvaluator implements BitmapConsumer {
int[][] runs = null;
int minY, minMarked, maxMarked;
/** A "magic" number. We are looking for a width that is this much wider than the width of the middle scanline.
*/
final double widthProportion;
final int scale;
/** A "magic" number. We are looking for a serif on the left of the glyph. This is the minimum
* distance to the left of the middle scanline we need to be to call something a serif.
*/
final double minDistanceFromVertical;
/** If the font is italic, we still want to calculate things as if the stem were perfectly vertical.
* This is the multiple that gets us back to vertical.
*/
final double tangent;
/**
* @param scale the size of the bitmap we are evaluating. We want it to be big enough that we have
* enough pixels to definitively say we are looking at a serif without wasting alot of computation/memory
* @param italicAngle the italic angle for the font.
*/
SerifEvaluator(int scale, double italicAngle)
{
this.scale = scale;
this.widthProportion = scale * .005;
this.minDistanceFromVertical = scale * .008;
this.tangent = Math.tan(Math.toRadians(-italicAngle));
}
boolean hasSerif()
{
if (runs == null)
return false;
int middle = minMarked + (maxMarked - minMarked) / 2;
double minWidth = (int)Math.floor((runs[middle][1] - runs[middle][0]) * 1.2);
for (int i = minMarked; i <= maxMarked; i++) {
int width = runs[i][1] - runs[i][0];
// if the run is wide enough, look for a serif on the left
if (width > minWidth) {
int middleXMoved = (int)Math.floor(runs[middle][0] + (i - middle) * tangent);
// if the run is left enough, it is a left serif...
if (middleXMoved - runs[i][0] > minDistanceFromVertical)
return true;
}
}
return false;
}
public void addRun(double xOn, double xOff, double y) {
int i;
if (runs == null)
{
minY = (int)y-scale;
runs = new int[scale*2][2];
for (int j = 0; j < scale*2; j++)
{
runs[j][0] = Integer.MAX_VALUE;
runs[j][1] = 0;
}
i = scale;
minMarked = maxMarked = i;
runs[i][0] = (int)xOn;
runs[i][1] = (int)xOff;
} else if ((int)y < minY)
{
int[][] newRuns = new int[minY - (int)y + runs.length][2];
for (int j = 0; j < minY; j++)
{
newRuns[j][0] = Integer.MAX_VALUE;
newRuns[j][1] = 0;
}
minMarked += minY - (int)y;
maxMarked += minY - (int)y;
System.arraycopy(runs, 0, newRuns, minY - (int)y, runs.length);
runs = newRuns;
minY = (int)y;
i = 0;
runs[i][0] = (int)xOn;
runs[i][1] = (int)xOff;
} else
{
i = (int)y - minY;
if ((int)y >= minY + runs.length)
{
int[][] newRuns = new int[(int)y - minY+1][2];
for (int j = runs.length; j <= (int)y-minY; j++)
{
newRuns[j][0] = 0;
newRuns[j][1] = 0;
}
System.arraycopy(runs, 0, newRuns, 0, runs.length);
runs = newRuns;
runs[i][0] = (int)xOn;
runs[i][1] = (int)xOff;
}
else {
// look for the left-most run.
if (xOn <= runs[i][0]) {
if (xOff < runs[i][0])
runs[i][1] = (int)xOff;
runs[i][0] = (int)xOn;
} else if (xOn <= runs[i][1]) {
if (xOff > runs[i][1]) {
runs[i][1] = (int)xOff;
}
}
}
}
if (i < minMarked)
minMarked = i;
if (i > maxMarked)
maxMarked = i;
}
public void endBitmap() {
}
public void startBitmap(int n) {
runs = null;
}
}
//------------------------------------------------------------ CSS support ---
/** Return the set of CSS font-family names that this font matches.
* Members of the returned Set are String objects.
*/
abstract protected Set getCSSFamilyNames()
throws InvalidFontException, UnsupportedFontException;
/** Return the preferred name to be used in CSS.
* This is the name that authors should put in their documents.
*/
abstract protected String getPreferredCSSFamilyName()
throws InvalidFontException, UnsupportedFontException;
/** Tell if the font matches the CSS font-style normal. */
abstract protected boolean isCSSStyleNormal ()
throws InvalidFontException, UnsupportedFontException;
/** Tell if the font matches the CSS font-style italic. */
abstract protected boolean isCSSStyleItalic ()
throws InvalidFontException, UnsupportedFontException;
/** Tell if the font matches the CSS font-style oblique. */
abstract protected boolean isCSSStyleOblique ()
throws InvalidFontException, UnsupportedFontException;
/** Tell if the font matches the CSS font-variant normal. */
abstract protected boolean isCSSVariantNormal ()
throws InvalidFontException, UnsupportedFontException;
/** Tell if the font matches the CSS font-style small-caps. */
abstract protected boolean isCSSVariantSmallCaps ()
throws InvalidFontException, UnsupportedFontException;
/** Return the CSS weight of this font. */
abstract protected int getCSSWeight ()
throws InvalidFontException, UnsupportedFontException;
/** Return the CSS fontstretch of this font. */
abstract protected CSSStretchValue getCSSStretchValue ()
throws InvalidFontException, UnsupportedFontException;
/** Return the CacheSupportInfo that this font matches */
public abstract CacheSupportInfo getCacheSupportInfo ()
throws UnsupportedFontException, InvalidFontException;
/** Return the postscript descriptions that this font matches. */
abstract public PostscriptFontDescription[] getPostscriptFontDescription ()
throws InvalidFontException, UnsupportedFontException;
/** Return the FXG descriptions for this font. */
abstract public FXGFontDescription[] getFXGFontDescription(Platform platform, ULocale locale)
throws InvalidFontException, UnsupportedFontException;
/** Return the platform descriptions for this font. */
abstract public PlatformFontDescription[] getPlatformFontDescription(Platform platform, ULocale locale)
throws InvalidFontException, UnsupportedFontException;
/** Return the range of point sizes for which this font has been designed.
*
* @return an array with exactly two elements. The first is the smallest
* intended point size (inclusive), the second is the largest
* intended point size (exclusive). Both numbers are in decipoints.
*/
public double[] getPointSizeRange ()
throws InvalidFontException, UnsupportedFontException {
return new double[] {0d, Double.POSITIVE_INFINITY};
}
public CSS20FontDescription[] getCSS20FontDescription ()
throws InvalidFontException, UnsupportedFontException
{
int numNames;
Set names = getCSSFamilyNames();
numNames = names.size();
int numVariants = 0;
CSSVariantValue[] variants = new CSSVariantValue[2];
if (isCSSVariantSmallCaps())
variants[numVariants++] = CSSVariantValue.SMALL_CAPS;
if (isCSSVariantNormal())
variants[numVariants++] = CSSVariantValue.NORMAL;
int numStyles = 0;
CSSStyleValue[] styles = new CSSStyleValue[3];
if (isCSSStyleOblique())
styles[numStyles++] = CSSStyleValue.OBLIQUE;
if (isCSSStyleItalic())
styles[numStyles++] = CSSStyleValue.ITALIC;
if (isCSSStyleNormal())
styles[numStyles++] = CSSStyleValue.NORMAL;
int cssWeight = getCSSWeight();
CSSStretchValue stretch = getCSSStretchValue();
int numDescriptions = numNames * numStyles * numVariants;
CSS20FontDescription[] retval = new CSS20FontDescription[numDescriptions];
Iterator nameIter = names.iterator();
int currIndex = 0;
double[] pointSizeRange = getPointSizeRange ();
for (int i = 0; i < numNames; i++)
{
String familyName = (String)nameIter.next();
for (int j = 0; j < numStyles; j++)
{
for (int k = 0; k < numVariants; k++)
{
retval[currIndex++] = new CSS20FontDescription(familyName,
styles[j], variants[k], stretch, cssWeight,
pointSizeRange [0] / 10.0d, pointSizeRange [1] / 10.0d);
}
}
}
return retval;
}
public CSS20FontDescription getPreferredCSS20FontDescription ()
throws InvalidFontException, UnsupportedFontException
{
String name = getPreferredCSSFamilyName();
if (name == null)
return null;
CSSVariantValue variant;
if (isCSSVariantNormal())
variant = CSSVariantValue.NORMAL;
else if (isCSSVariantSmallCaps())
variant = CSSVariantValue.SMALL_CAPS;
else
return null;
CSSStyleValue style;
if (isCSSStyleNormal())
style = CSSStyleValue.NORMAL;
else if (isCSSStyleItalic())
style = CSSStyleValue.ITALIC;
else if (isCSSStyleOblique())
style = CSSStyleValue.OBLIQUE;
else
return null;
int cssWeight = getCSSWeight();
CSSStretchValue stretch = getCSSStretchValue();
double[] pointSizeRange = getPointSizeRange ();
CSS20FontDescription retval = new CSS20FontDescription(name,
style, variant, stretch, cssWeight,
pointSizeRange [0] / 10.0d, pointSizeRange [1] / 10.0d);
return retval;
}
//----------------------------------------------------------------------------
abstract public CatalogDescription getSelectionDescription ()
throws InvalidFontException, UnsupportedFontException;
}