/*
* Copyright (C) 2011 Laurent Caillette
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.novelang.rendering.font;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Set;
import javax.xml.transform.TransformerConfigurationException;
import com.google.common.base.Preconditions;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import org.apache.fop.fonts.FontTriplet;
import org.dom4j.Namespace;
import org.joda.time.DateTime;
import org.joda.time.ReadableDateTime;
import org.xml.sax.SAXException;
import org.novelang.common.metadata.DocumentMetadata;
import org.novelang.common.metadata.Page;
import org.novelang.configuration.FontQuadruplet;
import org.novelang.configuration.FopFontStatus;
import org.novelang.configuration.RenderingConfiguration;
import org.novelang.outfit.loader.ResourceName;
import org.novelang.outfit.xml.TransformerCompositeException;
import org.novelang.parser.GeneratedLexemes;
import org.novelang.rendering.PdfWriter;
import org.novelang.rendering.XslWriter;
/**
* Generates a PDF in an {@code OutputStream}.
* This class delegates to an {@link XslWriter}.
* Showing all characters, including those not recognized in a source document, requires
* direct XML communication with FOP. This is achieved through SAX events as the document
* is not complex enough to require a new framework and dom4j fails to handle namespaces
* correctly.
*
* @author Laurent Caillette
*/
public class FontDiscoveryStreamer {
protected static final Namespace NAMESPACE = Namespace.get(
"nf",
"http://novelang.org/font-list-xml/1.0"
) ;
private static final String ELEMENT_ROOT = "fonts" ;
private static final String ELEMENT_FAMILY = "family" ;
private static final String ELEMENT_NAME = "name" ;
private static final String ELEMENT_EMBEDFILE = "embed-file" ;
private static final String ELEMENT_STYLE = "style" ;
private static final String ELEMENT_BROKEN = "broken" ;
private static final String ELEMENT_NOFONTFOUNT = "no-font-found" ;
private static final String ELEMENT_WEIGHT = "weight" ;
private static final String ELEMENT_CHARACTERS = "characters" ;
private static final String ELEMENT_CHARACTER = "character" ;
private static final String ELEMENT_SENTENCES = "sentences" ;
private static final String ELEMENT_SENTENCE = "sentence" ;
private final XslWriter xslWriter ;
private final FopFontStatus fopFontStatus ;
/**
* Only because
* {@link org.novelang.rendering.Renderer#render(org.novelang.common.Renderable, java.io.OutputStream, org.novelang.common.metadata.Page, java.io.File)}
* requires a reference to content directory for other cases.
*/
private final URL contentRoot ;
public FontDiscoveryStreamer(
final RenderingConfiguration renderingConfiguration,
final ResourceName resourceName,
final URL contentDirectory
) throws IOException, TransformerConfigurationException, SAXException, TransformerCompositeException
{
xslWriter = createXslWriter( renderingConfiguration, resourceName ) ;
fopFontStatus = renderingConfiguration.getCurrentFopFontStatus() ;
contentRoot = Preconditions.checkNotNull( contentDirectory ) ;
}
public void generate( final OutputStream outputStream, final Charset charset ) throws Exception {
final Iterable< Character > supportedCharacters ;
{
final Set< Character > characterSet = Sets.newHashSet( GeneratedLexemes.getCharacters() ) ;
characterSet.addAll( CHARACTERS_SUPPLEMENT ) ;
characterSet.removeAll( CHARACTERS_TO_REMOVE ) ;
supportedCharacters = CHARACTER_ORDERING.sortedCopy( characterSet ) ;
}
final Multimap< String, FontQuadruplet > quadruplets =
SyntheticFontMap.createSyntheticFontMap( fopFontStatus ) ;
final DocumentMetadata documentMetadata = new DocumentMetadata() {
@Override
public ReadableDateTime getCreationTimestamp() {
return new DateTime() ; // No null allowed!
}
@Override
public Charset getCharset() {
return charset ;
}
@Override
public Page getPage() {
return null ;
}
@Override
public URL getContentDirectory() {
return contentRoot ;
}
} ;
xslWriter.startWriting( outputStream, documentMetadata ) ;
xslWriter.start( ELEMENT_ROOT, true ) ;
if( fopFontStatus.getFailedFontFileNames().isEmpty() && quadruplets.keySet().isEmpty() ) {
xslWriter.start( ELEMENT_NOFONTFOUNT ) ;
xslWriter.end( ELEMENT_NOFONTFOUNT ) ;
}
if( ! fopFontStatus.getFailedFontFileNames().isEmpty() ) {
xslWriter.start( ELEMENT_BROKEN ) ;
for( final String embedFileName : fopFontStatus.getFailedFontFileNames() ) {
xslWriter.start( ELEMENT_EMBEDFILE ) ;
xslWriter.write( embedFileName ) ;
xslWriter.end( ELEMENT_EMBEDFILE ) ;
}
xslWriter.end( ELEMENT_BROKEN ) ;
}
for( final String fontName : quadruplets.keySet() ) {
for( final FontQuadruplet quadruplet : quadruplets.get( fontName ) ) {
final FontTriplet fontTriplet = quadruplet.getFontTriplet() ;
xslWriter.start( ELEMENT_FAMILY ) ; {
xslWriter.start( ELEMENT_NAME ) ;
xslWriter.write( fontTriplet.getName() ) ;
xslWriter.end( ELEMENT_NAME ) ;
xslWriter.start( ELEMENT_STYLE ) ;
xslWriter.write( fontTriplet.getStyle() ) ;
xslWriter.end( ELEMENT_STYLE ) ;
xslWriter.start( ELEMENT_WEIGHT ) ;
xslWriter.write( "" + fontTriplet.getWeight() ) ;
xslWriter.end( ELEMENT_WEIGHT ) ;
xslWriter.start( ELEMENT_EMBEDFILE ) ;
xslWriter.write( quadruplet.getEmbedFileName() ) ;
xslWriter.end( ELEMENT_EMBEDFILE ) ;
} xslWriter.end( ELEMENT_FAMILY ) ;
}
}
xslWriter.start( ELEMENT_CHARACTERS ) ;
for( final Character character : supportedCharacters ) {
xslWriter.start( ELEMENT_CHARACTER ) ;
xslWriter.write( "" + character ) ;
xslWriter.end( ELEMENT_CHARACTER ) ;
}
xslWriter.end( ELEMENT_CHARACTERS ) ;
xslWriter.start( ELEMENT_SENTENCES ) ; {
xslWriter.start( ELEMENT_SENTENCE ) ;
xslWriter.write( "AaBbCcDdEeFf GgHhIiJjKkLl MmNnOoPpQqRr SsTtUuVvWwXx YyZz" ) ;
xslWriter.end( ELEMENT_SENTENCE ) ;
xslWriter.start( ELEMENT_SENTENCE ) ;
xslWriter.write( "The quick brown fox jumps over the lazy dog." ) ;
xslWriter.end( ELEMENT_SENTENCE ) ;
xslWriter.start( ELEMENT_SENTENCE ) ;
xslWriter.write( "Voix ambig\u00fce d'un c\u0153ur qui au z\u00e9phyr " +
"pr\u00e9f\u00e8re les jattes de kiwis." ) ;
xslWriter.end( ELEMENT_SENTENCE ) ;
} xslWriter.end( ELEMENT_SENTENCES ) ;
xslWriter.end( ELEMENT_ROOT ) ;
xslWriter.finishWriting() ;
}
protected XslWriter createXslWriter(
final RenderingConfiguration renderingConfiguration,
final ResourceName resourceName
) throws IOException, TransformerConfigurationException, SAXException, TransformerCompositeException {
return new PdfWriter(
renderingConfiguration,
resourceName,
FontDiscoveryStreamer.NAMESPACE.getURI(),
FontDiscoveryStreamer.NAMESPACE.getPrefix()
) ;
// return new XslWriter(
// FontDiscoveryStreamer.NAMESPACE.getURI(),
// FontDiscoveryStreamer.NAMESPACE.getPrefix(),
// renderingConfiguration,
// new ResourceName( "identity.xsl" )
// ) ;
}
private static final Ordering< Character > CHARACTER_ORDERING = new Ordering< Character >() {
@Override
public int compare( final Character character0, final Character character1 ) {
return character0.compareTo( character1 ) ;
}
} ;
/**
* There are some characters to not include in generated source document as they would
* mess escaping or whatever.
*/
private static final Set< Character > CHARACTERS_TO_REMOVE = Sets.newHashSet(
'\n',
'\r',
' '
) ;
public static final Set< Character > CHARACTERS_SUPPLEMENT = Sets.newHashSet(
'\u2014',
'\u2013',
'\u2026'
) ;
}