![JAR search and dependency download from the Maven repository](/logo.png)
com.adobe.fontengine.font.SWFFont4Description Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
/*
* File: SWFFont4Description.java
*
*
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2008 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.font;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import com.adobe.agl.lang.UCharacter;
import com.adobe.agl.lang.UProperty;
import com.adobe.agl.text.CanonicalIterator;
import com.adobe.agl.text.Normalizer;
import com.adobe.agl.text.UCharacterIterator;
import com.adobe.fontengine.Properties;
/**
* Fetch the data needed to create a DefineFont4 tag in SWF.
*/
public abstract class SWFFont4Description
{
private static final boolean DEBUG = false;
protected SWFFont4Description()
{
}
/**
* Expands the characters in an iteration set to include those that are needed by TLE/CTS.
* The expansion includes mirroring (for LTR scripts), character substitutions used by TLE/CTS,
* and Unicode character composition.
*
* It does not handle any casing of any kind. If various cases need to be in the resultant
* subset then the codepoints for those cases must be included in the original iterator
* supplied to this class during construction.
*
*/
public static final class FTESubsetCompletionIterator implements Iterator
{
// unconditional additions
// added before replacements are made
private static final int[] additionsArray = {
0x0020
};
private static final Map> REPLACEMENT_MAP;
static {
// alternating arrays where the
// first array is the codepoint to replace; and the
// second array is the codepoints to replace it with
int[][] replacementsArray = {
{0x0009}, {0x0020},
{0x000A}, {0x0020},
{0x000D}, {0x0020},
{0x0085}, {0x0020},
{0x00AD}, {0x0020},
{0x034F}, {0x0020},
{0x115F}, {0x0020},
{0x1160}, {0x0020},
{0x17B4}, {0x0020},
{0x17B5}, {0x0020},
{0x200B}, {0x0020},
{0x200C}, {0x0020},
{0x200D}, {0x0020},
{0x200E}, {0x0020},
{0x200F}, {0x0020},
{0x2028}, {0x0020},
{0x2029}, {0x0020},
{0x202A}, {0x0020},
{0x202B}, {0x0020},
{0x202C}, {0x0020},
{0x202D}, {0x0020},
{0x202E}, {0x0020},
{0x2060}, {0x0020},
{0x2061}, {0x0020},
{0x2062}, {0x0020},
{0x2063}, {0x0020},
{0x2064}, {0x0020},
{0x2065}, {0x0020},
{0x2066}, {0x0020},
{0x2067}, {0x0020},
{0x2068}, {0x0020},
{0x2069}, {0x0020},
{0x206A}, {0x0020},
{0x206B}, {0x0020},
{0x206C}, {0x0020},
{0x206D}, {0x0020},
{0x206E}, {0x0020},
{0x206F}, {0x0020},
{0x3164}, {0x0020},
{0xFEFF}, {0x0020},
{0xFFA0}, {0x0020},
{0xFFF0}, {0x0020},
{0xFFF1}, {0x0020},
{0xFFF2}, {0x0020},
{0xFFF3}, {0x0020},
{0xFFF4}, {0x0020},
{0xFFF5}, {0x0020},
{0xFFF6}, {0x0020},
{0xFFF7}, {0x0020},
{0xFFF8}, {0x0020},
{0x1D173}, {0x0020},
{0x1D174}, {0x0020},
{0x1D175}, {0x0020},
{0x1D176}, {0x0020},
{0x1D177}, {0x0020},
{0x1D178}, {0x0020},
{0x1D179}, {0x0020},
{0x1D17A}, {0x0020},
{0x09CB}, {0x09C7, 0x09BE},
{0x09CC}, {0x09C7, 0x09D7},
{0x0B48}, {0x0B47, 0x0B56},
{0x0B4B}, {0x0B47, 0x0B3E},
{0x0B4C}, {0x0B47, 0x0B57},
{0x0BCA}, {0x0BC6, 0x0BBE},
{0x0BCB}, {0x0BC7, 0x0BBE},
{0x0BCC}, {0x0BC6, 0x0BD7},
{0x0C48}, {0x0C46, 0x0C56},
{0x0CC0}, {0x0CBF, 0x0CD5},
{0x0CC7}, {0x0CC6, 0x0CD5},
{0x0CC8}, {0x0CC6, 0x0CD6},
{0x0CCA}, {0x0CC6, 0x0CC2},
{0x0CCB}, {0x0CC6, 0x0CC2, 0x0CD5},
{0x0D4A}, {0x0D46, 0x0D3E},
{0x0D4B}, {0x0D47, 0x0D3E},
{0x0D4C}, {0x0D46, 0x0D57},
{0x0DDA}, {0x0DD9, 0x0DCA},
{0x0DDC}, {0x0DD9, 0x0DCF},
{0x0DDD}, {0x0DD9, 0x0DCF, 0x0DCA},
{0x0DDE}, {0x0DD9, 0x0DDF},
{0x0E0D}, {0x0E0D, 0xF70F},
{0x0E10}, {0x0E10, 0xF700},
{0x0E31}, {0x0E31, 0xF710},
{0x0E33}, {0x0E4D, 0x0E32, 0xF711},
{0x0E34}, {0x0E34, 0xF701},
{0x0E35}, {0x0E35, 0xF702},
{0x0E36}, {0x0E36, 0xF703},
{0x0E37}, {0x0E37, 0xF704},
{0x0E38}, {0x0E38, 0xF718},
{0x0E39}, {0x0E39, 0xF719},
{0x0E3A}, {0x0E3A, 0xF71A},
{0x0E47}, {0x0E47, 0xF712},
{0x0E48}, {0x0E48, 0xF713, 0xF70A, 0xF705},
{0x0E49}, {0x0E49, 0xF714, 0xF70B, 0xF706},
{0x0E4A}, {0x0E4A, 0xF715, 0xF70C, 0xF707},
{0x0E4B}, {0x0E4B, 0xF716, 0xF70D, 0xF708},
{0x0E4C}, {0x0E4C, 0xF717, 0xF70E, 0xF709},
{0x0E4D}, {0x0E4D, 0xF711},
{0x0EB3}, {0x0ECD, 0x0EB2},
{0x0F73}, {0x0F71, 0x0F72},
{0x0F75}, {0x0F71, 0x0F74},
{0x0F76}, {0x0FB2, 0x0F80},
{0x0F77}, {0x0FB2, 0x0F71, 0x0F80},
{0x0F78}, {0x0FB3, 0x0F80},
{0x0F79}, {0x0FB3, 0x0F71, 0x0F80},
{0x0F81}, {0x0F71, 0x0F80},
{0x17BE}, {0x17C1, 0x17BE},
{0x17BF}, {0x17C1, 0x17BF},
{0x17C0}, {0x17C1, 0x17C0},
{0x17C4}, {0x17C1, 0x17C4},
{0x17C5}, {0x17C1, 0x17C5},
// spaces - not replacements but additions
// so include the original character in the replacement set
{0x00A0}, {0x00A0, 0x0020},
{0x2000}, {0x2000, 0x0020},
{0x2001}, {0x2001, 0x0020},
{0x2002}, {0x2002, 0x0020},
{0x2003}, {0x2003, 0x0020},
{0x2004}, {0x2004, 0x0020},
{0x2005}, {0x2005, 0x0020},
{0x2006}, {0x2006, 0x0020},
{0x2007}, {0x2007, 0x0020, 0x0030},
{0x2008}, {0x2008, 0x0020, 0x002E},
{0x2009}, {0x2009, 0x0020},
{0x200A}, {0x200A, 0x0020},
{0x200B}, {0x200B, 0x0020},
{0x202F}, {0x202F, 0x0020},
{0x205F}, {0x205F, 0x0020},
{0x3000}, {0x3000, 0x0020}
};
Map> temporaryReplacementMap = new HashMap();
for (int i = 0; i < replacementsArray.length; i+=2)
{
List replacementsList = new ArrayList(replacementsArray[i].length);
for (Integer replacement : replacementsArray[i+1])
{
replacementsList.add(replacement);
}
temporaryReplacementMap.put(replacementsArray[i][0], replacementsList);
}
REPLACEMENT_MAP = Collections.unmodifiableMap(temporaryReplacementMap);
}
/**
* This is the crossover value for choosing composition of the given
* codepoints {@link #expandCombiningSequences(Set, Set)} to expand the combining sequences in the set
* or to use decomposition of the entire Unicode range {@link #decomposeUnicode(Set)}
* and look for the decomposed characters in the original set. The first method is quick
* for small sets though it roughly O(n^2) so as the set gets larger it gets slower quickly.
* The second method is of fixed time. This crossover is chosen to roughly find the point
* at which the first method becomes slower than the second.
*/
private static final int CROSSOVER_FOR_DECOMPOSITION = 900;
private Iterator completionIter;
/**
* Construct an iterator for subset completion.
* @param cpIter An iterator returning Integers that are the Unicode codepoints to be
* embedded in the font.
*/
public FTESubsetCompletionIterator(Iterator cpIter)
{
Set total = new HashSet();
Set combining = new HashSet();
fillInternalSet(cpIter, total, combining);
Set cases = caseCompletions(total);
total.addAll(cases);
combining.addAll(cases);
Set combinedSet = null;
if (total.size() < CROSSOVER_FOR_DECOMPOSITION)
{
combinedSet = expandCombiningSequences(total, combining);
}
else
{
combinedSet = decomposeUnicode(total);
}
total.addAll(combinedSet);
this.completionIter = total.iterator();
}
/**
* Fill the internal sets doing any expansion or replacement of characters as needed.
* @param iter the source of the codepoints
* @param total the total set with all the codepoints
* @param combining the combining characters
*/
private void fillInternalSet(Iterator iter, Set total, Set combining)
{
// do unconditional additions first, then replacements
for (int cp : additionsArray) {
total.add(cp);
combining.add(cp);
}
Queue replacementBuffer = new LinkedList();
while (iter.hasNext() || !replacementBuffer.isEmpty())
{
Integer cp = 0;
// if any in the buffer get one
if (!replacementBuffer.isEmpty())
{
cp = replacementBuffer.remove();
} else {
cp = iter.next();
// deal with mirrored characters
int mirror = UCharacter.getMirror(cp);
if (mirror != cp)
{
// check for replacements
List replacementList = REPLACEMENT_MAP.get(mirror);
if (replacementList != null)
{
replacementBuffer.addAll(replacementList);
} else {
replacementBuffer.add(mirror);
}
}
// check for replacements
List replacementList = REPLACEMENT_MAP.get(cp);
if (replacementList != null)
{
replacementBuffer.addAll(replacementList);
cp = replacementBuffer.remove();
}
}
//System.out.println("cp = " + Integer.toHexString(cp) + ", " + cp);
total.add(cp);
//System.out.println("0x" + Integer.toHexString(cp) + ", " + UCharacter.getCombiningClass(cp));
// can't do this since there are combining sequences with all class 0 e.g. jamo
//if (UCharacter.getCombiningClass(cp) != 0)
{
combining.add(cp);
}
}
}
/**
* Expand all possible combining sequences of any length using the two sets.
* @param baseSet the base codepoints
* @param combiningSet the combining codepoints
* @return the set of all combining sequences
*/
private Set expandCombiningSequences(Set baseSet, Set combiningSet)
{
if (DEBUG)
{
System.out.println("============ expandCombiningSequences");
System.out.println(baseSet);
System.out.println(combiningSet);
}
Set resultSet = new HashSet(baseSet.size());
Set previousPassCodes = baseSet;
while (!previousPassCodes.isEmpty()) {
Set newCodes = new HashSet();
for (Integer base : previousPassCodes) {
for (Integer combining : combiningSet) {
StringBuffer sequence = new StringBuffer();
sequence.appendCodePoint(base);
sequence.appendCodePoint(combining);
UCharacterIterator sequenceIter = UCharacterIterator.getInstance(sequence);
Normalizer nfc = new Normalizer(sequenceIter, Normalizer.NFC, 0 /*no options*/);
int composed = 0;
while ((composed = nfc.next()) != Normalizer.DONE) {
if (!baseSet.contains(composed) && !resultSet.contains(composed) && !combiningSet.contains(composed)) {
resultSet.add(composed);
newCodes.add(composed);
}
}
}
}
previousPassCodes = newCodes;
}
if (DEBUG)
{
System.out.println(resultSet);
}
return resultSet;
}
/**
* Retrieve from Unicdoe the codepoints whose decompostion only contains codepoints
* in the original set.
* @param completeSet all of the codepoints
* @return the set of all combining sequences that can be made from the orginal set
*/
private Set decomposeUnicode(Set completeSet)
{
if (DEBUG)
{
System.out.println("============ decomposeUnicode");
System.out.println(completeSet);
}
Set resultSet = new HashSet();
for (int originalCodepoint = 0; originalCodepoint < 0x10ffff; originalCodepoint++)
{
int dt = UCharacter.getIntPropertyValue (originalCodepoint, UProperty.DECOMPOSITION_TYPE);
if (dt == UCharacter.DecompositionType.CANONICAL)
{
StringBuffer sequence = new StringBuffer();
sequence.appendCodePoint(originalCodepoint);
CanonicalIterator canonIter = new CanonicalIterator(sequence.toString());
for (String decomposed = canonIter.next(); decomposed != null; decomposed = canonIter.next())
{
boolean insert = true;
UCharacterIterator sequenceIter = UCharacterIterator.getInstance(decomposed);
for (int decomposedCodepoint = sequenceIter.nextCodePoint(); decomposedCodepoint != UCharacterIterator.DONE; decomposedCodepoint = sequenceIter.nextCodePoint())
{
if (!completeSet.contains(decomposedCodepoint))
{
insert = false;
break;
}
}
if (insert)
{
resultSet.add(originalCodepoint);
break;
}
}
}
}
return resultSet;
}
/**
* Do the case completions for the final set.
* @param completeSet all of the codepoints
* @return the set of all case completions to be added to the final set
*/
private Set caseCompletions(Set completeSet)
{
int tailorings[] = {
Properties.CASE_TAILORING_SUBSCRIPT_IOTA,
Properties.CASE_TAILORING_SUBSCRIPT_IOTA | Properties.CASE_TAILORING_DOTTED_I
};
int mappings[] = new int[Properties.MAX_CASE_EXPANSION];
Set resultSet = new HashSet();
for (int tailoring : tailorings) {
for (int code : completeSet) {
int count = Properties.getFullUpperCase(code, tailoring, mappings);
if (count > 0 && (count > 1 || code != mappings[0])) {
for (int i = 0; i < count; i++) {
resultSet.add(mappings[i]);
}
}
count = Properties.getFullLowerCase(code, tailoring, mappings);
if (count > 0 && (count > 1 || code != mappings[0])) {
for (int i = 0; i < count; i++) {
resultSet.add(mappings[i]);
}
}
}
}
return resultSet;
}
/**
* Returns true if the iteration has more elements. (In other
* words, returns true if next would return an element
* rather than throwing an exception.)
*
* @return true if the iterator has more elements.
*/
public boolean hasNext()
{
return this.completionIter.hasNext();
}
/**
* Returns the next element in the iteration. Calling this method
* repeatedly until the {@link #hasNext()} method returns false will
* return each element in the underlying collection exactly once.
*
* @return the next element in the iteration.
* @exception NoSuchElementException iteration has no more elements.
*/
public Integer next()
{
return this.completionIter.next();
}
/**
*
* Removes from the underlying collection the last element returned by the
* iterator (optional operation). This method can be called only once per
* call to next. The behavior of an iterator is unspecified if
* the underlying collection is modified while the iteration is in
* progress in any way other than by calling this method.
*
* @exception IllegalStateException if the next method has not
* yet been called, or the remove method has already
* been called after the last call to the next
* method.
*/
public void remove()
{
this.completionIter.remove();
}
}
/**
* Expands the characters in an iteration set to include those that may be added by TLF.
* It is expected that, if you need this functionality, you will generally run the
* TLFSubsetCompletionIterator first and then pass the result to FTESubsetCompletionIterator.
* For now only additions are needed; no replacements of substitutions are made here.
*/
public static final class TLFSubsetCompletionIterator implements Iterator
{
// The current list of added Unicodes requied to support TLF
private static int additionsArray[] =
{
0x0009, // tab
0x2028, // line separator
0x2029 // paragraph separator
};
private Iterator mIter1;
private Iterator mIter2;
/**
* Construct an iterator for subset completion.
* @param cpIter An iterator returning Integers that are the Unicode codepoints to be
* embedded in the font.
*/
public TLFSubsetCompletionIterator(Iterator cpIter)
{
mIter1 = cpIter;
Set addedCodes = new HashSet();
for (int addedCode : additionsArray) {
addedCodes.add(addedCode);
}
mIter2 = addedCodes.iterator();
}
/**
* Returns true if the iteration has more elements. (In other
* words, returns true if next would return an element
* rather than throwing an exception.)
*
* @return true if the iterator has more elements.
*/
public boolean hasNext()
{
if (mIter1 != null) {
if (mIter1.hasNext()) {
return true;
} else {
mIter1 = null;
}
}
return mIter2.hasNext();
}
/**
* Returns the next element in the iteration. Calling this method
* repeatedly until the {@link #hasNext()} method returns false will
* return each element in the underlying collection exactly once.
*
* @return the next element in the iteration.
* @exception NoSuchElementException iteration has no more elements.
*/
public Integer next()
{
if (mIter1 != null) {
return mIter1.next();
} else {
return mIter2.next();
}
}
/**
*
* Removes from the underlying collection the last element returned by the
* iterator (optional operation). This method can be called only once per
* call to next. The behavior of an iterator is unspecified if
* the underlying collection is modified while the iteration is in
* progress in any way other than by calling this method.
*
* @exception IllegalStateException if the next method has not
* yet been called, or the remove method has already
* been called after the last call to the next
* method.
*/
public void remove()
{
if (mIter1 != null) {
mIter1.remove();
} else {
mIter2.remove();
}
}
}
/**
* Return true iff the font has a non-notdef glyph associated with it.
*/
public abstract boolean canDisplay(int c) throws UnsupportedFontException, InvalidFontException;
/**
* Get the lowest unicode scalar value that has a non-notdef glyph associated with it.
*/
public abstract int getFirstChar() throws InvalidFontException, UnsupportedFontException;
/**
* Get the largest BMP value that has a non-notdef glyph associated with it.
*/
public abstract int getLastChar() throws InvalidFontException, UnsupportedFontException;
/**
* Determine the permissions associated with this font.
*/
public abstract Permission getPermissions() throws InvalidFontException, UnsupportedFontException;
/**
* Get the family name for the font. Return null if none exists.
*/
public abstract String getFamily() throws InvalidFontException, UnsupportedFontException;
/**
* Get the subfamily name for the font. Return null if none exists.
*/
public abstract String getSubFamily() throws InvalidFontException, UnsupportedFontException;
/**
* Return true iff the font is the bold member of a family.
*/
public abstract boolean isBold() throws InvalidFontException, UnsupportedFontException;
/**
* Return true iff the font is the italic member of a family.
*/
public abstract boolean isItalic() throws InvalidFontException, UnsupportedFontException;
/**
* Convert this font into the format expected by DefineFont4's FontData field.
*
* @param codepoints An iterator returning Integers that are the unicode codepoints to be
* embedded in the font.
* @param stream The OutputStream to which AFE will write the font data.
*/
public abstract void streamFontData(Iterator codepoints, OutputStream stream)
throws InvalidFontException, UnsupportedFontException, IOException;
/**
* Convert this font into the format expected by DefineFont4's FontData field. The resulting stream contains all glyphs in the original font.
* @param stream The OutputStream to which AFE will write the font data.
* @throws InvalidFontException
* @throws UnsupportedFontException
* @throws IOException
*/
public abstract void streamFontData(OutputStream stream)
throws InvalidFontException, UnsupportedFontException, IOException;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy