/*
* 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.
*/
package org.apache.fontbox.cff;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.fontbox.cff.IndexData;
import org.apache.fontbox.cff.charset.CFFCharset;
import org.apache.fontbox.cff.encoding.CFFEncoding;
/**
* This class represents a CFF/Type2 Font.
*
* @author Villu Ruusmann
* @version $Revision$
*/
public class CFFFont
{
private String fontname = null;
private Map<String, Object> topDict = new LinkedHashMap<String, Object>();
private Map<String, Object> privateDict = new LinkedHashMap<String, Object>();
private CFFEncoding fontEncoding = null;
private CFFCharset fontCharset = null;
private Map<String, byte[]> charStringsDict = new LinkedHashMap<String, byte[]>();
private IndexData globalSubrIndex = null;
private IndexData localSubrIndex = null;
/**
* The name of the font.
* @return the name of the font
*/
public String getName()
{
return fontname;
}
/**
* Sets the name of the font.
* @param name the name of the font
*/
public void setName(String name)
{
fontname = name;
}
/**
* Returns the value for the given name from the dictionary.
* @param name the name of the value
* @return the value of the name if available
*/
public Object getProperty(String name)
{
Object topDictValue = topDict.get(name);
if (topDictValue != null)
{
return topDictValue;
}
Object privateDictValue = privateDict.get(name);
if (privateDictValue != null)
{
return privateDictValue;
}
return null;
}
/**
* Adds the given key/value pair to the top dictionary.
* @param name the given key
* @param value the given value
*/
public void addValueToTopDict(String name, Object value)
{
if (value != null)
{
topDict.put(name, value);
}
}
/**
* Returns the top dictionary.
* @return the dictionary
*/
public Map<String, Object> getTopDict()
{
return topDict;
}
/**
* Adds the given key/value pair to the private dictionary.
* @param name the given key
* @param value the given value
*/
public void addValueToPrivateDict(String name, Object value)
{
if (value != null)
{
privateDict.put(name, value);
}
}
/**
* Returns the private dictionary.
* @return the dictionary
*/
public Map<String, Object> getPrivateDict()
{
return privateDict;
}
/**
* Get the mapping (code/SID/charname/bytes) for this font.
* @return mappings for codes < 256 and for codes > = 256
*/
public Collection<Mapping> getMappings()
{
List<Mapping> mappings = new ArrayList<Mapping>();
Set<String> mappedNames = new HashSet<String>();
for (CFFEncoding.Entry entry : fontEncoding.getEntries())
{
String charName = fontCharset.getName(entry.getSID());
// Predefined encoding
if (charName == null)
{
continue;
}
byte[] bytes = charStringsDict.get(charName);
if (bytes == null)
{
continue;
}
Mapping mapping = new Mapping();
mapping.setCode(entry.getCode());
mapping.setSID(entry.getSID());
mapping.setName(charName);
mapping.setBytes(bytes);
mappings.add(mapping);
mappedNames.add(charName);
}
if (fontEncoding instanceof CFFParser.EmbeddedEncoding)
{
CFFParser.EmbeddedEncoding embeddedEncoding = (CFFParser.EmbeddedEncoding)fontEncoding;
for (CFFParser.EmbeddedEncoding.Supplement supplement : embeddedEncoding.getSupplements())
{
String charName = fontCharset.getName(supplement.getGlyph());
if (charName == null)
{
continue;
}
byte[] bytes = charStringsDict.get(charName);
if (bytes == null)
{
continue;
}
Mapping mapping = new Mapping();
mapping.setCode(supplement.getCode());
mapping.setSID(supplement.getGlyph());
mapping.setName(charName);
mapping.setBytes(bytes);
mappings.add(mapping);
mappedNames.add(charName);
}
}
// XXX
int code = 256;
for (CFFCharset.Entry entry : fontCharset.getEntries())
{
String name = entry.getName();
if (mappedNames.contains(name))
{
continue;
}
byte[] bytes = this.charStringsDict.get(name);
if (bytes == null)
{
continue;
}
Mapping mapping = new Mapping();
mapping.setCode(code++);
mapping.setSID(entry.getSID());
mapping.setName(name);
mapping.setBytes(bytes);
mappings.add(mapping);
mappedNames.add(name);
}
return mappings;
}
/**
* Return the Width value of the given Glyph identifier
*
* @param SID
* @return -1 if the SID is missing from the Font.
* @throws IOException
*/
public int getWidth(int SID) throws IOException {
int nominalWidth = privateDict.containsKey("nominalWidthX") ? ((Number)privateDict.get("nominalWidthX")).intValue() : 0;
int defaultWidth = privateDict.containsKey("defaultWidthX") ? ((Number)privateDict.get("defaultWidthX")).intValue() : 1000 ;
for (Mapping m : getMappings() ){
if (m.getSID() == SID) {
CharStringRenderer csr = null;
if (((Number)getProperty("CharstringType")).intValue() == 2 ) {
List<Object> lSeq = m.toType2Sequence();
csr = new CharStringRenderer(false);
csr.render(lSeq);
} else {
List<Object> lSeq = m.toType1Sequence();
csr = new CharStringRenderer();
csr.render(lSeq);
}
// ---- If the CharString has a Width nominalWidthX must be added,
// otherwise it is the default width.
return csr.getWidth() != 0 ? csr.getWidth() + nominalWidth : defaultWidth;
}
}
// ---- Width not found, return the default width
return defaultWidth;
}
/**
* Returns the CFFEncoding of the font.
* @return the encoding
*/
public CFFEncoding getEncoding()
{
return fontEncoding;
}
/**
* Sets the CFFEncoding of the font.
* @param encoding the given CFFEncoding
*/
public void setEncoding(CFFEncoding encoding)
{
fontEncoding = encoding;
}
/**
* Returns the CFFCharset of the font.
* @return the charset
*/
public CFFCharset getCharset()
{
return fontCharset;
}
/**
* Sets the CFFCharset of the font.
* @param charset the given CFFCharset
*/
public void setCharset(CFFCharset charset)
{
fontCharset = charset;
}
/**
* Returns the character strings dictionary.
* @return the dictionary
*/
public Map<String, byte[]> getCharStringsDict()
{
return charStringsDict;
}
/**
* Creates a CharStringConverter for this font.
* @return the new CharStringConverter
*/
public CharStringConverter createConverter()
{
Number defaultWidthX = (Number) getProperty("defaultWidthX");
Number nominalWidthX = (Number) getProperty("nominalWidthX");
return new CharStringConverter(defaultWidthX.intValue(), nominalWidthX
.intValue(), getGlobalSubrIndex(), getLocalSubrIndex());
}
/**
* Creates a CharStringRenderer for this font.
* @return the new CharStringRenderer
*/
public CharStringRenderer createRenderer()
{
return new CharStringRenderer();
}
/**
* {@inheritDoc}
*/
public String toString()
{
return getClass().getName() + "[name=" + fontname + ", topDict=" + topDict
+ ", privateDict=" + privateDict + ", encoding=" + fontEncoding
+ ", charset=" + fontCharset + ", charStringsDict="
+ charStringsDict + "]";
}
/**
* Sets the global subroutine index data.
* @param globalSubrIndex the IndexData object containing the global subroutines
*/
public void setGlobalSubrIndex(IndexData globalSubrIndex) {
this.globalSubrIndex = globalSubrIndex;
}
/**
* Returns the global subroutine index data.
* @return the dictionary
*/
public IndexData getGlobalSubrIndex() {
return globalSubrIndex;
}
/**
* Returns the local subroutine index data.
* @return the dictionary
*/
public IndexData getLocalSubrIndex() {
return localSubrIndex;
}
/**
* Sets the local subroutine index data.
* @param localSubrIndex the IndexData object containing the local subroutines
*/
public void setLocalSubrIndex(IndexData localSubrIndex) {
this.localSubrIndex = localSubrIndex;
}
/**
* This class is used for the font mapping.
*
*/
public class Mapping
{
private int mappedCode;
private int mappedSID;
private String mappedName;
private byte[] mappedBytes;
/**
* Converts the mapping into a Type1-sequence.
* @return the Type1-sequence
* @throws IOException if an error occurs during reading
*/
public List<Object> toType1Sequence() throws IOException
{
CharStringConverter converter = createConverter();
return converter.convert(toType2Sequence());
}
/**
* Converts the mapping into a Type2-sequence.
* @return the Type2-sequence
* @throws IOException if an error occurs during reading
*/
public List<Object> toType2Sequence() throws IOException
{
Type2CharStringParser parser = new Type2CharStringParser();
return parser.parse(getBytes());
}
/**
* Gets the value for the code.
* @return the code
*/
public int getCode()
{
return mappedCode;
}
private void setCode(int code)
{
mappedCode = code;
}
/**
* Gets the value for the SID.
* @return the SID
*/
public int getSID()
{
return mappedSID;
}
private void setSID(int sid)
{
this.mappedSID = sid;
}
/**
* Gets the value for the name.
* @return the name
*/
public String getName()
{
return mappedName;
}
private void setName(String name)
{
this.mappedName = name;
}
/**
* Gets the value for the bytes.
* @return the bytes
*/
public byte[] getBytes()
{
return mappedBytes;
}
private void setBytes(byte[] bytes)
{
this.mappedBytes = bytes;
}
}
}