com.adobe.fontengine.inlineformatting.FallbackFontSet Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*
* File: FallbackFontSet.java
*
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2005 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.
*
*/
package com.adobe.fontengine.inlineformatting;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.TreeMap;
import com.adobe.agl.util.ULocale;
import com.adobe.fontengine.font.Font;
import com.adobe.fontengine.font.FontException;
import com.adobe.fontengine.fontmanagement.postscript.PostscriptFontDescription;
/**
* A set of fonts indexed by {@link ULocale}, typically used for fallback.
*
*
* A {@link FallbackFontSet} is a set of fonts indexed by {@link ULocale}. A
* typical use is during formatting of text: if the requested font cannot be
* found or does not contain suitable glyphs for a portion of the text being
* formatted, the formatter can enumerate the fallback fonts for the locale
* of the text until a suitable font is found. Indexing by locale (by opposition
* to a flat set) improves the quality of the generated rendering; for example,
* some characters are used to write both Japanese and Chinese, but different
* shapes are expected.
*
*
* The {@link #addFallbackFont(ULocale, Font)} and
* {@link #addFallbackFonts(ULocale, Font[])} methods allow the addition of one
* or more fonts to a set, for a given locale.
*
*
* The {@link #getFallbackFonts(ULocale)} method returns an {@link Iterator}
* which first enumerates the fonts for a locale (in the order in which they
* were added), then the fonts for the fallback locale of that locale, and so on
* up to the fonts for the {@link ULocale#ROOT}locale.
*
*
* For example, with a set setup like this:
*
*
* set.add (new ULocale ("en_US"), us1);
* set.add (new ULocale ("en_US"), us2);
* set.add (new ULocale ("en_CA"), ca1);
* set.add (new ULocale ("en_CA"), ca2);
* set.add (new ULocale ("en"), en1);
* set.add (new ULocale ("en"), en2);
* set.add (ULocale.ROOT, r1);
* set.add (ULocale.ROOT, r2);
*
*
* then the fallback fonts for the locale en_US
are
* {us1, us2, en1, en2, r1, r2}
in that order, the fallback fonts
* for the locale en_CA
are
* {ca1, ca2, en1, en2, r1, r2}
in that order, the fallback fonts
* for the locale en
are {en1, en2, r1, r2}
in
* that order, and the fallback fonts for the locale it
are
* {r1, r2}
in that order.
*
* Synchronization
*
* {@link FallbackFontSet}objects are suitably synchronized for use in multiple
* threads.
*
* Serialization
*
* {@link FallbackFontSet}objects can be serialized, provided that they contain
* only fonts which can be serialized.
*/
public final class FallbackFontSet implements Serializable
{
/* Serialization signature is explicitly set and should be
* incremented on each release to prevent compatibility.
*/
static final long serialVersionUID = 1;
/* synchronized via this. */
private final HashMap /*>*/ fonts;
private static final ULocale REAL_ROOT = new ULocale("");
/**
* Create an empty FallbackFontSet.
*/
public FallbackFontSet ()
{
this.fonts = new HashMap ();
}
/**
* Create a FallbackFontSet by copying an existing one.
*/
public FallbackFontSet (FallbackFontSet ffs)
{
this.fonts = new HashMap ();
synchronized (ffs)
{
for (Iterator it = ffs.fonts.keySet().iterator(); it.hasNext (); )
{
Object locale = it.next ();
List l = new ArrayList/**/ ();
this.fonts.put (locale, l);
for (Iterator it2 = ((List) ffs.fonts.get (locale)).iterator(); it2.hasNext(); )
{
l.add (it2.next ());
}
}
}
}
/**
* Add each font in an array to the set.
*
* @param locale the locale for which those fonts are appropriate
* @param fontsToAdd the fonts to add
*/
public void addFallbackFonts (ULocale locale, Font[] fontsToAdd)
{
for (Font font : fontsToAdd)
{
addFallbackFont(locale, font);
}
}
/**
* Add a font to the set
*
* @param locale the locale for which the font is appropriate
* @param font the font to add
*/
public void addFallbackFont (ULocale locale, Font font)
{
synchronized (this)
{
if (locale.equals(ULocale.ROOT))
{
locale = FallbackFontSet.REAL_ROOT;
}
List/**/ l = (List/**/) fonts.get (locale);
if (l == null)
{
l = new ArrayList/**/ ();
fonts.put (locale, l);
}
l.add (font);
}
}
private final class FallbackFontIterator implements Iterator
{
/* invariant: the fonts that remain to be enumerated are
* - those remaining in localeIt, if it is non-null
* - those associated with locale and its fallback locales
*
* Those two members are synchronized via this.
*/
private ULocale locale;
private Iterator/**/ localeIt;
public FallbackFontIterator (ULocale locale) {
this.locale = locale;
this.localeIt = null;
}
public boolean hasNext ()
{
synchronized (this)
{
while (localeIt != null || locale != null)
{
if (localeIt == null)
{
List/**/ l = (List/**/) fonts.get (locale);
if (l != null)
{
localeIt = l.iterator ();
}
locale = locale.getFallback ();
} else {
if (localeIt.hasNext ())
{
return true;
}
localeIt = null;
}
}
}
return false;
}
public Object next () throws NoSuchElementException
{
synchronized (this)
{
if (hasNext ())
{
// hasNext returns true only when localeIt != null and
// localeIt.hasNext == true, so this is safe.
return localeIt.next ();
}
}
throw new NoSuchElementException ();
}
public void remove () throws UnsupportedOperationException
{
throw new UnsupportedOperationException ("cannot remove elements from a FallbackFontIterator");
}
}
/**
* Enumerate the fonts for a locale.
*
* The fonts for locale
are enumerated, then the fonts for the
* fallback locale of locale
, and so on.
*/
public Iterator getFallbackFonts (ULocale locale)
{
synchronized (this)
{
return new FallbackFontIterator (locale);
}
}
public boolean equals (Object otherObject)
{
if (otherObject == this)
{
return true;
}
if (otherObject == null || ! (otherObject instanceof FallbackFontSet))
{
return false;
}
return this.fonts.equals (((FallbackFontSet) otherObject).fonts);
}
public int hashCode ()
{
return fonts.hashCode ();
}
/**
* A test on whether this fontset contains any fonts.
*
* @return true if the fontset is empty, false otherwise
*/
public boolean isEmpty ()
{
return this.fonts.isEmpty();
}
/**
* A test on whether this fontset contains any fonts for the given locale.
*
* @param locale the locale to test
* @return true if the fontset has no fonts for the given locale, false otherwise
*/
public boolean isEmpty (ULocale locale)
{
return this.fonts.containsKey(locale);
}
public String toString()
{
TreeMap tempMap = new TreeMap();
for (Iterator it = fonts.keySet().iterator(); it.hasNext(); ) {
ULocale locale = (ULocale)it.next();
tempMap.put(locale.getDisplayName(), locale);
}
HashMap tempMap1 = new HashMap();
for (Iterator it = tempMap.keySet().iterator(); it.hasNext(); ) {
String displayName = (String)it.next();
ULocale locale = (ULocale)tempMap.get(displayName);
TreeMap tempMap2 = new TreeMap();
for (Iterator it1 = ((List)fonts.get(locale)).iterator(); it1.hasNext(); ) {
Font f = (Font)it1.next();
PostscriptFontDescription[] descs;
TreeMap tempMap3 = new TreeMap();
try {
descs = f.getPostscriptFontDescription();
for (int i = 0; i < descs.length; i++) {
String name = descs[i].toString();
int count = 0;
if (tempMap3.containsKey(name)) {
count = ((Integer)tempMap3.get(name)).intValue();
}
tempMap3.put(name, new Integer(++count));
}
} catch (FontException e) {
}
char suffix = '0';
String tempKey = tempMap3.isEmpty() ? "" : (String)tempMap3.firstKey();
while (tempMap2.containsKey(tempKey + suffix)) {
suffix++;
}
tempMap2.put(tempKey + suffix, tempMap3);
}
tempMap1.put(locale, tempMap2);
}
StringBuffer sb = new StringBuffer();
for (Iterator it = tempMap.keySet().iterator(); it.hasNext(); ) {
String displayName = (String)it.next();
ULocale locale = (ULocale)tempMap.get(displayName);
sb.append(displayName);
sb.append(": ");
String prefix2 = "";
TreeMap tempMap2 = (TreeMap)tempMap1.get(locale);
for (Iterator it1 = tempMap2.keySet().iterator(); it1.hasNext(); ) {
TreeMap tempMap3 = (TreeMap)tempMap2.get(it1.next());
sb.append(prefix2);
sb.append("{");
String prefix = "";
for (Iterator it2 = tempMap3.keySet().iterator(); it2.hasNext(); ) {
String name = (String)it2.next();
int count = ((Integer)tempMap3.get(name)).intValue();
while (count-- > 0) {
sb.append(prefix);
sb.append(name);
prefix = ", ";
}
}
sb.append("}");
sb.append(prefix2);
prefix2 = ", ";
}
sb.append("\n");
}
return sb.toString();
}
}