All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.fontengine.font.SWFFont4Description Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 *	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