blob: 0634957debf465d7372f28cc02b2595edf7e425d [file] [log] [blame]
/*
* 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.
*/
/* $Id$ */
package org.apache.fop.fonts.truetype;
import java.awt.Rectangle;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.apache.fop.apps.io.InternalResourceResolver;
import org.apache.fop.fonts.CFFToType1Font;
import org.apache.fop.fonts.CIDFontType;
import org.apache.fop.fonts.CMapSegment;
import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.EmbeddingMode;
import org.apache.fop.fonts.EncodingMode;
import org.apache.fop.fonts.FontLoader;
import org.apache.fop.fonts.FontType;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.NamedCharacter;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion;
import org.apache.fop.util.HexEncoder;
/**
* Loads a TrueType font into memory directly from the original font file.
*/
public class OFFontLoader extends FontLoader {
private MultiByteFont multiFont;
private SingleByteFont singleFont;
private final String subFontName;
private EncodingMode encodingMode;
private EmbeddingMode embeddingMode;
private boolean simulateStyle;
private boolean embedAsType1;
/**
* Default constructor
* @param fontFileURI the URI representing the font file
* @param resourceResolver the resource resolver for font URI resolution
*/
public OFFontLoader(URI fontFileURI, InternalResourceResolver resourceResolver) {
this(fontFileURI, null, true, EmbeddingMode.AUTO, EncodingMode.AUTO, true, true, resourceResolver, false,
false);
}
/**
* Additional constructor for TrueType Collections.
* @param fontFileURI the URI representing the font file
* @param subFontName the sub-fontname of a font in a TrueType Collection (or null for normal
* TrueType fonts)
* @param embedded indicates whether the font is embedded or referenced
* @param embeddingMode the embedding mode of the font
* @param encodingMode the requested encoding mode
* @param useKerning true to enable loading kerning info if available, false to disable
* @param useAdvanced true to enable loading advanced info if available, false to disable
* @param resolver the FontResolver for font URI resolution
* @param simulateStyle Determines whether to simulate font styles if a font does not support those by default.
*/
public OFFontLoader(URI fontFileURI, String subFontName, boolean embedded,
EmbeddingMode embeddingMode, EncodingMode encodingMode, boolean useKerning,
boolean useAdvanced, InternalResourceResolver resolver, boolean simulateStyle, boolean embedAsType1) {
super(fontFileURI, embedded, useKerning, useAdvanced, resolver);
this.subFontName = subFontName;
this.encodingMode = encodingMode;
this.embeddingMode = embeddingMode;
this.simulateStyle = simulateStyle;
this.embedAsType1 = embedAsType1;
if (this.encodingMode == EncodingMode.AUTO) {
this.encodingMode = EncodingMode.CID; //Default to CID mode for TrueType
}
if (this.embeddingMode == EmbeddingMode.AUTO) {
this.embeddingMode = EmbeddingMode.SUBSET;
}
}
/** {@inheritDoc} */
protected void read() throws IOException {
read(this.subFontName);
}
/**
* Reads a TrueType font.
* @param ttcFontName the TrueType sub-font name of TrueType Collection (may be null for
* normal TrueType fonts)
* @throws IOException if an I/O error occurs
*/
private void read(String ttcFontName) throws IOException {
InputStream in = resourceResolver.getResource(this.fontFileURI);
try {
FontFileReader reader = new FontFileReader(in);
String header = readHeader(reader);
boolean isCFF = header.equals("OTTO");
OpenFont otf = (isCFF) ? new OTFFile(useKerning, useAdvanced) : new TTFFile(useKerning, useAdvanced);
boolean supported = otf.readFont(reader, header, ttcFontName);
if (!supported) {
throw new IOException("The font does not have a Unicode cmap table: " + fontFileURI);
}
buildFont(otf, ttcFontName, embedAsType1);
loaded = true;
} finally {
IOUtils.closeQuietly(in);
}
}
public static String readHeader(FontFileReader fontFile) throws IOException {
if (fontFile != null) {
fontFile.seekSet(0);
return fontFile.readTTFString(4); // TTF_FIXED_SIZE (4 bytes)
}
return null;
}
private void buildFont(OpenFont otf, String ttcFontName, boolean embedAsType1) {
boolean isCid = this.embedded;
if (this.encodingMode == EncodingMode.SINGLE_BYTE) {
isCid = false;
}
CustomFont font;
if (isCid) {
if (otf instanceof OTFFile && embedAsType1) {
multiFont = new CFFToType1Font(resourceResolver, embeddingMode);
} else {
multiFont = new MultiByteFont(resourceResolver, embeddingMode);
}
multiFont.setIsOTFFile(otf instanceof OTFFile);
returnFont = multiFont;
multiFont.setTTCName(ttcFontName);
font = multiFont;
} else {
singleFont = new SingleByteFont(resourceResolver, embeddingMode);
returnFont = singleFont;
font = singleFont;
}
font.setSimulateStyle(simulateStyle);
returnFont.setFontURI(fontFileURI);
if (!otf.getEmbedFontName().equals("")) {
returnFont.setFontName(otf.getEmbedFontName());
} else {
returnFont.setFontName(otf.getPostScriptName());
}
returnFont.setFullName(otf.getFullName());
returnFont.setFamilyNames(otf.getFamilyNames());
returnFont.setFontSubFamilyName(otf.getSubFamilyName());
returnFont.setCapHeight(otf.getCapHeight());
returnFont.setXHeight(otf.getXHeight());
returnFont.setAscender(otf.getLowerCaseAscent());
returnFont.setDescender(otf.getLowerCaseDescent());
returnFont.setFontBBox(otf.getFontBBox());
returnFont.setUnderlinePosition(otf.getUnderlinePosition() - otf.getUnderlineThickness() / 2);
returnFont.setUnderlineThickness(otf.getUnderlineThickness());
returnFont.setStrikeoutPosition(otf.getStrikeoutPosition() - otf.getStrikeoutThickness() / 2);
returnFont.setStrikeoutThickness(otf.getStrikeoutThickness());
returnFont.setFlags(otf.getFlags());
returnFont.setStemV(Integer.parseInt(otf.getStemV())); //not used for TTF
returnFont.setItalicAngle(Integer.parseInt(otf.getItalicAngle()));
returnFont.setMissingWidth(0);
returnFont.setWeight(otf.getWeightClass());
if (isCid) {
if (otf instanceof OTFFile) {
if (((OTFFile) otf).isType1() && embeddingMode == EmbeddingMode.SUBSET && !embedAsType1) {
multiFont.setFontType(FontType.TYPE1C);
copyGlyphMetricsSingleByte(otf);
}
multiFont.setCIDType(CIDFontType.CIDTYPE0);
} else {
multiFont.setCIDType(CIDFontType.CIDTYPE2);
}
multiFont.setWidthArray(otf.getWidths());
multiFont.setBBoxArray(otf.getBoundingBoxes());
} else {
singleFont.setFontType(FontType.TRUETYPE);
singleFont.setEncoding(otf.getCharSetName());
returnFont.setFirstChar(otf.getFirstChar());
returnFont.setLastChar(otf.getLastChar());
singleFont.setTrueTypePostScriptVersion(otf.getPostScriptVersion());
copyGlyphMetricsSingleByte(otf);
}
returnFont.setCMap(getCMap(otf));
if (otf.getKerning() != null && useKerning) {
copyKerning(otf, isCid);
}
if (useAdvanced) {
copyAdvanced(otf);
}
if (this.embedded) {
if (otf.isEmbeddable()) {
returnFont.setEmbedURI(this.fontFileURI);
} else {
String msg = "The font " + this.fontFileURI + " is not embeddable due to a"
+ " licensing restriction.";
throw new RuntimeException(msg);
}
}
}
private CMapSegment[] getCMap(OpenFont otf) {
CMapSegment[] array = new CMapSegment[otf.getCMaps().size()];
return otf.getCMaps().toArray(array);
}
private void copyGlyphMetricsSingleByte(OpenFont otf) {
int[] wx = otf.getWidths();
Rectangle[] bboxes = otf.getBoundingBoxes();
if (singleFont != null) {
for (int i = singleFont.getFirstChar(); i <= singleFont.getLastChar(); i++) {
singleFont.setWidth(i, otf.getCharWidth(i));
int[] bbox = otf.getBBox(i);
singleFont.setBoundingBox(i,
new Rectangle(bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]));
}
}
for (CMapSegment segment : otf.getCMaps()) {
if (segment.getUnicodeStart() < 0xFFFE) {
for (char u = (char)segment.getUnicodeStart(); u <= segment.getUnicodeEnd(); u++) {
int codePoint = 0;
if (singleFont != null) {
codePoint = singleFont.getEncoding().mapChar(u);
}
if (codePoint <= 0) {
int glyphIndex = segment.getGlyphStartIndex() + u - segment.getUnicodeStart();
String glyphName = otf.getGlyphName(glyphIndex);
if (glyphName.length() == 0 && otf.getPostScriptVersion() != PostScriptVersion.V2) {
glyphName = "u" + HexEncoder.encode(u);
}
if (glyphName.length() > 0) {
String unicode = Character.toString(u);
NamedCharacter nc = new NamedCharacter(glyphName, unicode);
returnFont.addUnencodedCharacter(nc, wx[glyphIndex], bboxes[glyphIndex]);
}
}
}
}
}
}
/**
* Copy kerning information.
*/
private void copyKerning(OpenFont otf, boolean isCid) {
// Get kerning
Set<Integer> kerningSet;
if (isCid) {
kerningSet = otf.getKerning().keySet();
} else {
kerningSet = otf.getAnsiKerning().keySet();
}
for (Integer kpx1 : kerningSet) {
Map<Integer, Integer> h2;
if (isCid) {
h2 = otf.getKerning().get(kpx1);
} else {
h2 = otf.getAnsiKerning().get(kpx1);
}
returnFont.putKerningEntry(kpx1, h2);
}
}
/**
* Copy advanced typographic information.
*/
private void copyAdvanced(OpenFont otf) {
if (returnFont instanceof MultiByteFont) {
MultiByteFont mbf = (MultiByteFont) returnFont;
mbf.setGDEF(otf.getGDEF());
mbf.setGSUB(otf.getGSUB());
mbf.setGPOS(otf.getGPOS());
}
}
}