org.apache.pdfbox.pdmodel.font.TrueTypeEmbedder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pdfbox Show documentation
Show all versions of pdfbox Show documentation
The Apache PDFBox library is an open source Java tool for working with PDF documents.
/*
* 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.pdfbox.pdmodel.font;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.fontbox.ttf.CmapSubtable;
import org.apache.fontbox.ttf.HeaderTable;
import org.apache.fontbox.ttf.HorizontalHeaderTable;
import org.apache.fontbox.ttf.OS2WindowsMetricsTable;
import org.apache.fontbox.ttf.PostScriptTable;
import org.apache.fontbox.ttf.TTFParser;
import org.apache.fontbox.ttf.TTFSubsetter;
import org.apache.fontbox.ttf.TrueTypeFont;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.PDStream;
/**
* Common functionality for embedding TrueType fonts.
*
* @author Ben Litchfield
* @author John Hewson
*/
abstract class TrueTypeEmbedder implements Subsetter
{
private static final int ITALIC = 1;
private static final int OBLIQUE = 512;
private static final String BASE25 = "BCDEFGHIJKLMNOPQRSTUVWXYZ";
private final PDDocument document;
protected TrueTypeFont ttf;
protected PDFontDescriptor fontDescriptor;
protected final CmapSubtable cmap;
private final Set subsetCodePoints = new HashSet();
private final boolean embedSubset;
/**
* Creates a new TrueType font for embedding.
*/
TrueTypeEmbedder(PDDocument document, COSDictionary dict, InputStream ttfStream,
boolean embedSubset) throws IOException
{
this.document = document;
this.embedSubset = embedSubset;
buildFontFile2(ttfStream);
dict.setName(COSName.BASE_FONT, ttf.getName());
// choose a Unicode "cmap"
cmap = ttf.getUnicodeCmap();
}
/**
* Creates a new TrueType font for embedding.
*/
TrueTypeEmbedder(PDDocument document, COSDictionary dict, TrueTypeFont ttf,
boolean embedSubset) throws IOException
{
this.document = document;
this.embedSubset = embedSubset;
this.ttf = ttf;
fontDescriptor = createFontDescriptor(ttf);
dict.setName(COSName.BASE_FONT, ttf.getName());
// choose a Unicode "cmap"
cmap = ttf.getUnicodeCmap();
}
public void buildFontFile2(InputStream ttfStream) throws IOException
{
PDStream stream = new PDStream(document, ttfStream, COSName.FLATE_DECODE);
stream.getCOSObject().setInt(COSName.LENGTH1, stream.toByteArray().length);
// as the stream was closed within the PDStream constructor, we have to recreate it
InputStream input = null;
try
{
input = stream.createInputStream();
if (ttf != null)
{
// close the replaced true type font
ttf.close();
}
ttf = new TTFParser().parseEmbedded(input);
if (!isEmbeddingPermitted(ttf))
{
throw new IOException("This font does not permit embedding");
}
if (fontDescriptor == null)
{
fontDescriptor = createFontDescriptor(ttf);
}
}
finally
{
IOUtils.closeQuietly(input);
}
fontDescriptor.setFontFile2(stream);
}
/**
* Returns true if the fsType in the OS/2 table permits embedding.
*/
private boolean isEmbeddingPermitted(TrueTypeFont ttf) throws IOException
{
if (ttf.getOS2Windows() != null)
{
int fsType = ttf.getOS2Windows().getFsType();
int exclusive = fsType & 0x8; // bits 0-3 are a set of exclusive bits
if ((exclusive & OS2WindowsMetricsTable.FSTYPE_RESTRICTED) ==
OS2WindowsMetricsTable.FSTYPE_RESTRICTED)
{
// restricted License embedding
return false;
}
else if ((exclusive & OS2WindowsMetricsTable.FSTYPE_BITMAP_ONLY) ==
OS2WindowsMetricsTable.FSTYPE_BITMAP_ONLY)
{
// bitmap embedding only
return false;
}
}
return true;
}
/**
* Returns true if the fsType in the OS/2 table permits subsetting.
*/
private boolean isSubsettingPermitted(TrueTypeFont ttf) throws IOException
{
if (ttf.getOS2Windows() != null)
{
int fsType = ttf.getOS2Windows().getFsType();
if ((fsType & OS2WindowsMetricsTable.FSTYPE_NO_SUBSETTING) ==
OS2WindowsMetricsTable.FSTYPE_NO_SUBSETTING)
{
return false;
}
}
return true;
}
/**
* Creates a new font descriptor dictionary for the given TTF.
*/
private PDFontDescriptor createFontDescriptor(TrueTypeFont ttf) throws IOException
{
PDFontDescriptor fd = new PDFontDescriptor();
fd.setFontName(ttf.getName());
OS2WindowsMetricsTable os2 = ttf.getOS2Windows();
PostScriptTable post = ttf.getPostScript();
// Flags
fd.setFixedPitch(post.getIsFixedPitch() > 0 ||
ttf.getHorizontalHeader().getNumberOfHMetrics() == 1);
int fsSelection = os2.getFsSelection();
fd.setItalic(((fsSelection & (ITALIC | OBLIQUE)) != 0));
switch (os2.getFamilyClass())
{
case OS2WindowsMetricsTable.FAMILY_CLASS_CLAREDON_SERIFS:
case OS2WindowsMetricsTable.FAMILY_CLASS_FREEFORM_SERIFS:
case OS2WindowsMetricsTable.FAMILY_CLASS_MODERN_SERIFS:
case OS2WindowsMetricsTable.FAMILY_CLASS_OLDSTYLE_SERIFS:
case OS2WindowsMetricsTable.FAMILY_CLASS_SLAB_SERIFS:
fd.setSerif(true);
break;
case OS2WindowsMetricsTable.FAMILY_CLASS_SCRIPTS:
fd.setScript(true);
break;
}
fd.setFontWeight(os2.getWeightClass());
fd.setSymbolic(true);
fd.setNonSymbolic(false);
// ItalicAngle
fd.setItalicAngle(post.getItalicAngle());
// FontBBox
HeaderTable header = ttf.getHeader();
PDRectangle rect = new PDRectangle();
float scaling = 1000f / header.getUnitsPerEm();
rect.setLowerLeftX(header.getXMin() * scaling);
rect.setLowerLeftY(header.getYMin() * scaling);
rect.setUpperRightX(header.getXMax() * scaling);
rect.setUpperRightY(header.getYMax() * scaling);
fd.setFontBoundingBox(rect);
// Ascent, Descent
HorizontalHeaderTable hHeader = ttf.getHorizontalHeader();
fd.setAscent(hHeader.getAscender() * scaling);
fd.setDescent(hHeader.getDescender() * scaling);
// CapHeight, XHeight
if (os2.getVersion() >= 1.2)
{
fd.setCapHeight(os2.getCapHeight() * scaling);
fd.setXHeight(os2.getHeight() * scaling);
}
else
{
// estimate by summing the typographical +ve ascender and -ve descender
fd.setCapHeight((os2.getTypoAscender() + os2.getTypoDescender()) * scaling);
// estimate by halving the typographical ascender
fd.setXHeight(os2.getTypoAscender() / 2.0f * scaling);
}
// StemV - there's no true TTF equivalent of this, so we estimate it
fd.setStemV(fd.getFontBoundingBox().getWidth() * .13f);
return fd;
}
/**
* Returns the FontBox font.
*/
public TrueTypeFont getTrueTypeFont()
{
return ttf;
}
/**
* Returns the font descriptor.
*/
public PDFontDescriptor getFontDescriptor()
{
return fontDescriptor;
}
@Override
public void addToSubset(int codePoint)
{
subsetCodePoints.add(codePoint);
}
@Override
public void subset() throws IOException
{
if (!isSubsettingPermitted(ttf))
{
throw new IOException("This font does not permit subsetting");
}
if (!embedSubset)
{
throw new IllegalStateException("Subsetting is disabled");
}
// PDF spec required tables (if present), all others will be removed
List tables = new ArrayList();
tables.add("head");
tables.add("hhea");
tables.add("loca");
tables.add("maxp");
tables.add("cvt ");
tables.add("prep");
tables.add("glyf");
tables.add("hmtx");
tables.add("fpgm");
// Windows ClearType
tables.add("gasp");
// set the GIDs to subset
TTFSubsetter subsetter = new TTFSubsetter(getTrueTypeFont(), tables);
subsetter.addAll(subsetCodePoints);
// calculate deterministic tag based on the chosen subset
Map gidToCid = subsetter.getGIDMap();
String tag = getTag(gidToCid);
subsetter.setPrefix(tag);
// save the subset font
ByteArrayOutputStream out = new ByteArrayOutputStream();
subsetter.writeToStream(out);
// re-build the embedded font
buildSubset(new ByteArrayInputStream(out.toByteArray()), tag, gidToCid);
ttf.close();
}
/**
* Returns true if the font needs to be subset.
*/
public boolean needsSubset()
{
return embedSubset;
}
/**
* Rebuild a font subset.
*/
protected abstract void buildSubset(InputStream ttfSubset, String tag,
Map gidToCid) throws IOException;
/**
* Returns an uppercase 6-character unique tag for the given subset.
*/
public String getTag(Map gidToCid)
{
// deterministic
long num = gidToCid.hashCode();
// base25 encode
StringBuilder sb = new StringBuilder();
do
{
long div = num / 25;
int mod = (int)(num % 25);
sb.append(BASE25.charAt(mod));
num = div;
} while (num != 0 && sb.length() < 6);
// pad
while (sb.length() < 6)
{
sb.insert(0, 'A');
}
sb.append('+');
return sb.toString();
}
}