package gutenberg.itext;
import com.google.common.base.Function;
import com.google.common.collect.Maps;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfWriter;
import gutenberg.util.Collector;
import gutenberg.util.Consumer;
import gutenberg.util.KeyValues;
import gutenberg.util.Margin;
import gutenberg.util.New;
import gutenberg.util.VariableResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.Stack;
/**
* @author <a href="http://twitter.com/aloyer">@aloyer</a>
*/
public class ITextContext {
private Logger log = LoggerFactory.getLogger(ITextContext.class);
private Rectangle pageSize = PageSize.A4;
private Margin documentMargin;
//
private Document document;
private File fileOut;
private PdfWriter pdfWriter;
//
//
private PageNumber pageNumber;
private TableOfContents tableOfContents;
private Chapter pendingChapter;
//
private final KeyValues keyValues;
private final Sections sections;
private final Styles styles;
//
private final Stack<Consumer<Element>> consumers = New.newStack();
private final Map<Object, Emitter> registeredEmitters = Maps.newConcurrentMap();
private final VariableResolver variableResolver = new VariableResolver();
public ITextContext(KeyValues keyValues, Styles styles) {
this(keyValues, styles, new Margin(50));
}
public ITextContext(KeyValues keyValues, Styles styles, Margin documentMargin) {
if (keyValues == null)
throw new IllegalArgumentException("KeyValues cannot be null");
if (styles == null)
throw new IllegalArgumentException("Styles cannot be null");
this.keyValues = keyValues;
this.sections = new Sections(keyValues, styles);
this.styles = styles;
this.documentMargin = (documentMargin == null) ? new Margin(50) : documentMargin;
}
public KeyValues keyValues() {
return keyValues;
}
public VariableResolver variableResolver() {
return variableResolver;
}
public TableOfContents tableOfContents() {
return tableOfContents;
}
public Styles styles() {
return styles;
}
public Document getDocument() {
return document;
}
public PdfWriter getPdfWriter() {
return pdfWriter;
}
public ITextContext open(File fileOut) throws FileNotFoundException, DocumentException {
this.document = createDocument();
this.fileOut = fileOut;
this.pageNumber = new PageNumber();
this.tableOfContents = new TableOfContents(pageNumber);
Function<PageInfos, Phrase> header = constant(new Phrase(""));
Function<PageInfos, Phrase> footer = constant(new Phrase(""));
OutputStream outStream = new FileOutputStream(fileOut);
this.pdfWriter = PdfWriter.getInstance(document, outStream);
this.pdfWriter.setBoxSize("art", getDocumentArtBox());
this.pdfWriter.setPageEvent(pageNumber);
this.pdfWriter.setPageEvent(tableOfContents);
this.pdfWriter.setPageEvent(new HeaderFooter(pageNumber, styles, header, footer));
//
document.open();
return this;
}
private static <T, R> Function<T, R> constant(final R element) {
return new Function<T, R>() {
@Override
public R apply(T pageInfos) {
return element;
}
};
}
public void close() {
flushPendingChapter();
log.info("File generated at {}", fileOut.getAbsolutePath());
document.close();
}
protected Document createDocument() {
return new Document(pageSize,
documentMargin.marginLeft,
documentMargin.marginRight,
documentMargin.marginTop,
documentMargin.marginBottom);
}
public Rectangle getDocumentArtBox() {
return new Rectangle(
documentMargin.marginLeft,
documentMargin.marginBottom,
pageSize.getWidth() - documentMargin.marginRight,
pageSize.getHeight() - documentMargin.marginTop);
}
public Sections sections() {
return sections;
}
public PageNumber pageNumber() {
return pageNumber;
}
@SuppressWarnings("unchecked")
public <T> Emitter<T> emitterFor(Class<T> type) {
Emitter emitter = registeredEmitters.get(type);
if (emitter == null)
throw new IllegalArgumentException("No emitter registered for type '" + type + "'");
return emitter;
}
public void appendAll(Iterable<? extends Element> elements) {
for (Element e : elements)
append(e);
}
public void append(Element element) {
if (element == null)
return;
try {
if (!consumers.isEmpty()) {
consumers.peek().consume(element);
return;
}
if (element instanceof Chapter) {
flushPendingChapter();
getDocument().add(element);
sections.leaveSection(1);
return;
}
if (element instanceof Section) {
throw new IllegalStateException("Except chapter, section is automatically added to its parent...");
}
Section section = sections.currentSection();
if (section == null) {
getDocument().add(element);
} else {
section.add(element);
}
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
public <T> void emit(Class<? super T> klazz, T value) {
Emitter<? super T> emitter = emitterFor(klazz);
if (emitter == null)
throw new RuntimeException("No emitter registered for type " + klazz);
emitter.emit(value, this);
}
@SuppressWarnings("unchecked")
public <T> void emit(T value) {
if (value instanceof SimpleEmitter) {
((SimpleEmitter) value).emit(this);
return;
}
if (value instanceof Emitter) {
throw new IllegalArgumentException("An Emitter cannot be emitted... " + value);
}
Class klazz = value.getClass();
for (Map.Entry<Object, Emitter> entry : registeredEmitters.entrySet()) {
if (entry.getKey() instanceof Class) {
Class supportedType = (Class) entry.getKey();
if (supportedType.isAssignableFrom(klazz)) {
Emitter emitter = entry.getValue();
emitter.emit(value, this);
return;
}
}
}
throw new RuntimeException("No emitter registered or suitable for type " + klazz);
}
public <T> void register(Class<T> type, Emitter<? super T> emitter) {
registeredEmitters.put(type, emitter);
}
public void pushElementConsumer(Consumer<Element> consumer) {
consumers.push(consumer);
}
public Consumer<Element> popElementConsumer() {
return consumers.pop();
}
public List<Element> emitButCollectElements(Object value) {
Collector<Element> elementCollector = new Collector<Element>();
pushElementConsumer(elementCollector);
try {
emit(value);
} finally {
popElementConsumer();
}
return elementCollector.getCollected();
}
public void flushPendingChapter() {
if (pendingChapter != null) {
try {
getDocument().add(pendingChapter);
} catch (DocumentException e) {
log.warn("Fail to add pending chapter {}", pendingChapter, e);
}
}
pendingChapter = null;
}
public void definePendingChapter(Chapter pendingChapter) {
this.pendingChapter = pendingChapter;
sections().restoreChapter(pendingChapter);
}
}